react-tooltip 6.0.0-beta.1179.rc.8 → 6.0.0
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/README.md +3 -1
- package/dist/react-tooltip.cjs +716 -391
- package/dist/react-tooltip.cjs.map +1 -1
- package/dist/react-tooltip.css +2 -1
- package/dist/react-tooltip.d.ts +5 -1
- package/dist/react-tooltip.min.cjs +2 -2
- package/dist/react-tooltip.min.cjs.map +1 -1
- package/dist/react-tooltip.min.css +1 -1
- package/dist/react-tooltip.min.mjs +2 -2
- package/dist/react-tooltip.min.mjs.map +1 -1
- package/dist/react-tooltip.mjs +717 -392
- package/dist/react-tooltip.mjs.map +1 -1
- package/dist/react-tooltip.umd.js +716 -391
- package/dist/react-tooltip.umd.js.map +1 -1
- package/dist/react-tooltip.umd.min.js +2 -2
- package/dist/react-tooltip.umd.min.js.map +1 -1
- package/eslint.config.js +155 -0
- package/package.json +47 -46
package/dist/react-tooltip.cjs
CHANGED
|
@@ -44,11 +44,9 @@ function injectStyle({ css, id = REACT_TOOLTIP_BASE_STYLES_ID, type = 'base', re
|
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
46
|
if (type === 'core') {
|
|
47
|
-
// eslint-disable-next-line no-param-reassign
|
|
48
47
|
id = REACT_TOOLTIP_CORE_STYLES_ID;
|
|
49
48
|
}
|
|
50
49
|
if (!ref) {
|
|
51
|
-
// eslint-disable-next-line no-param-reassign
|
|
52
50
|
ref = {};
|
|
53
51
|
}
|
|
54
52
|
const { insertAt } = ref;
|
|
@@ -79,7 +77,6 @@ function injectStyle({ css, id = REACT_TOOLTIP_BASE_STYLES_ID, type = 'base', re
|
|
|
79
77
|
style.appendChild(document.createTextNode(css));
|
|
80
78
|
}
|
|
81
79
|
if (typeof state[type] !== 'undefined') {
|
|
82
|
-
// eslint-disable-next-line no-param-reassign
|
|
83
80
|
state[type] = true;
|
|
84
81
|
}
|
|
85
82
|
else {
|
|
@@ -87,16 +84,12 @@ function injectStyle({ css, id = REACT_TOOLTIP_BASE_STYLES_ID, type = 'base', re
|
|
|
87
84
|
}
|
|
88
85
|
}
|
|
89
86
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}),
|
|
95
|
-
dom.shift({ padding: 5 }),
|
|
96
|
-
], border, arrowSize = 8, }) => {
|
|
87
|
+
// Hoisted constant middlewares — these configs never change
|
|
88
|
+
const defaultFlip = dom.flip({ fallbackAxisSideDirection: 'start' });
|
|
89
|
+
const defaultShift = dom.shift({ padding: 5 });
|
|
90
|
+
const computeTooltipPosition = async ({ elementReference = null, tooltipReference = null, tooltipArrowReference = null, place = 'top', offset: offsetValue = 10, strategy = 'absolute', middlewares = [dom.offset(Number(offsetValue)), defaultFlip, defaultShift], border, arrowSize = 8, }) => {
|
|
97
91
|
if (!elementReference) {
|
|
98
92
|
// elementReference can be null or undefined and we will not compute the position
|
|
99
|
-
// eslint-disable-next-line no-console
|
|
100
93
|
// console.error('The reference element for tooltip was not defined: ', elementReference)
|
|
101
94
|
return { tooltipStyles: {}, tooltipArrowStyles: {}, place };
|
|
102
95
|
}
|
|
@@ -181,6 +174,7 @@ const cssTimeToMs = (time) => {
|
|
|
181
174
|
*/
|
|
182
175
|
const debounce = (func, wait, immediate) => {
|
|
183
176
|
let timeout = null;
|
|
177
|
+
let currentFunc = func;
|
|
184
178
|
const debounced = function debounced(...args) {
|
|
185
179
|
const later = () => {
|
|
186
180
|
timeout = null;
|
|
@@ -190,7 +184,7 @@ const debounce = (func, wait, immediate) => {
|
|
|
190
184
|
* there's no need to clear the timeout
|
|
191
185
|
* since we expect it to resolve and set `timeout = null`
|
|
192
186
|
*/
|
|
193
|
-
|
|
187
|
+
currentFunc.apply(this, args);
|
|
194
188
|
timeout = setTimeout(later, wait);
|
|
195
189
|
}
|
|
196
190
|
};
|
|
@@ -203,36 +197,12 @@ const debounce = (func, wait, immediate) => {
|
|
|
203
197
|
clearTimeout(timeout);
|
|
204
198
|
timeout = null;
|
|
205
199
|
};
|
|
200
|
+
debounced.setCallback = (newFunc) => {
|
|
201
|
+
currentFunc = newFunc;
|
|
202
|
+
};
|
|
206
203
|
return debounced;
|
|
207
204
|
};
|
|
208
205
|
|
|
209
|
-
const isObject = (object) => {
|
|
210
|
-
return object !== null && !Array.isArray(object) && typeof object === 'object';
|
|
211
|
-
};
|
|
212
|
-
const deepEqual = (object1, object2) => {
|
|
213
|
-
if (object1 === object2) {
|
|
214
|
-
return true;
|
|
215
|
-
}
|
|
216
|
-
if (Array.isArray(object1) && Array.isArray(object2)) {
|
|
217
|
-
if (object1.length !== object2.length) {
|
|
218
|
-
return false;
|
|
219
|
-
}
|
|
220
|
-
return object1.every((val, index) => deepEqual(val, object2[index]));
|
|
221
|
-
}
|
|
222
|
-
if (Array.isArray(object1) !== Array.isArray(object2)) {
|
|
223
|
-
return false;
|
|
224
|
-
}
|
|
225
|
-
if (!isObject(object1) || !isObject(object2)) {
|
|
226
|
-
return object1 === object2;
|
|
227
|
-
}
|
|
228
|
-
const keys1 = Object.keys(object1);
|
|
229
|
-
const keys2 = Object.keys(object2);
|
|
230
|
-
if (keys1.length !== keys2.length) {
|
|
231
|
-
return false;
|
|
232
|
-
}
|
|
233
|
-
return keys1.every((key) => deepEqual(object1[key], object2[key]));
|
|
234
|
-
};
|
|
235
|
-
|
|
236
206
|
const isScrollable = (node) => {
|
|
237
207
|
if (!(node instanceof HTMLElement || node instanceof SVGElement)) {
|
|
238
208
|
return false;
|
|
@@ -273,17 +243,43 @@ const useIsomorphicLayoutEffect = isHopefullyDomEnvironment ? React.useLayoutEff
|
|
|
273
243
|
const clearTimeoutRef = (ref) => {
|
|
274
244
|
if (ref.current) {
|
|
275
245
|
clearTimeout(ref.current);
|
|
276
|
-
// eslint-disable-next-line no-param-reassign
|
|
277
246
|
ref.current = null;
|
|
278
247
|
}
|
|
279
248
|
};
|
|
280
249
|
|
|
250
|
+
function parseDataTooltipIdSelector(selector) {
|
|
251
|
+
const match = selector.match(/^\[data-tooltip-id=(['"])((?:\\.|(?!\1).)*)\1\]$/);
|
|
252
|
+
if (!match) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
return match[2].replace(/\\(['"])/g, '$1');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function resolveDataTooltipAnchor(targetElement, tooltipId) {
|
|
259
|
+
let currentElement = targetElement;
|
|
260
|
+
while (currentElement) {
|
|
261
|
+
if (currentElement.dataset.tooltipId === tooltipId) {
|
|
262
|
+
return currentElement;
|
|
263
|
+
}
|
|
264
|
+
currentElement = currentElement.parentElement;
|
|
265
|
+
}
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
|
|
281
269
|
var coreStyles = {"tooltip":"core-styles-module_tooltip__3vRRp","fixed":"core-styles-module_fixed__pcSol","arrow":"core-styles-module_arrow__cvMwQ","content":"core-styles-module_content__BRKdB","noArrow":"core-styles-module_noArrow__xock6","clickable":"core-styles-module_clickable__ZuTTB","show":"core-styles-module_show__Nt9eE","closing":"core-styles-module_closing__sGnxF"};
|
|
282
270
|
|
|
283
271
|
var styles = {"tooltip":"styles-module_tooltip__mnnfp","content":"styles-module_content__ydYdI","arrow":"styles-module_arrow__K0L3T","dark":"styles-module_dark__xNqje","light":"styles-module_light__Z6W-X","success":"styles-module_success__A2AKt","warning":"styles-module_warning__SCK0X","error":"styles-module_error__JvumD","info":"styles-module_info__BWdHW"};
|
|
284
272
|
|
|
285
273
|
const registry = new Map();
|
|
286
274
|
let documentObserver = null;
|
|
275
|
+
/**
|
|
276
|
+
* Extract a tooltip ID from a simple `[data-tooltip-id='value']` selector.
|
|
277
|
+
* Returns null for complex or custom selectors.
|
|
278
|
+
*/
|
|
279
|
+
function extractTooltipId(selector) {
|
|
280
|
+
const match = selector.match(/^\[data-tooltip-id=(['"])((?:\\.|(?!\1).)*)\1\]$/);
|
|
281
|
+
return match ? match[2].replace(/\\(['"])/g, '$1') : null;
|
|
282
|
+
}
|
|
287
283
|
function areAnchorListsEqual(left, right) {
|
|
288
284
|
if (left.length !== right.length) {
|
|
289
285
|
return false;
|
|
@@ -305,8 +301,7 @@ function readAnchorsForSelector(selector) {
|
|
|
305
301
|
}
|
|
306
302
|
}
|
|
307
303
|
function notifySubscribers(entry) {
|
|
308
|
-
|
|
309
|
-
entry.subscribers.forEach((subscriber) => subscriber(anchors, entry.error));
|
|
304
|
+
entry.subscribers.forEach((subscriber) => subscriber(entry.anchors, entry.error));
|
|
310
305
|
}
|
|
311
306
|
function refreshEntry(selector, entry) {
|
|
312
307
|
var _a, _b, _c, _d;
|
|
@@ -330,12 +325,117 @@ function refreshAllEntries() {
|
|
|
330
325
|
refreshEntry(selector, entry);
|
|
331
326
|
});
|
|
332
327
|
}
|
|
328
|
+
let refreshScheduled = false;
|
|
329
|
+
let pendingTooltipIds = null;
|
|
330
|
+
let pendingFullRefresh = false;
|
|
331
|
+
function scheduleRefresh(affectedTooltipIds) {
|
|
332
|
+
if (affectedTooltipIds) {
|
|
333
|
+
if (!pendingTooltipIds) {
|
|
334
|
+
pendingTooltipIds = new Set();
|
|
335
|
+
}
|
|
336
|
+
affectedTooltipIds.forEach((id) => pendingTooltipIds.add(id));
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
pendingFullRefresh = true;
|
|
340
|
+
}
|
|
341
|
+
if (refreshScheduled) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
refreshScheduled = true;
|
|
345
|
+
const flush = () => {
|
|
346
|
+
refreshScheduled = false;
|
|
347
|
+
const fullRefresh = pendingFullRefresh;
|
|
348
|
+
const ids = pendingTooltipIds;
|
|
349
|
+
pendingFullRefresh = false;
|
|
350
|
+
pendingTooltipIds = null;
|
|
351
|
+
if (fullRefresh) {
|
|
352
|
+
refreshAllEntries();
|
|
353
|
+
}
|
|
354
|
+
else if (ids && ids.size > 0) {
|
|
355
|
+
refreshEntriesForTooltipIds(ids);
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
359
|
+
requestAnimationFrame(flush);
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
Promise.resolve().then(flush);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Only refresh entries whose tooltipId is in the affected set,
|
|
367
|
+
* plus any entries with custom (non-tooltipId) selectors.
|
|
368
|
+
*/
|
|
369
|
+
function refreshEntriesForTooltipIds(affectedIds) {
|
|
370
|
+
registry.forEach((entry, selector) => {
|
|
371
|
+
if (entry.tooltipId === null || affectedIds.has(entry.tooltipId)) {
|
|
372
|
+
refreshEntry(selector, entry);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Collect tooltip IDs from mutation records. Returns null when targeted
|
|
378
|
+
* analysis is not worthwhile (few registry entries, or too many nodes to scan).
|
|
379
|
+
*/
|
|
380
|
+
function collectAffectedTooltipIds(records) {
|
|
381
|
+
var _a;
|
|
382
|
+
// Targeted refresh only pays off when there are many distinct selectors.
|
|
383
|
+
// With few entries, full refresh is already cheap — skip the analysis overhead.
|
|
384
|
+
if (registry.size <= 4) {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
const ids = new Set();
|
|
388
|
+
for (const record of records) {
|
|
389
|
+
if (record.type === 'attributes') {
|
|
390
|
+
const target = record.target;
|
|
391
|
+
const currentId = (_a = target.getAttribute) === null || _a === void 0 ? void 0 : _a.call(target, 'data-tooltip-id');
|
|
392
|
+
if (currentId)
|
|
393
|
+
ids.add(currentId);
|
|
394
|
+
if (record.oldValue)
|
|
395
|
+
ids.add(record.oldValue);
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (record.type === 'childList') {
|
|
399
|
+
const gatherIds = (nodes) => {
|
|
400
|
+
var _a, _b;
|
|
401
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
402
|
+
const node = nodes[i];
|
|
403
|
+
if (node.nodeType !== Node.ELEMENT_NODE)
|
|
404
|
+
continue;
|
|
405
|
+
const el = node;
|
|
406
|
+
const id = (_a = el.getAttribute) === null || _a === void 0 ? void 0 : _a.call(el, 'data-tooltip-id');
|
|
407
|
+
if (id)
|
|
408
|
+
ids.add(id);
|
|
409
|
+
// For large subtrees, bail out to full refresh to avoid double-scanning
|
|
410
|
+
const descendants = (_b = el.querySelectorAll) === null || _b === void 0 ? void 0 : _b.call(el, '[data-tooltip-id]');
|
|
411
|
+
if (descendants) {
|
|
412
|
+
if (descendants.length > 50) {
|
|
413
|
+
return true; // signal bail-out
|
|
414
|
+
}
|
|
415
|
+
for (let j = 0; j < descendants.length; j++) {
|
|
416
|
+
const descId = descendants[j].getAttribute('data-tooltip-id');
|
|
417
|
+
if (descId)
|
|
418
|
+
ids.add(descId);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return false;
|
|
423
|
+
};
|
|
424
|
+
if (gatherIds(record.addedNodes) || gatherIds(record.removedNodes)) {
|
|
425
|
+
return null; // large mutation — full refresh is cheaper
|
|
426
|
+
}
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return ids;
|
|
431
|
+
}
|
|
333
432
|
function ensureDocumentObserver() {
|
|
334
433
|
if (documentObserver || typeof MutationObserver === 'undefined') {
|
|
335
434
|
return;
|
|
336
435
|
}
|
|
337
|
-
documentObserver = new MutationObserver(() => {
|
|
338
|
-
|
|
436
|
+
documentObserver = new MutationObserver((records) => {
|
|
437
|
+
const affectedIds = collectAffectedTooltipIds(records);
|
|
438
|
+
scheduleRefresh(affectedIds);
|
|
339
439
|
});
|
|
340
440
|
documentObserver.observe(document.body, {
|
|
341
441
|
childList: true,
|
|
@@ -360,6 +460,7 @@ function subscribeAnchorSelector(selector, subscriber) {
|
|
|
360
460
|
anchors: initialState.anchors,
|
|
361
461
|
error: initialState.error,
|
|
362
462
|
subscribers: new Set(),
|
|
463
|
+
tooltipId: extractTooltipId(selector),
|
|
363
464
|
};
|
|
364
465
|
registry.set(selector, entry);
|
|
365
466
|
}
|
|
@@ -387,7 +488,7 @@ const getAnchorSelector = ({ id, anchorSelect, imperativeAnchorSelect, }) => {
|
|
|
387
488
|
}
|
|
388
489
|
return selector;
|
|
389
490
|
};
|
|
390
|
-
const useTooltipAnchors = ({ id, anchorSelect, imperativeAnchorSelect, activeAnchor, disableTooltip, onActiveAnchorRemoved, }) => {
|
|
491
|
+
const useTooltipAnchors = ({ id, anchorSelect, imperativeAnchorSelect, activeAnchor, disableTooltip, onActiveAnchorRemoved, trackAnchors, }) => {
|
|
391
492
|
const [rawAnchorElements, setRawAnchorElements] = React.useState([]);
|
|
392
493
|
const [selectorError, setSelectorError] = React.useState(null);
|
|
393
494
|
const warnedSelectorRef = React.useRef(null);
|
|
@@ -403,9 +504,10 @@ const useTooltipAnchors = ({ id, anchorSelect, imperativeAnchorSelect, activeAnc
|
|
|
403
504
|
catch (_a) {
|
|
404
505
|
return false;
|
|
405
506
|
}
|
|
406
|
-
|
|
507
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
508
|
+
}, [activeAnchor, selector, anchorElements]);
|
|
407
509
|
React.useEffect(() => {
|
|
408
|
-
if (!selector) {
|
|
510
|
+
if (!selector || !trackAnchors) {
|
|
409
511
|
setRawAnchorElements([]);
|
|
410
512
|
setSelectorError(null);
|
|
411
513
|
return undefined;
|
|
@@ -414,7 +516,7 @@ const useTooltipAnchors = ({ id, anchorSelect, imperativeAnchorSelect, activeAnc
|
|
|
414
516
|
setRawAnchorElements(anchors);
|
|
415
517
|
setSelectorError(error);
|
|
416
518
|
});
|
|
417
|
-
}, [selector]);
|
|
519
|
+
}, [selector, trackAnchors]);
|
|
418
520
|
React.useEffect(() => {
|
|
419
521
|
if (!selectorError || warnedSelectorRef.current === selector) {
|
|
420
522
|
return;
|
|
@@ -434,111 +536,81 @@ const useTooltipAnchors = ({ id, anchorSelect, imperativeAnchorSelect, activeAnc
|
|
|
434
536
|
onActiveAnchorRemoved();
|
|
435
537
|
}
|
|
436
538
|
}, [activeAnchor, anchorElements, activeAnchorMatchesSelector, onActiveAnchorRemoved]);
|
|
437
|
-
return
|
|
539
|
+
return {
|
|
540
|
+
anchorElements,
|
|
541
|
+
selector,
|
|
542
|
+
};
|
|
438
543
|
};
|
|
439
544
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
if (tooltipHideDelayTimerRef.current) {
|
|
510
|
-
clearTimeout(tooltipHideDelayTimerRef.current);
|
|
511
|
-
}
|
|
512
|
-
};
|
|
513
|
-
const handleHideTooltip = () => {
|
|
514
|
-
if (clickable) {
|
|
515
|
-
handleHideTooltipDelayed(delayHide || 100);
|
|
516
|
-
}
|
|
517
|
-
else if (delayHide) {
|
|
518
|
-
handleHideTooltipDelayed();
|
|
519
|
-
}
|
|
520
|
-
else {
|
|
521
|
-
handleShow(false);
|
|
522
|
-
}
|
|
523
|
-
if (tooltipShowDelayTimerRef.current) {
|
|
524
|
-
clearTimeout(tooltipShowDelayTimerRef.current);
|
|
525
|
-
}
|
|
526
|
-
};
|
|
527
|
-
const internalDebouncedHandleShowTooltip = debounce(handleShowTooltip, 50);
|
|
528
|
-
const internalDebouncedHandleHideTooltip = debounce(handleHideTooltip, 50);
|
|
529
|
-
const debouncedHandleShowTooltip = (anchor) => {
|
|
530
|
-
internalDebouncedHandleHideTooltip.cancel();
|
|
531
|
-
internalDebouncedHandleShowTooltip(anchor);
|
|
532
|
-
};
|
|
533
|
-
const debouncedHandleHideTooltip = () => {
|
|
534
|
-
internalDebouncedHandleShowTooltip.cancel();
|
|
535
|
-
internalDebouncedHandleHideTooltip();
|
|
536
|
-
};
|
|
537
|
-
const handleScrollResize = () => {
|
|
538
|
-
handleShow(false);
|
|
539
|
-
};
|
|
540
|
-
const hasClickEvent = openOnClick || (openEvents === null || openEvents === void 0 ? void 0 : openEvents.click) || (openEvents === null || openEvents === void 0 ? void 0 : openEvents.dblclick) || (openEvents === null || openEvents === void 0 ? void 0 : openEvents.mousedown);
|
|
541
|
-
const actualOpenEvents = openEvents
|
|
545
|
+
/**
|
|
546
|
+
* Shared document event delegation.
|
|
547
|
+
*
|
|
548
|
+
* Instead of N tooltips each calling document.addEventListener(type, handler),
|
|
549
|
+
* we maintain ONE document listener per event type. When the event fires,
|
|
550
|
+
* we iterate through all registered handlers for that type.
|
|
551
|
+
*
|
|
552
|
+
* This reduces document-level listeners from O(N × eventTypes) to O(eventTypes).
|
|
553
|
+
*/
|
|
554
|
+
const handlersByType = new Map();
|
|
555
|
+
function getOrCreateSet(eventType) {
|
|
556
|
+
let set = handlersByType.get(eventType);
|
|
557
|
+
if (!set) {
|
|
558
|
+
set = new Set();
|
|
559
|
+
handlersByType.set(eventType, set);
|
|
560
|
+
document.addEventListener(eventType, dispatch);
|
|
561
|
+
}
|
|
562
|
+
return set;
|
|
563
|
+
}
|
|
564
|
+
function dispatch(event) {
|
|
565
|
+
const handlers = handlersByType.get(event.type);
|
|
566
|
+
if (handlers) {
|
|
567
|
+
// Safe to iterate directly — mutations (add/remove) only happen in
|
|
568
|
+
// setup/cleanup, not during dispatch. Set iteration is stable for
|
|
569
|
+
// entries that existed when iteration began.
|
|
570
|
+
handlers.forEach((handler) => {
|
|
571
|
+
handler(event);
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Register a handler for a document-level event type.
|
|
577
|
+
* Returns an unsubscribe function.
|
|
578
|
+
*/
|
|
579
|
+
function addDelegatedEventListener(eventType, handler) {
|
|
580
|
+
const set = getOrCreateSet(eventType);
|
|
581
|
+
set.add(handler);
|
|
582
|
+
return () => {
|
|
583
|
+
set.delete(handler);
|
|
584
|
+
if (set.size === 0) {
|
|
585
|
+
handlersByType.delete(eventType);
|
|
586
|
+
document.removeEventListener(eventType, dispatch);
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const useTooltipEvents = ({ activeAnchor, anchorElements, anchorSelector, clickable, closeEvents, delayHide, delayShow, disableTooltip, float, globalCloseEvents, handleHideTooltipDelayed, handleShow, handleShowTooltipDelayed, handleTooltipPosition, hoveringTooltip, imperativeModeOnly, lastFloatPosition, openEvents, openOnClick, setActiveAnchor, show, tooltipHideDelayTimerRef, tooltipRef, tooltipShowDelayTimerRef, updateTooltipPosition, }) => {
|
|
592
|
+
// Ref-stable debounced handlers — avoids recreating debounce instances on every effect run
|
|
593
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
594
|
+
const debouncedShowRef = React.useRef(debounce((_anchor) => { }, 50));
|
|
595
|
+
const debouncedHideRef = React.useRef(debounce(() => { }, 50));
|
|
596
|
+
// Cache scroll parents — only recompute when the element actually changes
|
|
597
|
+
const anchorScrollParentRef = React.useRef(null);
|
|
598
|
+
const tooltipScrollParentRef = React.useRef(null);
|
|
599
|
+
const prevAnchorRef = React.useRef(null);
|
|
600
|
+
const prevTooltipRef = React.useRef(null);
|
|
601
|
+
if (activeAnchor !== prevAnchorRef.current) {
|
|
602
|
+
prevAnchorRef.current = activeAnchor;
|
|
603
|
+
anchorScrollParentRef.current = getScrollParent(activeAnchor);
|
|
604
|
+
}
|
|
605
|
+
const currentTooltipEl = tooltipRef.current;
|
|
606
|
+
if (currentTooltipEl !== prevTooltipRef.current) {
|
|
607
|
+
prevTooltipRef.current = currentTooltipEl;
|
|
608
|
+
tooltipScrollParentRef.current = getScrollParent(currentTooltipEl);
|
|
609
|
+
}
|
|
610
|
+
// Memoize event config objects — only rebuild when the relevant props change
|
|
611
|
+
const hasClickEvent = openOnClick || (openEvents === null || openEvents === void 0 ? void 0 : openEvents.click) || (openEvents === null || openEvents === void 0 ? void 0 : openEvents.dblclick) || (openEvents === null || openEvents === void 0 ? void 0 : openEvents.mousedown);
|
|
612
|
+
const actualOpenEvents = React.useMemo(() => {
|
|
613
|
+
const events = openEvents
|
|
542
614
|
? { ...openEvents }
|
|
543
615
|
: {
|
|
544
616
|
mouseenter: true,
|
|
@@ -548,13 +620,25 @@ const useTooltipEvents = ({ activeAnchor, anchorElements, clickable, closeEvents
|
|
|
548
620
|
mousedown: false,
|
|
549
621
|
};
|
|
550
622
|
if (!openEvents && openOnClick) {
|
|
551
|
-
Object.assign(
|
|
623
|
+
Object.assign(events, {
|
|
552
624
|
mouseenter: false,
|
|
553
625
|
focus: false,
|
|
554
626
|
click: true,
|
|
555
627
|
});
|
|
556
628
|
}
|
|
557
|
-
|
|
629
|
+
if (imperativeModeOnly) {
|
|
630
|
+
Object.assign(events, {
|
|
631
|
+
mouseenter: false,
|
|
632
|
+
focus: false,
|
|
633
|
+
click: false,
|
|
634
|
+
dblclick: false,
|
|
635
|
+
mousedown: false,
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
return events;
|
|
639
|
+
}, [openEvents, openOnClick, imperativeModeOnly]);
|
|
640
|
+
const actualCloseEvents = React.useMemo(() => {
|
|
641
|
+
const events = closeEvents
|
|
558
642
|
? { ...closeEvents }
|
|
559
643
|
: {
|
|
560
644
|
mouseleave: true,
|
|
@@ -564,12 +648,24 @@ const useTooltipEvents = ({ activeAnchor, anchorElements, clickable, closeEvents
|
|
|
564
648
|
mouseup: false,
|
|
565
649
|
};
|
|
566
650
|
if (!closeEvents && openOnClick) {
|
|
567
|
-
Object.assign(
|
|
651
|
+
Object.assign(events, {
|
|
568
652
|
mouseleave: false,
|
|
569
653
|
blur: false,
|
|
570
654
|
});
|
|
571
655
|
}
|
|
572
|
-
|
|
656
|
+
if (imperativeModeOnly) {
|
|
657
|
+
Object.assign(events, {
|
|
658
|
+
mouseleave: false,
|
|
659
|
+
blur: false,
|
|
660
|
+
click: false,
|
|
661
|
+
dblclick: false,
|
|
662
|
+
mouseup: false,
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
return events;
|
|
666
|
+
}, [closeEvents, openOnClick, imperativeModeOnly]);
|
|
667
|
+
const actualGlobalCloseEvents = React.useMemo(() => {
|
|
668
|
+
const events = globalCloseEvents
|
|
573
669
|
? { ...globalCloseEvents }
|
|
574
670
|
: {
|
|
575
671
|
escape: false,
|
|
@@ -578,108 +674,157 @@ const useTooltipEvents = ({ activeAnchor, anchorElements, clickable, closeEvents
|
|
|
578
674
|
clickOutsideAnchor: hasClickEvent || false,
|
|
579
675
|
};
|
|
580
676
|
if (imperativeModeOnly) {
|
|
581
|
-
Object.assign(
|
|
582
|
-
mouseenter: false,
|
|
583
|
-
focus: false,
|
|
584
|
-
click: false,
|
|
585
|
-
dblclick: false,
|
|
586
|
-
mousedown: false,
|
|
587
|
-
});
|
|
588
|
-
Object.assign(actualCloseEvents, {
|
|
589
|
-
mouseleave: false,
|
|
590
|
-
blur: false,
|
|
591
|
-
click: false,
|
|
592
|
-
dblclick: false,
|
|
593
|
-
mouseup: false,
|
|
594
|
-
});
|
|
595
|
-
Object.assign(actualGlobalCloseEvents, {
|
|
677
|
+
Object.assign(events, {
|
|
596
678
|
escape: false,
|
|
597
679
|
scroll: false,
|
|
598
680
|
resize: false,
|
|
599
681
|
clickOutsideAnchor: false,
|
|
600
682
|
});
|
|
601
683
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
684
|
+
return events;
|
|
685
|
+
}, [globalCloseEvents, hasClickEvent, imperativeModeOnly]);
|
|
686
|
+
// --- Refs for values read inside event handlers (avoids effect deps) ---
|
|
687
|
+
const activeAnchorRef = React.useRef(activeAnchor);
|
|
688
|
+
activeAnchorRef.current = activeAnchor;
|
|
689
|
+
const showRef = React.useRef(show);
|
|
690
|
+
showRef.current = show;
|
|
691
|
+
const anchorElementsRef = React.useRef(anchorElements);
|
|
692
|
+
anchorElementsRef.current = anchorElements;
|
|
693
|
+
const handleShowRef = React.useRef(handleShow);
|
|
694
|
+
handleShowRef.current = handleShow;
|
|
695
|
+
const handleTooltipPositionRef = React.useRef(handleTooltipPosition);
|
|
696
|
+
handleTooltipPositionRef.current = handleTooltipPosition;
|
|
697
|
+
const updateTooltipPositionRef = React.useRef(updateTooltipPosition);
|
|
698
|
+
updateTooltipPositionRef.current = updateTooltipPosition;
|
|
699
|
+
// --- Handler refs (updated every render, read via ref indirection in effects) ---
|
|
700
|
+
const resolveAnchorElementRef = React.useRef(() => null);
|
|
701
|
+
const handleShowTooltipRef = React.useRef(() => { });
|
|
702
|
+
const handleHideTooltipRef = React.useRef(() => { });
|
|
703
|
+
const dataTooltipId = anchorSelector ? parseDataTooltipIdSelector(anchorSelector) : null;
|
|
704
|
+
resolveAnchorElementRef.current = (target) => {
|
|
705
|
+
var _a, _b;
|
|
706
|
+
const targetElement = target;
|
|
707
|
+
if (!(targetElement === null || targetElement === void 0 ? void 0 : targetElement.isConnected)) {
|
|
708
|
+
return null;
|
|
609
709
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
710
|
+
if (dataTooltipId) {
|
|
711
|
+
const matchedAnchor = resolveDataTooltipAnchor(targetElement, dataTooltipId);
|
|
712
|
+
if (matchedAnchor && !(disableTooltip === null || disableTooltip === void 0 ? void 0 : disableTooltip(matchedAnchor))) {
|
|
713
|
+
return matchedAnchor;
|
|
714
|
+
}
|
|
613
715
|
}
|
|
614
|
-
else if (
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
716
|
+
else if (anchorSelector) {
|
|
717
|
+
try {
|
|
718
|
+
const matchedAnchor = (_a = (targetElement.matches(anchorSelector)
|
|
719
|
+
? targetElement
|
|
720
|
+
: targetElement.closest(anchorSelector))) !== null && _a !== void 0 ? _a : null;
|
|
721
|
+
if (matchedAnchor && !(disableTooltip === null || disableTooltip === void 0 ? void 0 : disableTooltip(matchedAnchor))) {
|
|
722
|
+
return matchedAnchor;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
catch (_c) {
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
620
728
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
729
|
+
return ((_b = anchorElementsRef.current.find((anchor) => anchor === targetElement || anchor.contains(targetElement))) !== null && _b !== void 0 ? _b : null);
|
|
730
|
+
};
|
|
731
|
+
handleShowTooltipRef.current = (anchor) => {
|
|
732
|
+
if (!anchor) {
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
if (!anchor.isConnected) {
|
|
736
|
+
setActiveAnchor(null);
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (disableTooltip === null || disableTooltip === void 0 ? void 0 : disableTooltip(anchor)) {
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
if (delayShow) {
|
|
743
|
+
handleShowTooltipDelayed();
|
|
744
|
+
}
|
|
745
|
+
else {
|
|
746
|
+
handleShow(true);
|
|
747
|
+
}
|
|
748
|
+
if (delayShow && activeAnchorRef.current && anchor !== activeAnchorRef.current) {
|
|
749
|
+
// Moving to a different anchor while one is already active — defer the anchor
|
|
750
|
+
// switch until the show delay fires to prevent content/position from updating
|
|
751
|
+
// before visibility transitions complete.
|
|
752
|
+
if (tooltipShowDelayTimerRef.current) {
|
|
753
|
+
clearTimeout(tooltipShowDelayTimerRef.current);
|
|
624
754
|
}
|
|
755
|
+
tooltipShowDelayTimerRef.current = setTimeout(() => {
|
|
756
|
+
setActiveAnchor(anchor);
|
|
757
|
+
handleShow(true);
|
|
758
|
+
}, delayShow);
|
|
759
|
+
}
|
|
760
|
+
else {
|
|
761
|
+
setActiveAnchor(anchor);
|
|
762
|
+
}
|
|
763
|
+
if (tooltipHideDelayTimerRef.current) {
|
|
764
|
+
clearTimeout(tooltipHideDelayTimerRef.current);
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
handleHideTooltipRef.current = () => {
|
|
768
|
+
if (clickable) {
|
|
769
|
+
handleHideTooltipDelayed(delayHide || 100);
|
|
770
|
+
}
|
|
771
|
+
else if (delayHide) {
|
|
772
|
+
handleHideTooltipDelayed();
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
625
775
|
handleShow(false);
|
|
626
|
-
};
|
|
627
|
-
if (actualGlobalCloseEvents.escape) {
|
|
628
|
-
window.addEventListener('keydown', handleEsc);
|
|
629
776
|
}
|
|
630
|
-
if (
|
|
631
|
-
|
|
777
|
+
if (tooltipShowDelayTimerRef.current) {
|
|
778
|
+
clearTimeout(tooltipShowDelayTimerRef.current);
|
|
632
779
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
780
|
+
};
|
|
781
|
+
// Update debounced callbacks to always delegate to latest handler refs
|
|
782
|
+
const debouncedShow = debouncedShowRef.current;
|
|
783
|
+
const debouncedHide = debouncedHideRef.current;
|
|
784
|
+
debouncedShow.setCallback((anchor) => handleShowTooltipRef.current(anchor));
|
|
785
|
+
debouncedHide.setCallback(() => handleHideTooltipRef.current());
|
|
786
|
+
// --- Effect 1: Delegated anchor events + tooltip hover ---
|
|
787
|
+
// Only re-runs when the set of active event types or interaction mode changes.
|
|
788
|
+
// Handlers read reactive values (activeAnchor, show, etc.) from refs at invocation
|
|
789
|
+
// time, so this effect is decoupled from show/hide state changes.
|
|
790
|
+
React.useEffect(() => {
|
|
791
|
+
const cleanupFns = [];
|
|
792
|
+
const addDelegatedListener = (eventType, listener) => {
|
|
793
|
+
cleanupFns.push(addDelegatedEventListener(eventType, listener));
|
|
644
794
|
};
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
795
|
+
const activeAnchorContainsTarget = (event) => { var _a; return Boolean((event === null || event === void 0 ? void 0 : event.target) && ((_a = activeAnchorRef.current) === null || _a === void 0 ? void 0 : _a.contains(event.target))); };
|
|
796
|
+
const debouncedHandleShowTooltip = (anchor) => {
|
|
797
|
+
debouncedHide.cancel();
|
|
798
|
+
debouncedShow(anchor);
|
|
799
|
+
};
|
|
800
|
+
const debouncedHandleHideTooltip = () => {
|
|
801
|
+
debouncedShow.cancel();
|
|
802
|
+
debouncedHide();
|
|
650
803
|
};
|
|
651
|
-
const regularEvents = ['mouseover', 'mouseout', 'mouseenter', 'mouseleave', 'focus', 'blur'];
|
|
652
|
-
const clickEvents = ['click', 'dblclick', 'mousedown', 'mouseup'];
|
|
653
|
-
const delegatedEvents = [];
|
|
654
804
|
const addDelegatedHoverOpenListener = () => {
|
|
655
|
-
|
|
656
|
-
event
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
}
|
|
666
|
-
debouncedHandleShowTooltip(anchor);
|
|
667
|
-
},
|
|
805
|
+
addDelegatedListener('mouseover', (event) => {
|
|
806
|
+
const anchor = resolveAnchorElementRef.current(event.target);
|
|
807
|
+
if (!anchor) {
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
const relatedAnchor = resolveAnchorElementRef.current(event.relatedTarget);
|
|
811
|
+
if (relatedAnchor === anchor) {
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
debouncedHandleShowTooltip(anchor);
|
|
668
815
|
});
|
|
669
816
|
};
|
|
670
817
|
const addDelegatedHoverCloseListener = () => {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
debouncedHandleHideTooltip();
|
|
682
|
-
},
|
|
818
|
+
addDelegatedListener('mouseout', (event) => {
|
|
819
|
+
var _a;
|
|
820
|
+
if (!activeAnchorContainsTarget(event)) {
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
const relatedTarget = event.relatedTarget;
|
|
824
|
+
if ((_a = activeAnchorRef.current) === null || _a === void 0 ? void 0 : _a.contains(relatedTarget)) {
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
debouncedHandleHideTooltip();
|
|
683
828
|
});
|
|
684
829
|
};
|
|
685
830
|
if (actualOpenEvents.mouseenter) {
|
|
@@ -695,37 +840,48 @@ const useTooltipEvents = ({ activeAnchor, anchorElements, clickable, closeEvents
|
|
|
695
840
|
addDelegatedHoverCloseListener();
|
|
696
841
|
}
|
|
697
842
|
if (actualOpenEvents.focus) {
|
|
698
|
-
|
|
699
|
-
event
|
|
700
|
-
listener: (event) => {
|
|
701
|
-
debouncedHandleShowTooltip(resolveAnchorElement(event.target));
|
|
702
|
-
},
|
|
843
|
+
addDelegatedListener('focusin', (event) => {
|
|
844
|
+
debouncedHandleShowTooltip(resolveAnchorElementRef.current(event.target));
|
|
703
845
|
});
|
|
704
846
|
}
|
|
705
847
|
if (actualCloseEvents.blur) {
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
debouncedHandleHideTooltip();
|
|
717
|
-
},
|
|
848
|
+
addDelegatedListener('focusout', (event) => {
|
|
849
|
+
var _a;
|
|
850
|
+
if (!activeAnchorContainsTarget(event)) {
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
const relatedTarget = event.relatedTarget;
|
|
854
|
+
if ((_a = activeAnchorRef.current) === null || _a === void 0 ? void 0 : _a.contains(relatedTarget)) {
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
debouncedHandleHideTooltip();
|
|
718
858
|
});
|
|
719
859
|
}
|
|
860
|
+
const regularEvents = ['mouseover', 'mouseout', 'mouseenter', 'mouseleave', 'focus', 'blur'];
|
|
861
|
+
const clickEvents = ['click', 'dblclick', 'mousedown', 'mouseup'];
|
|
862
|
+
const handleClickOpenTooltipAnchor = (event) => {
|
|
863
|
+
var _a;
|
|
864
|
+
const anchor = resolveAnchorElementRef.current((_a = event === null || event === void 0 ? void 0 : event.target) !== null && _a !== void 0 ? _a : null);
|
|
865
|
+
if (!anchor) {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
if (showRef.current && activeAnchorRef.current === anchor) {
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
handleShowTooltipRef.current(anchor);
|
|
872
|
+
};
|
|
873
|
+
const handleClickCloseTooltipAnchor = (event) => {
|
|
874
|
+
if (!showRef.current || !activeAnchorContainsTarget(event)) {
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
handleHideTooltipRef.current();
|
|
878
|
+
};
|
|
720
879
|
Object.entries(actualOpenEvents).forEach(([event, enabled]) => {
|
|
721
880
|
if (!enabled || regularEvents.includes(event)) {
|
|
722
881
|
return;
|
|
723
882
|
}
|
|
724
883
|
if (clickEvents.includes(event)) {
|
|
725
|
-
|
|
726
|
-
event,
|
|
727
|
-
listener: handleClickOpenTooltipAnchor,
|
|
728
|
-
});
|
|
884
|
+
addDelegatedListener(event, handleClickOpenTooltipAnchor);
|
|
729
885
|
}
|
|
730
886
|
});
|
|
731
887
|
Object.entries(actualCloseEvents).forEach(([event, enabled]) => {
|
|
@@ -733,38 +889,111 @@ const useTooltipEvents = ({ activeAnchor, anchorElements, clickable, closeEvents
|
|
|
733
889
|
return;
|
|
734
890
|
}
|
|
735
891
|
if (clickEvents.includes(event)) {
|
|
736
|
-
|
|
737
|
-
event,
|
|
738
|
-
listener: handleClickCloseTooltipAnchor,
|
|
739
|
-
});
|
|
892
|
+
addDelegatedListener(event, handleClickCloseTooltipAnchor);
|
|
740
893
|
}
|
|
741
894
|
});
|
|
742
895
|
if (float) {
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
896
|
+
addDelegatedListener('pointermove', (event) => {
|
|
897
|
+
const currentActiveAnchor = activeAnchorRef.current;
|
|
898
|
+
if (!currentActiveAnchor) {
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
const targetAnchor = resolveAnchorElementRef.current(event.target);
|
|
902
|
+
if (targetAnchor !== currentActiveAnchor) {
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
const mouseEvent = event;
|
|
906
|
+
const mousePosition = {
|
|
907
|
+
x: mouseEvent.clientX,
|
|
908
|
+
y: mouseEvent.clientY,
|
|
909
|
+
};
|
|
910
|
+
handleTooltipPositionRef.current(mousePosition);
|
|
911
|
+
lastFloatPosition.current = mousePosition;
|
|
746
912
|
});
|
|
747
913
|
}
|
|
914
|
+
const tooltipElement = tooltipRef.current;
|
|
748
915
|
const handleMouseOverTooltip = () => {
|
|
749
|
-
// eslint-disable-next-line no-param-reassign
|
|
750
916
|
hoveringTooltip.current = true;
|
|
751
917
|
};
|
|
752
918
|
const handleMouseOutTooltip = () => {
|
|
753
|
-
// eslint-disable-next-line no-param-reassign
|
|
754
919
|
hoveringTooltip.current = false;
|
|
755
|
-
|
|
920
|
+
handleHideTooltipRef.current();
|
|
756
921
|
};
|
|
757
922
|
const addHoveringTooltipListeners = clickable && (actualCloseEvents.mouseout || actualCloseEvents.mouseleave);
|
|
758
923
|
if (addHoveringTooltipListeners) {
|
|
759
924
|
tooltipElement === null || tooltipElement === void 0 ? void 0 : tooltipElement.addEventListener('mouseover', handleMouseOverTooltip);
|
|
760
925
|
tooltipElement === null || tooltipElement === void 0 ? void 0 : tooltipElement.addEventListener('mouseout', handleMouseOutTooltip);
|
|
761
926
|
}
|
|
762
|
-
delegatedEvents.forEach(({ event, listener }) => {
|
|
763
|
-
document.addEventListener(event, listener);
|
|
764
|
-
});
|
|
765
927
|
return () => {
|
|
928
|
+
cleanupFns.forEach((fn) => fn());
|
|
929
|
+
if (addHoveringTooltipListeners) {
|
|
930
|
+
tooltipElement === null || tooltipElement === void 0 ? void 0 : tooltipElement.removeEventListener('mouseover', handleMouseOverTooltip);
|
|
931
|
+
tooltipElement === null || tooltipElement === void 0 ? void 0 : tooltipElement.removeEventListener('mouseout', handleMouseOutTooltip);
|
|
932
|
+
}
|
|
933
|
+
debouncedShow.cancel();
|
|
934
|
+
debouncedHide.cancel();
|
|
935
|
+
};
|
|
936
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
937
|
+
}, [actualOpenEvents, actualCloseEvents, float, clickable]);
|
|
938
|
+
// --- Effect 2: Global close events + auto-update ---
|
|
939
|
+
// Re-runs when the global close config changes, or when the active anchor changes
|
|
940
|
+
// (for scroll parent listeners and floating-ui autoUpdate).
|
|
941
|
+
React.useEffect(() => {
|
|
942
|
+
const handleScrollResize = () => {
|
|
943
|
+
handleShowRef.current(false);
|
|
944
|
+
};
|
|
945
|
+
const tooltipScrollParent = tooltipScrollParentRef.current;
|
|
946
|
+
const anchorScrollParent = anchorScrollParentRef.current;
|
|
947
|
+
if (actualGlobalCloseEvents.scroll) {
|
|
948
|
+
window.addEventListener('scroll', handleScrollResize);
|
|
949
|
+
anchorScrollParent === null || anchorScrollParent === void 0 ? void 0 : anchorScrollParent.addEventListener('scroll', handleScrollResize);
|
|
950
|
+
tooltipScrollParent === null || tooltipScrollParent === void 0 ? void 0 : tooltipScrollParent.addEventListener('scroll', handleScrollResize);
|
|
951
|
+
}
|
|
952
|
+
let updateTooltipCleanup = null;
|
|
953
|
+
if (actualGlobalCloseEvents.resize) {
|
|
954
|
+
window.addEventListener('resize', handleScrollResize);
|
|
955
|
+
}
|
|
956
|
+
else if (activeAnchor && tooltipRef.current) {
|
|
957
|
+
updateTooltipCleanup = dom.autoUpdate(activeAnchor, tooltipRef.current, () => updateTooltipPositionRef.current(), {
|
|
958
|
+
ancestorResize: true,
|
|
959
|
+
elementResize: true,
|
|
960
|
+
layoutShift: true,
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
const handleEsc = (event) => {
|
|
964
|
+
if (event.key !== 'Escape') {
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
handleShowRef.current(false);
|
|
968
|
+
};
|
|
969
|
+
if (actualGlobalCloseEvents.escape) {
|
|
970
|
+
window.addEventListener('keydown', handleEsc);
|
|
971
|
+
}
|
|
972
|
+
const handleClickOutsideAnchors = (event) => {
|
|
973
|
+
var _a, _b;
|
|
974
|
+
if (!showRef.current) {
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
const target = event.target;
|
|
978
|
+
if (!(target === null || target === void 0 ? void 0 : target.isConnected)) {
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
if ((_a = tooltipRef.current) === null || _a === void 0 ? void 0 : _a.contains(target)) {
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
if ((_b = activeAnchorRef.current) === null || _b === void 0 ? void 0 : _b.contains(target)) {
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
if (anchorElementsRef.current.some((anchor) => anchor === null || anchor === void 0 ? void 0 : anchor.contains(target))) {
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
handleShowRef.current(false);
|
|
766
991
|
clearTimeoutRef(tooltipShowDelayTimerRef);
|
|
767
|
-
|
|
992
|
+
};
|
|
993
|
+
if (actualGlobalCloseEvents.clickOutsideAnchor) {
|
|
994
|
+
window.addEventListener('click', handleClickOutsideAnchors);
|
|
995
|
+
}
|
|
996
|
+
return () => {
|
|
768
997
|
if (actualGlobalCloseEvents.scroll) {
|
|
769
998
|
window.removeEventListener('scroll', handleScrollResize);
|
|
770
999
|
anchorScrollParent === null || anchorScrollParent === void 0 ? void 0 : anchorScrollParent.removeEventListener('scroll', handleScrollResize);
|
|
@@ -776,53 +1005,22 @@ const useTooltipEvents = ({ activeAnchor, anchorElements, clickable, closeEvents
|
|
|
776
1005
|
if (updateTooltipCleanup) {
|
|
777
1006
|
updateTooltipCleanup();
|
|
778
1007
|
}
|
|
779
|
-
if (actualGlobalCloseEvents.clickOutsideAnchor) {
|
|
780
|
-
window.removeEventListener('click', handleClickOutsideAnchors);
|
|
781
|
-
}
|
|
782
1008
|
if (actualGlobalCloseEvents.escape) {
|
|
783
1009
|
window.removeEventListener('keydown', handleEsc);
|
|
784
1010
|
}
|
|
785
|
-
if (
|
|
786
|
-
|
|
787
|
-
tooltipElement === null || tooltipElement === void 0 ? void 0 : tooltipElement.removeEventListener('mouseout', handleMouseOutTooltip);
|
|
1011
|
+
if (actualGlobalCloseEvents.clickOutsideAnchor) {
|
|
1012
|
+
window.removeEventListener('click', handleClickOutsideAnchors);
|
|
788
1013
|
}
|
|
789
|
-
delegatedEvents.forEach(({ event, listener }) => {
|
|
790
|
-
document.removeEventListener(event, listener);
|
|
791
|
-
});
|
|
792
|
-
internalDebouncedHandleShowTooltip.cancel();
|
|
793
|
-
internalDebouncedHandleHideTooltip.cancel();
|
|
794
1014
|
};
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
anchorElements,
|
|
798
|
-
clickable,
|
|
799
|
-
closeEvents,
|
|
800
|
-
delayHide,
|
|
801
|
-
delayShow,
|
|
802
|
-
disableTooltip,
|
|
803
|
-
float,
|
|
804
|
-
globalCloseEvents,
|
|
805
|
-
handleHideTooltipDelayed,
|
|
806
|
-
handleShow,
|
|
807
|
-
handleShowTooltipDelayed,
|
|
808
|
-
handleTooltipPosition,
|
|
809
|
-
imperativeModeOnly,
|
|
810
|
-
lastFloatPosition,
|
|
811
|
-
openEvents,
|
|
812
|
-
openOnClick,
|
|
813
|
-
setActiveAnchor,
|
|
814
|
-
show,
|
|
815
|
-
tooltipHideDelayTimerRef,
|
|
816
|
-
tooltipRef,
|
|
817
|
-
tooltipShowDelayTimerRef,
|
|
818
|
-
updateTooltipPosition,
|
|
819
|
-
hoveringTooltip,
|
|
820
|
-
]);
|
|
1015
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1016
|
+
}, [actualGlobalCloseEvents, activeAnchor]);
|
|
821
1017
|
};
|
|
822
1018
|
|
|
1019
|
+
// Shared across all tooltip instances — the CSS variable is on :root and never changes per-instance
|
|
1020
|
+
let globalTransitionShowDelay = null;
|
|
823
1021
|
const Tooltip = ({
|
|
824
1022
|
// props
|
|
825
|
-
forwardRef, id, className, classNameArrow, variant = 'dark', portalRoot, anchorSelect, place = 'top', offset = 10, openOnClick = false, positionStrategy = 'absolute', middlewares, wrapper: WrapperElement, delayShow = 0, delayHide = 0, float = false, hidden = false, noArrow = false, clickable = false, openEvents, closeEvents, globalCloseEvents, imperativeModeOnly, style: externalStyles, position, afterShow, afterHide, disableTooltip,
|
|
1023
|
+
forwardRef, id, className, classNameArrow, variant = 'dark', portalRoot, anchorSelect, place = 'top', offset = 10, openOnClick = false, positionStrategy = 'absolute', middlewares, wrapper: WrapperElement, delayShow = 0, delayHide = 0, autoClose, float = false, hidden = false, noArrow = false, clickable = false, openEvents, closeEvents, globalCloseEvents, imperativeModeOnly, style: externalStyles, position, afterShow, afterHide, disableTooltip,
|
|
826
1024
|
// props handled by controller
|
|
827
1025
|
content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, previousActiveAnchor, activeAnchor, setActiveAnchor, border, opacity, arrowColor, arrowSize = 8, role = 'tooltip', }) => {
|
|
828
1026
|
var _a;
|
|
@@ -830,6 +1028,7 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, previousAc
|
|
|
830
1028
|
const tooltipArrowRef = React.useRef(null);
|
|
831
1029
|
const tooltipShowDelayTimerRef = React.useRef(null);
|
|
832
1030
|
const tooltipHideDelayTimerRef = React.useRef(null);
|
|
1031
|
+
const tooltipAutoCloseTimerRef = React.useRef(null);
|
|
833
1032
|
const missedTransitionTimerRef = React.useRef(null);
|
|
834
1033
|
const [computedPosition, setComputedPosition] = React.useState({
|
|
835
1034
|
tooltipStyles: {},
|
|
@@ -843,6 +1042,18 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, previousAc
|
|
|
843
1042
|
const lastFloatPosition = React.useRef(null);
|
|
844
1043
|
const hoveringTooltip = React.useRef(false);
|
|
845
1044
|
const mounted = React.useRef(false);
|
|
1045
|
+
const virtualElementRef = React.useRef({
|
|
1046
|
+
getBoundingClientRect: () => ({
|
|
1047
|
+
x: 0,
|
|
1048
|
+
y: 0,
|
|
1049
|
+
width: 0,
|
|
1050
|
+
height: 0,
|
|
1051
|
+
top: 0,
|
|
1052
|
+
left: 0,
|
|
1053
|
+
right: 0,
|
|
1054
|
+
bottom: 0,
|
|
1055
|
+
}),
|
|
1056
|
+
});
|
|
846
1057
|
/**
|
|
847
1058
|
* useLayoutEffect runs before useEffect,
|
|
848
1059
|
* but should be used carefully because of caveats
|
|
@@ -903,7 +1114,6 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, previousAc
|
|
|
903
1114
|
else {
|
|
904
1115
|
removeAriaDescribedBy(activeAnchor);
|
|
905
1116
|
}
|
|
906
|
-
// eslint-disable-next-line consistent-return
|
|
907
1117
|
return () => {
|
|
908
1118
|
// cleanup aria-describedby when the tooltip is closed
|
|
909
1119
|
removeAriaDescribedBy(activeAnchor);
|
|
@@ -941,8 +1151,11 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, previousAc
|
|
|
941
1151
|
/**
|
|
942
1152
|
* see `onTransitionEnd` on tooltip wrapper
|
|
943
1153
|
*/
|
|
944
|
-
|
|
945
|
-
|
|
1154
|
+
if (globalTransitionShowDelay === null) {
|
|
1155
|
+
const style = getComputedStyle(document.body);
|
|
1156
|
+
globalTransitionShowDelay = cssTimeToMs(style.getPropertyValue('--rt-transition-show-delay'));
|
|
1157
|
+
}
|
|
1158
|
+
const transitionShowDelay = globalTransitionShowDelay;
|
|
946
1159
|
missedTransitionTimerRef.current = setTimeout(() => {
|
|
947
1160
|
/**
|
|
948
1161
|
* if the tooltip switches from `show === true` to `show === false` too fast
|
|
@@ -955,13 +1168,44 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, previousAc
|
|
|
955
1168
|
}, transitionShowDelay + 25);
|
|
956
1169
|
}
|
|
957
1170
|
}, [afterHide, afterShow, show]);
|
|
1171
|
+
React.useEffect(() => {
|
|
1172
|
+
clearTimeoutRef(tooltipAutoCloseTimerRef);
|
|
1173
|
+
if (!show || !autoClose || autoClose <= 0) {
|
|
1174
|
+
return () => {
|
|
1175
|
+
clearTimeoutRef(tooltipAutoCloseTimerRef);
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
tooltipAutoCloseTimerRef.current = setTimeout(() => {
|
|
1179
|
+
handleShow(false);
|
|
1180
|
+
}, autoClose);
|
|
1181
|
+
return () => {
|
|
1182
|
+
clearTimeoutRef(tooltipAutoCloseTimerRef);
|
|
1183
|
+
};
|
|
1184
|
+
}, [activeAnchor, autoClose, handleShow, show]);
|
|
958
1185
|
const handleComputedPosition = React.useCallback((newComputedPosition) => {
|
|
959
1186
|
if (!mounted.current) {
|
|
960
1187
|
return;
|
|
961
1188
|
}
|
|
962
|
-
setComputedPosition((oldComputedPosition) =>
|
|
963
|
-
|
|
964
|
-
|
|
1189
|
+
setComputedPosition((oldComputedPosition) => {
|
|
1190
|
+
if (oldComputedPosition.place === newComputedPosition.place &&
|
|
1191
|
+
oldComputedPosition.tooltipStyles.left === newComputedPosition.tooltipStyles.left &&
|
|
1192
|
+
oldComputedPosition.tooltipStyles.top === newComputedPosition.tooltipStyles.top &&
|
|
1193
|
+
oldComputedPosition.tooltipStyles.border === newComputedPosition.tooltipStyles.border &&
|
|
1194
|
+
oldComputedPosition.tooltipArrowStyles.left ===
|
|
1195
|
+
newComputedPosition.tooltipArrowStyles.left &&
|
|
1196
|
+
oldComputedPosition.tooltipArrowStyles.top === newComputedPosition.tooltipArrowStyles.top &&
|
|
1197
|
+
oldComputedPosition.tooltipArrowStyles.right ===
|
|
1198
|
+
newComputedPosition.tooltipArrowStyles.right &&
|
|
1199
|
+
oldComputedPosition.tooltipArrowStyles.bottom ===
|
|
1200
|
+
newComputedPosition.tooltipArrowStyles.bottom &&
|
|
1201
|
+
oldComputedPosition.tooltipArrowStyles.borderBottom ===
|
|
1202
|
+
newComputedPosition.tooltipArrowStyles.borderBottom &&
|
|
1203
|
+
oldComputedPosition.tooltipArrowStyles.borderRight ===
|
|
1204
|
+
newComputedPosition.tooltipArrowStyles.borderRight) {
|
|
1205
|
+
return oldComputedPosition;
|
|
1206
|
+
}
|
|
1207
|
+
return newComputedPosition;
|
|
1208
|
+
});
|
|
965
1209
|
}, []);
|
|
966
1210
|
const handleShowTooltipDelayed = React.useCallback((delay = delayShow) => {
|
|
967
1211
|
if (tooltipShowDelayTimerRef.current) {
|
|
@@ -989,24 +1233,20 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, previousAc
|
|
|
989
1233
|
}, [delayHide, handleShow]);
|
|
990
1234
|
const handleTooltipPosition = React.useCallback(({ x, y }) => {
|
|
991
1235
|
var _a;
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
bottom: y,
|
|
1003
|
-
};
|
|
1004
|
-
},
|
|
1005
|
-
};
|
|
1236
|
+
virtualElementRef.current.getBoundingClientRect = () => ({
|
|
1237
|
+
x,
|
|
1238
|
+
y,
|
|
1239
|
+
width: 0,
|
|
1240
|
+
height: 0,
|
|
1241
|
+
top: y,
|
|
1242
|
+
left: x,
|
|
1243
|
+
right: x,
|
|
1244
|
+
bottom: y,
|
|
1245
|
+
});
|
|
1006
1246
|
computeTooltipPosition({
|
|
1007
1247
|
place: (_a = imperativeOptions === null || imperativeOptions === void 0 ? void 0 : imperativeOptions.place) !== null && _a !== void 0 ? _a : place,
|
|
1008
1248
|
offset,
|
|
1009
|
-
elementReference:
|
|
1249
|
+
elementReference: virtualElementRef.current,
|
|
1010
1250
|
tooltipReference: tooltipRef.current,
|
|
1011
1251
|
tooltipArrowReference: tooltipArrowRef.current,
|
|
1012
1252
|
strategy: positionStrategy,
|
|
@@ -1089,18 +1329,26 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, previousAc
|
|
|
1089
1329
|
setActiveAnchor(null);
|
|
1090
1330
|
clearTimeoutRef(tooltipShowDelayTimerRef);
|
|
1091
1331
|
clearTimeoutRef(tooltipHideDelayTimerRef);
|
|
1332
|
+
clearTimeoutRef(tooltipAutoCloseTimerRef);
|
|
1092
1333
|
}, [handleShow, setActiveAnchor]);
|
|
1093
|
-
const
|
|
1334
|
+
const shouldTrackAnchors = rendered ||
|
|
1335
|
+
defaultIsOpen ||
|
|
1336
|
+
Boolean(isOpen) ||
|
|
1337
|
+
Boolean(activeAnchor) ||
|
|
1338
|
+
Boolean(imperativeOptions === null || imperativeOptions === void 0 ? void 0 : imperativeOptions.anchorSelect);
|
|
1339
|
+
const { anchorElements, selector: anchorSelector } = useTooltipAnchors({
|
|
1094
1340
|
id,
|
|
1095
1341
|
anchorSelect,
|
|
1096
1342
|
imperativeAnchorSelect: imperativeOptions === null || imperativeOptions === void 0 ? void 0 : imperativeOptions.anchorSelect,
|
|
1097
1343
|
activeAnchor,
|
|
1098
1344
|
disableTooltip,
|
|
1099
1345
|
onActiveAnchorRemoved: handleActiveAnchorRemoved,
|
|
1346
|
+
trackAnchors: shouldTrackAnchors,
|
|
1100
1347
|
});
|
|
1101
1348
|
useTooltipEvents({
|
|
1102
1349
|
activeAnchor,
|
|
1103
1350
|
anchorElements,
|
|
1351
|
+
anchorSelector,
|
|
1104
1352
|
clickable,
|
|
1105
1353
|
closeEvents,
|
|
1106
1354
|
delayHide,
|
|
@@ -1124,11 +1372,16 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, previousAc
|
|
|
1124
1372
|
tooltipShowDelayTimerRef,
|
|
1125
1373
|
updateTooltipPosition,
|
|
1126
1374
|
});
|
|
1375
|
+
const updateTooltipPositionRef = React.useRef(updateTooltipPosition);
|
|
1376
|
+
updateTooltipPositionRef.current = updateTooltipPosition;
|
|
1127
1377
|
React.useEffect(() => {
|
|
1378
|
+
if (!rendered) {
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1128
1381
|
updateTooltipPosition();
|
|
1129
|
-
}, [updateTooltipPosition]);
|
|
1382
|
+
}, [rendered, updateTooltipPosition]);
|
|
1130
1383
|
React.useEffect(() => {
|
|
1131
|
-
if (!(contentWrapperRef === null || contentWrapperRef === void 0 ? void 0 : contentWrapperRef.current)) {
|
|
1384
|
+
if (!rendered || !(contentWrapperRef === null || contentWrapperRef === void 0 ? void 0 : contentWrapperRef.current)) {
|
|
1132
1385
|
return () => null;
|
|
1133
1386
|
}
|
|
1134
1387
|
let timeoutId = null;
|
|
@@ -1139,7 +1392,7 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, previousAc
|
|
|
1139
1392
|
}
|
|
1140
1393
|
timeoutId = setTimeout(() => {
|
|
1141
1394
|
if (mounted.current) {
|
|
1142
|
-
|
|
1395
|
+
updateTooltipPositionRef.current();
|
|
1143
1396
|
}
|
|
1144
1397
|
timeoutId = null;
|
|
1145
1398
|
}, 0);
|
|
@@ -1151,9 +1404,13 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, previousAc
|
|
|
1151
1404
|
clearTimeout(timeoutId);
|
|
1152
1405
|
}
|
|
1153
1406
|
};
|
|
1154
|
-
}, [content, contentWrapperRef,
|
|
1407
|
+
}, [content, contentWrapperRef, rendered]);
|
|
1155
1408
|
React.useEffect(() => {
|
|
1156
1409
|
var _a;
|
|
1410
|
+
const shouldResolveInitialActiveAnchor = rendered || defaultIsOpen || Boolean(isOpen);
|
|
1411
|
+
if (!shouldResolveInitialActiveAnchor) {
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1157
1414
|
const activeAnchorMatchesImperativeSelector = (() => {
|
|
1158
1415
|
if (!activeAnchor || !(imperativeOptions === null || imperativeOptions === void 0 ? void 0 : imperativeOptions.anchorSelect)) {
|
|
1159
1416
|
return false;
|
|
@@ -1176,7 +1433,15 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, previousAc
|
|
|
1176
1433
|
}
|
|
1177
1434
|
setActiveAnchor((_a = anchorElements[0]) !== null && _a !== void 0 ? _a : null);
|
|
1178
1435
|
}
|
|
1179
|
-
}, [
|
|
1436
|
+
}, [
|
|
1437
|
+
activeAnchor,
|
|
1438
|
+
anchorElements,
|
|
1439
|
+
defaultIsOpen,
|
|
1440
|
+
imperativeOptions === null || imperativeOptions === void 0 ? void 0 : imperativeOptions.anchorSelect,
|
|
1441
|
+
isOpen,
|
|
1442
|
+
rendered,
|
|
1443
|
+
setActiveAnchor,
|
|
1444
|
+
]);
|
|
1180
1445
|
React.useEffect(() => {
|
|
1181
1446
|
if (defaultIsOpen) {
|
|
1182
1447
|
handleShow(true);
|
|
@@ -1184,6 +1449,7 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, previousAc
|
|
|
1184
1449
|
return () => {
|
|
1185
1450
|
clearTimeoutRef(tooltipShowDelayTimerRef);
|
|
1186
1451
|
clearTimeoutRef(tooltipHideDelayTimerRef);
|
|
1452
|
+
clearTimeoutRef(tooltipAutoCloseTimerRef);
|
|
1187
1453
|
clearTimeoutRef(missedTransitionTimerRef);
|
|
1188
1454
|
};
|
|
1189
1455
|
}, [defaultIsOpen, handleShow]);
|
|
@@ -1199,7 +1465,20 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, previousAc
|
|
|
1199
1465
|
}, [delayShow, handleShowTooltipDelayed]);
|
|
1200
1466
|
const actualContent = (_a = imperativeOptions === null || imperativeOptions === void 0 ? void 0 : imperativeOptions.content) !== null && _a !== void 0 ? _a : content;
|
|
1201
1467
|
const hasContent = actualContent !== null && actualContent !== undefined;
|
|
1202
|
-
const canShow = show &&
|
|
1468
|
+
const canShow = show && computedPosition.tooltipStyles.left !== undefined;
|
|
1469
|
+
const tooltipStyle = React.useMemo(() => ({
|
|
1470
|
+
...externalStyles,
|
|
1471
|
+
...computedPosition.tooltipStyles,
|
|
1472
|
+
opacity: opacity !== undefined && canShow ? opacity : undefined,
|
|
1473
|
+
}), [externalStyles, computedPosition.tooltipStyles, opacity, canShow]);
|
|
1474
|
+
const arrowBackground = React.useMemo(() => arrowColor
|
|
1475
|
+
? `linear-gradient(to right bottom, transparent 50%, ${arrowColor} 50%)`
|
|
1476
|
+
: undefined, [arrowColor]);
|
|
1477
|
+
const arrowStyle = React.useMemo(() => ({
|
|
1478
|
+
...computedPosition.tooltipArrowStyles,
|
|
1479
|
+
background: arrowBackground,
|
|
1480
|
+
'--rt-arrow-size': `${arrowSize}px`,
|
|
1481
|
+
}), [computedPosition.tooltipArrowStyles, arrowBackground, arrowSize]);
|
|
1203
1482
|
React.useImperativeHandle(forwardRef, () => ({
|
|
1204
1483
|
open: (options) => {
|
|
1205
1484
|
let imperativeAnchor = null;
|
|
@@ -1242,6 +1521,7 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, previousAc
|
|
|
1242
1521
|
// Final cleanup to ensure no memory leaks
|
|
1243
1522
|
clearTimeoutRef(tooltipShowDelayTimerRef);
|
|
1244
1523
|
clearTimeoutRef(tooltipHideDelayTimerRef);
|
|
1524
|
+
clearTimeoutRef(tooltipAutoCloseTimerRef);
|
|
1245
1525
|
clearTimeoutRef(missedTransitionTimerRef);
|
|
1246
1526
|
};
|
|
1247
1527
|
}, []);
|
|
@@ -1253,19 +1533,9 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, previousAc
|
|
|
1253
1533
|
setRendered(false);
|
|
1254
1534
|
setImperativeOptions(null);
|
|
1255
1535
|
afterHide === null || afterHide === void 0 ? void 0 : afterHide();
|
|
1256
|
-
}, style:
|
|
1257
|
-
...externalStyles,
|
|
1258
|
-
...computedPosition.tooltipStyles,
|
|
1259
|
-
opacity: opacity !== undefined && canShow ? opacity : undefined,
|
|
1260
|
-
}, ref: tooltipRef },
|
|
1536
|
+
}, style: tooltipStyle, ref: tooltipRef },
|
|
1261
1537
|
React.createElement(WrapperElement, { className: clsx('react-tooltip-content-wrapper', coreStyles['content'], styles['content']) }, actualContent),
|
|
1262
|
-
React.createElement(WrapperElement, { className: clsx('react-tooltip-arrow', coreStyles['arrow'], styles['arrow'], classNameArrow, noArrow && coreStyles['noArrow']), style:
|
|
1263
|
-
...computedPosition.tooltipArrowStyles,
|
|
1264
|
-
background: arrowColor
|
|
1265
|
-
? `linear-gradient(to right bottom, transparent 50%, ${arrowColor} 50%)`
|
|
1266
|
-
: undefined,
|
|
1267
|
-
'--rt-arrow-size': `${arrowSize}px`,
|
|
1268
|
-
}, ref: tooltipArrowRef }))) : null;
|
|
1538
|
+
React.createElement(WrapperElement, { className: clsx('react-tooltip-arrow', coreStyles['arrow'], styles['arrow'], classNameArrow, noArrow && coreStyles['noArrow']), style: arrowStyle, ref: tooltipArrowRef }))) : null;
|
|
1269
1539
|
if (!tooltipNode) {
|
|
1270
1540
|
return null;
|
|
1271
1541
|
}
|
|
@@ -1276,12 +1546,82 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, previousAc
|
|
|
1276
1546
|
};
|
|
1277
1547
|
var Tooltip$1 = React.memo(Tooltip);
|
|
1278
1548
|
|
|
1279
|
-
|
|
1549
|
+
/**
|
|
1550
|
+
* Shared MutationObserver for data-tooltip-* attribute changes.
|
|
1551
|
+
* Instead of N observers (one per tooltip), a single observer watches
|
|
1552
|
+
* all active anchors and dispatches changes to registered callbacks.
|
|
1553
|
+
*/
|
|
1554
|
+
const observedElements = new Map();
|
|
1555
|
+
let sharedObserver = null;
|
|
1556
|
+
const observerConfig = {
|
|
1557
|
+
attributes: true,
|
|
1558
|
+
childList: false,
|
|
1559
|
+
subtree: false,
|
|
1560
|
+
};
|
|
1561
|
+
function getObserver() {
|
|
1562
|
+
if (!sharedObserver) {
|
|
1563
|
+
sharedObserver = new MutationObserver((mutationList) => {
|
|
1564
|
+
var _a;
|
|
1565
|
+
for (const mutation of mutationList) {
|
|
1566
|
+
if (mutation.type !== 'attributes' ||
|
|
1567
|
+
!((_a = mutation.attributeName) === null || _a === void 0 ? void 0 : _a.startsWith('data-tooltip-'))) {
|
|
1568
|
+
continue;
|
|
1569
|
+
}
|
|
1570
|
+
const target = mutation.target;
|
|
1571
|
+
const callbacks = observedElements.get(target);
|
|
1572
|
+
if (callbacks) {
|
|
1573
|
+
callbacks.forEach((cb) => cb(target));
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
return sharedObserver;
|
|
1579
|
+
}
|
|
1580
|
+
function observeAnchorAttributes(element, callback) {
|
|
1581
|
+
const observer = getObserver();
|
|
1582
|
+
let callbacks = observedElements.get(element);
|
|
1583
|
+
if (!callbacks) {
|
|
1584
|
+
callbacks = new Set();
|
|
1585
|
+
observedElements.set(element, callbacks);
|
|
1586
|
+
observer.observe(element, observerConfig);
|
|
1587
|
+
}
|
|
1588
|
+
callbacks.add(callback);
|
|
1589
|
+
return () => {
|
|
1590
|
+
const cbs = observedElements.get(element);
|
|
1591
|
+
if (cbs) {
|
|
1592
|
+
cbs.delete(callback);
|
|
1593
|
+
if (cbs.size === 0) {
|
|
1594
|
+
observedElements.delete(element);
|
|
1595
|
+
// MutationObserver doesn't have unobserve — if no elements left, disconnect & reset
|
|
1596
|
+
if (observedElements.size === 0) {
|
|
1597
|
+
observer.disconnect();
|
|
1598
|
+
}
|
|
1599
|
+
else {
|
|
1600
|
+
// Re-observe remaining elements (MutationObserver has no per-target unobserve)
|
|
1601
|
+
observer.disconnect();
|
|
1602
|
+
observedElements.forEach((_cbs, el) => {
|
|
1603
|
+
observer.observe(el, observerConfig);
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
const TooltipController = React.forwardRef(({ id, anchorSelect, content, render, className, classNameArrow, variant = 'dark', portalRoot, place = 'top', offset = 10, wrapper = 'div', children = null, openOnClick = false, positionStrategy = 'absolute', middlewares, delayShow = 0, delayHide = 0, autoClose, float = false, hidden = false, noArrow = false, clickable = false, openEvents, closeEvents, globalCloseEvents, imperativeModeOnly = false, style, position, isOpen, defaultIsOpen = false, disableStyleInjection = false, border, opacity, arrowColor, arrowSize, setIsOpen, afterShow, afterHide, disableTooltip, role = 'tooltip', }, ref) => {
|
|
1280
1612
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1281
1613
|
const [activeAnchor, setActiveAnchor] = React.useState(null);
|
|
1282
1614
|
const [anchorDataAttributes, setAnchorDataAttributes] = React.useState({});
|
|
1283
1615
|
const previousActiveAnchorRef = React.useRef(null);
|
|
1284
1616
|
const styleInjectionRef = React.useRef(disableStyleInjection);
|
|
1617
|
+
const handleSetActiveAnchor = React.useCallback((anchor) => {
|
|
1618
|
+
setActiveAnchor((prev) => {
|
|
1619
|
+
if (!(anchor === null || anchor === void 0 ? void 0 : anchor.isSameNode(prev))) {
|
|
1620
|
+
previousActiveAnchorRef.current = prev;
|
|
1621
|
+
}
|
|
1622
|
+
return anchor;
|
|
1623
|
+
});
|
|
1624
|
+
}, []);
|
|
1285
1625
|
/* c8 ignore start */
|
|
1286
1626
|
const getDataAttributesFromAnchorElement = (elementReference) => {
|
|
1287
1627
|
const dataAttributes = elementReference === null || elementReference === void 0 ? void 0 : elementReference.getAttributeNames().reduce((acc, name) => {
|
|
@@ -1313,37 +1653,24 @@ const TooltipController = React.forwardRef(({ id, anchorSelect, content, render,
|
|
|
1313
1653
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1314
1654
|
}, []);
|
|
1315
1655
|
React.useEffect(() => {
|
|
1316
|
-
|
|
1317
|
-
mutationList.forEach((mutation) => {
|
|
1318
|
-
var _a;
|
|
1319
|
-
if (!activeAnchor ||
|
|
1320
|
-
mutation.type !== 'attributes' ||
|
|
1321
|
-
!((_a = mutation.attributeName) === null || _a === void 0 ? void 0 : _a.startsWith('data-tooltip-'))) {
|
|
1322
|
-
return;
|
|
1323
|
-
}
|
|
1324
|
-
// make sure to get all set attributes, since all unset attributes are reset
|
|
1325
|
-
const dataAttributes = getDataAttributesFromAnchorElement(activeAnchor);
|
|
1326
|
-
setAnchorDataAttributes(dataAttributes);
|
|
1327
|
-
});
|
|
1328
|
-
};
|
|
1329
|
-
// Create an observer instance linked to the callback function
|
|
1330
|
-
const observer = new MutationObserver(observerCallback);
|
|
1331
|
-
// do not check for subtree and childrens, we only want to know attribute changes
|
|
1332
|
-
// to stay watching `data-attributes-*` from anchor element
|
|
1333
|
-
const observerConfig = { attributes: true, childList: false, subtree: false };
|
|
1334
|
-
if (activeAnchor) {
|
|
1335
|
-
const dataAttributes = getDataAttributesFromAnchorElement(activeAnchor);
|
|
1336
|
-
setAnchorDataAttributes(dataAttributes);
|
|
1337
|
-
// Start observing the target node for configured mutations
|
|
1338
|
-
observer.observe(activeAnchor, observerConfig);
|
|
1339
|
-
}
|
|
1340
|
-
else {
|
|
1656
|
+
if (!activeAnchor) {
|
|
1341
1657
|
setAnchorDataAttributes({});
|
|
1658
|
+
return () => { };
|
|
1342
1659
|
}
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1660
|
+
const updateAttributes = (element) => {
|
|
1661
|
+
const attrs = getDataAttributesFromAnchorElement(element);
|
|
1662
|
+
setAnchorDataAttributes((prev) => {
|
|
1663
|
+
const keys = Object.keys(attrs);
|
|
1664
|
+
const prevKeys = Object.keys(prev);
|
|
1665
|
+
if (keys.length === prevKeys.length && keys.every((key) => attrs[key] === prev[key])) {
|
|
1666
|
+
return prev;
|
|
1667
|
+
}
|
|
1668
|
+
return attrs;
|
|
1669
|
+
});
|
|
1346
1670
|
};
|
|
1671
|
+
updateAttributes(activeAnchor);
|
|
1672
|
+
const unsubscribe = observeAnchorAttributes(activeAnchor, updateAttributes);
|
|
1673
|
+
return unsubscribe;
|
|
1347
1674
|
}, [activeAnchor, anchorSelect]);
|
|
1348
1675
|
React.useEffect(() => {
|
|
1349
1676
|
/* c8 ignore start */
|
|
@@ -1367,6 +1694,9 @@ const TooltipController = React.forwardRef(({ id, anchorSelect, content, render,
|
|
|
1367
1694
|
const tooltipDelayHide = anchorDataAttributes['delay-hide'] == null
|
|
1368
1695
|
? delayHide
|
|
1369
1696
|
: Number(anchorDataAttributes['delay-hide']);
|
|
1697
|
+
const tooltipAutoClose = anchorDataAttributes['auto-close'] == null
|
|
1698
|
+
? autoClose
|
|
1699
|
+
: Number(anchorDataAttributes['auto-close']);
|
|
1370
1700
|
const tooltipFloat = anchorDataAttributes.float == null ? float : anchorDataAttributes.float === 'true';
|
|
1371
1701
|
const tooltipHidden = anchorDataAttributes.hidden == null ? hidden : anchorDataAttributes.hidden === 'true';
|
|
1372
1702
|
const tooltipClassName = (_f = anchorDataAttributes['class-name']) !== null && _f !== void 0 ? _f : null;
|
|
@@ -1398,6 +1728,7 @@ const TooltipController = React.forwardRef(({ id, anchorSelect, content, render,
|
|
|
1398
1728
|
middlewares,
|
|
1399
1729
|
delayShow: tooltipDelayShow,
|
|
1400
1730
|
delayHide: tooltipDelayHide,
|
|
1731
|
+
autoClose: tooltipAutoClose,
|
|
1401
1732
|
float: tooltipFloat,
|
|
1402
1733
|
hidden: tooltipHidden,
|
|
1403
1734
|
noArrow,
|
|
@@ -1420,19 +1751,12 @@ const TooltipController = React.forwardRef(({ id, anchorSelect, content, render,
|
|
|
1420
1751
|
disableTooltip,
|
|
1421
1752
|
activeAnchor,
|
|
1422
1753
|
previousActiveAnchor: previousActiveAnchorRef.current,
|
|
1423
|
-
setActiveAnchor:
|
|
1424
|
-
setActiveAnchor((prev) => {
|
|
1425
|
-
if (!(anchor === null || anchor === void 0 ? void 0 : anchor.isSameNode(prev))) {
|
|
1426
|
-
previousActiveAnchorRef.current = prev;
|
|
1427
|
-
}
|
|
1428
|
-
return anchor;
|
|
1429
|
-
});
|
|
1430
|
-
},
|
|
1754
|
+
setActiveAnchor: handleSetActiveAnchor,
|
|
1431
1755
|
role,
|
|
1432
1756
|
};
|
|
1433
1757
|
return React.createElement(Tooltip$1, { ...props });
|
|
1434
1758
|
});
|
|
1435
|
-
var
|
|
1759
|
+
var TooltipController_default = React.memo(TooltipController);
|
|
1436
1760
|
|
|
1437
1761
|
// those content will be replaced in build time with the `react-tooltip.css` builded content
|
|
1438
1762
|
const TooltipCoreStyles = `:root {
|
|
@@ -1454,7 +1778,6 @@ const TooltipCoreStyles = `:root {
|
|
|
1454
1778
|
left: 0;
|
|
1455
1779
|
pointer-events: none;
|
|
1456
1780
|
opacity: 0;
|
|
1457
|
-
will-change: opacity;
|
|
1458
1781
|
}
|
|
1459
1782
|
|
|
1460
1783
|
.core-styles-module_fixed__pcSol {
|
|
@@ -1485,11 +1808,13 @@ const TooltipCoreStyles = `:root {
|
|
|
1485
1808
|
.core-styles-module_show__Nt9eE {
|
|
1486
1809
|
opacity: var(--rt-opacity);
|
|
1487
1810
|
transition: opacity var(--rt-transition-show-delay) ease-out;
|
|
1811
|
+
will-change: opacity;
|
|
1488
1812
|
}
|
|
1489
1813
|
|
|
1490
1814
|
.core-styles-module_closing__sGnxF {
|
|
1491
1815
|
opacity: 0;
|
|
1492
1816
|
transition: opacity var(--rt-transition-closing-delay) ease-in;
|
|
1817
|
+
will-change: opacity;
|
|
1493
1818
|
}
|
|
1494
1819
|
|
|
1495
1820
|
`;
|
|
@@ -1570,5 +1895,5 @@ if (typeof window !== 'undefined') {
|
|
|
1570
1895
|
}));
|
|
1571
1896
|
}
|
|
1572
1897
|
|
|
1573
|
-
exports.Tooltip =
|
|
1898
|
+
exports.Tooltip = TooltipController_default;
|
|
1574
1899
|
//# sourceMappingURL=react-tooltip.cjs.map
|