1+ <script >
2+ import { Portal } from ' @sveltestrap/sveltestrap' ;
3+ import { onMount , onDestroy } from ' svelte' ;
4+ import { createPopper } from ' @popperjs/core' ;
5+ import { v4 as uuidv4 } from ' uuid' ;
6+ import { classnames } from ' $lib/helpers/utils/common' ;
7+ import { clickoutsideDirective } from " $lib/helpers/directives" ;
8+
9+ /**
10+ * Additional CSS class names for the tooltip.
11+ * @type {string}
12+ */
13+ export let containerClasses = ' ' ;
14+
15+ /**
16+ * Flag to enable animation for the tooltip.
17+ * @type {boolean}
18+ */
19+ export let animation = true ;
20+
21+ /**
22+ * Unique identifier for the tooltip.
23+ * @type {string}
24+ */
25+ export let id = ` tooltip_${ uuidv4 ()} ` ;
26+
27+ /**
28+ * Controls the visibility of the tooltip.
29+ * @type {boolean}
30+ */
31+ export let isOpen = false ;
32+
33+ /**
34+ * Controls the visibility of the tooltip after hover the attached element.
35+ * @type {boolean}
36+ */
37+ export let persist = false ;
38+
39+ /**
40+ * The preferred placement of the tooltip.
41+ * @type {string}
42+ */
43+ export let placement = ' top' ;
44+
45+ /**
46+ * The target element to which the tooltip is attached.
47+ * @type {string | HTMLElement}
48+ */
49+ export let target = ' ' ;
50+
51+ /**
52+ * The theme name override to apply to this component instance.
53+ * @type {string | null}
54+ */
55+ export let theme = null ;
56+
57+ /**
58+ * The delay for showing the tooltip (in milliseconds).
59+ * @type {number}
60+ */
61+ export let delay = 0 ;
62+
63+ /** @type {string} */
64+ let bsPlacement = ' start' ;
65+ /** @type {object} */
66+ let popperInstance;
67+ /** @type {string} */
68+ let popperPlacement = placement;
69+ /** @type {HTMLDivElement | null} */
70+ let targetEl;
71+ /** @type {HTMLDivElement | null} */
72+ let tooltipEl;
73+ /** @type {number} */
74+ let showTimer;
75+
76+ const checkPopperPlacement = {
77+ name: ' checkPopperPlacement' ,
78+ enabled: true ,
79+ phase: ' main' ,
80+ // @ts-ignore
81+ fn (args ) {
82+ popperPlacement = args .state .placement ;
83+ }
84+ };
85+
86+
87+ onMount (() => {
88+ registerEventListeners ();
89+ });
90+
91+ onDestroy (() => {
92+ unregisterEventListeners ();
93+ clearTimeout (showTimer);
94+ });
95+
96+ const open = () => {
97+ clearTimeout (showTimer);
98+ showTimer = setTimeout (() => (isOpen = true ), delay);
99+ };
100+
101+ const close = () => {
102+ clearTimeout (showTimer);
103+ isOpen = false ;
104+ };
105+
106+ function registerEventListeners () {
107+ // eslint-disable-next-line eqeqeq
108+ if (target == null || ! target) {
109+ targetEl = null ;
110+ return ;
111+ }
112+
113+ try {
114+ if (target instanceof HTMLElement ) {
115+ // @ts-ignore
116+ targetEl = target;
117+ }
118+ } catch (e) {}
119+
120+ // eslint-disable-next-line eqeqeq
121+ if (targetEl == null ) {
122+ try {
123+ targetEl = document .querySelector (` #${ target} ` );
124+ } catch (e) {}
125+ }
126+
127+ if (targetEl) {
128+ targetEl .addEventListener (' mouseover' , open);
129+ if (! persist) {
130+ targetEl .addEventListener (' mouseleave' , close);
131+ }
132+ }
133+ }
134+
135+ function unregisterEventListeners () {
136+ if (targetEl) {
137+ targetEl .removeEventListener (' mouseover' , open);
138+ targetEl .removeEventListener (' mouseleave' , close);
139+ targetEl .removeAttribute (' aria-describedby' );
140+ }
141+
142+ if (tooltipEl && persist) {
143+ tooltipEl .removeEventListener (" mouseleave" , close);
144+ }
145+ }
146+
147+ /** @param {any} e */
148+ function handleClickOutside (e ) {
149+ e .preventDefault ();
150+
151+ if (! persist) return ;
152+
153+ const curNode = e .detail .currentNode ;
154+ const targetNode = e .detail .targetNode ;
155+
156+ if (! curNode? .contains (targetNode)) {
157+ isOpen = false ;
158+ }
159+ }
160+
161+ $: classes = classnames (
162+ containerClasses,
163+ ' tooltip' ,
164+ ` bs-tooltip-${ bsPlacement} ` ,
165+ animation ? ' fade' : null ,
166+ isOpen ? ' show' : null
167+ );
168+
169+ $: {
170+ if (isOpen && tooltipEl) {
171+ // @ts-ignore
172+ popperInstance = createPopper (targetEl, tooltipEl, {
173+ placement,
174+ modifiers: [checkPopperPlacement]
175+ });
176+ } else if (popperInstance) {
177+ // @ts-ignore
178+ popperInstance .destroy ();
179+ // @ts-ignore
180+ popperInstance = undefined ;
181+ }
182+ }
183+
184+ $: if (target) {
185+ unregisterEventListeners ();
186+ registerEventListeners ();
187+ }
188+
189+ $: if (targetEl) {
190+ if (isOpen) {
191+ targetEl .setAttribute (' aria-describedby' , id);
192+ } else {
193+ targetEl .removeAttribute (' aria-describedby' );
194+ }
195+ }
196+
197+ $: if (persist && tooltipEl) {
198+ tooltipEl .addEventListener (" mouseleave" , close);
199+ }
200+
201+ $: {
202+ if (popperPlacement === ' left' ) {
203+ bsPlacement = ' start' ;
204+ } else if (popperPlacement === ' right' ) {
205+ bsPlacement = ' end' ;
206+ } else {
207+ bsPlacement = popperPlacement;
208+ }
209+ }
210+ < / script>
211+
212+ {#if isOpen}
213+ < svelte: component this = {Portal}>
214+ < div
215+ bind: this = {tooltipEl}
216+ use: clickoutsideDirective
217+ on: clickoutside= {handleClickOutside}
218+ {... $$restProps}
219+ class = {classes}
220+ {id}
221+ role= " tooltip"
222+ data- bs- theme= {theme}
223+ data- bs- delay= {delay}
224+ x- placement= {popperPlacement}
225+ >
226+ < div class = " tooltip-arrow" data- popper- arrow>< / div>
227+ < div class = " tooltip-inner" >
228+ < slot / >
229+ < / div>
230+ < / div>
231+ < / svelte: component>
232+ {/ if }
0 commit comments