@kupola/kupola 1.5.2 → 1.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/js/tooltip.js CHANGED
@@ -1,346 +1,349 @@
1
1
  import { kupolaInitializer } from './initializer.js';
2
+ import { getUiConfig } from './kupola-config.js';
2
3
 
3
4
  class Tooltip {
4
- constructor(element, options = {}) {
5
- this.element = element;
6
- this.tooltipEl = null;
7
-
8
- this.options = options;
9
- this.delay = options.delay || parseInt(element.getAttribute('data-tooltip-delay')) || 0;
10
- this.hideDelay = options.hideDelay || parseInt(element.getAttribute('data-tooltip-hide-delay')) || 0;
11
- this.trigger = options.trigger || element.getAttribute('data-tooltip-trigger') || 'hover'; // hover | click | focus | manual
12
- this.html = options.html || element.hasAttribute('data-tooltip-html');
13
- this.theme = options.theme || element.getAttribute('data-tooltip-theme') || 'default'; // default | dark | light
14
- this.position = options.position || element.getAttribute('data-tooltip-position') || 'top';
15
- this.animation = options.animation !== false;
16
- this.mouseFollow = options.mouseFollow || element.hasAttribute('data-tooltip-mouse-follow');
17
-
18
- this._showTooltip = null;
19
- this._hideTooltip = null;
5
+ constructor(element, options = {}) {
6
+ this.element = element;
7
+ this.tooltipEl = null;
8
+
9
+ this.options = options;
10
+ const uiConfig = getUiConfig();
11
+ const defaultDelay = uiConfig.tooltip?.delay !== undefined ? uiConfig.tooltip.delay : 300;
12
+ this.delay = options.delay !== undefined ? options.delay : (parseInt(element.getAttribute('data-tooltip-delay')) || defaultDelay);
13
+ this.hideDelay = options.hideDelay || parseInt(element.getAttribute('data-tooltip-hide-delay')) || 0;
14
+ this.trigger = options.trigger || element.getAttribute('data-tooltip-trigger') || 'hover'; // hover | click | focus | manual
15
+ this.html = options.html || element.hasAttribute('data-tooltip-html');
16
+ this.theme = options.theme || element.getAttribute('data-tooltip-theme') || 'default'; // default | dark | light
17
+ this.position = options.position || element.getAttribute('data-tooltip-position') || 'top';
18
+ this.animation = options.animation !== false;
19
+ this.mouseFollow = options.mouseFollow || element.hasAttribute('data-tooltip-mouse-follow');
20
+
21
+ this._showTooltip = null;
22
+ this._hideTooltip = null;
23
+ this._showTimer = null;
24
+ this._hideTimer = null;
25
+ this._clickHandler = null;
26
+ this._focusHandler = null;
27
+ this._blurHandler = null;
28
+ this._mouseMoveHandler = null;
29
+
30
+ this.isVisible = false;
31
+ }
32
+
33
+ init() {
34
+ if (this.element.__kupolaInitialized) {return;}
35
+
36
+ this._showTooltip = () => {
37
+ if (this.delay > 0) {
38
+ this._showTimer = setTimeout(() => this.show(), this.delay);
39
+ } else {
40
+ this.show();
41
+ }
42
+ };
43
+
44
+ this._hideTooltip = () => {
45
+ if (this._showTimer) {
46
+ clearTimeout(this._showTimer);
20
47
  this._showTimer = null;
21
- this._hideTimer = null;
22
- this._clickHandler = null;
23
- this._focusHandler = null;
24
- this._blurHandler = null;
25
- this._mouseMoveHandler = null;
26
-
27
- this.isVisible = false;
48
+ }
49
+ if (this.hideDelay > 0) {
50
+ this._hideTimer = setTimeout(() => this.hide(), this.hideDelay);
51
+ } else {
52
+ this.hide();
53
+ }
54
+ };
55
+
56
+ this._clickHandler = () => {
57
+ if (this.isVisible) {
58
+ this.hide();
59
+ } else {
60
+ this.show();
61
+ }
62
+ };
63
+
64
+ this._mouseMoveHandler = (e) => {
65
+ if (!this.isVisible || !this.mouseFollow || !this.tooltipEl) {return;}
66
+
67
+ const tooltipRect = this.tooltipEl.getBoundingClientRect();
68
+ let left = e.clientX + 10;
69
+ let top = e.clientY + 10;
70
+
71
+ const viewportWidth = window.innerWidth;
72
+ const viewportHeight = window.innerHeight;
73
+
74
+ if (left + tooltipRect.width > viewportWidth) {
75
+ left = e.clientX - tooltipRect.width - 10;
76
+ }
77
+ if (top + tooltipRect.height > viewportHeight) {
78
+ top = e.clientY - tooltipRect.height - 10;
79
+ }
80
+
81
+ this.tooltipEl.style.left = `${left}px`;
82
+ this.tooltipEl.style.top = `${top}px`;
83
+ };
84
+
85
+ if (this.trigger === 'hover' || this.trigger === 'focus') {
86
+ this.element.addEventListener('mouseenter', this._showTooltip);
87
+ this.element.addEventListener('mouseleave', this._hideTooltip);
88
+
89
+ if (this.mouseFollow) {
90
+ this.element.addEventListener('mousemove', this._mouseMoveHandler);
91
+ }
28
92
  }
29
93
 
30
- init() {
31
- if (this.element.__kupolaInitialized) return;
32
-
33
- this._showTooltip = () => {
34
- if (this.delay > 0) {
35
- this._showTimer = setTimeout(() => this.show(), this.delay);
36
- } else {
37
- this.show();
38
- }
39
- };
40
-
41
- this._hideTooltip = () => {
42
- if (this._showTimer) {
43
- clearTimeout(this._showTimer);
44
- this._showTimer = null;
45
- }
46
- if (this.hideDelay > 0) {
47
- this._hideTimer = setTimeout(() => this.hide(), this.hideDelay);
48
- } else {
49
- this.hide();
50
- }
51
- };
52
-
53
- this._clickHandler = () => {
54
- if (this.isVisible) {
55
- this.hide();
56
- } else {
57
- this.show();
58
- }
59
- };
60
-
61
- this._mouseMoveHandler = (e) => {
62
- if (!this.isVisible || !this.mouseFollow || !this.tooltipEl) return;
63
-
64
- const tooltipRect = this.tooltipEl.getBoundingClientRect();
65
- let left = e.clientX + 10;
66
- let top = e.clientY + 10;
67
-
68
- const viewportWidth = window.innerWidth;
69
- const viewportHeight = window.innerHeight;
70
-
71
- if (left + tooltipRect.width > viewportWidth) {
72
- left = e.clientX - tooltipRect.width - 10;
73
- }
74
- if (top + tooltipRect.height > viewportHeight) {
75
- top = e.clientY - tooltipRect.height - 10;
76
- }
77
-
78
- this.tooltipEl.style.left = `${left}px`;
79
- this.tooltipEl.style.top = `${top}px`;
80
- };
81
-
82
- if (this.trigger === 'hover' || this.trigger === 'focus') {
83
- this.element.addEventListener('mouseenter', this._showTooltip);
84
- this.element.addEventListener('mouseleave', this._hideTooltip);
85
-
86
- if (this.mouseFollow) {
87
- this.element.addEventListener('mousemove', this._mouseMoveHandler);
88
- }
89
- }
90
-
91
- if (this.trigger === 'click') {
92
- this.element.addEventListener('click', this._clickHandler);
93
-
94
- document.addEventListener('click', (e) => {
95
- if (this.isVisible && !this.element.contains(e.target) && !this.tooltipEl?.contains(e.target)) {
96
- this.hide();
97
- }
98
- });
99
- }
100
-
101
- if (this.trigger === 'focus' || this.trigger === 'hover') {
102
- this.element.addEventListener('focus', this._showTooltip);
103
- this.element.addEventListener('blur', this._hideTooltip);
94
+ if (this.trigger === 'click') {
95
+ this.element.addEventListener('click', this._clickHandler);
96
+
97
+ document.addEventListener('click', (e) => {
98
+ if (this.isVisible && !this.element.contains(e.target) && !this.tooltipEl?.contains(e.target)) {
99
+ this.hide();
104
100
  }
105
-
106
- this.element.__kupolaInitialized = true;
101
+ });
107
102
  }
108
103
 
109
- show() {
110
- if (this.isVisible) return;
111
-
112
- const text = this.element.getAttribute('data-tooltip');
113
- if (!text) return;
114
-
115
- this.tooltipEl = document.createElement('div');
116
- this.tooltipEl.className = `ds-tooltip ds-tooltip--${this.position} ds-tooltip--${this.theme}`;
117
-
118
- if (this.html) {
119
- this.tooltipEl.innerHTML = text;
120
- } else {
121
- this.tooltipEl.textContent = text;
122
- }
123
-
124
- document.body.appendChild(this.tooltipEl);
125
-
126
- requestAnimationFrame(() => {
127
- this.tooltipEl.classList.add('is-visible');
128
-
129
- if (!this.mouseFollow) {
130
- this._positionTooltip();
131
- }
132
-
133
- this.isVisible = true;
134
- this.element.dispatchEvent(new CustomEvent('kupola:tooltip-show', {
135
- detail: { tooltip: this.tooltipEl },
136
- bubbles: true
137
- }));
138
- });
104
+ if (this.trigger === 'focus' || this.trigger === 'hover') {
105
+ this.element.addEventListener('focus', this._showTooltip);
106
+ this.element.addEventListener('blur', this._hideTooltip);
139
107
  }
140
108
 
141
- hide() {
142
- if (!this.isVisible || !this.tooltipEl) return;
143
-
144
- this.tooltipEl.classList.remove('is-visible');
145
-
146
- const removeEl = this.tooltipEl;
147
- setTimeout(() => {
148
- if (removeEl === this.tooltipEl) {
149
- removeEl.remove();
150
- this.tooltipEl = null;
151
- }
152
- }, this.animation ? 200 : 0);
153
-
154
- this.isVisible = false;
155
- this.element.dispatchEvent(new CustomEvent('kupola:tooltip-hide', {
156
- detail: { tooltip: removeEl },
157
- bubbles: true
158
- }));
109
+ this.element.__kupolaInitialized = true;
110
+ }
111
+
112
+ show() {
113
+ if (this.isVisible) {return;}
114
+
115
+ const text = this.element.getAttribute('data-tooltip');
116
+ if (!text) {return;}
117
+
118
+ this.tooltipEl = document.createElement('div');
119
+ this.tooltipEl.className = `ds-tooltip ds-tooltip--${this.position} ds-tooltip--${this.theme}`;
120
+
121
+ if (this.html) {
122
+ this.tooltipEl.innerHTML = text;
123
+ } else {
124
+ this.tooltipEl.textContent = text;
159
125
  }
160
126
 
161
- toggle() {
162
- if (this.isVisible) {
163
- this.hide();
164
- } else {
165
- this.show();
166
- }
127
+ document.body.appendChild(this.tooltipEl);
128
+
129
+ requestAnimationFrame(() => {
130
+ this.tooltipEl.classList.add('is-visible');
131
+
132
+ if (!this.mouseFollow) {
133
+ this._positionTooltip();
134
+ }
135
+
136
+ this.isVisible = true;
137
+ this.element.dispatchEvent(new CustomEvent('kupola:tooltip-show', {
138
+ detail: { tooltip: this.tooltipEl },
139
+ bubbles: true,
140
+ }));
141
+ });
142
+ }
143
+
144
+ hide() {
145
+ if (!this.isVisible || !this.tooltipEl) {return;}
146
+
147
+ this.tooltipEl.classList.remove('is-visible');
148
+
149
+ const removeEl = this.tooltipEl;
150
+ setTimeout(() => {
151
+ if (removeEl === this.tooltipEl) {
152
+ removeEl.remove();
153
+ this.tooltipEl = null;
154
+ }
155
+ }, this.animation ? 200 : 0);
156
+
157
+ this.isVisible = false;
158
+ this.element.dispatchEvent(new CustomEvent('kupola:tooltip-hide', {
159
+ detail: { tooltip: removeEl },
160
+ bubbles: true,
161
+ }));
162
+ }
163
+
164
+ toggle() {
165
+ if (this.isVisible) {
166
+ this.hide();
167
+ } else {
168
+ this.show();
167
169
  }
170
+ }
168
171
 
169
- _positionTooltip() {
170
- if (!this.tooltipEl) return;
171
-
172
- const rect = this.element.getBoundingClientRect();
173
- const tooltipRect = this.tooltipEl.getBoundingClientRect();
174
- const viewportWidth = window.innerWidth;
175
- const viewportHeight = window.innerHeight;
176
-
177
- let left, top;
178
-
179
- switch (this.position) {
180
- case 'bottom':
181
- left = rect.left + rect.width / 2 - tooltipRect.width / 2;
182
- top = rect.bottom + 8;
183
- break;
184
- case 'right':
185
- left = rect.right + 8;
186
- top = rect.top + rect.height / 2 - tooltipRect.height / 2;
187
- break;
188
- case 'left':
189
- left = rect.left - tooltipRect.width - 8;
190
- top = rect.top + rect.height / 2 - tooltipRect.height / 2;
191
- break;
192
- case 'top':
193
- default:
194
- left = rect.left + rect.width / 2 - tooltipRect.width / 2;
195
- top = rect.top - tooltipRect.height - 8;
196
- break;
197
- }
198
-
199
- if (left < 8) left = 8;
200
- if (left + tooltipRect.width > viewportWidth) left = viewportWidth - tooltipRect.width - 8;
201
- if (top < 8) top = 8;
202
- if (top + tooltipRect.height > viewportHeight) top = viewportHeight - tooltipRect.height - 8;
203
-
204
- this.tooltipEl.style.left = `${left}px`;
205
- this.tooltipEl.style.top = `${top}px`;
206
- this.tooltipEl.style.position = 'fixed';
172
+ _positionTooltip() {
173
+ if (!this.tooltipEl) {return;}
174
+
175
+ const rect = this.element.getBoundingClientRect();
176
+ const tooltipRect = this.tooltipEl.getBoundingClientRect();
177
+ const viewportWidth = window.innerWidth;
178
+ const viewportHeight = window.innerHeight;
179
+
180
+ let left, top;
181
+
182
+ switch (this.position) {
183
+ case 'bottom':
184
+ left = rect.left + rect.width / 2 - tooltipRect.width / 2;
185
+ top = rect.bottom + 8;
186
+ break;
187
+ case 'right':
188
+ left = rect.right + 8;
189
+ top = rect.top + rect.height / 2 - tooltipRect.height / 2;
190
+ break;
191
+ case 'left':
192
+ left = rect.left - tooltipRect.width - 8;
193
+ top = rect.top + rect.height / 2 - tooltipRect.height / 2;
194
+ break;
195
+ case 'top':
196
+ default:
197
+ left = rect.left + rect.width / 2 - tooltipRect.width / 2;
198
+ top = rect.top - tooltipRect.height - 8;
199
+ break;
207
200
  }
208
201
 
209
- updateContent(content, isHtml = false) {
210
- this.element.setAttribute('data-tooltip', content);
211
- if (isHtml) {
212
- this.element.setAttribute('data-tooltip-html', '');
213
- } else {
214
- this.element.removeAttribute('data-tooltip-html');
215
- }
216
- this.html = isHtml;
217
-
218
- if (this.isVisible && this.tooltipEl) {
219
- if (this.html) {
220
- this.tooltipEl.innerHTML = content;
221
- } else {
222
- this.tooltipEl.textContent = content;
223
- }
224
- this._positionTooltip();
225
- }
202
+ if (left < 8) {left = 8;}
203
+ if (left + tooltipRect.width > viewportWidth) {left = viewportWidth - tooltipRect.width - 8;}
204
+ if (top < 8) {top = 8;}
205
+ if (top + tooltipRect.height > viewportHeight) {top = viewportHeight - tooltipRect.height - 8;}
206
+
207
+ this.tooltipEl.style.left = `${left}px`;
208
+ this.tooltipEl.style.top = `${top}px`;
209
+ this.tooltipEl.style.position = 'fixed';
210
+ }
211
+
212
+ updateContent(content, isHtml = false) {
213
+ this.element.setAttribute('data-tooltip', content);
214
+ if (isHtml) {
215
+ this.element.setAttribute('data-tooltip-html', '');
216
+ } else {
217
+ this.element.removeAttribute('data-tooltip-html');
226
218
  }
219
+ this.html = isHtml;
227
220
 
228
- setPosition(position) {
229
- if (['top', 'bottom', 'left', 'right'].includes(position)) {
230
- this.position = position;
231
- this.element.setAttribute('data-tooltip-position', position);
232
-
233
- if (this.tooltipEl) {
234
- this.tooltipEl.className = `ds-tooltip ds-tooltip--${this.position} ds-tooltip--${this.theme}`;
235
- if (this.isVisible) {
236
- this._positionTooltip();
237
- }
238
- }
239
- }
221
+ if (this.isVisible && this.tooltipEl) {
222
+ if (this.html) {
223
+ this.tooltipEl.innerHTML = content;
224
+ } else {
225
+ this.tooltipEl.textContent = content;
226
+ }
227
+ this._positionTooltip();
240
228
  }
229
+ }
230
+
231
+ setPosition(position) {
232
+ if ([ 'top', 'bottom', 'left', 'right' ].includes(position)) {
233
+ this.position = position;
234
+ this.element.setAttribute('data-tooltip-position', position);
241
235
 
242
- setTheme(theme) {
243
- this.theme = theme;
244
- this.element.setAttribute('data-tooltip-theme', theme);
245
-
246
- if (this.tooltipEl) {
247
- this.tooltipEl.className = `ds-tooltip ds-tooltip--${this.position} ds-tooltip--${this.theme}`;
236
+ if (this.tooltipEl) {
237
+ this.tooltipEl.className = `ds-tooltip ds-tooltip--${this.position} ds-tooltip--${this.theme}`;
238
+ if (this.isVisible) {
239
+ this._positionTooltip();
248
240
  }
241
+ }
249
242
  }
243
+ }
244
+
245
+ setTheme(theme) {
246
+ this.theme = theme;
247
+ this.element.setAttribute('data-tooltip-theme', theme);
250
248
 
251
- setDelay(delay) {
252
- this.delay = delay;
253
- this.element.setAttribute('data-tooltip-delay', delay);
249
+ if (this.tooltipEl) {
250
+ this.tooltipEl.className = `ds-tooltip ds-tooltip--${this.position} ds-tooltip--${this.theme}`;
254
251
  }
252
+ }
255
253
 
256
- setHideDelay(delay) {
257
- this.hideDelay = delay;
258
- this.element.setAttribute('data-tooltip-hide-delay', delay);
254
+ setDelay(delay) {
255
+ this.delay = delay;
256
+ this.element.setAttribute('data-tooltip-delay', delay);
257
+ }
258
+
259
+ setHideDelay(delay) {
260
+ this.hideDelay = delay;
261
+ this.element.setAttribute('data-tooltip-hide-delay', delay);
262
+ }
263
+
264
+ setTrigger(trigger) {
265
+ if ([ 'hover', 'click', 'focus', 'manual' ].includes(trigger)) {
266
+ this.destroy();
267
+ this.trigger = trigger;
268
+ this.element.setAttribute('data-tooltip-trigger', trigger);
269
+ this.init();
259
270
  }
271
+ }
260
272
 
261
- setTrigger(trigger) {
262
- if (['hover', 'click', 'focus', 'manual'].includes(trigger)) {
263
- this.destroy();
264
- this.trigger = trigger;
265
- this.element.setAttribute('data-tooltip-trigger', trigger);
266
- this.init();
267
- }
273
+ enableMouseFollow(enable) {
274
+ this.mouseFollow = enable;
275
+ if (enable) {
276
+ this.element.setAttribute('data-tooltip-mouse-follow', '');
277
+ this.element.addEventListener('mousemove', this._mouseMoveHandler);
278
+ } else {
279
+ this.element.removeAttribute('data-tooltip-mouse-follow');
280
+ this.element.removeEventListener('mousemove', this._mouseMoveHandler);
268
281
  }
282
+ }
269
283
 
270
- enableMouseFollow(enable) {
271
- this.mouseFollow = enable;
272
- if (enable) {
273
- this.element.setAttribute('data-tooltip-mouse-follow', '');
274
- this.element.addEventListener('mousemove', this._mouseMoveHandler);
275
- } else {
276
- this.element.removeAttribute('data-tooltip-mouse-follow');
277
- this.element.removeEventListener('mousemove', this._mouseMoveHandler);
278
- }
284
+ destroy() {
285
+ if (!this.element.__kupolaInitialized) {return;}
286
+
287
+ if (this._showTimer) {
288
+ clearTimeout(this._showTimer);
289
+ this._showTimer = null;
290
+ }
291
+ if (this._hideTimer) {
292
+ clearTimeout(this._hideTimer);
293
+ this._hideTimer = null;
279
294
  }
280
295
 
281
- destroy() {
282
- if (!this.element.__kupolaInitialized) return;
296
+ if (this.trigger === 'hover' || this.trigger === 'focus') {
297
+ this.element.removeEventListener('mouseenter', this._showTooltip);
298
+ this.element.removeEventListener('mouseleave', this._hideTooltip);
299
+ }
283
300
 
284
- if (this._showTimer) {
285
- clearTimeout(this._showTimer);
286
- this._showTimer = null;
287
- }
288
- if (this._hideTimer) {
289
- clearTimeout(this._hideTimer);
290
- this._hideTimer = null;
291
- }
301
+ if (this.trigger === 'click') {
302
+ this.element.removeEventListener('click', this._clickHandler);
303
+ }
292
304
 
293
- if (this.trigger === 'hover' || this.trigger === 'focus') {
294
- this.element.removeEventListener('mouseenter', this._showTooltip);
295
- this.element.removeEventListener('mouseleave', this._hideTooltip);
296
- }
297
-
298
- if (this.trigger === 'click') {
299
- this.element.removeEventListener('click', this._clickHandler);
300
- }
301
-
302
- if (this.trigger === 'focus' || this.trigger === 'hover') {
303
- this.element.removeEventListener('focus', this._showTooltip);
304
- this.element.removeEventListener('blur', this._hideTooltip);
305
- }
306
-
307
- if (this.mouseFollow) {
308
- this.element.removeEventListener('mousemove', this._mouseMoveHandler);
309
- }
305
+ if (this.trigger === 'focus' || this.trigger === 'hover') {
306
+ this.element.removeEventListener('focus', this._showTooltip);
307
+ this.element.removeEventListener('blur', this._hideTooltip);
308
+ }
310
309
 
311
- if (this.tooltipEl) {
312
- this.tooltipEl.remove();
313
- this.tooltipEl = null;
314
- }
315
-
316
- this.isVisible = false;
317
- this._showTooltip = null;
318
- this._hideTooltip = null;
319
- this._clickHandler = null;
320
- this._mouseMoveHandler = null;
321
- this.element.__kupolaInitialized = false;
310
+ if (this.mouseFollow) {
311
+ this.element.removeEventListener('mousemove', this._mouseMoveHandler);
312
+ }
313
+
314
+ if (this.tooltipEl) {
315
+ this.tooltipEl.remove();
316
+ this.tooltipEl = null;
322
317
  }
318
+
319
+ this.isVisible = false;
320
+ this._showTooltip = null;
321
+ this._hideTooltip = null;
322
+ this._clickHandler = null;
323
+ this._mouseMoveHandler = null;
324
+ this.element.__kupolaInitialized = false;
325
+ }
323
326
  }
324
327
 
325
328
  function initTooltip(element, options) {
326
- const tooltip = new Tooltip(element, options);
327
- tooltip.init();
328
- element._kupolaTooltip = tooltip;
329
+ const tooltip = new Tooltip(element, options);
330
+ tooltip.init();
331
+ element._kupolaTooltip = tooltip;
329
332
  }
330
333
 
331
334
  function initTooltips(root = document) {
332
- root.querySelectorAll('[data-tooltip]').forEach(element => {
333
- initTooltip(element);
334
- });
335
+ root.querySelectorAll('[data-tooltip]').forEach(element => {
336
+ initTooltip(element);
337
+ });
335
338
  }
336
339
 
337
340
  function cleanupTooltip(element) {
338
- if (element._kupolaTooltip) {
339
- element._kupolaTooltip.destroy();
340
- element._kupolaTooltip = null;
341
- }
341
+ if (element._kupolaTooltip) {
342
+ element._kupolaTooltip.destroy();
343
+ element._kupolaTooltip = null;
344
+ }
342
345
  }
343
346
 
344
347
  export { Tooltip, initTooltip, initTooltips, cleanupTooltip };
345
348
 
346
- kupolaInitializer.register('tooltip', initTooltip, cleanupTooltip);
349
+ kupolaInitializer.register('tooltip', initTooltip, cleanupTooltip);