react-tooltip 6.0.0-beta.1179.rc.2 → 6.0.0-beta.1179.rc.21
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/{CONTRIBUTION.md → CONTRIBUTING.md} +0 -1
- package/README.md +30 -2
- package/dist/react-tooltip-tokens.css +1 -0
- package/dist/react-tooltip.cjs +1302 -798
- package/dist/react-tooltip.cjs.map +1 -1
- package/dist/react-tooltip.css +19 -4
- package/dist/react-tooltip.d.ts +19 -21
- 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 +1303 -799
- package/dist/react-tooltip.mjs.map +1 -1
- package/dist/react-tooltip.umd.js +1304 -801
- 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 +70 -58
- package/.eslintrc.json +0 -97
- package/.gitattributes +0 -3
- package/.prettierrc.json +0 -10
- package/.stylelintrc.json +0 -19
- package/beta-release.js +0 -81
- package/rollup.config.dev.mjs +0 -88
- package/rollup.config.prod.mjs +0 -126
- package/rollup.config.types.mjs +0 -21
- package/tsconfig.json +0 -109
package/dist/react-tooltip.mjs
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
* @copyright ReactTooltip Team
|
|
6
6
|
* @license MIT
|
|
7
7
|
*/
|
|
8
|
-
import React, { useLayoutEffect, useEffect, useRef,
|
|
9
|
-
import { arrow, computePosition, offset, flip, shift, autoUpdate } from '@floating-ui/dom';
|
|
8
|
+
import React, { useLayoutEffect, useEffect, useState, useRef, useMemo, memo, useCallback, useImperativeHandle } from 'react';
|
|
10
9
|
import clsx from 'clsx';
|
|
10
|
+
import { createPortal } from 'react-dom';
|
|
11
|
+
import { flip, shift, arrow, computePosition, offset, autoUpdate } from '@floating-ui/dom';
|
|
11
12
|
|
|
12
13
|
// This is the ID for the core styles of ReactTooltip
|
|
13
14
|
const REACT_TOOLTIP_CORE_STYLES_ID = 'react-tooltip-core-styles';
|
|
@@ -17,27 +18,33 @@ const injected = {
|
|
|
17
18
|
core: false,
|
|
18
19
|
base: false,
|
|
19
20
|
};
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Note about `state` parameter:
|
|
23
|
+
* This parameter is used to keep track of the state of the styles
|
|
24
|
+
* into the tests since the const `injected` is not acessible or resettable in the tests
|
|
25
|
+
*/
|
|
26
|
+
function injectStyle({ css, id = REACT_TOOLTIP_BASE_STYLES_ID, type = 'base', ref, state = {}, }) {
|
|
27
|
+
if (!css ||
|
|
28
|
+
typeof document === 'undefined' ||
|
|
29
|
+
(typeof state[type] !== 'undefined' ? state[type] : injected[type])) {
|
|
23
30
|
return;
|
|
24
31
|
}
|
|
25
32
|
if (type === 'core' &&
|
|
26
33
|
typeof process !== 'undefined' && // this validation prevents docs from breaking even with `process?`
|
|
27
|
-
|
|
34
|
+
process.env &&
|
|
35
|
+
process.env.REACT_TOOLTIP_DISABLE_CORE_STYLES) {
|
|
28
36
|
return;
|
|
29
37
|
}
|
|
30
|
-
if (type
|
|
38
|
+
if (type === 'base' &&
|
|
31
39
|
typeof process !== 'undefined' && // this validation prevents docs from breaking even with `process?`
|
|
32
|
-
|
|
40
|
+
process.env &&
|
|
41
|
+
process.env.REACT_TOOLTIP_DISABLE_BASE_STYLES) {
|
|
33
42
|
return;
|
|
34
43
|
}
|
|
35
44
|
if (type === 'core') {
|
|
36
|
-
// eslint-disable-next-line no-param-reassign
|
|
37
45
|
id = REACT_TOOLTIP_CORE_STYLES_ID;
|
|
38
46
|
}
|
|
39
47
|
if (!ref) {
|
|
40
|
-
// eslint-disable-next-line no-param-reassign
|
|
41
48
|
ref = {};
|
|
42
49
|
}
|
|
43
50
|
const { insertAt } = ref;
|
|
@@ -67,26 +74,27 @@ function injectStyle({ css, id = REACT_TOOLTIP_BASE_STYLES_ID, type = 'base', re
|
|
|
67
74
|
else {
|
|
68
75
|
style.appendChild(document.createTextNode(css));
|
|
69
76
|
}
|
|
70
|
-
|
|
77
|
+
if (typeof state[type] !== 'undefined') {
|
|
78
|
+
state[type] = true;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
injected[type] = true; // internal global state that jest doesn't have access
|
|
82
|
+
}
|
|
71
83
|
}
|
|
72
84
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}),
|
|
78
|
-
shift({ padding: 5 }),
|
|
79
|
-
], border, }) => {
|
|
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, }) => {
|
|
80
89
|
if (!elementReference) {
|
|
81
90
|
// elementReference can be null or undefined and we will not compute the position
|
|
82
|
-
// eslint-disable-next-line no-console
|
|
83
91
|
// console.error('The reference element for tooltip was not defined: ', elementReference)
|
|
84
92
|
return { tooltipStyles: {}, tooltipArrowStyles: {}, place };
|
|
85
93
|
}
|
|
86
94
|
if (tooltipReference === null) {
|
|
87
95
|
return { tooltipStyles: {}, tooltipArrowStyles: {}, place };
|
|
88
96
|
}
|
|
89
|
-
const middleware = middlewares;
|
|
97
|
+
const middleware = [...middlewares];
|
|
90
98
|
if (tooltipArrowReference) {
|
|
91
99
|
middleware.push(arrow({ element: tooltipArrowReference, padding: 5 }));
|
|
92
100
|
return computePosition(elementReference, tooltipReference, {
|
|
@@ -130,7 +138,7 @@ const computeTooltipPosition = async ({ elementReference = null, tooltipReferenc
|
|
|
130
138
|
right: '',
|
|
131
139
|
bottom: '',
|
|
132
140
|
...borderSide,
|
|
133
|
-
[staticSide]: `-${
|
|
141
|
+
[staticSide]: `-${arrowSize / 2 + borderWidth - 1}px`,
|
|
134
142
|
};
|
|
135
143
|
/* c8 ignore end */
|
|
136
144
|
return { tooltipStyles: styles, tooltipArrowStyles: arrowStyle, place: placement };
|
|
@@ -164,6 +172,7 @@ const cssTimeToMs = (time) => {
|
|
|
164
172
|
*/
|
|
165
173
|
const debounce = (func, wait, immediate) => {
|
|
166
174
|
let timeout = null;
|
|
175
|
+
let currentFunc = func;
|
|
167
176
|
const debounced = function debounced(...args) {
|
|
168
177
|
const later = () => {
|
|
169
178
|
timeout = null;
|
|
@@ -173,7 +182,7 @@ const debounce = (func, wait, immediate) => {
|
|
|
173
182
|
* there's no need to clear the timeout
|
|
174
183
|
* since we expect it to resolve and set `timeout = null`
|
|
175
184
|
*/
|
|
176
|
-
|
|
185
|
+
currentFunc.apply(this, args);
|
|
177
186
|
timeout = setTimeout(later, wait);
|
|
178
187
|
}
|
|
179
188
|
};
|
|
@@ -186,36 +195,12 @@ const debounce = (func, wait, immediate) => {
|
|
|
186
195
|
clearTimeout(timeout);
|
|
187
196
|
timeout = null;
|
|
188
197
|
};
|
|
198
|
+
debounced.setCallback = (newFunc) => {
|
|
199
|
+
currentFunc = newFunc;
|
|
200
|
+
};
|
|
189
201
|
return debounced;
|
|
190
202
|
};
|
|
191
203
|
|
|
192
|
-
const isObject = (object) => {
|
|
193
|
-
return object !== null && !Array.isArray(object) && typeof object === 'object';
|
|
194
|
-
};
|
|
195
|
-
const deepEqual = (object1, object2) => {
|
|
196
|
-
if (object1 === object2) {
|
|
197
|
-
return true;
|
|
198
|
-
}
|
|
199
|
-
if (Array.isArray(object1) && Array.isArray(object2)) {
|
|
200
|
-
if (object1.length !== object2.length) {
|
|
201
|
-
return false;
|
|
202
|
-
}
|
|
203
|
-
return object1.every((val, index) => deepEqual(val, object2[index]));
|
|
204
|
-
}
|
|
205
|
-
if (Array.isArray(object1) !== Array.isArray(object2)) {
|
|
206
|
-
return false;
|
|
207
|
-
}
|
|
208
|
-
if (!isObject(object1) || !isObject(object2)) {
|
|
209
|
-
return object1 === object2;
|
|
210
|
-
}
|
|
211
|
-
const keys1 = Object.keys(object1);
|
|
212
|
-
const keys2 = Object.keys(object2);
|
|
213
|
-
if (keys1.length !== keys2.length) {
|
|
214
|
-
return false;
|
|
215
|
-
}
|
|
216
|
-
return keys1.every((key) => deepEqual(object1[key], object2[key]));
|
|
217
|
-
};
|
|
218
|
-
|
|
219
204
|
const isScrollable = (node) => {
|
|
220
205
|
if (!(node instanceof HTMLElement || node instanceof SVGElement)) {
|
|
221
206
|
return false;
|
|
@@ -240,336 +225,390 @@ const getScrollParent = (node) => {
|
|
|
240
225
|
return document.scrollingElement || document.documentElement;
|
|
241
226
|
};
|
|
242
227
|
|
|
243
|
-
|
|
228
|
+
// React currently throws a warning when using useLayoutEffect on the server.
|
|
229
|
+
// To get around it, we can conditionally useEffect on the server (no-op) and
|
|
230
|
+
// useLayoutEffect in the browser. We need useLayoutEffect to ensure the store
|
|
231
|
+
// subscription callback always has the selector from the latest render commit
|
|
232
|
+
// available, otherwise a store update may happen between render and the effect,
|
|
233
|
+
// which may cause missed updates; we also must ensure the store subscription
|
|
234
|
+
// is created synchronously, otherwise a store update may occur before the
|
|
235
|
+
// subscription is created and an inconsistent state may be observed
|
|
236
|
+
const isHopefullyDomEnvironment = typeof window !== 'undefined' &&
|
|
237
|
+
typeof window.document !== 'undefined' &&
|
|
238
|
+
typeof window.document.createElement !== 'undefined';
|
|
239
|
+
const useIsomorphicLayoutEffect = isHopefullyDomEnvironment ? useLayoutEffect : useEffect;
|
|
244
240
|
|
|
245
241
|
const clearTimeoutRef = (ref) => {
|
|
246
242
|
if (ref.current) {
|
|
247
243
|
clearTimeout(ref.current);
|
|
248
|
-
// eslint-disable-next-line no-param-reassign
|
|
249
244
|
ref.current = null;
|
|
250
245
|
}
|
|
251
246
|
};
|
|
252
247
|
|
|
253
|
-
|
|
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
|
+
}
|
|
254
255
|
|
|
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
|
+
}
|
|
256
266
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
* useLayoutEffect runs before useEffect,
|
|
283
|
-
* but should be used carefully because of caveats
|
|
284
|
-
* https://beta.reactjs.org/reference/react/useLayoutEffect#caveats
|
|
285
|
-
*/
|
|
286
|
-
useIsomorphicLayoutEffect(() => {
|
|
287
|
-
mounted.current = true;
|
|
288
|
-
return () => {
|
|
289
|
-
mounted.current = false;
|
|
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"};
|
|
268
|
+
|
|
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"};
|
|
270
|
+
|
|
271
|
+
const registry = new Map();
|
|
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
|
+
}
|
|
281
|
+
function areAnchorListsEqual(left, right) {
|
|
282
|
+
if (left.length !== right.length) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
return left.every((anchor, index) => anchor === right[index]);
|
|
286
|
+
}
|
|
287
|
+
function readAnchorsForSelector(selector) {
|
|
288
|
+
try {
|
|
289
|
+
return {
|
|
290
|
+
anchors: Array.from(document.querySelectorAll(selector)),
|
|
291
|
+
error: null,
|
|
290
292
|
};
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
return {
|
|
296
|
+
anchors: [],
|
|
297
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function notifySubscribers(entry) {
|
|
302
|
+
entry.subscribers.forEach((subscriber) => subscriber(entry.anchors, entry.error));
|
|
303
|
+
}
|
|
304
|
+
function refreshEntry(selector, entry) {
|
|
305
|
+
var _a, _b, _c, _d;
|
|
306
|
+
const nextState = readAnchorsForSelector(selector);
|
|
307
|
+
const nextErrorMessage = (_b = (_a = nextState.error) === null || _a === void 0 ? void 0 : _a.message) !== null && _b !== void 0 ? _b : null;
|
|
308
|
+
const previousErrorMessage = (_d = (_c = entry.error) === null || _c === void 0 ? void 0 : _c.message) !== null && _d !== void 0 ? _d : null;
|
|
309
|
+
if (areAnchorListsEqual(entry.anchors, nextState.anchors) &&
|
|
310
|
+
nextErrorMessage === previousErrorMessage) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const nextEntry = {
|
|
314
|
+
...entry,
|
|
315
|
+
anchors: nextState.anchors,
|
|
316
|
+
error: nextState.error,
|
|
317
|
+
};
|
|
318
|
+
registry.set(selector, nextEntry);
|
|
319
|
+
notifySubscribers(nextEntry);
|
|
320
|
+
}
|
|
321
|
+
function refreshAllEntries() {
|
|
322
|
+
registry.forEach((entry, selector) => {
|
|
323
|
+
refreshEntry(selector, entry);
|
|
324
|
+
});
|
|
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();
|
|
295
333
|
}
|
|
296
|
-
|
|
297
|
-
|
|
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();
|
|
298
351
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
* before actually showing
|
|
302
|
-
*/
|
|
303
|
-
setTimeout(() => {
|
|
304
|
-
if (!mounted.current) {
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
setIsOpen === null || setIsOpen === void 0 ? void 0 : setIsOpen(value);
|
|
308
|
-
if (isOpen === undefined) {
|
|
309
|
-
setShow(value);
|
|
310
|
-
}
|
|
311
|
-
}, 10);
|
|
312
|
-
}, [isOpen, setIsOpen]);
|
|
313
|
-
/**
|
|
314
|
-
* this replicates the effect from `handleShow()`
|
|
315
|
-
* when `isOpen` is changed from outside
|
|
316
|
-
*/
|
|
317
|
-
useEffect(() => {
|
|
318
|
-
if (isOpen === undefined) {
|
|
319
|
-
return () => null;
|
|
352
|
+
else if (ids && ids.size > 0) {
|
|
353
|
+
refreshEntriesForTooltipIds(ids);
|
|
320
354
|
}
|
|
321
|
-
|
|
322
|
-
|
|
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);
|
|
323
371
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
+
}
|
|
430
|
+
function ensureDocumentObserver() {
|
|
431
|
+
if (documentObserver || typeof MutationObserver === 'undefined') {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
documentObserver = new MutationObserver((records) => {
|
|
435
|
+
const affectedIds = collectAffectedTooltipIds(records);
|
|
436
|
+
scheduleRefresh(affectedIds);
|
|
437
|
+
});
|
|
438
|
+
documentObserver.observe(document.body, {
|
|
439
|
+
childList: true,
|
|
440
|
+
subtree: true,
|
|
441
|
+
attributes: true,
|
|
442
|
+
attributeFilter: ['data-tooltip-id'],
|
|
443
|
+
attributeOldValue: true,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
function cleanupDocumentObserverIfUnused() {
|
|
447
|
+
if (registry.size !== 0 || !documentObserver) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
documentObserver.disconnect();
|
|
451
|
+
documentObserver = null;
|
|
452
|
+
}
|
|
453
|
+
function subscribeAnchorSelector(selector, subscriber) {
|
|
454
|
+
let entry = registry.get(selector);
|
|
455
|
+
if (!entry) {
|
|
456
|
+
const initialState = readAnchorsForSelector(selector);
|
|
457
|
+
entry = {
|
|
458
|
+
anchors: initialState.anchors,
|
|
459
|
+
error: initialState.error,
|
|
460
|
+
subscribers: new Set(),
|
|
461
|
+
tooltipId: extractTooltipId(selector),
|
|
329
462
|
};
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
463
|
+
registry.set(selector, entry);
|
|
464
|
+
}
|
|
465
|
+
entry.subscribers.add(subscriber);
|
|
466
|
+
ensureDocumentObserver();
|
|
467
|
+
subscriber([...entry.anchors], entry.error);
|
|
468
|
+
return () => {
|
|
469
|
+
const currentEntry = registry.get(selector);
|
|
470
|
+
if (!currentEntry) {
|
|
333
471
|
return;
|
|
334
472
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
afterShow === null || afterShow === void 0 ? void 0 : afterShow();
|
|
339
|
-
}
|
|
340
|
-
else {
|
|
341
|
-
/**
|
|
342
|
-
* see `onTransitionEnd` on tooltip wrapper
|
|
343
|
-
*/
|
|
344
|
-
const style = getComputedStyle(document.body);
|
|
345
|
-
const transitionShowDelay = cssTimeToMs(style.getPropertyValue('--rt-transition-show-delay'));
|
|
346
|
-
missedTransitionTimerRef.current = setTimeout(() => {
|
|
347
|
-
/**
|
|
348
|
-
* if the tooltip switches from `show === true` to `show === false` too fast
|
|
349
|
-
* the transition never runs, so `onTransitionEnd` callback never gets fired
|
|
350
|
-
*/
|
|
351
|
-
setRendered(false);
|
|
352
|
-
setImperativeOptions(null);
|
|
353
|
-
afterHide === null || afterHide === void 0 ? void 0 : afterHide();
|
|
354
|
-
// +25ms just to make sure `onTransitionEnd` (if it gets fired) has time to run
|
|
355
|
-
}, transitionShowDelay + 25);
|
|
473
|
+
currentEntry.subscribers.delete(subscriber);
|
|
474
|
+
if (currentEntry.subscribers.size === 0) {
|
|
475
|
+
registry.delete(selector);
|
|
356
476
|
}
|
|
357
|
-
|
|
358
|
-
const handleComputedPosition = (newComputedPosition) => {
|
|
359
|
-
setComputedPosition((oldComputedPosition) => deepEqual(oldComputedPosition, newComputedPosition)
|
|
360
|
-
? oldComputedPosition
|
|
361
|
-
: newComputedPosition);
|
|
477
|
+
cleanupDocumentObserverIfUnused();
|
|
362
478
|
};
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const getAnchorSelector = ({ id, anchorSelect, imperativeAnchorSelect, }) => {
|
|
482
|
+
var _a;
|
|
483
|
+
let selector = (_a = imperativeAnchorSelect !== null && imperativeAnchorSelect !== void 0 ? imperativeAnchorSelect : anchorSelect) !== null && _a !== void 0 ? _a : '';
|
|
484
|
+
if (!selector && id) {
|
|
485
|
+
selector = `[data-tooltip-id='${id.replace(/'/g, "\\'")}']`;
|
|
486
|
+
}
|
|
487
|
+
return selector;
|
|
488
|
+
};
|
|
489
|
+
const useTooltipAnchors = ({ id, anchorSelect, imperativeAnchorSelect, activeAnchor, disableTooltip, onActiveAnchorRemoved, trackAnchors, }) => {
|
|
490
|
+
const [rawAnchorElements, setRawAnchorElements] = useState([]);
|
|
491
|
+
const [selectorError, setSelectorError] = useState(null);
|
|
492
|
+
const warnedSelectorRef = useRef(null);
|
|
493
|
+
const selector = useMemo(() => getAnchorSelector({ id, anchorSelect, imperativeAnchorSelect }), [id, anchorSelect, imperativeAnchorSelect]);
|
|
494
|
+
const anchorElements = useMemo(() => rawAnchorElements.filter((anchor) => !(disableTooltip === null || disableTooltip === void 0 ? void 0 : disableTooltip(anchor))), [rawAnchorElements, disableTooltip]);
|
|
495
|
+
const activeAnchorMatchesSelector = useMemo(() => {
|
|
496
|
+
if (!activeAnchor || !selector) {
|
|
497
|
+
return false;
|
|
366
498
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
handleShow(true);
|
|
370
|
-
return;
|
|
499
|
+
try {
|
|
500
|
+
return activeAnchor.matches(selector);
|
|
371
501
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}, delay);
|
|
375
|
-
}, [delayShow, handleShow, rendered]);
|
|
376
|
-
const handleHideTooltipDelayed = useCallback((delay = delayHide) => {
|
|
377
|
-
if (tooltipHideDelayTimerRef.current) {
|
|
378
|
-
clearTimeout(tooltipHideDelayTimerRef.current);
|
|
502
|
+
catch (_a) {
|
|
503
|
+
return false;
|
|
379
504
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
return {
|
|
392
|
-
x,
|
|
393
|
-
y,
|
|
394
|
-
width: 0,
|
|
395
|
-
height: 0,
|
|
396
|
-
top: y,
|
|
397
|
-
left: x,
|
|
398
|
-
right: x,
|
|
399
|
-
bottom: y,
|
|
400
|
-
};
|
|
401
|
-
},
|
|
402
|
-
};
|
|
403
|
-
computeTooltipPosition({
|
|
404
|
-
place: (_a = imperativeOptions === null || imperativeOptions === void 0 ? void 0 : imperativeOptions.place) !== null && _a !== void 0 ? _a : place,
|
|
405
|
-
offset,
|
|
406
|
-
elementReference: virtualElement,
|
|
407
|
-
tooltipReference: tooltipRef.current,
|
|
408
|
-
tooltipArrowReference: tooltipArrowRef.current,
|
|
409
|
-
strategy: positionStrategy,
|
|
410
|
-
middlewares,
|
|
411
|
-
border,
|
|
412
|
-
}).then((computedStylesData) => {
|
|
413
|
-
handleComputedPosition(computedStylesData);
|
|
505
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
506
|
+
}, [activeAnchor, selector, anchorElements]);
|
|
507
|
+
useEffect(() => {
|
|
508
|
+
if (!selector || !trackAnchors) {
|
|
509
|
+
setRawAnchorElements([]);
|
|
510
|
+
setSelectorError(null);
|
|
511
|
+
return undefined;
|
|
512
|
+
}
|
|
513
|
+
return subscribeAnchorSelector(selector, (anchors, error) => {
|
|
514
|
+
setRawAnchorElements(anchors);
|
|
515
|
+
setSelectorError(error);
|
|
414
516
|
});
|
|
415
|
-
}, [
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
const actualPosition = (_a = imperativeOptions === null || imperativeOptions === void 0 ? void 0 : imperativeOptions.position) !== null && _a !== void 0 ? _a : position;
|
|
419
|
-
if (actualPosition) {
|
|
420
|
-
// if `position` is set, override regular and `float` positioning
|
|
421
|
-
handleTooltipPosition(actualPosition);
|
|
517
|
+
}, [selector, trackAnchors]);
|
|
518
|
+
useEffect(() => {
|
|
519
|
+
if (!selectorError || warnedSelectorRef.current === selector) {
|
|
422
520
|
return;
|
|
423
521
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
To see why this matters, comment this line, run `yarn dev` and click the
|
|
431
|
-
"Hover me!" anchor.
|
|
432
|
-
*/
|
|
433
|
-
handleTooltipPosition(lastFloatPosition.current);
|
|
434
|
-
}
|
|
435
|
-
// if `float` is set, override regular positioning
|
|
522
|
+
warnedSelectorRef.current = selector;
|
|
523
|
+
/* c8 ignore end */
|
|
524
|
+
}, [selector, selectorError]);
|
|
525
|
+
useEffect(() => {
|
|
526
|
+
if (!activeAnchor) {
|
|
436
527
|
return;
|
|
437
528
|
}
|
|
438
|
-
if (!
|
|
529
|
+
if (!activeAnchor.isConnected) {
|
|
530
|
+
onActiveAnchorRemoved();
|
|
439
531
|
return;
|
|
440
532
|
}
|
|
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
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
* at the same time the tooltip gets triggered
|
|
521
|
-
*/
|
|
522
|
-
setActiveAnchor(null);
|
|
523
|
-
return;
|
|
524
|
-
}
|
|
525
|
-
if (delayShow) {
|
|
526
|
-
handleShowTooltipDelayed();
|
|
527
|
-
}
|
|
528
|
-
else {
|
|
529
|
-
handleShow(true);
|
|
530
|
-
}
|
|
531
|
-
setActiveAnchor(target);
|
|
532
|
-
if (tooltipHideDelayTimerRef.current) {
|
|
533
|
-
clearTimeout(tooltipHideDelayTimerRef.current);
|
|
534
|
-
}
|
|
535
|
-
};
|
|
536
|
-
const handleHideTooltip = () => {
|
|
537
|
-
if (clickable) {
|
|
538
|
-
// allow time for the mouse to reach the tooltip, in case there's a gap
|
|
539
|
-
handleHideTooltipDelayed(delayHide || 100);
|
|
540
|
-
}
|
|
541
|
-
else if (delayHide) {
|
|
542
|
-
handleHideTooltipDelayed();
|
|
543
|
-
}
|
|
544
|
-
else {
|
|
545
|
-
handleShow(false);
|
|
546
|
-
}
|
|
547
|
-
if (tooltipShowDelayTimerRef.current) {
|
|
548
|
-
clearTimeout(tooltipShowDelayTimerRef.current);
|
|
549
|
-
}
|
|
550
|
-
};
|
|
551
|
-
// debounce handler to prevent call twice when
|
|
552
|
-
// mouse enter and focus events being triggered toggether
|
|
553
|
-
const internalDebouncedHandleShowTooltip = debounce(handleShowTooltip, 50);
|
|
554
|
-
const internalDebouncedHandleHideTooltip = debounce(handleHideTooltip, 50);
|
|
555
|
-
// If either of the functions is called while the other is still debounced,
|
|
556
|
-
// reset the timeout. Otherwise if there is a sub-50ms (leave A, enter B, leave B)
|
|
557
|
-
// sequence of events, the tooltip will stay open because the hide debounce
|
|
558
|
-
// from leave A prevented the leave B event from calling it, leaving the
|
|
559
|
-
// tooltip visible.
|
|
560
|
-
const debouncedHandleShowTooltip = (e) => {
|
|
561
|
-
internalDebouncedHandleHideTooltip.cancel();
|
|
562
|
-
internalDebouncedHandleShowTooltip(e);
|
|
563
|
-
};
|
|
564
|
-
const debouncedHandleHideTooltip = () => {
|
|
565
|
-
internalDebouncedHandleShowTooltip.cancel();
|
|
566
|
-
internalDebouncedHandleHideTooltip();
|
|
567
|
-
};
|
|
568
|
-
const handleScrollResize = () => {
|
|
569
|
-
handleShow(false);
|
|
570
|
-
};
|
|
571
|
-
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);
|
|
572
|
-
const actualOpenEvents = openEvents
|
|
533
|
+
if (!anchorElements.includes(activeAnchor) && !activeAnchorMatchesSelector) {
|
|
534
|
+
onActiveAnchorRemoved();
|
|
535
|
+
}
|
|
536
|
+
}, [activeAnchor, anchorElements, activeAnchorMatchesSelector, onActiveAnchorRemoved]);
|
|
537
|
+
return {
|
|
538
|
+
anchorElements,
|
|
539
|
+
selector,
|
|
540
|
+
};
|
|
541
|
+
};
|
|
542
|
+
|
|
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
|
|
573
612
|
? { ...openEvents }
|
|
574
613
|
: {
|
|
575
614
|
mouseenter: true,
|
|
@@ -579,13 +618,25 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, activeAnch
|
|
|
579
618
|
mousedown: false,
|
|
580
619
|
};
|
|
581
620
|
if (!openEvents && openOnClick) {
|
|
582
|
-
Object.assign(
|
|
621
|
+
Object.assign(events, {
|
|
583
622
|
mouseenter: false,
|
|
584
623
|
focus: false,
|
|
585
624
|
click: true,
|
|
586
625
|
});
|
|
587
626
|
}
|
|
588
|
-
|
|
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
|
|
589
640
|
? { ...closeEvents }
|
|
590
641
|
: {
|
|
591
642
|
mouseleave: true,
|
|
@@ -595,12 +646,24 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, activeAnch
|
|
|
595
646
|
mouseup: false,
|
|
596
647
|
};
|
|
597
648
|
if (!closeEvents && openOnClick) {
|
|
598
|
-
Object.assign(
|
|
649
|
+
Object.assign(events, {
|
|
650
|
+
mouseleave: false,
|
|
651
|
+
blur: false,
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
if (imperativeModeOnly) {
|
|
655
|
+
Object.assign(events, {
|
|
599
656
|
mouseleave: false,
|
|
600
657
|
blur: false,
|
|
658
|
+
click: false,
|
|
659
|
+
dblclick: false,
|
|
660
|
+
mouseup: false,
|
|
601
661
|
});
|
|
602
662
|
}
|
|
603
|
-
|
|
663
|
+
return events;
|
|
664
|
+
}, [closeEvents, openOnClick, imperativeModeOnly]);
|
|
665
|
+
const actualGlobalCloseEvents = useMemo(() => {
|
|
666
|
+
const events = globalCloseEvents
|
|
604
667
|
? { ...globalCloseEvents }
|
|
605
668
|
: {
|
|
606
669
|
escape: false,
|
|
@@ -609,30 +672,276 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, activeAnch
|
|
|
609
672
|
clickOutsideAnchor: hasClickEvent || false,
|
|
610
673
|
};
|
|
611
674
|
if (imperativeModeOnly) {
|
|
612
|
-
Object.assign(
|
|
613
|
-
mouseenter: false,
|
|
614
|
-
focus: false,
|
|
615
|
-
click: false,
|
|
616
|
-
dblclick: false,
|
|
617
|
-
mousedown: false,
|
|
618
|
-
});
|
|
619
|
-
Object.assign(actualCloseEvents, {
|
|
620
|
-
mouseleave: false,
|
|
621
|
-
blur: false,
|
|
622
|
-
click: false,
|
|
623
|
-
dblclick: false,
|
|
624
|
-
mouseup: false,
|
|
625
|
-
});
|
|
626
|
-
Object.assign(actualGlobalCloseEvents, {
|
|
675
|
+
Object.assign(events, {
|
|
627
676
|
escape: false,
|
|
628
677
|
scroll: false,
|
|
629
678
|
resize: false,
|
|
630
679
|
clickOutsideAnchor: false,
|
|
631
680
|
});
|
|
632
681
|
}
|
|
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;
|
|
707
|
+
}
|
|
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
|
+
}
|
|
713
|
+
}
|
|
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
|
+
}
|
|
726
|
+
}
|
|
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);
|
|
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 {
|
|
773
|
+
handleShow(false);
|
|
774
|
+
}
|
|
775
|
+
if (tooltipShowDelayTimerRef.current) {
|
|
776
|
+
clearTimeout(tooltipShowDelayTimerRef.current);
|
|
777
|
+
}
|
|
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));
|
|
792
|
+
};
|
|
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();
|
|
801
|
+
};
|
|
802
|
+
const addDelegatedHoverOpenListener = () => {
|
|
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);
|
|
813
|
+
});
|
|
814
|
+
};
|
|
815
|
+
const addDelegatedHoverCloseListener = () => {
|
|
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();
|
|
826
|
+
});
|
|
827
|
+
};
|
|
828
|
+
if (actualOpenEvents.mouseenter) {
|
|
829
|
+
addDelegatedHoverOpenListener();
|
|
830
|
+
}
|
|
831
|
+
if (actualCloseEvents.mouseleave) {
|
|
832
|
+
addDelegatedHoverCloseListener();
|
|
833
|
+
}
|
|
834
|
+
if (actualOpenEvents.mouseover) {
|
|
835
|
+
addDelegatedHoverOpenListener();
|
|
836
|
+
}
|
|
837
|
+
if (actualCloseEvents.mouseout) {
|
|
838
|
+
addDelegatedHoverCloseListener();
|
|
839
|
+
}
|
|
840
|
+
if (actualOpenEvents.focus) {
|
|
841
|
+
addDelegatedListener('focusin', (event) => {
|
|
842
|
+
debouncedHandleShowTooltip(resolveAnchorElementRef.current(event.target));
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
if (actualCloseEvents.blur) {
|
|
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();
|
|
856
|
+
});
|
|
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
|
+
};
|
|
877
|
+
Object.entries(actualOpenEvents).forEach(([event, enabled]) => {
|
|
878
|
+
if (!enabled || regularEvents.includes(event)) {
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
if (clickEvents.includes(event)) {
|
|
882
|
+
addDelegatedListener(event, handleClickOpenTooltipAnchor);
|
|
883
|
+
}
|
|
884
|
+
});
|
|
885
|
+
Object.entries(actualCloseEvents).forEach(([event, enabled]) => {
|
|
886
|
+
if (!enabled || regularEvents.includes(event)) {
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
if (clickEvents.includes(event)) {
|
|
890
|
+
addDelegatedListener(event, handleClickCloseTooltipAnchor);
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
if (float) {
|
|
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;
|
|
910
|
+
});
|
|
911
|
+
}
|
|
633
912
|
const tooltipElement = tooltipRef.current;
|
|
634
|
-
const
|
|
635
|
-
|
|
913
|
+
const handleMouseOverTooltip = () => {
|
|
914
|
+
hoveringTooltip.current = true;
|
|
915
|
+
};
|
|
916
|
+
const handleMouseOutTooltip = () => {
|
|
917
|
+
hoveringTooltip.current = false;
|
|
918
|
+
handleHideTooltipRef.current();
|
|
919
|
+
};
|
|
920
|
+
const addHoveringTooltipListeners = clickable && (actualCloseEvents.mouseout || actualCloseEvents.mouseleave);
|
|
921
|
+
if (addHoveringTooltipListeners) {
|
|
922
|
+
tooltipElement === null || tooltipElement === void 0 ? void 0 : tooltipElement.addEventListener('mouseover', handleMouseOverTooltip);
|
|
923
|
+
tooltipElement === null || tooltipElement === void 0 ? void 0 : tooltipElement.addEventListener('mouseout', handleMouseOutTooltip);
|
|
924
|
+
}
|
|
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;
|
|
636
945
|
if (actualGlobalCloseEvents.scroll) {
|
|
637
946
|
window.addEventListener('scroll', handleScrollResize);
|
|
638
947
|
anchorScrollParent === null || anchorScrollParent === void 0 ? void 0 : anchorScrollParent.addEventListener('scroll', handleScrollResize);
|
|
@@ -643,7 +952,7 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, activeAnch
|
|
|
643
952
|
window.addEventListener('resize', handleScrollResize);
|
|
644
953
|
}
|
|
645
954
|
else if (activeAnchor && tooltipRef.current) {
|
|
646
|
-
updateTooltipCleanup = autoUpdate(activeAnchor, tooltipRef.current,
|
|
955
|
+
updateTooltipCleanup = autoUpdate(activeAnchor, tooltipRef.current, () => updateTooltipPositionRef.current(), {
|
|
647
956
|
ancestorResize: true,
|
|
648
957
|
elementResize: true,
|
|
649
958
|
layoutShift: true,
|
|
@@ -653,295 +962,484 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, activeAnch
|
|
|
653
962
|
if (event.key !== 'Escape') {
|
|
654
963
|
return;
|
|
655
964
|
}
|
|
656
|
-
|
|
657
|
-
};
|
|
658
|
-
if (actualGlobalCloseEvents.escape) {
|
|
659
|
-
window.addEventListener('keydown', handleEsc);
|
|
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);
|
|
989
|
+
clearTimeoutRef(tooltipShowDelayTimerRef);
|
|
990
|
+
};
|
|
991
|
+
if (actualGlobalCloseEvents.clickOutsideAnchor) {
|
|
992
|
+
window.addEventListener('click', handleClickOutsideAnchors);
|
|
993
|
+
}
|
|
994
|
+
return () => {
|
|
995
|
+
if (actualGlobalCloseEvents.scroll) {
|
|
996
|
+
window.removeEventListener('scroll', handleScrollResize);
|
|
997
|
+
anchorScrollParent === null || anchorScrollParent === void 0 ? void 0 : anchorScrollParent.removeEventListener('scroll', handleScrollResize);
|
|
998
|
+
tooltipScrollParent === null || tooltipScrollParent === void 0 ? void 0 : tooltipScrollParent.removeEventListener('scroll', handleScrollResize);
|
|
999
|
+
}
|
|
1000
|
+
if (actualGlobalCloseEvents.resize) {
|
|
1001
|
+
window.removeEventListener('resize', handleScrollResize);
|
|
1002
|
+
}
|
|
1003
|
+
if (updateTooltipCleanup) {
|
|
1004
|
+
updateTooltipCleanup();
|
|
1005
|
+
}
|
|
1006
|
+
if (actualGlobalCloseEvents.escape) {
|
|
1007
|
+
window.removeEventListener('keydown', handleEsc);
|
|
1008
|
+
}
|
|
1009
|
+
if (actualGlobalCloseEvents.clickOutsideAnchor) {
|
|
1010
|
+
window.removeEventListener('click', handleClickOutsideAnchors);
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
1013
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1014
|
+
}, [actualGlobalCloseEvents, activeAnchor]);
|
|
1015
|
+
};
|
|
1016
|
+
|
|
1017
|
+
// Shared across all tooltip instances — the CSS variable is on :root and never changes per-instance
|
|
1018
|
+
let globalTransitionShowDelay = null;
|
|
1019
|
+
const Tooltip = ({
|
|
1020
|
+
// props
|
|
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,
|
|
1022
|
+
// props handled by controller
|
|
1023
|
+
content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, previousActiveAnchor, activeAnchor, setActiveAnchor, border, opacity, arrowColor, arrowSize = 8, role = 'tooltip', }) => {
|
|
1024
|
+
var _a;
|
|
1025
|
+
const tooltipRef = useRef(null);
|
|
1026
|
+
const tooltipArrowRef = useRef(null);
|
|
1027
|
+
const tooltipShowDelayTimerRef = useRef(null);
|
|
1028
|
+
const tooltipHideDelayTimerRef = useRef(null);
|
|
1029
|
+
const tooltipAutoCloseTimerRef = useRef(null);
|
|
1030
|
+
const missedTransitionTimerRef = useRef(null);
|
|
1031
|
+
const [computedPosition, setComputedPosition] = useState({
|
|
1032
|
+
tooltipStyles: {},
|
|
1033
|
+
tooltipArrowStyles: {},
|
|
1034
|
+
place,
|
|
1035
|
+
});
|
|
1036
|
+
const [show, setShow] = useState(false);
|
|
1037
|
+
const [rendered, setRendered] = useState(false);
|
|
1038
|
+
const [imperativeOptions, setImperativeOptions] = useState(null);
|
|
1039
|
+
const wasShowing = useRef(false);
|
|
1040
|
+
const lastFloatPosition = useRef(null);
|
|
1041
|
+
const hoveringTooltip = useRef(false);
|
|
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
|
+
});
|
|
1055
|
+
/**
|
|
1056
|
+
* useLayoutEffect runs before useEffect,
|
|
1057
|
+
* but should be used carefully because of caveats
|
|
1058
|
+
* https://beta.reactjs.org/reference/react/useLayoutEffect#caveats
|
|
1059
|
+
*/
|
|
1060
|
+
useIsomorphicLayoutEffect(() => {
|
|
1061
|
+
mounted.current = true;
|
|
1062
|
+
return () => {
|
|
1063
|
+
mounted.current = false;
|
|
1064
|
+
};
|
|
1065
|
+
}, []);
|
|
1066
|
+
const handleShow = useCallback((value) => {
|
|
1067
|
+
if (!mounted.current) {
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
if (value) {
|
|
1071
|
+
setRendered(true);
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* wait for the component to render and calculate position
|
|
1075
|
+
* before actually showing
|
|
1076
|
+
*/
|
|
1077
|
+
setTimeout(() => {
|
|
1078
|
+
if (!mounted.current) {
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
setIsOpen === null || setIsOpen === void 0 ? void 0 : setIsOpen(value);
|
|
1082
|
+
if (isOpen === undefined) {
|
|
1083
|
+
setShow(value);
|
|
1084
|
+
}
|
|
1085
|
+
}, 10);
|
|
1086
|
+
}, [isOpen, setIsOpen]);
|
|
1087
|
+
/**
|
|
1088
|
+
* Add aria-describedby to activeAnchor when tooltip is active
|
|
1089
|
+
*/
|
|
1090
|
+
useEffect(() => {
|
|
1091
|
+
if (!id)
|
|
1092
|
+
return;
|
|
1093
|
+
function getAriaDescribedBy(element) {
|
|
1094
|
+
var _a;
|
|
1095
|
+
return ((_a = element === null || element === void 0 ? void 0 : element.getAttribute('aria-describedby')) === null || _a === void 0 ? void 0 : _a.split(' ')) || [];
|
|
1096
|
+
}
|
|
1097
|
+
function removeAriaDescribedBy(element) {
|
|
1098
|
+
const newDescribedBy = getAriaDescribedBy(element).filter((s) => s !== id);
|
|
1099
|
+
if (newDescribedBy.length) {
|
|
1100
|
+
element === null || element === void 0 ? void 0 : element.setAttribute('aria-describedby', newDescribedBy.join(' '));
|
|
1101
|
+
}
|
|
1102
|
+
else {
|
|
1103
|
+
element === null || element === void 0 ? void 0 : element.removeAttribute('aria-describedby');
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
if (show) {
|
|
1107
|
+
removeAriaDescribedBy(previousActiveAnchor);
|
|
1108
|
+
const currentDescribedBy = getAriaDescribedBy(activeAnchor);
|
|
1109
|
+
const describedBy = [...new Set([...currentDescribedBy, id])].filter(Boolean).join(' ');
|
|
1110
|
+
activeAnchor === null || activeAnchor === void 0 ? void 0 : activeAnchor.setAttribute('aria-describedby', describedBy);
|
|
1111
|
+
}
|
|
1112
|
+
else {
|
|
1113
|
+
removeAriaDescribedBy(activeAnchor);
|
|
1114
|
+
}
|
|
1115
|
+
return () => {
|
|
1116
|
+
// cleanup aria-describedby when the tooltip is closed
|
|
1117
|
+
removeAriaDescribedBy(activeAnchor);
|
|
1118
|
+
removeAriaDescribedBy(previousActiveAnchor);
|
|
1119
|
+
};
|
|
1120
|
+
}, [activeAnchor, show, id, previousActiveAnchor]);
|
|
1121
|
+
/**
|
|
1122
|
+
* this replicates the effect from `handleShow()`
|
|
1123
|
+
* when `isOpen` is changed from outside
|
|
1124
|
+
*/
|
|
1125
|
+
useEffect(() => {
|
|
1126
|
+
if (isOpen === undefined) {
|
|
1127
|
+
return () => null;
|
|
1128
|
+
}
|
|
1129
|
+
if (isOpen) {
|
|
1130
|
+
setRendered(true);
|
|
1131
|
+
}
|
|
1132
|
+
const timeout = setTimeout(() => {
|
|
1133
|
+
setShow(isOpen);
|
|
1134
|
+
}, 10);
|
|
1135
|
+
return () => {
|
|
1136
|
+
clearTimeout(timeout);
|
|
1137
|
+
};
|
|
1138
|
+
}, [isOpen]);
|
|
1139
|
+
useEffect(() => {
|
|
1140
|
+
if (show === wasShowing.current) {
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
clearTimeoutRef(missedTransitionTimerRef);
|
|
1144
|
+
wasShowing.current = show;
|
|
1145
|
+
if (show) {
|
|
1146
|
+
afterShow === null || afterShow === void 0 ? void 0 : afterShow();
|
|
1147
|
+
}
|
|
1148
|
+
else {
|
|
1149
|
+
/**
|
|
1150
|
+
* see `onTransitionEnd` on tooltip wrapper
|
|
1151
|
+
*/
|
|
1152
|
+
if (globalTransitionShowDelay === null) {
|
|
1153
|
+
const style = getComputedStyle(document.body);
|
|
1154
|
+
globalTransitionShowDelay = cssTimeToMs(style.getPropertyValue('--rt-transition-show-delay'));
|
|
1155
|
+
}
|
|
1156
|
+
const transitionShowDelay = globalTransitionShowDelay;
|
|
1157
|
+
missedTransitionTimerRef.current = setTimeout(() => {
|
|
1158
|
+
/**
|
|
1159
|
+
* if the tooltip switches from `show === true` to `show === false` too fast
|
|
1160
|
+
* the transition never runs, so `onTransitionEnd` callback never gets fired
|
|
1161
|
+
*/
|
|
1162
|
+
setRendered(false);
|
|
1163
|
+
setImperativeOptions(null);
|
|
1164
|
+
afterHide === null || afterHide === void 0 ? void 0 : afterHide();
|
|
1165
|
+
// +25ms just to make sure `onTransitionEnd` (if it gets fired) has time to run
|
|
1166
|
+
}, transitionShowDelay + 25);
|
|
1167
|
+
}
|
|
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]);
|
|
1183
|
+
const handleComputedPosition = useCallback((newComputedPosition) => {
|
|
1184
|
+
if (!mounted.current) {
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
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
|
+
});
|
|
1207
|
+
}, []);
|
|
1208
|
+
const handleShowTooltipDelayed = useCallback((delay = delayShow) => {
|
|
1209
|
+
if (tooltipShowDelayTimerRef.current) {
|
|
1210
|
+
clearTimeout(tooltipShowDelayTimerRef.current);
|
|
1211
|
+
}
|
|
1212
|
+
if (rendered) {
|
|
1213
|
+
// if the tooltip is already rendered, ignore delay
|
|
1214
|
+
handleShow(true);
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
tooltipShowDelayTimerRef.current = setTimeout(() => {
|
|
1218
|
+
handleShow(true);
|
|
1219
|
+
}, delay);
|
|
1220
|
+
}, [delayShow, handleShow, rendered]);
|
|
1221
|
+
const handleHideTooltipDelayed = useCallback((delay = delayHide) => {
|
|
1222
|
+
if (tooltipHideDelayTimerRef.current) {
|
|
1223
|
+
clearTimeout(tooltipHideDelayTimerRef.current);
|
|
1224
|
+
}
|
|
1225
|
+
tooltipHideDelayTimerRef.current = setTimeout(() => {
|
|
1226
|
+
if (hoveringTooltip.current) {
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
handleShow(false);
|
|
1230
|
+
}, delay);
|
|
1231
|
+
}, [delayHide, handleShow]);
|
|
1232
|
+
const handleTooltipPosition = useCallback(({ x, y }) => {
|
|
1233
|
+
var _a;
|
|
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
|
+
});
|
|
1244
|
+
computeTooltipPosition({
|
|
1245
|
+
place: (_a = imperativeOptions === null || imperativeOptions === void 0 ? void 0 : imperativeOptions.place) !== null && _a !== void 0 ? _a : place,
|
|
1246
|
+
offset,
|
|
1247
|
+
elementReference: virtualElementRef.current,
|
|
1248
|
+
tooltipReference: tooltipRef.current,
|
|
1249
|
+
tooltipArrowReference: tooltipArrowRef.current,
|
|
1250
|
+
strategy: positionStrategy,
|
|
1251
|
+
middlewares,
|
|
1252
|
+
border,
|
|
1253
|
+
arrowSize,
|
|
1254
|
+
}).then((computedStylesData) => {
|
|
1255
|
+
handleComputedPosition(computedStylesData);
|
|
1256
|
+
});
|
|
1257
|
+
}, [
|
|
1258
|
+
imperativeOptions === null || imperativeOptions === void 0 ? void 0 : imperativeOptions.place,
|
|
1259
|
+
place,
|
|
1260
|
+
offset,
|
|
1261
|
+
positionStrategy,
|
|
1262
|
+
middlewares,
|
|
1263
|
+
border,
|
|
1264
|
+
arrowSize,
|
|
1265
|
+
handleComputedPosition,
|
|
1266
|
+
]);
|
|
1267
|
+
const updateTooltipPosition = useCallback(() => {
|
|
1268
|
+
var _a, _b;
|
|
1269
|
+
const actualPosition = (_a = imperativeOptions === null || imperativeOptions === void 0 ? void 0 : imperativeOptions.position) !== null && _a !== void 0 ? _a : position;
|
|
1270
|
+
if (actualPosition) {
|
|
1271
|
+
// if `position` is set, override regular and `float` positioning
|
|
1272
|
+
handleTooltipPosition(actualPosition);
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
if (float) {
|
|
1276
|
+
if (lastFloatPosition.current) {
|
|
1277
|
+
/*
|
|
1278
|
+
Without this, changes to `content`, `place`, `offset`, ..., will only
|
|
1279
|
+
trigger a position calculation after a `mousemove` event.
|
|
1280
|
+
|
|
1281
|
+
To see why this matters, comment this line, run `yarn dev` and click the
|
|
1282
|
+
"Hover me!" anchor.
|
|
1283
|
+
*/
|
|
1284
|
+
handleTooltipPosition(lastFloatPosition.current);
|
|
1285
|
+
}
|
|
1286
|
+
// if `float` is set, override regular positioning
|
|
1287
|
+
return;
|
|
660
1288
|
}
|
|
661
|
-
if (
|
|
662
|
-
|
|
1289
|
+
if (!(activeAnchor === null || activeAnchor === void 0 ? void 0 : activeAnchor.isConnected)) {
|
|
1290
|
+
return;
|
|
663
1291
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
/**
|
|
678
|
-
* ignore clicking the anchor that was NOT used to open the tooltip.
|
|
679
|
-
* this avoids closing the tooltip when clicking on a
|
|
680
|
-
* new anchor with the tooltip already open.
|
|
681
|
-
*/
|
|
682
|
-
return;
|
|
683
|
-
}
|
|
684
|
-
handleHideTooltip();
|
|
685
|
-
};
|
|
686
|
-
const regularEvents = ['mouseover', 'mouseout', 'mouseenter', 'mouseleave', 'focus', 'blur'];
|
|
687
|
-
const clickEvents = ['click', 'dblclick', 'mousedown', 'mouseup'];
|
|
688
|
-
Object.entries(actualOpenEvents).forEach(([event, enabled]) => {
|
|
689
|
-
if (!enabled) {
|
|
690
|
-
return;
|
|
691
|
-
}
|
|
692
|
-
if (regularEvents.includes(event)) {
|
|
693
|
-
enabledEvents.push({ event, listener: debouncedHandleShowTooltip });
|
|
694
|
-
}
|
|
695
|
-
else if (clickEvents.includes(event)) {
|
|
696
|
-
enabledEvents.push({ event, listener: handleClickOpenTooltipAnchor });
|
|
697
|
-
}
|
|
698
|
-
else ;
|
|
699
|
-
});
|
|
700
|
-
Object.entries(actualCloseEvents).forEach(([event, enabled]) => {
|
|
701
|
-
if (!enabled) {
|
|
1292
|
+
computeTooltipPosition({
|
|
1293
|
+
place: (_b = imperativeOptions === null || imperativeOptions === void 0 ? void 0 : imperativeOptions.place) !== null && _b !== void 0 ? _b : place,
|
|
1294
|
+
offset,
|
|
1295
|
+
elementReference: activeAnchor,
|
|
1296
|
+
tooltipReference: tooltipRef.current,
|
|
1297
|
+
tooltipArrowReference: tooltipArrowRef.current,
|
|
1298
|
+
strategy: positionStrategy,
|
|
1299
|
+
middlewares,
|
|
1300
|
+
border,
|
|
1301
|
+
arrowSize,
|
|
1302
|
+
}).then((computedStylesData) => {
|
|
1303
|
+
if (!mounted.current) {
|
|
1304
|
+
// invalidate computed positions after remount
|
|
702
1305
|
return;
|
|
703
1306
|
}
|
|
704
|
-
|
|
705
|
-
enabledEvents.push({ event, listener: debouncedHandleHideTooltip });
|
|
706
|
-
}
|
|
707
|
-
else if (clickEvents.includes(event)) {
|
|
708
|
-
enabledEvents.push({ event, listener: handleClickCloseTooltipAnchor });
|
|
709
|
-
}
|
|
710
|
-
else ;
|
|
711
|
-
});
|
|
712
|
-
if (float) {
|
|
713
|
-
enabledEvents.push({
|
|
714
|
-
event: 'pointermove',
|
|
715
|
-
listener: handlePointerMove,
|
|
716
|
-
});
|
|
717
|
-
}
|
|
718
|
-
const handleMouseEnterTooltip = () => {
|
|
719
|
-
hoveringTooltip.current = true;
|
|
720
|
-
};
|
|
721
|
-
const handleMouseLeaveTooltip = () => {
|
|
722
|
-
hoveringTooltip.current = false;
|
|
723
|
-
handleHideTooltip();
|
|
724
|
-
};
|
|
725
|
-
if (clickable && !hasClickEvent) {
|
|
726
|
-
// used to keep the tooltip open when hovering content.
|
|
727
|
-
// not needed if using click events.
|
|
728
|
-
tooltipElement === null || tooltipElement === void 0 ? void 0 : tooltipElement.addEventListener('mouseenter', handleMouseEnterTooltip);
|
|
729
|
-
tooltipElement === null || tooltipElement === void 0 ? void 0 : tooltipElement.addEventListener('mouseleave', handleMouseLeaveTooltip);
|
|
730
|
-
}
|
|
731
|
-
enabledEvents.forEach(({ event, listener }) => {
|
|
732
|
-
anchorElements.forEach((anchor) => {
|
|
733
|
-
anchor.addEventListener(event, listener);
|
|
734
|
-
});
|
|
1307
|
+
handleComputedPosition(computedStylesData);
|
|
735
1308
|
});
|
|
736
|
-
return () => {
|
|
737
|
-
if (actualGlobalCloseEvents.scroll) {
|
|
738
|
-
window.removeEventListener('scroll', handleScrollResize);
|
|
739
|
-
anchorScrollParent === null || anchorScrollParent === void 0 ? void 0 : anchorScrollParent.removeEventListener('scroll', handleScrollResize);
|
|
740
|
-
tooltipScrollParent === null || tooltipScrollParent === void 0 ? void 0 : tooltipScrollParent.removeEventListener('scroll', handleScrollResize);
|
|
741
|
-
}
|
|
742
|
-
if (actualGlobalCloseEvents.resize) {
|
|
743
|
-
window.removeEventListener('resize', handleScrollResize);
|
|
744
|
-
}
|
|
745
|
-
else {
|
|
746
|
-
updateTooltipCleanup === null || updateTooltipCleanup === void 0 ? void 0 : updateTooltipCleanup();
|
|
747
|
-
}
|
|
748
|
-
if (actualGlobalCloseEvents.clickOutsideAnchor) {
|
|
749
|
-
window.removeEventListener('click', handleClickOutsideAnchors);
|
|
750
|
-
}
|
|
751
|
-
if (actualGlobalCloseEvents.escape) {
|
|
752
|
-
window.removeEventListener('keydown', handleEsc);
|
|
753
|
-
}
|
|
754
|
-
if (clickable && !hasClickEvent) {
|
|
755
|
-
tooltipElement === null || tooltipElement === void 0 ? void 0 : tooltipElement.removeEventListener('mouseenter', handleMouseEnterTooltip);
|
|
756
|
-
tooltipElement === null || tooltipElement === void 0 ? void 0 : tooltipElement.removeEventListener('mouseleave', handleMouseLeaveTooltip);
|
|
757
|
-
}
|
|
758
|
-
enabledEvents.forEach(({ event, listener }) => {
|
|
759
|
-
anchorElements.forEach((anchor) => {
|
|
760
|
-
anchor.removeEventListener(event, listener);
|
|
761
|
-
});
|
|
762
|
-
});
|
|
763
|
-
};
|
|
764
|
-
/**
|
|
765
|
-
* rendered is also a dependency to ensure anchor observers are re-registered
|
|
766
|
-
* since `tooltipRef` becomes stale after removing/adding the tooltip to the DOM
|
|
767
|
-
*/
|
|
768
1309
|
}, [
|
|
1310
|
+
imperativeOptions === null || imperativeOptions === void 0 ? void 0 : imperativeOptions.position,
|
|
1311
|
+
imperativeOptions === null || imperativeOptions === void 0 ? void 0 : imperativeOptions.place,
|
|
1312
|
+
position,
|
|
1313
|
+
float,
|
|
1314
|
+
activeAnchor,
|
|
1315
|
+
place,
|
|
1316
|
+
offset,
|
|
1317
|
+
positionStrategy,
|
|
1318
|
+
middlewares,
|
|
1319
|
+
border,
|
|
1320
|
+
handleTooltipPosition,
|
|
1321
|
+
handleComputedPosition,
|
|
1322
|
+
arrowSize,
|
|
1323
|
+
]);
|
|
1324
|
+
const handleActiveAnchorRemoved = useCallback(() => {
|
|
1325
|
+
setRendered(false);
|
|
1326
|
+
handleShow(false);
|
|
1327
|
+
setActiveAnchor(null);
|
|
1328
|
+
clearTimeoutRef(tooltipShowDelayTimerRef);
|
|
1329
|
+
clearTimeoutRef(tooltipHideDelayTimerRef);
|
|
1330
|
+
clearTimeoutRef(tooltipAutoCloseTimerRef);
|
|
1331
|
+
}, [handleShow, setActiveAnchor]);
|
|
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({
|
|
1338
|
+
id,
|
|
1339
|
+
anchorSelect,
|
|
1340
|
+
imperativeAnchorSelect: imperativeOptions === null || imperativeOptions === void 0 ? void 0 : imperativeOptions.anchorSelect,
|
|
1341
|
+
activeAnchor,
|
|
1342
|
+
disableTooltip,
|
|
1343
|
+
onActiveAnchorRemoved: handleActiveAnchorRemoved,
|
|
1344
|
+
trackAnchors: shouldTrackAnchors,
|
|
1345
|
+
});
|
|
1346
|
+
useTooltipEvents({
|
|
769
1347
|
activeAnchor,
|
|
770
1348
|
anchorElements,
|
|
1349
|
+
anchorSelector,
|
|
771
1350
|
clickable,
|
|
772
1351
|
closeEvents,
|
|
773
1352
|
delayHide,
|
|
774
1353
|
delayShow,
|
|
1354
|
+
disableTooltip,
|
|
775
1355
|
float,
|
|
776
1356
|
globalCloseEvents,
|
|
777
1357
|
handleHideTooltipDelayed,
|
|
778
1358
|
handleShow,
|
|
779
1359
|
handleShowTooltipDelayed,
|
|
780
1360
|
handleTooltipPosition,
|
|
1361
|
+
hoveringTooltip,
|
|
781
1362
|
imperativeModeOnly,
|
|
1363
|
+
lastFloatPosition,
|
|
782
1364
|
openEvents,
|
|
783
1365
|
openOnClick,
|
|
784
1366
|
setActiveAnchor,
|
|
785
1367
|
show,
|
|
1368
|
+
tooltipHideDelayTimerRef,
|
|
1369
|
+
tooltipRef,
|
|
1370
|
+
tooltipShowDelayTimerRef,
|
|
786
1371
|
updateTooltipPosition,
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
/**
|
|
791
|
-
* TODO(V6): break down observer callback for clarity
|
|
792
|
-
* - `handleAddedAnchors()`
|
|
793
|
-
* - `handleRemovedAnchors()`
|
|
794
|
-
*/
|
|
795
|
-
let selector = (_b = (_a = imperativeOptions === null || imperativeOptions === void 0 ? void 0 : imperativeOptions.anchorSelect) !== null && _a !== void 0 ? _a : anchorSelect) !== null && _b !== void 0 ? _b : '';
|
|
796
|
-
if (!selector && id) {
|
|
797
|
-
selector = `[data-tooltip-id='${id.replace(/'/g, "\\'")}']`;
|
|
798
|
-
}
|
|
799
|
-
const documentObserverCallback = (mutationList) => {
|
|
800
|
-
const addedAnchors = new Set();
|
|
801
|
-
const removedAnchors = new Set();
|
|
802
|
-
mutationList.forEach((mutation) => {
|
|
803
|
-
if (mutation.type === 'attributes' && mutation.attributeName === 'data-tooltip-id') {
|
|
804
|
-
const target = mutation.target;
|
|
805
|
-
const newId = target.getAttribute('data-tooltip-id');
|
|
806
|
-
if (newId === id) {
|
|
807
|
-
addedAnchors.add(target);
|
|
808
|
-
}
|
|
809
|
-
else if (mutation.oldValue === id) {
|
|
810
|
-
// data-tooltip-id has now been changed, so we need to remove this anchor
|
|
811
|
-
removedAnchors.add(target);
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
if (mutation.type !== 'childList') {
|
|
815
|
-
return;
|
|
816
|
-
}
|
|
817
|
-
const removedNodes = [...mutation.removedNodes].filter((node) => node.nodeType === 1);
|
|
818
|
-
if (activeAnchor) {
|
|
819
|
-
removedNodes.some((node) => {
|
|
820
|
-
var _a;
|
|
821
|
-
/**
|
|
822
|
-
* TODO(V6)
|
|
823
|
-
* - isn't `!activeAnchor.isConnected` better?
|
|
824
|
-
* - maybe move to `handleDisconnectedAnchor()`
|
|
825
|
-
*/
|
|
826
|
-
if ((_a = node === null || node === void 0 ? void 0 : node.contains) === null || _a === void 0 ? void 0 : _a.call(node, activeAnchor)) {
|
|
827
|
-
setRendered(false);
|
|
828
|
-
handleShow(false);
|
|
829
|
-
setActiveAnchor(null);
|
|
830
|
-
clearTimeoutRef(tooltipShowDelayTimerRef);
|
|
831
|
-
clearTimeoutRef(tooltipHideDelayTimerRef);
|
|
832
|
-
return true;
|
|
833
|
-
}
|
|
834
|
-
return false;
|
|
835
|
-
});
|
|
836
|
-
}
|
|
837
|
-
if (!selector) {
|
|
838
|
-
return;
|
|
839
|
-
}
|
|
840
|
-
try {
|
|
841
|
-
removedNodes.forEach((node) => {
|
|
842
|
-
const element = node;
|
|
843
|
-
if (element.matches(selector)) {
|
|
844
|
-
// the element itself is an anchor
|
|
845
|
-
removedAnchors.add(element);
|
|
846
|
-
}
|
|
847
|
-
else {
|
|
848
|
-
/**
|
|
849
|
-
* TODO(V6): do we care if an element which is an anchor,
|
|
850
|
-
* has children which are also anchors?
|
|
851
|
-
* (i.e. should we remove `else` and always do this)
|
|
852
|
-
*/
|
|
853
|
-
// the element has children which are anchors
|
|
854
|
-
element
|
|
855
|
-
.querySelectorAll(selector)
|
|
856
|
-
.forEach((innerNode) => removedAnchors.add(innerNode));
|
|
857
|
-
}
|
|
858
|
-
});
|
|
859
|
-
}
|
|
860
|
-
catch (_a) {
|
|
861
|
-
/* c8 ignore start */
|
|
862
|
-
{
|
|
863
|
-
// eslint-disable-next-line no-console
|
|
864
|
-
console.warn(`[react-tooltip] "${selector}" is not a valid CSS selector`);
|
|
865
|
-
}
|
|
866
|
-
/* c8 ignore end */
|
|
867
|
-
}
|
|
868
|
-
try {
|
|
869
|
-
const addedNodes = [...mutation.addedNodes].filter((node) => node.nodeType === 1);
|
|
870
|
-
addedNodes.forEach((node) => {
|
|
871
|
-
const element = node;
|
|
872
|
-
if (element.matches(selector)) {
|
|
873
|
-
// the element itself is an anchor
|
|
874
|
-
addedAnchors.add(element);
|
|
875
|
-
}
|
|
876
|
-
else {
|
|
877
|
-
/**
|
|
878
|
-
* TODO(V6): do we care if an element which is an anchor,
|
|
879
|
-
* has children which are also anchors?
|
|
880
|
-
* (i.e. should we remove `else` and always do this)
|
|
881
|
-
*/
|
|
882
|
-
// the element has children which are anchors
|
|
883
|
-
element
|
|
884
|
-
.querySelectorAll(selector)
|
|
885
|
-
.forEach((innerNode) => addedAnchors.add(innerNode));
|
|
886
|
-
}
|
|
887
|
-
});
|
|
888
|
-
}
|
|
889
|
-
catch (_b) {
|
|
890
|
-
/* c8 ignore start */
|
|
891
|
-
{
|
|
892
|
-
// eslint-disable-next-line no-console
|
|
893
|
-
console.warn(`[react-tooltip] "${selector}" is not a valid CSS selector`);
|
|
894
|
-
}
|
|
895
|
-
/* c8 ignore end */
|
|
896
|
-
}
|
|
897
|
-
});
|
|
898
|
-
if (addedAnchors.size || removedAnchors.size) {
|
|
899
|
-
setAnchorElements((anchors) => [
|
|
900
|
-
...anchors.filter((anchor) => !removedAnchors.has(anchor)),
|
|
901
|
-
...addedAnchors,
|
|
902
|
-
]);
|
|
903
|
-
}
|
|
904
|
-
};
|
|
905
|
-
const documentObserver = new MutationObserver(documentObserverCallback);
|
|
906
|
-
// watch for anchor being removed from the DOM
|
|
907
|
-
documentObserver.observe(document.body, {
|
|
908
|
-
childList: true,
|
|
909
|
-
subtree: true,
|
|
910
|
-
attributes: true,
|
|
911
|
-
attributeFilter: ['data-tooltip-id'],
|
|
912
|
-
// to track the prev value if we need to remove anchor when data-tooltip-id gets changed
|
|
913
|
-
attributeOldValue: true,
|
|
914
|
-
});
|
|
915
|
-
return () => {
|
|
916
|
-
documentObserver.disconnect();
|
|
917
|
-
};
|
|
918
|
-
}, [id, anchorSelect, imperativeOptions === null || imperativeOptions === void 0 ? void 0 : imperativeOptions.anchorSelect, activeAnchor, handleShow, setActiveAnchor]);
|
|
1372
|
+
});
|
|
1373
|
+
const updateTooltipPositionRef = useRef(updateTooltipPosition);
|
|
1374
|
+
updateTooltipPositionRef.current = updateTooltipPosition;
|
|
919
1375
|
useEffect(() => {
|
|
1376
|
+
if (!rendered) {
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
920
1379
|
updateTooltipPosition();
|
|
921
|
-
}, [updateTooltipPosition]);
|
|
1380
|
+
}, [rendered, updateTooltipPosition]);
|
|
922
1381
|
useEffect(() => {
|
|
923
|
-
if (!(contentWrapperRef === null || contentWrapperRef === void 0 ? void 0 : contentWrapperRef.current)) {
|
|
1382
|
+
if (!rendered || !(contentWrapperRef === null || contentWrapperRef === void 0 ? void 0 : contentWrapperRef.current)) {
|
|
924
1383
|
return () => null;
|
|
925
1384
|
}
|
|
1385
|
+
let timeoutId = null;
|
|
926
1386
|
const contentObserver = new ResizeObserver(() => {
|
|
927
|
-
|
|
1387
|
+
// Clear any existing timeout to prevent memory leaks
|
|
1388
|
+
if (timeoutId) {
|
|
1389
|
+
clearTimeout(timeoutId);
|
|
1390
|
+
}
|
|
1391
|
+
timeoutId = setTimeout(() => {
|
|
1392
|
+
if (mounted.current) {
|
|
1393
|
+
updateTooltipPositionRef.current();
|
|
1394
|
+
}
|
|
1395
|
+
timeoutId = null;
|
|
1396
|
+
}, 0);
|
|
928
1397
|
});
|
|
929
1398
|
contentObserver.observe(contentWrapperRef.current);
|
|
930
1399
|
return () => {
|
|
931
1400
|
contentObserver.disconnect();
|
|
1401
|
+
if (timeoutId) {
|
|
1402
|
+
clearTimeout(timeoutId);
|
|
1403
|
+
}
|
|
932
1404
|
};
|
|
933
|
-
}, [content, contentWrapperRef,
|
|
1405
|
+
}, [content, contentWrapperRef, rendered]);
|
|
934
1406
|
useEffect(() => {
|
|
935
1407
|
var _a;
|
|
1408
|
+
const shouldResolveInitialActiveAnchor = rendered || defaultIsOpen || Boolean(isOpen);
|
|
1409
|
+
if (!shouldResolveInitialActiveAnchor) {
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
const activeAnchorMatchesImperativeSelector = (() => {
|
|
1413
|
+
if (!activeAnchor || !(imperativeOptions === null || imperativeOptions === void 0 ? void 0 : imperativeOptions.anchorSelect)) {
|
|
1414
|
+
return false;
|
|
1415
|
+
}
|
|
1416
|
+
try {
|
|
1417
|
+
return activeAnchor.matches(imperativeOptions.anchorSelect);
|
|
1418
|
+
}
|
|
1419
|
+
catch (_a) {
|
|
1420
|
+
return false;
|
|
1421
|
+
}
|
|
1422
|
+
})();
|
|
936
1423
|
if (!activeAnchor || !anchorElements.includes(activeAnchor)) {
|
|
937
1424
|
/**
|
|
938
1425
|
* if there is no active anchor,
|
|
939
1426
|
* or if the current active anchor is not amongst the allowed ones,
|
|
940
1427
|
* reset it
|
|
941
1428
|
*/
|
|
1429
|
+
if (activeAnchorMatchesImperativeSelector) {
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
942
1432
|
setActiveAnchor((_a = anchorElements[0]) !== null && _a !== void 0 ? _a : null);
|
|
943
1433
|
}
|
|
944
|
-
}, [
|
|
1434
|
+
}, [
|
|
1435
|
+
activeAnchor,
|
|
1436
|
+
anchorElements,
|
|
1437
|
+
defaultIsOpen,
|
|
1438
|
+
imperativeOptions === null || imperativeOptions === void 0 ? void 0 : imperativeOptions.anchorSelect,
|
|
1439
|
+
isOpen,
|
|
1440
|
+
rendered,
|
|
1441
|
+
setActiveAnchor,
|
|
1442
|
+
]);
|
|
945
1443
|
useEffect(() => {
|
|
946
1444
|
if (defaultIsOpen) {
|
|
947
1445
|
handleShow(true);
|
|
@@ -949,26 +1447,10 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, activeAnch
|
|
|
949
1447
|
return () => {
|
|
950
1448
|
clearTimeoutRef(tooltipShowDelayTimerRef);
|
|
951
1449
|
clearTimeoutRef(tooltipHideDelayTimerRef);
|
|
1450
|
+
clearTimeoutRef(tooltipAutoCloseTimerRef);
|
|
1451
|
+
clearTimeoutRef(missedTransitionTimerRef);
|
|
952
1452
|
};
|
|
953
1453
|
}, [defaultIsOpen, handleShow]);
|
|
954
|
-
useEffect(() => {
|
|
955
|
-
var _a;
|
|
956
|
-
let selector = (_a = imperativeOptions === null || imperativeOptions === void 0 ? void 0 : imperativeOptions.anchorSelect) !== null && _a !== void 0 ? _a : anchorSelect;
|
|
957
|
-
if (!selector && id) {
|
|
958
|
-
selector = `[data-tooltip-id='${id.replace(/'/g, "\\'")}']`;
|
|
959
|
-
}
|
|
960
|
-
if (!selector) {
|
|
961
|
-
return;
|
|
962
|
-
}
|
|
963
|
-
try {
|
|
964
|
-
const anchors = Array.from(document.querySelectorAll(selector));
|
|
965
|
-
setAnchorElements(anchors);
|
|
966
|
-
}
|
|
967
|
-
catch (_b) {
|
|
968
|
-
// warning was already issued in the controller
|
|
969
|
-
setAnchorElements([]);
|
|
970
|
-
}
|
|
971
|
-
}, [id, anchorSelect, imperativeOptions === null || imperativeOptions === void 0 ? void 0 : imperativeOptions.anchorSelect]);
|
|
972
1454
|
useEffect(() => {
|
|
973
1455
|
if (tooltipShowDelayTimerRef.current) {
|
|
974
1456
|
/**
|
|
@@ -980,20 +1462,37 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, activeAnch
|
|
|
980
1462
|
}
|
|
981
1463
|
}, [delayShow, handleShowTooltipDelayed]);
|
|
982
1464
|
const actualContent = (_a = imperativeOptions === null || imperativeOptions === void 0 ? void 0 : imperativeOptions.content) !== null && _a !== void 0 ? _a : content;
|
|
983
|
-
const
|
|
1465
|
+
const hasContent = actualContent !== null && actualContent !== undefined;
|
|
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]);
|
|
984
1480
|
useImperativeHandle(forwardRef, () => ({
|
|
985
1481
|
open: (options) => {
|
|
1482
|
+
let imperativeAnchor = null;
|
|
986
1483
|
if (options === null || options === void 0 ? void 0 : options.anchorSelect) {
|
|
987
1484
|
try {
|
|
988
|
-
document.querySelector(options.anchorSelect);
|
|
1485
|
+
imperativeAnchor = document.querySelector(options.anchorSelect);
|
|
989
1486
|
}
|
|
990
1487
|
catch (_a) {
|
|
991
|
-
{
|
|
992
|
-
// eslint-disable-next-line no-console
|
|
993
|
-
console.warn(`[react-tooltip] "${options.anchorSelect}" is not a valid CSS selector`);
|
|
994
|
-
}
|
|
995
1488
|
return;
|
|
996
1489
|
}
|
|
1490
|
+
if (!imperativeAnchor) {
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
if (imperativeAnchor) {
|
|
1495
|
+
setActiveAnchor(imperativeAnchor);
|
|
997
1496
|
}
|
|
998
1497
|
setImperativeOptions(options !== null && options !== void 0 ? options : null);
|
|
999
1498
|
if (options === null || options === void 0 ? void 0 : options.delay) {
|
|
@@ -1013,9 +1512,18 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, activeAnch
|
|
|
1013
1512
|
},
|
|
1014
1513
|
activeAnchor,
|
|
1015
1514
|
place: computedPosition.place,
|
|
1016
|
-
isOpen: Boolean(rendered && !hidden &&
|
|
1515
|
+
isOpen: Boolean(rendered && !hidden && hasContent && canShow),
|
|
1017
1516
|
}));
|
|
1018
|
-
|
|
1517
|
+
useEffect(() => {
|
|
1518
|
+
return () => {
|
|
1519
|
+
// Final cleanup to ensure no memory leaks
|
|
1520
|
+
clearTimeoutRef(tooltipShowDelayTimerRef);
|
|
1521
|
+
clearTimeoutRef(tooltipHideDelayTimerRef);
|
|
1522
|
+
clearTimeoutRef(tooltipAutoCloseTimerRef);
|
|
1523
|
+
clearTimeoutRef(missedTransitionTimerRef);
|
|
1524
|
+
};
|
|
1525
|
+
}, []);
|
|
1526
|
+
const tooltipNode = rendered && !hidden && hasContent ? (React.createElement(WrapperElement, { id: id, role: role, className: clsx('react-tooltip', coreStyles['tooltip'], styles['tooltip'], styles[variant], className, `react-tooltip__place-${computedPosition.place}`, coreStyles[canShow ? 'show' : 'closing'], canShow ? 'react-tooltip__show' : 'react-tooltip__closing', positionStrategy === 'fixed' && coreStyles['fixed'], clickable && coreStyles['clickable']), onTransitionEnd: (event) => {
|
|
1019
1527
|
clearTimeoutRef(missedTransitionTimerRef);
|
|
1020
1528
|
if (show || event.propertyName !== 'opacity') {
|
|
1021
1529
|
return;
|
|
@@ -1023,34 +1531,96 @@ content, contentWrapperRef, isOpen, defaultIsOpen = false, setIsOpen, activeAnch
|
|
|
1023
1531
|
setRendered(false);
|
|
1024
1532
|
setImperativeOptions(null);
|
|
1025
1533
|
afterHide === null || afterHide === void 0 ? void 0 : afterHide();
|
|
1026
|
-
}, style:
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1534
|
+
}, style: tooltipStyle, ref: tooltipRef },
|
|
1535
|
+
React.createElement(WrapperElement, { className: clsx('react-tooltip-content-wrapper', coreStyles['content'], styles['content']) }, actualContent),
|
|
1536
|
+
React.createElement(WrapperElement, { className: clsx('react-tooltip-arrow', coreStyles['arrow'], styles['arrow'], classNameArrow, noArrow && coreStyles['noArrow']), style: arrowStyle, ref: tooltipArrowRef }))) : null;
|
|
1537
|
+
if (!tooltipNode) {
|
|
1538
|
+
return null;
|
|
1539
|
+
}
|
|
1540
|
+
if (portalRoot) {
|
|
1541
|
+
return createPortal(tooltipNode, portalRoot);
|
|
1542
|
+
}
|
|
1543
|
+
return tooltipNode;
|
|
1544
|
+
};
|
|
1545
|
+
var Tooltip$1 = memo(Tooltip);
|
|
1546
|
+
|
|
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,
|
|
1038
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
|
+
}
|
|
1039
1608
|
|
|
1040
|
-
const TooltipController = React.forwardRef(({ id, anchorSelect, content, render, className, classNameArrow, variant = 'dark', place = 'top', offset = 10, wrapper = 'div', children = null, openOnClick = false, positionStrategy = 'absolute', middlewares, delayShow = 0, delayHide = 0, float = false, hidden = false, noArrow = false, clickable = false, openEvents, closeEvents, globalCloseEvents, imperativeModeOnly = false, style, position, isOpen, defaultIsOpen = false, disableStyleInjection = false, border, opacity, arrowColor, setIsOpen, afterShow, afterHide, role = 'tooltip', }, ref) => {
|
|
1041
|
-
|
|
1042
|
-
const [tooltipPlace, setTooltipPlace] = useState(place);
|
|
1043
|
-
const [tooltipVariant, setTooltipVariant] = useState(variant);
|
|
1044
|
-
const [tooltipOffset, setTooltipOffset] = useState(offset);
|
|
1045
|
-
const [tooltipDelayShow, setTooltipDelayShow] = useState(delayShow);
|
|
1046
|
-
const [tooltipDelayHide, setTooltipDelayHide] = useState(delayHide);
|
|
1047
|
-
const [tooltipFloat, setTooltipFloat] = useState(float);
|
|
1048
|
-
const [tooltipHidden, setTooltipHidden] = useState(hidden);
|
|
1049
|
-
const [tooltipWrapper, setTooltipWrapper] = useState(wrapper);
|
|
1050
|
-
const [tooltipPositionStrategy, setTooltipPositionStrategy] = useState(positionStrategy);
|
|
1051
|
-
const [tooltipClassName, setTooltipClassName] = useState(null);
|
|
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) => {
|
|
1610
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1052
1611
|
const [activeAnchor, setActiveAnchor] = useState(null);
|
|
1612
|
+
const [anchorDataAttributes, setAnchorDataAttributes] = useState({});
|
|
1613
|
+
const previousActiveAnchorRef = useRef(null);
|
|
1053
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
|
+
}, []);
|
|
1623
|
+
/* c8 ignore start */
|
|
1054
1624
|
const getDataAttributesFromAnchorElement = (elementReference) => {
|
|
1055
1625
|
const dataAttributes = elementReference === null || elementReference === void 0 ? void 0 : elementReference.getAttributeNames().reduce((acc, name) => {
|
|
1056
1626
|
var _a;
|
|
@@ -1062,101 +1632,11 @@ const TooltipController = React.forwardRef(({ id, anchorSelect, content, render,
|
|
|
1062
1632
|
}, {});
|
|
1063
1633
|
return dataAttributes;
|
|
1064
1634
|
};
|
|
1065
|
-
|
|
1066
|
-
const handleDataAttributes = {
|
|
1067
|
-
place: (value) => {
|
|
1068
|
-
var _a;
|
|
1069
|
-
setTooltipPlace((_a = value) !== null && _a !== void 0 ? _a : place);
|
|
1070
|
-
},
|
|
1071
|
-
content: (value) => {
|
|
1072
|
-
setTooltipContent(value !== null && value !== void 0 ? value : content);
|
|
1073
|
-
},
|
|
1074
|
-
variant: (value) => {
|
|
1075
|
-
var _a;
|
|
1076
|
-
setTooltipVariant((_a = value) !== null && _a !== void 0 ? _a : variant);
|
|
1077
|
-
},
|
|
1078
|
-
offset: (value) => {
|
|
1079
|
-
setTooltipOffset(value === null ? offset : Number(value));
|
|
1080
|
-
},
|
|
1081
|
-
wrapper: (value) => {
|
|
1082
|
-
var _a;
|
|
1083
|
-
setTooltipWrapper((_a = value) !== null && _a !== void 0 ? _a : wrapper);
|
|
1084
|
-
},
|
|
1085
|
-
'position-strategy': (value) => {
|
|
1086
|
-
var _a;
|
|
1087
|
-
setTooltipPositionStrategy((_a = value) !== null && _a !== void 0 ? _a : positionStrategy);
|
|
1088
|
-
},
|
|
1089
|
-
'delay-show': (value) => {
|
|
1090
|
-
setTooltipDelayShow(value === null ? delayShow : Number(value));
|
|
1091
|
-
},
|
|
1092
|
-
'delay-hide': (value) => {
|
|
1093
|
-
setTooltipDelayHide(value === null ? delayHide : Number(value));
|
|
1094
|
-
},
|
|
1095
|
-
float: (value) => {
|
|
1096
|
-
setTooltipFloat(value === null ? float : value === 'true');
|
|
1097
|
-
},
|
|
1098
|
-
hidden: (value) => {
|
|
1099
|
-
setTooltipHidden(value === null ? hidden : value === 'true');
|
|
1100
|
-
},
|
|
1101
|
-
'class-name': (value) => {
|
|
1102
|
-
setTooltipClassName(value);
|
|
1103
|
-
},
|
|
1104
|
-
};
|
|
1105
|
-
// reset unset data attributes to default values
|
|
1106
|
-
// without this, data attributes from the last active anchor will still be used
|
|
1107
|
-
Object.values(handleDataAttributes).forEach((handler) => handler(null));
|
|
1108
|
-
Object.entries(dataAttributes).forEach(([key, value]) => {
|
|
1109
|
-
var _a;
|
|
1110
|
-
(_a = handleDataAttributes[key]) === null || _a === void 0 ? void 0 : _a.call(handleDataAttributes, value);
|
|
1111
|
-
});
|
|
1112
|
-
}, [
|
|
1113
|
-
content,
|
|
1114
|
-
delayHide,
|
|
1115
|
-
delayShow,
|
|
1116
|
-
float,
|
|
1117
|
-
hidden,
|
|
1118
|
-
offset,
|
|
1119
|
-
place,
|
|
1120
|
-
positionStrategy,
|
|
1121
|
-
variant,
|
|
1122
|
-
wrapper,
|
|
1123
|
-
]);
|
|
1124
|
-
useEffect(() => {
|
|
1125
|
-
setTooltipContent(content);
|
|
1126
|
-
}, [content]);
|
|
1127
|
-
useEffect(() => {
|
|
1128
|
-
setTooltipPlace(place);
|
|
1129
|
-
}, [place]);
|
|
1130
|
-
useEffect(() => {
|
|
1131
|
-
setTooltipVariant(variant);
|
|
1132
|
-
}, [variant]);
|
|
1133
|
-
useEffect(() => {
|
|
1134
|
-
setTooltipOffset(offset);
|
|
1135
|
-
}, [offset]);
|
|
1136
|
-
useEffect(() => {
|
|
1137
|
-
setTooltipDelayShow(delayShow);
|
|
1138
|
-
}, [delayShow]);
|
|
1139
|
-
useEffect(() => {
|
|
1140
|
-
setTooltipDelayHide(delayHide);
|
|
1141
|
-
}, [delayHide]);
|
|
1142
|
-
useEffect(() => {
|
|
1143
|
-
setTooltipFloat(float);
|
|
1144
|
-
}, [float]);
|
|
1145
|
-
useEffect(() => {
|
|
1146
|
-
setTooltipHidden(hidden);
|
|
1147
|
-
}, [hidden]);
|
|
1148
|
-
useEffect(() => {
|
|
1149
|
-
setTooltipPositionStrategy(positionStrategy);
|
|
1150
|
-
}, [positionStrategy]);
|
|
1635
|
+
/* c8 ignore end */
|
|
1151
1636
|
useEffect(() => {
|
|
1152
1637
|
if (styleInjectionRef.current === disableStyleInjection) {
|
|
1153
1638
|
return;
|
|
1154
1639
|
}
|
|
1155
|
-
/* c8 ignore start */
|
|
1156
|
-
{
|
|
1157
|
-
// eslint-disable-next-line no-console
|
|
1158
|
-
console.warn('[react-tooltip] Do not change `disableStyleInjection` dynamically.');
|
|
1159
|
-
}
|
|
1160
1640
|
/* c8 ignore end */
|
|
1161
1641
|
}, [disableStyleInjection]);
|
|
1162
1642
|
useEffect(() => {
|
|
@@ -1171,58 +1651,61 @@ const TooltipController = React.forwardRef(({ id, anchorSelect, content, render,
|
|
|
1171
1651
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1172
1652
|
}, []);
|
|
1173
1653
|
useEffect(() => {
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1654
|
+
if (!activeAnchor) {
|
|
1655
|
+
setAnchorDataAttributes({});
|
|
1656
|
+
return () => { };
|
|
1657
|
+
}
|
|
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;
|
|
1181
1665
|
}
|
|
1182
|
-
|
|
1183
|
-
const dataAttributes = getDataAttributesFromAnchorElement(activeAnchor);
|
|
1184
|
-
applyAllDataAttributesFromAnchorElement(dataAttributes);
|
|
1666
|
+
return attrs;
|
|
1185
1667
|
});
|
|
1186
1668
|
};
|
|
1187
|
-
|
|
1188
|
-
const
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
const observerConfig = { attributes: true, childList: false, subtree: false };
|
|
1192
|
-
if (activeAnchor) {
|
|
1193
|
-
const dataAttributes = getDataAttributesFromAnchorElement(activeAnchor);
|
|
1194
|
-
applyAllDataAttributesFromAnchorElement(dataAttributes);
|
|
1195
|
-
// Start observing the target node for configured mutations
|
|
1196
|
-
observer.observe(activeAnchor, observerConfig);
|
|
1197
|
-
}
|
|
1198
|
-
return () => {
|
|
1199
|
-
// Remove the observer when the tooltip is destroyed
|
|
1200
|
-
observer.disconnect();
|
|
1201
|
-
};
|
|
1202
|
-
}, [activeAnchor, anchorSelect, applyAllDataAttributesFromAnchorElement]);
|
|
1669
|
+
updateAttributes(activeAnchor);
|
|
1670
|
+
const unsubscribe = observeAnchorAttributes(activeAnchor, updateAttributes);
|
|
1671
|
+
return unsubscribe;
|
|
1672
|
+
}, [activeAnchor, anchorSelect]);
|
|
1203
1673
|
useEffect(() => {
|
|
1204
|
-
/* c8 ignore
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
console.warn('[react-tooltip] Do not set `style.border`. Use `border` prop instead.');
|
|
1208
|
-
}
|
|
1209
|
-
if (style === null || style === void 0 ? void 0 : style.opacity) {
|
|
1210
|
-
// eslint-disable-next-line no-console
|
|
1211
|
-
console.warn('[react-tooltip] Do not set `style.opacity`. Use `opacity` prop instead.');
|
|
1674
|
+
/* c8 ignore start */
|
|
1675
|
+
{
|
|
1676
|
+
return;
|
|
1212
1677
|
}
|
|
1213
1678
|
}, [border, opacity, style === null || style === void 0 ? void 0 : style.border, style === null || style === void 0 ? void 0 : style.opacity]);
|
|
1214
1679
|
/**
|
|
1215
1680
|
* content priority: children < render or content < html
|
|
1216
1681
|
* children should be lower priority so that it can be used as the "default" content
|
|
1217
1682
|
*/
|
|
1683
|
+
const tooltipContent = (_a = anchorDataAttributes.content) !== null && _a !== void 0 ? _a : content;
|
|
1684
|
+
const tooltipPlace = (_b = anchorDataAttributes.place) !== null && _b !== void 0 ? _b : place;
|
|
1685
|
+
const tooltipVariant = (_c = anchorDataAttributes.variant) !== null && _c !== void 0 ? _c : variant;
|
|
1686
|
+
const tooltipOffset = anchorDataAttributes.offset == null ? offset : Number(anchorDataAttributes.offset);
|
|
1687
|
+
const tooltipWrapper = (_d = anchorDataAttributes.wrapper) !== null && _d !== void 0 ? _d : wrapper;
|
|
1688
|
+
const tooltipPositionStrategy = (_e = anchorDataAttributes['position-strategy']) !== null && _e !== void 0 ? _e : positionStrategy;
|
|
1689
|
+
const tooltipDelayShow = anchorDataAttributes['delay-show'] == null
|
|
1690
|
+
? delayShow
|
|
1691
|
+
: Number(anchorDataAttributes['delay-show']);
|
|
1692
|
+
const tooltipDelayHide = anchorDataAttributes['delay-hide'] == null
|
|
1693
|
+
? delayHide
|
|
1694
|
+
: Number(anchorDataAttributes['delay-hide']);
|
|
1695
|
+
const tooltipAutoClose = anchorDataAttributes['auto-close'] == null
|
|
1696
|
+
? autoClose
|
|
1697
|
+
: Number(anchorDataAttributes['auto-close']);
|
|
1698
|
+
const tooltipFloat = anchorDataAttributes.float == null ? float : anchorDataAttributes.float === 'true';
|
|
1699
|
+
const tooltipHidden = anchorDataAttributes.hidden == null ? hidden : anchorDataAttributes.hidden === 'true';
|
|
1700
|
+
const tooltipClassName = (_f = anchorDataAttributes['class-name']) !== null && _f !== void 0 ? _f : null;
|
|
1218
1701
|
let renderedContent = children;
|
|
1219
1702
|
const contentWrapperRef = useRef(null);
|
|
1220
1703
|
if (render) {
|
|
1221
|
-
const actualContent = (
|
|
1704
|
+
const actualContent = (_h = (_g = anchorDataAttributes.content) !== null && _g !== void 0 ? _g : tooltipContent) !== null && _h !== void 0 ? _h : null;
|
|
1222
1705
|
const rendered = render({ content: actualContent, activeAnchor });
|
|
1223
1706
|
renderedContent = rendered ? (React.createElement("div", { ref: contentWrapperRef, className: "react-tooltip-content-wrapper" }, rendered)) : null;
|
|
1224
1707
|
}
|
|
1225
|
-
else if (tooltipContent) {
|
|
1708
|
+
else if (tooltipContent !== null && tooltipContent !== undefined) {
|
|
1226
1709
|
renderedContent = tooltipContent;
|
|
1227
1710
|
}
|
|
1228
1711
|
const props = {
|
|
@@ -1233,6 +1716,7 @@ const TooltipController = React.forwardRef(({ id, anchorSelect, content, render,
|
|
|
1233
1716
|
classNameArrow,
|
|
1234
1717
|
content: renderedContent,
|
|
1235
1718
|
contentWrapperRef,
|
|
1719
|
+
portalRoot,
|
|
1236
1720
|
place: tooltipPlace,
|
|
1237
1721
|
variant: tooltipVariant,
|
|
1238
1722
|
offset: tooltipOffset,
|
|
@@ -1242,6 +1726,7 @@ const TooltipController = React.forwardRef(({ id, anchorSelect, content, render,
|
|
|
1242
1726
|
middlewares,
|
|
1243
1727
|
delayShow: tooltipDelayShow,
|
|
1244
1728
|
delayHide: tooltipDelayHide,
|
|
1729
|
+
autoClose: tooltipAutoClose,
|
|
1245
1730
|
float: tooltipFloat,
|
|
1246
1731
|
hidden: tooltipHidden,
|
|
1247
1732
|
noArrow,
|
|
@@ -1257,15 +1742,19 @@ const TooltipController = React.forwardRef(({ id, anchorSelect, content, render,
|
|
|
1257
1742
|
border,
|
|
1258
1743
|
opacity,
|
|
1259
1744
|
arrowColor,
|
|
1745
|
+
arrowSize,
|
|
1260
1746
|
setIsOpen,
|
|
1261
1747
|
afterShow,
|
|
1262
1748
|
afterHide,
|
|
1749
|
+
disableTooltip,
|
|
1263
1750
|
activeAnchor,
|
|
1264
|
-
|
|
1751
|
+
previousActiveAnchor: previousActiveAnchorRef.current,
|
|
1752
|
+
setActiveAnchor: handleSetActiveAnchor,
|
|
1265
1753
|
role,
|
|
1266
1754
|
};
|
|
1267
|
-
return React.createElement(Tooltip, { ...props });
|
|
1755
|
+
return React.createElement(Tooltip$1, { ...props });
|
|
1268
1756
|
});
|
|
1757
|
+
var TooltipController_default = memo(TooltipController);
|
|
1269
1758
|
|
|
1270
1759
|
// those content will be replaced in build time with the `react-tooltip.css` builded content
|
|
1271
1760
|
const TooltipCoreStyles = `:root {
|
|
@@ -1278,6 +1767,7 @@ const TooltipCoreStyles = `:root {
|
|
|
1278
1767
|
--rt-opacity: 0.9;
|
|
1279
1768
|
--rt-transition-show-delay: 0.15s;
|
|
1280
1769
|
--rt-transition-closing-delay: 0.15s;
|
|
1770
|
+
--rt-arrow-size: 8px;
|
|
1281
1771
|
}
|
|
1282
1772
|
|
|
1283
1773
|
.core-styles-module_tooltip__3vRRp {
|
|
@@ -1286,7 +1776,6 @@ const TooltipCoreStyles = `:root {
|
|
|
1286
1776
|
left: 0;
|
|
1287
1777
|
pointer-events: none;
|
|
1288
1778
|
opacity: 0;
|
|
1289
|
-
will-change: opacity;
|
|
1290
1779
|
}
|
|
1291
1780
|
|
|
1292
1781
|
.core-styles-module_fixed__pcSol {
|
|
@@ -1296,6 +1785,14 @@ const TooltipCoreStyles = `:root {
|
|
|
1296
1785
|
.core-styles-module_arrow__cvMwQ {
|
|
1297
1786
|
position: absolute;
|
|
1298
1787
|
background: inherit;
|
|
1788
|
+
z-index: -1;
|
|
1789
|
+
-webkit-backface-visibility: hidden;
|
|
1790
|
+
backface-visibility: hidden;
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
.core-styles-module_content__BRKdB {
|
|
1794
|
+
position: relative;
|
|
1795
|
+
z-index: 1;
|
|
1299
1796
|
}
|
|
1300
1797
|
|
|
1301
1798
|
.core-styles-module_noArrow__xock6 {
|
|
@@ -1309,26 +1806,33 @@ const TooltipCoreStyles = `:root {
|
|
|
1309
1806
|
.core-styles-module_show__Nt9eE {
|
|
1310
1807
|
opacity: var(--rt-opacity);
|
|
1311
1808
|
transition: opacity var(--rt-transition-show-delay) ease-out;
|
|
1809
|
+
will-change: opacity;
|
|
1312
1810
|
}
|
|
1313
1811
|
|
|
1314
1812
|
.core-styles-module_closing__sGnxF {
|
|
1315
1813
|
opacity: 0;
|
|
1316
1814
|
transition: opacity var(--rt-transition-closing-delay) ease-in;
|
|
1815
|
+
will-change: opacity;
|
|
1317
1816
|
}
|
|
1318
1817
|
|
|
1319
1818
|
`;
|
|
1320
1819
|
const TooltipStyles = `
|
|
1321
1820
|
|
|
1322
1821
|
.styles-module_tooltip__mnnfp {
|
|
1323
|
-
padding: 8px 16px;
|
|
1324
1822
|
border-radius: 3px;
|
|
1325
1823
|
font-size: 90%;
|
|
1326
1824
|
width: max-content;
|
|
1327
1825
|
}
|
|
1328
1826
|
|
|
1827
|
+
.styles-module_content__ydYdI {
|
|
1828
|
+
background: inherit;
|
|
1829
|
+
border-radius: inherit;
|
|
1830
|
+
padding: 8px 16px;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1329
1833
|
.styles-module_arrow__K0L3T {
|
|
1330
|
-
width:
|
|
1331
|
-
height:
|
|
1834
|
+
width: var(--rt-arrow-size);
|
|
1835
|
+
height: var(--rt-arrow-size);
|
|
1332
1836
|
}
|
|
1333
1837
|
|
|
1334
1838
|
[class*='react-tooltip__place-top'] > .styles-module_arrow__K0L3T {
|
|
@@ -1389,5 +1893,5 @@ if (typeof window !== 'undefined') {
|
|
|
1389
1893
|
}));
|
|
1390
1894
|
}
|
|
1391
1895
|
|
|
1392
|
-
export {
|
|
1896
|
+
export { TooltipController_default as Tooltip };
|
|
1393
1897
|
//# sourceMappingURL=react-tooltip.mjs.map
|