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