@lablup/ui-common 0.1.0-alpha.14 → 0.1.0-alpha.15
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
.tooltip__wrapper{display:inline-flex}.tooltip__wrapper:focus-visible{outline:var(--token-focusRingWidth,2px) var(--token-focusRingStyle,solid) var(--token-focusRingColor,var(--token-colorPrimary));outline-offset:var(--token-focusRingOffset,2px);border-radius:var(--token-borderRadiusSM,.25rem)}.tooltip__wrapper:focus:not(:focus-visible){outline:none}.tooltip__content{z-index:var(--token-zIndexTooltip,1300);color:#f3f4f6;max-width:280px;font-size:var(--token-fontSize,.875rem);
|
|
1
|
+
.tooltip__wrapper{display:inline-flex}.tooltip__wrapper:focus-visible{outline:var(--token-focusRingWidth,2px) var(--token-focusRingStyle,solid) var(--token-focusRingColor,var(--token-colorPrimary));outline-offset:var(--token-focusRingOffset,2px);border-radius:var(--token-borderRadiusSM,.25rem)}.tooltip__wrapper:focus:not(:focus-visible){outline:none}.tooltip__content{z-index:var(--token-zIndexTooltip,1300);color:#f3f4f6;max-width:280px;font-size:var(--token-fontSize,.875rem);background-color:#1f2937;border-radius:6px;padding:.5rem .75rem;line-height:1.4;position:fixed;box-shadow:0 4px 12px #00000026}.tooltip__content p,.tooltip__content span{color:#f3f4f6}.tooltip__text{color:#f3f4f6;margin:0}.tooltip__content.param-tooltip__content{width:280px}
|
|
@@ -3,66 +3,89 @@ import { useCallback as e, useEffect as t, useId as n, useRef as r, useState as
|
|
|
3
3
|
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
4
4
|
import { createPortal as s } from "react-dom";
|
|
5
5
|
//#region src/components/Tooltip/Tooltip.tsx
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
var c = 120;
|
|
7
|
+
function l({ content: l, children: u, className: d, contentClassName: f, tooltipId: p, tabIndex: m = 0 }) {
|
|
8
|
+
let [h, g] = i(!1), [_, v] = i(null), y = r(null), b = r(null), x = r(!0), S = r(null), C = n(), w = p ?? `tooltip-${C}`;
|
|
9
|
+
t(() => (x.current = !0, () => {
|
|
10
|
+
x.current = !1, S.current !== null && clearTimeout(S.current);
|
|
10
11
|
}), []), t(() => {
|
|
11
|
-
let e =
|
|
12
|
+
let e = b.current;
|
|
12
13
|
if (!e) return;
|
|
13
14
|
let t = () => {
|
|
14
|
-
|
|
15
|
+
x.current && g(!0);
|
|
15
16
|
}, n = () => {
|
|
16
|
-
|
|
17
|
+
x.current && (g(!1), v(null));
|
|
17
18
|
};
|
|
18
19
|
return e.addEventListener("focusin", t), e.addEventListener("focusout", n), () => {
|
|
19
20
|
e.removeEventListener("focusin", t), e.removeEventListener("focusout", n);
|
|
20
21
|
};
|
|
21
|
-
}, [])
|
|
22
|
-
|
|
23
|
-
let e = y.current
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
placement: a
|
|
33
|
-
});
|
|
22
|
+
}, []);
|
|
23
|
+
let T = e(() => {
|
|
24
|
+
let e = b.current, t = y.current;
|
|
25
|
+
if (!x.current || !e || !t) return;
|
|
26
|
+
let n = e.getBoundingClientRect(), r = t.getBoundingClientRect(), i = n.top, a = window.innerHeight - n.bottom, o = r.height, s = i < o + 10 && a > i ? "bottom" : "top", c = n.left + n.width / 2, l = r.width, u = c - l / 2, d = window.innerWidth;
|
|
27
|
+
u + l > d - 16 && (u = d - l - 16), u < 16 && (u = 16);
|
|
28
|
+
let f = s === "top" ? n.top - o - 8 : n.bottom + 8;
|
|
29
|
+
v({
|
|
30
|
+
top: f,
|
|
31
|
+
left: u,
|
|
32
|
+
placement: s
|
|
34
33
|
});
|
|
35
|
-
}, [
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
}, []);
|
|
35
|
+
t(() => {
|
|
36
|
+
if (!h) return;
|
|
37
|
+
let e = requestAnimationFrame(T);
|
|
38
|
+
return window.addEventListener("scroll", T, {
|
|
39
|
+
capture: !0,
|
|
40
|
+
passive: !0
|
|
41
|
+
}), window.addEventListener("resize", T), () => {
|
|
42
|
+
cancelAnimationFrame(e), window.removeEventListener("scroll", T, { capture: !0 }), window.removeEventListener("resize", T);
|
|
43
|
+
};
|
|
44
|
+
}, [h, T]);
|
|
45
|
+
let E = e(() => {
|
|
46
|
+
S.current !== null && (clearTimeout(S.current), S.current = null), g(!0);
|
|
47
|
+
}, []), D = e(() => {
|
|
48
|
+
S.current !== null && (clearTimeout(S.current), S.current = null), g(!1), v(null);
|
|
49
|
+
}, []), O = e(() => {
|
|
50
|
+
S.current !== null && clearTimeout(S.current), S.current = setTimeout(() => {
|
|
51
|
+
S.current = null, x.current && (g(!1), v(null));
|
|
52
|
+
}, c);
|
|
53
|
+
}, []);
|
|
54
|
+
t(() => {
|
|
55
|
+
if (!h) return;
|
|
56
|
+
let e = (e) => {
|
|
57
|
+
e.key === "Escape" && D();
|
|
58
|
+
};
|
|
59
|
+
return document.addEventListener("keydown", e), () => {
|
|
60
|
+
document.removeEventListener("keydown", e);
|
|
61
|
+
};
|
|
62
|
+
}, [h, D]);
|
|
63
|
+
let k = h && /* @__PURE__ */ a("div", {
|
|
64
|
+
ref: y,
|
|
65
|
+
id: w,
|
|
66
|
+
className: `tooltip__content tooltip__content--${_?.placement ?? "top"} ${f ?? ""}`,
|
|
67
|
+
style: _ ? {
|
|
68
|
+
top: `${String(_.top)}px`,
|
|
69
|
+
left: `${String(_.left)}px`
|
|
47
70
|
} : { visibility: "hidden" },
|
|
48
71
|
role: "tooltip",
|
|
49
|
-
children: typeof
|
|
72
|
+
children: typeof l == "string" ? /* @__PURE__ */ a("p", {
|
|
50
73
|
className: "tooltip__text",
|
|
51
|
-
children:
|
|
52
|
-
}) :
|
|
74
|
+
children: l
|
|
75
|
+
}) : l
|
|
53
76
|
});
|
|
54
77
|
return /* @__PURE__ */ o("div", {
|
|
55
|
-
ref:
|
|
56
|
-
className: `tooltip__wrapper ${
|
|
57
|
-
onMouseEnter:
|
|
58
|
-
onMouseLeave:
|
|
59
|
-
tabIndex:
|
|
60
|
-
"aria-describedby":
|
|
61
|
-
children: [
|
|
78
|
+
ref: b,
|
|
79
|
+
className: `tooltip__wrapper ${d ?? ""}`,
|
|
80
|
+
onMouseEnter: E,
|
|
81
|
+
onMouseLeave: O,
|
|
82
|
+
tabIndex: m,
|
|
83
|
+
"aria-describedby": h ? w : void 0,
|
|
84
|
+
children: [u, k && s(k, document.body)]
|
|
62
85
|
});
|
|
63
86
|
}
|
|
64
87
|
//#endregion
|
|
65
|
-
export {
|
|
88
|
+
export { l as Tooltip };
|
|
66
89
|
|
|
67
90
|
//# sourceMappingURL=Tooltip2.js.map
|
|
68
91
|
import "../../assets/components/Tooltip/Tooltip.css";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tooltip2.js","names":[],"sources":["../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["/**\n * Tooltip Component\n *\n * A reusable tooltip component that displays content on hover.\n * Supports both simple text and complex ReactNode content.\n *\n * Accessibility:\n * - Shows on mouse hover and keyboard focus (WCAG 1.4.13)\n * - Uses role=\"tooltip\" for screen readers\n * - Accessible via keyboard navigation\n */\n\nimport { useState, useRef, useEffect, useCallback, useId, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport \"./Tooltip.css\";\n\nexport interface TooltipProps {\n /** Tooltip content - can be string or ReactNode */\n content: ReactNode;\n /** Children to wrap with tooltip (trigger element) */\n children: ReactNode;\n /** Additional class name for the wrapper */\n className?: string;\n /** Additional class name for the tooltip content */\n contentClassName?: string;\n /** Custom ID for the tooltip (for aria-describedby association) */\n tooltipId?: string;\n /** Tab index for the wrapper. Use -1 when wrapping already-focusable elements like buttons. Defaults to 0. */\n tabIndex?: number;\n}\n\ninterface TooltipPosition {\n top: number;\n left: number;\n placement: \"top\" | \"bottom\";\n}\n\nexport function Tooltip({\n content,\n children,\n className,\n contentClassName,\n tooltipId,\n tabIndex = 0,\n}: TooltipProps) {\n const [isVisible, setIsVisible] = useState(false);\n const [tooltipPosition, setTooltipPosition] = useState<TooltipPosition | null>(null);\n const tooltipRef = useRef<HTMLDivElement>(null);\n const wrapperRef = useRef<HTMLDivElement>(null);\n const isMountedRef = useRef(true);\n const generatedId = useId();\n const effectiveTooltipId = tooltipId ?? `tooltip-${generatedId}`;\n\n // Track mount state\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n // Handle focus events using focusin/focusout which bubble from child elements\n // This ensures tooltips show when keyboard focus moves to children (WCAG 1.4.13)\n useEffect(() => {\n const wrapper = wrapperRef.current;\n if (!wrapper) return;\n\n // focusin/focusout bubble from child elements, unlike focus/blur\n const handleFocusIn = () => {\n if (isMountedRef.current) {\n setIsVisible(true);\n }\n };\n\n const handleFocusOut = () => {\n if (isMountedRef.current) {\n setIsVisible(false);\n setTooltipPosition(null);\n }\n };\n\n wrapper.addEventListener(\"focusin\", handleFocusIn);\n wrapper.addEventListener(\"focusout\", handleFocusOut);\n\n return () => {\n wrapper.removeEventListener(\"focusin\", handleFocusIn);\n wrapper.removeEventListener(\"focusout\", handleFocusOut);\n };\n }, []);\n\n // Calculate tooltip position based on available space using fixed positioning\n useEffect(() => {\n if (!isVisible || !wrapperRef.current) return;\n if (!isMountedRef.current) return;\n\n const wrapperRect = wrapperRef.current.getBoundingClientRect();\n\n // Use requestAnimationFrame to ensure DOM is updated\n requestAnimationFrame(() => {\n if (!isMountedRef.current || !tooltipRef.current) return;\n\n const tooltipRect = tooltipRef.current.getBoundingClientRect();\n const spaceAbove = wrapperRect.top;\n const spaceBelow = window.innerHeight - wrapperRect.bottom;\n\n const tooltipHeight = tooltipRect.height;\n const margin = 10;\n\n // Determine placement\n const placement: \"top\" | \"bottom\" =\n spaceAbove < tooltipHeight + margin && spaceBelow > spaceAbove\n ? \"bottom\"\n : \"top\";\n\n // Calculate left position (center on wrapper, but ensure it stays within viewport)\n const wrapperCenter = wrapperRect.left + wrapperRect.width / 2;\n const tooltipWidth = tooltipRect.width;\n let left = wrapperCenter - tooltipWidth / 2;\n const viewportWidth = window.innerWidth;\n\n // Keep tooltip within viewport bounds\n if (left + tooltipWidth > viewportWidth - 16) {\n left = viewportWidth - tooltipWidth - 16;\n }\n if (left < 16) {\n left = 16;\n }\n\n // Calculate top position\n let top: number;\n if (placement === \"top\") {\n top = wrapperRect.top - tooltipHeight - 8;\n } else {\n top = wrapperRect.bottom + 8;\n }\n\n setTooltipPosition({ top, left, placement });\n });\n }, [isVisible]);\n\n const handleShow = useCallback(() => {\n setIsVisible(true);\n }, []);\n\n const handleHide = useCallback(() => {\n setIsVisible(false);\n setTooltipPosition(null);\n }, []);\n\n const tooltipElement = isVisible && (\n <div\n ref={tooltipRef}\n id={effectiveTooltipId}\n className={`tooltip__content tooltip__content--${tooltipPosition?.placement ?? \"top\"} ${contentClassName ?? \"\"}`}\n style={\n tooltipPosition\n ? {\n top: `${String(tooltipPosition.top)}px`,\n left: `${String(tooltipPosition.left)}px`,\n }\n : { visibility: \"hidden\" }\n }\n role=\"tooltip\"\n >\n {typeof content === \"string\" ? (\n <p className=\"tooltip__text\">{content}</p>\n ) : (\n content\n )}\n </div>\n );\n\n return (\n <div\n ref={wrapperRef}\n className={`tooltip__wrapper ${className ?? \"\"}`}\n onMouseEnter={handleShow}\n onMouseLeave={handleHide}\n tabIndex={tabIndex}\n aria-describedby={isVisible ? effectiveTooltipId : undefined}\n >\n {children}\n {tooltipElement && createPortal(tooltipElement, document.body)}\n </div>\n );\n}\n"],"mappings":";;;;;AAqCA,SAAgB,EAAQ,EACtB,YACA,aACA,cACA,qBACA,cACA,cAAW,KACI;CACf,IAAM,CAAC,GAAW,KAAgB,EAAS,EAAK,GAC1C,CAAC,GAAiB,KAAsB,EAAiC,IAAI,GAC7E,IAAa,EAAuB,IAAI,GACxC,IAAa,EAAuB,IAAI,GACxC,IAAe,EAAO,EAAI,GAC1B,IAAc,EAAM,GACpB,IAAqB,KAAa,WAAW;CAwCnD,AArCA,SACE,EAAa,UAAU,UACV;EACX,EAAa,UAAU;CACzB,IACC,CAAC,CAAC,GAIL,QAAgB;EACd,IAAM,IAAU,EAAW;EAC3B,IAAI,CAAC,GAAS;EAGd,IAAM,UAAsB;GAC1B,AAAI,EAAa,WACf,EAAa,EAAI;EAErB,GAEM,UAAuB;GAC3B,AAAI,EAAa,YACf,EAAa,EAAK,GAClB,EAAmB,IAAI;EAE3B;EAKA,OAHA,EAAQ,iBAAiB,WAAW,CAAa,GACjD,EAAQ,iBAAiB,YAAY,CAAc,SAEtC;GAEX,AADA,EAAQ,oBAAoB,WAAW,CAAa,GACpD,EAAQ,oBAAoB,YAAY,CAAc;EACxD;CACF,GAAG,CAAC,CAAC,GAGL,QAAgB;EAEd,IADI,CAAC,KAAa,CAAC,EAAW,WAC1B,CAAC,EAAa,SAAS;EAE3B,IAAM,IAAc,EAAW,QAAQ,sBAAsB;EAG7D,4BAA4B;GAC1B,IAAI,CAAC,EAAa,WAAW,CAAC,EAAW,SAAS;GAElD,IAAM,IAAc,EAAW,QAAQ,sBAAsB,GACvD,IAAa,EAAY,KACzB,IAAa,OAAO,cAAc,EAAY,QAE9C,IAAgB,EAAY,QAI5B,IACJ,IAAa,IAAgB,MAAU,IAAa,IAChD,WACA,OAGA,IAAgB,EAAY,OAAO,EAAY,QAAQ,GACvD,IAAe,EAAY,OAC7B,IAAO,IAAgB,IAAe,GACpC,IAAgB,OAAO;GAM7B,AAHI,IAAO,IAAe,IAAgB,OACxC,IAAO,IAAgB,IAAe,KAEpC,IAAO,OACT,IAAO;GAIT,IAAI;GAOJ,AANA,AAGE,IAHE,MAAc,QACV,EAAY,MAAM,IAAgB,IAElC,EAAY,SAAS,GAG7B,EAAmB;IAAE;IAAK;IAAM;GAAU,CAAC;EAC7C,CAAC;CACH,GAAG,CAAC,CAAS,CAAC;CAEd,IAAM,IAAa,QAAkB;EACnC,EAAa,EAAI;CACnB,GAAG,CAAC,CAAC,GAEC,IAAa,QAAkB;EAEnC,AADA,EAAa,EAAK,GAClB,EAAmB,IAAI;CACzB,GAAG,CAAC,CAAC,GAEC,IAAiB,KACrB,kBAAC,OAAD;EACE,KAAK;EACL,IAAI;EACJ,WAAW,sCAAsC,GAAiB,aAAa,MAAM,GAAG,KAAoB;EAC5G,OACE,IACI;GACE,KAAK,GAAG,OAAO,EAAgB,GAAG,EAAE;GACpC,MAAM,GAAG,OAAO,EAAgB,IAAI,EAAE;EACxC,IACA,EAAE,YAAY,SAAS;EAE7B,MAAK;EAEJ,UAAA,OAAO,KAAY,WAClB,kBAAC,KAAD;GAAG,WAAU;GAAiB,UAAA;EAAW,CAAA,IAEzC;CAEC,CAAA;CAGP,OACE,kBAAC,OAAD;EACE,KAAK;EACL,WAAW,oBAAoB,KAAa;EAC5C,cAAc;EACd,cAAc;EACJ;EACV,oBAAkB,IAAY,IAAqB,KAAA;EANrD,UAAA,CAQG,GACA,KAAkB,EAAa,GAAgB,SAAS,IAAI,CAC1D;;AAET"}
|
|
1
|
+
{"version":3,"file":"Tooltip2.js","names":[],"sources":["../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["/**\n * Tooltip Component\n *\n * A reusable tooltip component that displays content on hover.\n * Supports both simple text and complex ReactNode content.\n *\n * Accessibility, against the three parts of WCAG 2.1 SC 1.4.13:\n * - Hoverable: the pointer can move onto the tooltip itself without it\n * disappearing. The content sits 8px away from the trigger, so the pointer\n * crosses a gap to get there and the hide is deferred over that crossing.\n * - Dismissible: Escape closes it without moving the pointer or focus.\n * - Persistent: it stays while the trigger is hovered or focused.\n *\n * Also uses role=\"tooltip\" and aria-describedby so a screen reader announces\n * the content with the trigger.\n */\n\nimport { useState, useRef, useEffect, useCallback, useId, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport \"./Tooltip.css\";\n\nexport interface TooltipProps {\n /** Tooltip content - can be string or ReactNode */\n content: ReactNode;\n /** Children to wrap with tooltip (trigger element) */\n children: ReactNode;\n /** Additional class name for the wrapper */\n className?: string;\n /** Additional class name for the tooltip content */\n contentClassName?: string;\n /** Custom ID for the tooltip (for aria-describedby association) */\n tooltipId?: string;\n /** Tab index for the wrapper. Use -1 when wrapping already-focusable elements like buttons. Defaults to 0. */\n tabIndex?: number;\n}\n\n/** Long enough to cross the 8px gap to the tooltip, short enough not to linger. */\nconst HIDE_GRACE_MS = 120;\n\ninterface TooltipPosition {\n top: number;\n left: number;\n placement: \"top\" | \"bottom\";\n}\n\nexport function Tooltip({\n content,\n children,\n className,\n contentClassName,\n tooltipId,\n tabIndex = 0,\n}: TooltipProps) {\n const [isVisible, setIsVisible] = useState(false);\n const [tooltipPosition, setTooltipPosition] = useState<TooltipPosition | null>(null);\n const tooltipRef = useRef<HTMLDivElement>(null);\n const wrapperRef = useRef<HTMLDivElement>(null);\n const isMountedRef = useRef(true);\n const hideTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const generatedId = useId();\n const effectiveTooltipId = tooltipId ?? `tooltip-${generatedId}`;\n\n // Track mount state\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n if (hideTimerRef.current !== null) clearTimeout(hideTimerRef.current);\n };\n }, []);\n\n // Handle focus events using focusin/focusout which bubble from child elements\n // This ensures tooltips show when keyboard focus moves to children (WCAG 1.4.13)\n useEffect(() => {\n const wrapper = wrapperRef.current;\n if (!wrapper) return;\n\n // focusin/focusout bubble from child elements, unlike focus/blur\n const handleFocusIn = () => {\n if (isMountedRef.current) {\n setIsVisible(true);\n }\n };\n\n const handleFocusOut = () => {\n if (isMountedRef.current) {\n setIsVisible(false);\n setTooltipPosition(null);\n }\n };\n\n wrapper.addEventListener(\"focusin\", handleFocusIn);\n wrapper.addEventListener(\"focusout\", handleFocusOut);\n\n return () => {\n wrapper.removeEventListener(\"focusin\", handleFocusIn);\n wrapper.removeEventListener(\"focusout\", handleFocusOut);\n };\n }, []);\n\n /**\n * Place the tooltip beside the trigger, in whichever direction has room.\n *\n * Reads both boxes at call time rather than closing over them, because this\n * runs again every time the trigger moves under the viewport.\n */\n const measure = useCallback(() => {\n const wrapper = wrapperRef.current;\n const tooltip = tooltipRef.current;\n if (!isMountedRef.current || !wrapper || !tooltip) return;\n\n const wrapperRect = wrapper.getBoundingClientRect();\n const tooltipRect = tooltip.getBoundingClientRect();\n const spaceAbove = wrapperRect.top;\n const spaceBelow = window.innerHeight - wrapperRect.bottom;\n\n const tooltipHeight = tooltipRect.height;\n const margin = 10;\n\n // Determine placement\n const placement: \"top\" | \"bottom\" =\n spaceAbove < tooltipHeight + margin && spaceBelow > spaceAbove ? \"bottom\" : \"top\";\n\n // Calculate left position (center on wrapper, but ensure it stays within viewport)\n const wrapperCenter = wrapperRect.left + wrapperRect.width / 2;\n const tooltipWidth = tooltipRect.width;\n let left = wrapperCenter - tooltipWidth / 2;\n const viewportWidth = window.innerWidth;\n\n // Keep tooltip within viewport bounds\n if (left + tooltipWidth > viewportWidth - 16) {\n left = viewportWidth - tooltipWidth - 16;\n }\n if (left < 16) {\n left = 16;\n }\n\n // Calculate top position\n const top =\n placement === \"top\"\n ? wrapperRect.top - tooltipHeight - 8\n : wrapperRect.bottom + 8;\n\n setTooltipPosition({ top, left, placement });\n }, []);\n\n /**\n * The content is `position: fixed` and lives in a portal, so nothing moves it\n * when the trigger moves. Any scroll between the two, on the page or on a\n * container in between, leaves it pointing at where the trigger used to be,\n * and a resize can leave it off the edge. Scroll events from a container do\n * not bubble, hence the capture phase.\n *\n * The first measurement waits for a frame so the content has been laid out\n * and has a height to place against.\n */\n useEffect(() => {\n if (!isVisible) return;\n\n const frame = requestAnimationFrame(measure);\n window.addEventListener(\"scroll\", measure, { capture: true, passive: true });\n window.addEventListener(\"resize\", measure);\n\n return () => {\n cancelAnimationFrame(frame);\n window.removeEventListener(\"scroll\", measure, { capture: true });\n window.removeEventListener(\"resize\", measure);\n };\n }, [isVisible, measure]);\n\n const handleShow = useCallback(() => {\n if (hideTimerRef.current !== null) {\n clearTimeout(hideTimerRef.current);\n hideTimerRef.current = null;\n }\n setIsVisible(true);\n }, []);\n\n const hideNow = useCallback(() => {\n if (hideTimerRef.current !== null) {\n clearTimeout(hideTimerRef.current);\n hideTimerRef.current = null;\n }\n setIsVisible(false);\n setTooltipPosition(null);\n }, []);\n\n /**\n * Deferred so the pointer can cross the 8px gap between the trigger and the\n * tooltip. React propagates enter and leave through the portal, so a direct\n * hop onto the content never fires this; the gap does, because the pointer\n * passes over a node that belongs to neither element. Hiding on that would\n * put the content out of reach (WCAG 2.1 SC 1.4.13, Hoverable). Arriving on\n * either element cancels the pending hide.\n */\n const handleHide = useCallback(() => {\n if (hideTimerRef.current !== null) clearTimeout(hideTimerRef.current);\n hideTimerRef.current = setTimeout(() => {\n hideTimerRef.current = null;\n if (!isMountedRef.current) return;\n setIsVisible(false);\n setTooltipPosition(null);\n }, HIDE_GRACE_MS);\n }, []);\n\n // Dismissible: Escape closes without moving the pointer or focus. Bound to\n // the document because focus may sit on a child of the trigger, or nowhere\n // at all when the tooltip was opened by hover.\n useEffect(() => {\n if (!isVisible) return;\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") hideNow();\n };\n document.addEventListener(\"keydown\", onKeyDown);\n return () => {\n document.removeEventListener(\"keydown\", onKeyDown);\n };\n }, [isVisible, hideNow]);\n\n const tooltipElement = isVisible && (\n <div\n ref={tooltipRef}\n id={effectiveTooltipId}\n className={`tooltip__content tooltip__content--${tooltipPosition?.placement ?? \"top\"} ${contentClassName ?? \"\"}`}\n style={\n tooltipPosition\n ? {\n top: `${String(tooltipPosition.top)}px`,\n left: `${String(tooltipPosition.left)}px`,\n }\n : { visibility: \"hidden\" }\n }\n role=\"tooltip\"\n >\n {typeof content === \"string\" ? (\n <p className=\"tooltip__text\">{content}</p>\n ) : (\n content\n )}\n </div>\n );\n\n return (\n <div\n ref={wrapperRef}\n className={`tooltip__wrapper ${className ?? \"\"}`}\n onMouseEnter={handleShow}\n onMouseLeave={handleHide}\n tabIndex={tabIndex}\n aria-describedby={isVisible ? effectiveTooltipId : undefined}\n >\n {children}\n {tooltipElement && createPortal(tooltipElement, document.body)}\n </div>\n );\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED