@arun-dev/headless 4.4.0 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/anchoring-Btxi0QwC.d.cts +12 -0
- package/dist/anchoring-Btxi0QwC.d.ts +12 -0
- package/dist/chunk-HCOCPQXS.js +27 -0
- package/dist/popover/index.cjs +25 -20
- package/dist/popover/index.d.cts +3 -2
- package/dist/popover/index.d.ts +3 -2
- package/dist/popover/index.js +8 -20
- package/dist/tooltip/index.cjs +364 -0
- package/dist/tooltip/index.d.cts +92 -0
- package/dist/tooltip/index.d.ts +92 -0
- package/dist/tooltip/index.js +206 -0
- package/package.json +11 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS anchor positioning for popups placed beside a trigger — Popover and Tooltip
|
|
3
|
+
* (decision 12). Internal: shared between components, not exported (decision 9).
|
|
4
|
+
*
|
|
5
|
+
* The trigger carries a generated `anchor-name`; the popup names it in `position-anchor`
|
|
6
|
+
* and is placed by `position-area`, flipping across the axis it opens along when there is
|
|
7
|
+
* no room. All inline, so the browser keeps it attached with nothing measured.
|
|
8
|
+
*/
|
|
9
|
+
type AnchorSide = 'top' | 'bottom' | 'left' | 'right';
|
|
10
|
+
type AnchorAlign = 'start' | 'center' | 'end';
|
|
11
|
+
|
|
12
|
+
export type { AnchorSide as A, AnchorAlign as a };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS anchor positioning for popups placed beside a trigger — Popover and Tooltip
|
|
3
|
+
* (decision 12). Internal: shared between components, not exported (decision 9).
|
|
4
|
+
*
|
|
5
|
+
* The trigger carries a generated `anchor-name`; the popup names it in `position-anchor`
|
|
6
|
+
* and is placed by `position-area`, flipping across the axis it opens along when there is
|
|
7
|
+
* no room. All inline, so the browser keeps it attached with nothing measured.
|
|
8
|
+
*/
|
|
9
|
+
type AnchorSide = 'top' | 'bottom' | 'left' | 'right';
|
|
10
|
+
type AnchorAlign = 'start' | 'center' | 'end';
|
|
11
|
+
|
|
12
|
+
export type { AnchorSide as A, AnchorAlign as a };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/core/anchoring.ts
|
|
2
|
+
function anchorNameFor(id) {
|
|
3
|
+
return `--hl-anchor-${id.replace(/[^a-zA-Z0-9_-]/g, "")}`;
|
|
4
|
+
}
|
|
5
|
+
var POSITION_AREA = {
|
|
6
|
+
bottom: { start: "bottom span-right", center: "bottom", end: "bottom span-left" },
|
|
7
|
+
top: { start: "top span-right", center: "top", end: "top span-left" },
|
|
8
|
+
right: { start: "right span-bottom", center: "right", end: "right span-top" },
|
|
9
|
+
left: { start: "left span-bottom", center: "left", end: "left span-top" }
|
|
10
|
+
};
|
|
11
|
+
function anchoredPopupStyle(anchorName, side, align) {
|
|
12
|
+
return {
|
|
13
|
+
positionAnchor: anchorName,
|
|
14
|
+
positionArea: POSITION_AREA[side][align],
|
|
15
|
+
// Flip across the axis it opens on; the other axis is left alone.
|
|
16
|
+
positionTryFallbacks: side === "top" || side === "bottom" ? "flip-block" : "flip-inline"
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function anchoredDataAttributes(side, align) {
|
|
20
|
+
return { "data-side": side, "data-align": align };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
anchorNameFor,
|
|
25
|
+
anchoredPopupStyle,
|
|
26
|
+
anchoredDataAttributes
|
|
27
|
+
};
|
package/dist/popover/index.cjs
CHANGED
|
@@ -36,6 +36,28 @@ __export(index_parts_exports, {
|
|
|
36
36
|
// src/popover/PopoverRoot.tsx
|
|
37
37
|
var import_react3 = require("react");
|
|
38
38
|
|
|
39
|
+
// src/core/anchoring.ts
|
|
40
|
+
function anchorNameFor(id) {
|
|
41
|
+
return `--hl-anchor-${id.replace(/[^a-zA-Z0-9_-]/g, "")}`;
|
|
42
|
+
}
|
|
43
|
+
var POSITION_AREA = {
|
|
44
|
+
bottom: { start: "bottom span-right", center: "bottom", end: "bottom span-left" },
|
|
45
|
+
top: { start: "top span-right", center: "top", end: "top span-left" },
|
|
46
|
+
right: { start: "right span-bottom", center: "right", end: "right span-top" },
|
|
47
|
+
left: { start: "left span-bottom", center: "left", end: "left span-top" }
|
|
48
|
+
};
|
|
49
|
+
function anchoredPopupStyle(anchorName, side, align) {
|
|
50
|
+
return {
|
|
51
|
+
positionAnchor: anchorName,
|
|
52
|
+
positionArea: POSITION_AREA[side][align],
|
|
53
|
+
// Flip across the axis it opens on; the other axis is left alone.
|
|
54
|
+
positionTryFallbacks: side === "top" || side === "bottom" ? "flip-block" : "flip-inline"
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function anchoredDataAttributes(side, align) {
|
|
58
|
+
return { "data-side": side, "data-align": align };
|
|
59
|
+
}
|
|
60
|
+
|
|
39
61
|
// src/core/useControlled.ts
|
|
40
62
|
var import_react = require("react");
|
|
41
63
|
function useControlled({
|
|
@@ -82,9 +104,6 @@ function usePopoverRootContext(part) {
|
|
|
82
104
|
|
|
83
105
|
// src/popover/PopoverRoot.tsx
|
|
84
106
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
85
|
-
function toAnchorName(id) {
|
|
86
|
-
return `--hl-anchor-${id.replace(/[^a-zA-Z0-9_-]/g, "")}`;
|
|
87
|
-
}
|
|
88
107
|
function PopoverRoot({
|
|
89
108
|
open: openProp,
|
|
90
109
|
defaultOpen,
|
|
@@ -108,7 +127,7 @@ function PopoverRoot({
|
|
|
108
127
|
const id = (0, import_react3.useId)();
|
|
109
128
|
const triggerRef = (0, import_react3.useRef)(null);
|
|
110
129
|
const context = (0, import_react3.useMemo)(
|
|
111
|
-
() => ({ open, setOpen, popupId: id, anchorName:
|
|
130
|
+
() => ({ open, setOpen, popupId: id, anchorName: anchorNameFor(id), triggerRef }),
|
|
112
131
|
[open, setOpen, id]
|
|
113
132
|
);
|
|
114
133
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PopoverRootContext.Provider, { value: context, children });
|
|
@@ -237,15 +256,6 @@ function PopoverTrigger({ className, children, render, ...rest }) {
|
|
|
237
256
|
|
|
238
257
|
// src/popover/PopoverPopup.tsx
|
|
239
258
|
var import_react5 = require("react");
|
|
240
|
-
var POSITION_AREA = {
|
|
241
|
-
bottom: { start: "bottom span-right", center: "bottom", end: "bottom span-left" },
|
|
242
|
-
top: { start: "top span-right", center: "top", end: "top span-left" },
|
|
243
|
-
right: { start: "right span-bottom", center: "right", end: "right span-top" },
|
|
244
|
-
left: { start: "left span-bottom", center: "left", end: "left span-top" }
|
|
245
|
-
};
|
|
246
|
-
function tryFallbacks(side) {
|
|
247
|
-
return side === "top" || side === "bottom" ? "flip-block" : "flip-inline";
|
|
248
|
-
}
|
|
249
259
|
function PopoverPopup({
|
|
250
260
|
side = "bottom",
|
|
251
261
|
align = "center",
|
|
@@ -272,14 +282,9 @@ function PopoverPopup({
|
|
|
272
282
|
id: popupId,
|
|
273
283
|
popover: "auto",
|
|
274
284
|
role: "dialog",
|
|
275
|
-
|
|
276
|
-
"data-align": align,
|
|
285
|
+
...anchoredDataAttributes(side, align),
|
|
277
286
|
...popoverDataAttributes({ open }),
|
|
278
|
-
style:
|
|
279
|
-
positionAnchor: anchorName,
|
|
280
|
-
positionArea: POSITION_AREA[side][align],
|
|
281
|
-
positionTryFallbacks: tryFallbacks(side)
|
|
282
|
-
},
|
|
287
|
+
style: anchoredPopupStyle(anchorName, side, align),
|
|
283
288
|
className,
|
|
284
289
|
children,
|
|
285
290
|
ref: elementRef,
|
package/dist/popover/index.d.cts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, ReactElement, Ref, ComponentPropsWithRef } from 'react';
|
|
3
|
+
import { A as AnchorSide, a as AnchorAlign } from '../anchoring-Btxi0QwC.cjs';
|
|
3
4
|
|
|
4
5
|
type PopoverRootProps = {
|
|
5
6
|
/**
|
|
@@ -44,8 +45,8 @@ type PopoverTriggerProps = PopoverTriggerOwnProps & Omit<ComponentPropsWithRef<'
|
|
|
44
45
|
*/
|
|
45
46
|
declare function PopoverTrigger({ className, children, render, ...rest }: PopoverTriggerProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
46
47
|
|
|
47
|
-
type PopoverSide =
|
|
48
|
-
type PopoverAlign =
|
|
48
|
+
type PopoverSide = AnchorSide;
|
|
49
|
+
type PopoverAlign = AnchorAlign;
|
|
49
50
|
type PopoverPopupOwnProps = {
|
|
50
51
|
/** Which side of the trigger to open on. Flips to the opposite side when there is no room. */
|
|
51
52
|
side?: PopoverSide;
|
package/dist/popover/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, ReactElement, Ref, ComponentPropsWithRef } from 'react';
|
|
3
|
+
import { A as AnchorSide, a as AnchorAlign } from '../anchoring-Btxi0QwC.js';
|
|
3
4
|
|
|
4
5
|
type PopoverRootProps = {
|
|
5
6
|
/**
|
|
@@ -44,8 +45,8 @@ type PopoverTriggerProps = PopoverTriggerOwnProps & Omit<ComponentPropsWithRef<'
|
|
|
44
45
|
*/
|
|
45
46
|
declare function PopoverTrigger({ className, children, render, ...rest }: PopoverTriggerProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
46
47
|
|
|
47
|
-
type PopoverSide =
|
|
48
|
-
type PopoverAlign =
|
|
48
|
+
type PopoverSide = AnchorSide;
|
|
49
|
+
type PopoverAlign = AnchorAlign;
|
|
49
50
|
type PopoverPopupOwnProps = {
|
|
50
51
|
/** Which side of the trigger to open on. Flips to the opposite side when there is no room. */
|
|
51
52
|
side?: PopoverSide;
|
package/dist/popover/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
anchorNameFor,
|
|
3
|
+
anchoredDataAttributes,
|
|
4
|
+
anchoredPopupStyle
|
|
5
|
+
} from "../chunk-HCOCPQXS.js";
|
|
1
6
|
import {
|
|
2
7
|
useControlled
|
|
3
8
|
} from "../chunk-UEIB3I3T.js";
|
|
@@ -31,9 +36,6 @@ function usePopoverRootContext(part) {
|
|
|
31
36
|
|
|
32
37
|
// src/popover/PopoverRoot.tsx
|
|
33
38
|
import { jsx } from "react/jsx-runtime";
|
|
34
|
-
function toAnchorName(id) {
|
|
35
|
-
return `--hl-anchor-${id.replace(/[^a-zA-Z0-9_-]/g, "")}`;
|
|
36
|
-
}
|
|
37
39
|
function PopoverRoot({
|
|
38
40
|
open: openProp,
|
|
39
41
|
defaultOpen,
|
|
@@ -57,7 +59,7 @@ function PopoverRoot({
|
|
|
57
59
|
const id = useId();
|
|
58
60
|
const triggerRef = useRef(null);
|
|
59
61
|
const context = useMemo(
|
|
60
|
-
() => ({ open, setOpen, popupId: id, anchorName:
|
|
62
|
+
() => ({ open, setOpen, popupId: id, anchorName: anchorNameFor(id), triggerRef }),
|
|
61
63
|
[open, setOpen, id]
|
|
62
64
|
);
|
|
63
65
|
return /* @__PURE__ */ jsx(PopoverRootContext.Provider, { value: context, children });
|
|
@@ -97,15 +99,6 @@ function PopoverTrigger({ className, children, render, ...rest }) {
|
|
|
97
99
|
|
|
98
100
|
// src/popover/PopoverPopup.tsx
|
|
99
101
|
import { useEffect, useReducer, useRef as useRef2 } from "react";
|
|
100
|
-
var POSITION_AREA = {
|
|
101
|
-
bottom: { start: "bottom span-right", center: "bottom", end: "bottom span-left" },
|
|
102
|
-
top: { start: "top span-right", center: "top", end: "top span-left" },
|
|
103
|
-
right: { start: "right span-bottom", center: "right", end: "right span-top" },
|
|
104
|
-
left: { start: "left span-bottom", center: "left", end: "left span-top" }
|
|
105
|
-
};
|
|
106
|
-
function tryFallbacks(side) {
|
|
107
|
-
return side === "top" || side === "bottom" ? "flip-block" : "flip-inline";
|
|
108
|
-
}
|
|
109
102
|
function PopoverPopup({
|
|
110
103
|
side = "bottom",
|
|
111
104
|
align = "center",
|
|
@@ -132,14 +125,9 @@ function PopoverPopup({
|
|
|
132
125
|
id: popupId,
|
|
133
126
|
popover: "auto",
|
|
134
127
|
role: "dialog",
|
|
135
|
-
|
|
136
|
-
"data-align": align,
|
|
128
|
+
...anchoredDataAttributes(side, align),
|
|
137
129
|
...popoverDataAttributes({ open }),
|
|
138
|
-
style:
|
|
139
|
-
positionAnchor: anchorName,
|
|
140
|
-
positionArea: POSITION_AREA[side][align],
|
|
141
|
-
positionTryFallbacks: tryFallbacks(side)
|
|
142
|
-
},
|
|
130
|
+
style: anchoredPopupStyle(anchorName, side, align),
|
|
143
131
|
className,
|
|
144
132
|
children,
|
|
145
133
|
ref: elementRef,
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/tooltip/index.ts
|
|
21
|
+
var tooltip_exports = {};
|
|
22
|
+
__export(tooltip_exports, {
|
|
23
|
+
Tooltip: () => index_parts_exports
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(tooltip_exports);
|
|
26
|
+
|
|
27
|
+
// src/tooltip/index.parts.ts
|
|
28
|
+
var index_parts_exports = {};
|
|
29
|
+
__export(index_parts_exports, {
|
|
30
|
+
Popup: () => TooltipPopup,
|
|
31
|
+
Root: () => TooltipRoot,
|
|
32
|
+
Trigger: () => TooltipTrigger
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// src/tooltip/TooltipRoot.tsx
|
|
36
|
+
var import_react3 = require("react");
|
|
37
|
+
|
|
38
|
+
// src/core/anchoring.ts
|
|
39
|
+
function anchorNameFor(id) {
|
|
40
|
+
return `--hl-anchor-${id.replace(/[^a-zA-Z0-9_-]/g, "")}`;
|
|
41
|
+
}
|
|
42
|
+
var POSITION_AREA = {
|
|
43
|
+
bottom: { start: "bottom span-right", center: "bottom", end: "bottom span-left" },
|
|
44
|
+
top: { start: "top span-right", center: "top", end: "top span-left" },
|
|
45
|
+
right: { start: "right span-bottom", center: "right", end: "right span-top" },
|
|
46
|
+
left: { start: "left span-bottom", center: "left", end: "left span-top" }
|
|
47
|
+
};
|
|
48
|
+
function anchoredPopupStyle(anchorName, side, align) {
|
|
49
|
+
return {
|
|
50
|
+
positionAnchor: anchorName,
|
|
51
|
+
positionArea: POSITION_AREA[side][align],
|
|
52
|
+
// Flip across the axis it opens on; the other axis is left alone.
|
|
53
|
+
positionTryFallbacks: side === "top" || side === "bottom" ? "flip-block" : "flip-inline"
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function anchoredDataAttributes(side, align) {
|
|
57
|
+
return { "data-side": side, "data-align": align };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/core/useControlled.ts
|
|
61
|
+
var import_react = require("react");
|
|
62
|
+
function useControlled({
|
|
63
|
+
controlled,
|
|
64
|
+
default: defaultValue,
|
|
65
|
+
name = "Component",
|
|
66
|
+
state = "value"
|
|
67
|
+
}) {
|
|
68
|
+
const { current: isControlled } = (0, import_react.useRef)(controlled !== void 0);
|
|
69
|
+
const [uncontrolled, setUncontrolled] = (0, import_react.useState)(defaultValue);
|
|
70
|
+
const defaultRef = (0, import_react.useRef)(defaultValue);
|
|
71
|
+
const value = isControlled ? controlled : uncontrolled;
|
|
72
|
+
if (process.env.NODE_ENV !== "production") {
|
|
73
|
+
if (isControlled !== (controlled !== void 0)) {
|
|
74
|
+
console.error(
|
|
75
|
+
`${name}: cannot switch between controlled and uncontrolled \`${state}\`. Decide which one this component is for the whole of its life.`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (!isControlled && defaultRef.current !== defaultValue) {
|
|
79
|
+
console.error(
|
|
80
|
+
`${name}: cannot change the default \`${state}\` after mount. It is only read once, so later changes are silently ignored.`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const setValue = (0, import_react.useCallback)(
|
|
85
|
+
(next) => {
|
|
86
|
+
if (!isControlled) setUncontrolled(next);
|
|
87
|
+
},
|
|
88
|
+
[isControlled]
|
|
89
|
+
);
|
|
90
|
+
return [value, setValue];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/tooltip/TooltipRootContext.ts
|
|
94
|
+
var import_react2 = require("react");
|
|
95
|
+
var TooltipRootContext = (0, import_react2.createContext)(null);
|
|
96
|
+
function useTooltipRootContext(part) {
|
|
97
|
+
const context = (0, import_react2.useContext)(TooltipRootContext);
|
|
98
|
+
if (context === null) {
|
|
99
|
+
throw new Error(`<Tooltip.${part}> must be rendered inside <Tooltip.Root>.`);
|
|
100
|
+
}
|
|
101
|
+
return context;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/tooltip/TooltipRoot.tsx
|
|
105
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
106
|
+
var WARM_WINDOW = 300;
|
|
107
|
+
var openCount = 0;
|
|
108
|
+
var lastClosedAt = Number.NEGATIVE_INFINITY;
|
|
109
|
+
function isWarm() {
|
|
110
|
+
return openCount > 0 || Date.now() - lastClosedAt < WARM_WINDOW;
|
|
111
|
+
}
|
|
112
|
+
function TooltipRoot({
|
|
113
|
+
open: openProp,
|
|
114
|
+
defaultOpen,
|
|
115
|
+
onOpenChange,
|
|
116
|
+
delay = 600,
|
|
117
|
+
closeDelay = 100,
|
|
118
|
+
children
|
|
119
|
+
}) {
|
|
120
|
+
const [open, setOpenState] = useControlled({
|
|
121
|
+
controlled: openProp,
|
|
122
|
+
default: defaultOpen ?? false,
|
|
123
|
+
name: "Tooltip.Root",
|
|
124
|
+
state: "open"
|
|
125
|
+
});
|
|
126
|
+
const timer = (0, import_react3.useRef)(void 0);
|
|
127
|
+
const cancelPending = (0, import_react3.useCallback)(() => {
|
|
128
|
+
clearTimeout(timer.current);
|
|
129
|
+
timer.current = void 0;
|
|
130
|
+
}, []);
|
|
131
|
+
const setOpen = (0, import_react3.useCallback)(
|
|
132
|
+
(next) => {
|
|
133
|
+
cancelPending();
|
|
134
|
+
if (next === open) return;
|
|
135
|
+
setOpenState(next);
|
|
136
|
+
onOpenChange?.(next);
|
|
137
|
+
},
|
|
138
|
+
[open, setOpenState, onOpenChange, cancelPending]
|
|
139
|
+
);
|
|
140
|
+
const openAfterDelay = (0, import_react3.useCallback)(() => {
|
|
141
|
+
cancelPending();
|
|
142
|
+
if (open) return;
|
|
143
|
+
if (isWarm()) setOpen(true);
|
|
144
|
+
else timer.current = setTimeout(() => setOpen(true), delay);
|
|
145
|
+
}, [open, delay, setOpen, cancelPending]);
|
|
146
|
+
const closeAfterGrace = (0, import_react3.useCallback)(() => {
|
|
147
|
+
cancelPending();
|
|
148
|
+
if (!open) return;
|
|
149
|
+
timer.current = setTimeout(() => setOpen(false), closeDelay);
|
|
150
|
+
}, [open, closeDelay, setOpen, cancelPending]);
|
|
151
|
+
(0, import_react3.useEffect)(() => cancelPending, [cancelPending]);
|
|
152
|
+
(0, import_react3.useEffect)(() => {
|
|
153
|
+
if (!open) return;
|
|
154
|
+
openCount += 1;
|
|
155
|
+
return () => {
|
|
156
|
+
openCount -= 1;
|
|
157
|
+
lastClosedAt = Date.now();
|
|
158
|
+
};
|
|
159
|
+
}, [open]);
|
|
160
|
+
const id = (0, import_react3.useId)();
|
|
161
|
+
const context = (0, import_react3.useMemo)(
|
|
162
|
+
() => ({
|
|
163
|
+
open,
|
|
164
|
+
setOpen,
|
|
165
|
+
openAfterDelay,
|
|
166
|
+
closeAfterGrace,
|
|
167
|
+
cancelPending,
|
|
168
|
+
popupId: id,
|
|
169
|
+
anchorName: anchorNameFor(id)
|
|
170
|
+
}),
|
|
171
|
+
[open, setOpen, openAfterDelay, closeAfterGrace, cancelPending, id]
|
|
172
|
+
);
|
|
173
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipRootContext.Provider, { value: context, children });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/core/useRender.ts
|
|
177
|
+
var import_react4 = require("react");
|
|
178
|
+
|
|
179
|
+
// src/core/mergeProps.ts
|
|
180
|
+
function isEventHandler(key) {
|
|
181
|
+
return key.length > 2 && key.startsWith("on") && key[2] === key[2]?.toUpperCase();
|
|
182
|
+
}
|
|
183
|
+
function mergeRefs(...refs) {
|
|
184
|
+
return (node) => {
|
|
185
|
+
const cleanups = refs.map((ref) => {
|
|
186
|
+
if (typeof ref === "function") {
|
|
187
|
+
const result = ref(node);
|
|
188
|
+
return typeof result === "function" ? result : () => ref(null);
|
|
189
|
+
}
|
|
190
|
+
if (ref && typeof ref === "object") {
|
|
191
|
+
ref.current = node;
|
|
192
|
+
return () => {
|
|
193
|
+
ref.current = null;
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return void 0;
|
|
197
|
+
});
|
|
198
|
+
return () => {
|
|
199
|
+
cleanups.forEach((cleanup) => {
|
|
200
|
+
if (typeof cleanup === "function") cleanup();
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function isSyntheticEvent(event) {
|
|
206
|
+
return typeof event === "object" && event !== null && "nativeEvent" in event;
|
|
207
|
+
}
|
|
208
|
+
function makeEventPreventable(event) {
|
|
209
|
+
if (Object.prototype.hasOwnProperty.call(event, "preventComponentHandler")) return;
|
|
210
|
+
const target = event;
|
|
211
|
+
target.preventComponentHandler = () => {
|
|
212
|
+
target.componentHandlerPrevented = true;
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function isComponentHandlerPrevented(event) {
|
|
216
|
+
return typeof event === "object" && event !== null && event.componentHandlerPrevented === true;
|
|
217
|
+
}
|
|
218
|
+
function chainHandlers(earlier, value) {
|
|
219
|
+
return (...args) => {
|
|
220
|
+
const event = args[0];
|
|
221
|
+
if (isSyntheticEvent(event)) makeEventPreventable(event);
|
|
222
|
+
value(...args);
|
|
223
|
+
if (isComponentHandlerPrevented(event)) return;
|
|
224
|
+
earlier(...args);
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function mergeProps(...objects) {
|
|
228
|
+
const merged = {};
|
|
229
|
+
for (const props of objects) {
|
|
230
|
+
if (!props) continue;
|
|
231
|
+
for (const key of Object.keys(props)) {
|
|
232
|
+
const value = props[key];
|
|
233
|
+
if (value === void 0) continue;
|
|
234
|
+
if (isEventHandler(key) && typeof value === "function") {
|
|
235
|
+
const earlier = merged[key];
|
|
236
|
+
merged[key] = typeof earlier === "function" ? chainHandlers(earlier, value) : value;
|
|
237
|
+
} else if (key === "className") {
|
|
238
|
+
merged.className = merged.className ? `${String(merged.className)} ${String(value)}` : value;
|
|
239
|
+
} else if (key === "style") {
|
|
240
|
+
merged.style = { ...merged.style, ...value };
|
|
241
|
+
} else if (key === "ref") {
|
|
242
|
+
merged.ref = merged.ref ? mergeRefs(merged.ref, value) : value;
|
|
243
|
+
} else {
|
|
244
|
+
merged[key] = value;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return merged;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/core/useRender.ts
|
|
252
|
+
function useRender({
|
|
253
|
+
render,
|
|
254
|
+
defaultTagName,
|
|
255
|
+
props,
|
|
256
|
+
consumerProps
|
|
257
|
+
}) {
|
|
258
|
+
const merged = mergeProps(props, consumerProps);
|
|
259
|
+
if ((0, import_react4.isValidElement)(render)) {
|
|
260
|
+
return (0, import_react4.cloneElement)(render, mergeProps(merged, render.props));
|
|
261
|
+
}
|
|
262
|
+
return (0, import_react4.createElement)(defaultTagName, merged);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/tooltip/tooltipDataAttributes.ts
|
|
266
|
+
function tooltipDataAttributes({ open }) {
|
|
267
|
+
return {
|
|
268
|
+
"data-open": open ? "" : void 0,
|
|
269
|
+
"data-closed": open ? void 0 : ""
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// src/tooltip/TooltipTrigger.tsx
|
|
274
|
+
function TooltipTrigger({
|
|
275
|
+
className,
|
|
276
|
+
children,
|
|
277
|
+
render,
|
|
278
|
+
"aria-describedby": describedBy,
|
|
279
|
+
...rest
|
|
280
|
+
}) {
|
|
281
|
+
const { open, setOpen, openAfterDelay, closeAfterGrace, popupId, anchorName } = useTooltipRootContext("Trigger");
|
|
282
|
+
return useRender({
|
|
283
|
+
render,
|
|
284
|
+
defaultTagName: "button",
|
|
285
|
+
props: {
|
|
286
|
+
type: "button",
|
|
287
|
+
"aria-describedby": describedBy ? `${describedBy} ${popupId}` : popupId,
|
|
288
|
+
...tooltipDataAttributes({ open }),
|
|
289
|
+
style: { anchorName },
|
|
290
|
+
className,
|
|
291
|
+
children,
|
|
292
|
+
onPointerEnter(event) {
|
|
293
|
+
if (event.pointerType !== "touch") openAfterDelay();
|
|
294
|
+
},
|
|
295
|
+
onPointerLeave(event) {
|
|
296
|
+
if (event.pointerType !== "touch") closeAfterGrace();
|
|
297
|
+
},
|
|
298
|
+
onPointerDown() {
|
|
299
|
+
setOpen(false);
|
|
300
|
+
},
|
|
301
|
+
onFocus(event) {
|
|
302
|
+
if (event.currentTarget.matches(":focus-visible")) setOpen(true);
|
|
303
|
+
},
|
|
304
|
+
onBlur() {
|
|
305
|
+
setOpen(false);
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
consumerProps: rest
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/tooltip/TooltipPopup.tsx
|
|
313
|
+
var import_react5 = require("react");
|
|
314
|
+
function TooltipPopup({
|
|
315
|
+
side = "top",
|
|
316
|
+
align = "center",
|
|
317
|
+
className,
|
|
318
|
+
children,
|
|
319
|
+
render,
|
|
320
|
+
...rest
|
|
321
|
+
}) {
|
|
322
|
+
const { open, setOpen, cancelPending, closeAfterGrace, popupId, anchorName } = useTooltipRootContext("Popup");
|
|
323
|
+
const elementRef = (0, import_react5.useRef)(null);
|
|
324
|
+
(0, import_react5.useEffect)(() => {
|
|
325
|
+
const popup = elementRef.current;
|
|
326
|
+
if (!popup || typeof popup.showPopover !== "function") return;
|
|
327
|
+
const shown = popup.matches(":popover-open");
|
|
328
|
+
if (open && !shown) popup.showPopover();
|
|
329
|
+
else if (!open && shown) popup.hidePopover();
|
|
330
|
+
});
|
|
331
|
+
(0, import_react5.useEffect)(() => {
|
|
332
|
+
if (!open) return;
|
|
333
|
+
function onKeyDown(event) {
|
|
334
|
+
if (event.key !== "Escape") return;
|
|
335
|
+
event.preventDefault();
|
|
336
|
+
event.stopPropagation();
|
|
337
|
+
setOpen(false);
|
|
338
|
+
}
|
|
339
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
340
|
+
return () => document.removeEventListener("keydown", onKeyDown, true);
|
|
341
|
+
}, [open, setOpen]);
|
|
342
|
+
return useRender({
|
|
343
|
+
render,
|
|
344
|
+
defaultTagName: "div",
|
|
345
|
+
props: {
|
|
346
|
+
id: popupId,
|
|
347
|
+
popover: "manual",
|
|
348
|
+
role: "tooltip",
|
|
349
|
+
...anchoredDataAttributes(side, align),
|
|
350
|
+
...tooltipDataAttributes({ open }),
|
|
351
|
+
style: anchoredPopupStyle(anchorName, side, align),
|
|
352
|
+
className,
|
|
353
|
+
children,
|
|
354
|
+
ref: elementRef,
|
|
355
|
+
onPointerEnter: cancelPending,
|
|
356
|
+
onPointerLeave: closeAfterGrace
|
|
357
|
+
},
|
|
358
|
+
consumerProps: rest
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
362
|
+
0 && (module.exports = {
|
|
363
|
+
Tooltip
|
|
364
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, ReactElement, Ref, ComponentPropsWithRef } from 'react';
|
|
3
|
+
import { A as AnchorSide, a as AnchorAlign } from '../anchoring-Btxi0QwC.cjs';
|
|
4
|
+
|
|
5
|
+
type TooltipRootProps = {
|
|
6
|
+
/**
|
|
7
|
+
* Controlled state. Provide `onOpenChange` alongside it.
|
|
8
|
+
*
|
|
9
|
+
* Never `undefined` once mounted: the mode is latched at mount, as for Switch.
|
|
10
|
+
*/
|
|
11
|
+
open?: boolean;
|
|
12
|
+
/** Initial state when uncontrolled. Read once, at mount. */
|
|
13
|
+
defaultOpen?: boolean;
|
|
14
|
+
/** Called on every open and close, after any delay has run. */
|
|
15
|
+
onOpenChange?: (open: boolean) => void;
|
|
16
|
+
/** Milliseconds a pointer rests on the trigger before it opens. Focus opens at once. */
|
|
17
|
+
delay?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Milliseconds between the pointer leaving and the tooltip closing — time to move onto
|
|
20
|
+
* the tooltip, which then stays open while hovered (WCAG 1.4.13).
|
|
21
|
+
*/
|
|
22
|
+
closeDelay?: number;
|
|
23
|
+
children?: ReactNode;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Holds a tooltip's state and timers, and shares them with its parts. Renders no element.
|
|
27
|
+
*
|
|
28
|
+
* The popup is a native `popover="manual"`, anchored like Popover (decision 12). `hint`
|
|
29
|
+
* would be the natural fit, but Safari lacks it; `manual` behaves the same everywhere and,
|
|
30
|
+
* like `hint`, does not close an open Popover when it shows. What `manual` leaves out —
|
|
31
|
+
* opening on hover and focus, closing on leave, blur and Esc — is the timing below.
|
|
32
|
+
*/
|
|
33
|
+
declare function TooltipRoot({ open: openProp, defaultOpen, onOpenChange, delay, closeDelay, children, }: TooltipRootProps): react.JSX.Element;
|
|
34
|
+
|
|
35
|
+
type TooltipTriggerOwnProps = {
|
|
36
|
+
/** Element to render instead of the default `<button>`. Props and ref are merged onto it. */
|
|
37
|
+
render?: ReactElement;
|
|
38
|
+
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
39
|
+
ref?: Ref<HTMLElement>;
|
|
40
|
+
};
|
|
41
|
+
type TooltipTriggerProps = TooltipTriggerOwnProps & Omit<ComponentPropsWithRef<'button'>, keyof TooltipTriggerOwnProps>;
|
|
42
|
+
/**
|
|
43
|
+
* The element the tooltip describes and is anchored to. A native `<button>` by default;
|
|
44
|
+
* render another focusable element when the tooltip belongs to one — a Popover.Trigger,
|
|
45
|
+
* say, or a link.
|
|
46
|
+
*
|
|
47
|
+
* - A mouse or pen resting on it opens the tooltip after the Root's `delay`; leaving
|
|
48
|
+
* closes it after `closeDelay`. Touch never opens it: there is no hover to end it.
|
|
49
|
+
* - Keyboard focus opens it at once; blur closes it. Focus from a click does not open it —
|
|
50
|
+
* only `:focus-visible` does.
|
|
51
|
+
* - Pressing it closes the tooltip, since the press is what the user came to do.
|
|
52
|
+
*
|
|
53
|
+
* `aria-describedby` points at the tooltip at all times — the description is read from the
|
|
54
|
+
* hidden element too — and is joined with any of yours.
|
|
55
|
+
*/
|
|
56
|
+
declare function TooltipTrigger({ className, children, render, 'aria-describedby': describedBy, ...rest }: TooltipTriggerProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
57
|
+
|
|
58
|
+
type TooltipSide = AnchorSide;
|
|
59
|
+
type TooltipAlign = AnchorAlign;
|
|
60
|
+
type TooltipPopupOwnProps = {
|
|
61
|
+
/** Which side of the trigger to show on. Flips to the opposite side when there is no room. */
|
|
62
|
+
side?: TooltipSide;
|
|
63
|
+
/** Where along that side: flush with the trigger's start or end edge, or centred on it. */
|
|
64
|
+
align?: TooltipAlign;
|
|
65
|
+
/** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
|
|
66
|
+
render?: ReactElement;
|
|
67
|
+
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
68
|
+
ref?: Ref<HTMLElement>;
|
|
69
|
+
};
|
|
70
|
+
type TooltipPopupProps = TooltipPopupOwnProps & Omit<ComponentPropsWithRef<'div'>, keyof TooltipPopupOwnProps | 'popover'>;
|
|
71
|
+
/**
|
|
72
|
+
* The tooltip: a native `popover="manual"` with `role="tooltip"`, anchored to the trigger
|
|
73
|
+
* as Popover is (decision 12). State drives the element, never the reverse.
|
|
74
|
+
*
|
|
75
|
+
* It stays open while the pointer is on it, so its text can be read and selected, and
|
|
76
|
+
* closes on Esc — which closes only the tooltip, not a Dialog or Popover beneath it.
|
|
77
|
+
*
|
|
78
|
+
* Text only, by the ARIA pattern: it describes the trigger and is never focused, so it must
|
|
79
|
+
* not hold anything interactive. Use a Popover for that.
|
|
80
|
+
*/
|
|
81
|
+
declare function TooltipPopup({ side, align, className, children, render, ...rest }: TooltipPopupProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
82
|
+
|
|
83
|
+
declare namespace index_parts {
|
|
84
|
+
export { TooltipPopup as Popup, TooltipRoot as Root, TooltipTrigger as Trigger };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** The state Tooltip.Root shares with its parts, and projects as `data-*` attributes. */
|
|
88
|
+
type TooltipState = {
|
|
89
|
+
open: boolean;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export { index_parts as Tooltip, type TooltipAlign, type TooltipPopupProps, type TooltipRootProps, type TooltipSide, type TooltipState, type TooltipTriggerProps };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, ReactElement, Ref, ComponentPropsWithRef } from 'react';
|
|
3
|
+
import { A as AnchorSide, a as AnchorAlign } from '../anchoring-Btxi0QwC.js';
|
|
4
|
+
|
|
5
|
+
type TooltipRootProps = {
|
|
6
|
+
/**
|
|
7
|
+
* Controlled state. Provide `onOpenChange` alongside it.
|
|
8
|
+
*
|
|
9
|
+
* Never `undefined` once mounted: the mode is latched at mount, as for Switch.
|
|
10
|
+
*/
|
|
11
|
+
open?: boolean;
|
|
12
|
+
/** Initial state when uncontrolled. Read once, at mount. */
|
|
13
|
+
defaultOpen?: boolean;
|
|
14
|
+
/** Called on every open and close, after any delay has run. */
|
|
15
|
+
onOpenChange?: (open: boolean) => void;
|
|
16
|
+
/** Milliseconds a pointer rests on the trigger before it opens. Focus opens at once. */
|
|
17
|
+
delay?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Milliseconds between the pointer leaving and the tooltip closing — time to move onto
|
|
20
|
+
* the tooltip, which then stays open while hovered (WCAG 1.4.13).
|
|
21
|
+
*/
|
|
22
|
+
closeDelay?: number;
|
|
23
|
+
children?: ReactNode;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Holds a tooltip's state and timers, and shares them with its parts. Renders no element.
|
|
27
|
+
*
|
|
28
|
+
* The popup is a native `popover="manual"`, anchored like Popover (decision 12). `hint`
|
|
29
|
+
* would be the natural fit, but Safari lacks it; `manual` behaves the same everywhere and,
|
|
30
|
+
* like `hint`, does not close an open Popover when it shows. What `manual` leaves out —
|
|
31
|
+
* opening on hover and focus, closing on leave, blur and Esc — is the timing below.
|
|
32
|
+
*/
|
|
33
|
+
declare function TooltipRoot({ open: openProp, defaultOpen, onOpenChange, delay, closeDelay, children, }: TooltipRootProps): react.JSX.Element;
|
|
34
|
+
|
|
35
|
+
type TooltipTriggerOwnProps = {
|
|
36
|
+
/** Element to render instead of the default `<button>`. Props and ref are merged onto it. */
|
|
37
|
+
render?: ReactElement;
|
|
38
|
+
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
39
|
+
ref?: Ref<HTMLElement>;
|
|
40
|
+
};
|
|
41
|
+
type TooltipTriggerProps = TooltipTriggerOwnProps & Omit<ComponentPropsWithRef<'button'>, keyof TooltipTriggerOwnProps>;
|
|
42
|
+
/**
|
|
43
|
+
* The element the tooltip describes and is anchored to. A native `<button>` by default;
|
|
44
|
+
* render another focusable element when the tooltip belongs to one — a Popover.Trigger,
|
|
45
|
+
* say, or a link.
|
|
46
|
+
*
|
|
47
|
+
* - A mouse or pen resting on it opens the tooltip after the Root's `delay`; leaving
|
|
48
|
+
* closes it after `closeDelay`. Touch never opens it: there is no hover to end it.
|
|
49
|
+
* - Keyboard focus opens it at once; blur closes it. Focus from a click does not open it —
|
|
50
|
+
* only `:focus-visible` does.
|
|
51
|
+
* - Pressing it closes the tooltip, since the press is what the user came to do.
|
|
52
|
+
*
|
|
53
|
+
* `aria-describedby` points at the tooltip at all times — the description is read from the
|
|
54
|
+
* hidden element too — and is joined with any of yours.
|
|
55
|
+
*/
|
|
56
|
+
declare function TooltipTrigger({ className, children, render, 'aria-describedby': describedBy, ...rest }: TooltipTriggerProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
57
|
+
|
|
58
|
+
type TooltipSide = AnchorSide;
|
|
59
|
+
type TooltipAlign = AnchorAlign;
|
|
60
|
+
type TooltipPopupOwnProps = {
|
|
61
|
+
/** Which side of the trigger to show on. Flips to the opposite side when there is no room. */
|
|
62
|
+
side?: TooltipSide;
|
|
63
|
+
/** Where along that side: flush with the trigger's start or end edge, or centred on it. */
|
|
64
|
+
align?: TooltipAlign;
|
|
65
|
+
/** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
|
|
66
|
+
render?: ReactElement;
|
|
67
|
+
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
68
|
+
ref?: Ref<HTMLElement>;
|
|
69
|
+
};
|
|
70
|
+
type TooltipPopupProps = TooltipPopupOwnProps & Omit<ComponentPropsWithRef<'div'>, keyof TooltipPopupOwnProps | 'popover'>;
|
|
71
|
+
/**
|
|
72
|
+
* The tooltip: a native `popover="manual"` with `role="tooltip"`, anchored to the trigger
|
|
73
|
+
* as Popover is (decision 12). State drives the element, never the reverse.
|
|
74
|
+
*
|
|
75
|
+
* It stays open while the pointer is on it, so its text can be read and selected, and
|
|
76
|
+
* closes on Esc — which closes only the tooltip, not a Dialog or Popover beneath it.
|
|
77
|
+
*
|
|
78
|
+
* Text only, by the ARIA pattern: it describes the trigger and is never focused, so it must
|
|
79
|
+
* not hold anything interactive. Use a Popover for that.
|
|
80
|
+
*/
|
|
81
|
+
declare function TooltipPopup({ side, align, className, children, render, ...rest }: TooltipPopupProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
82
|
+
|
|
83
|
+
declare namespace index_parts {
|
|
84
|
+
export { TooltipPopup as Popup, TooltipRoot as Root, TooltipTrigger as Trigger };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** The state Tooltip.Root shares with its parts, and projects as `data-*` attributes. */
|
|
88
|
+
type TooltipState = {
|
|
89
|
+
open: boolean;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export { index_parts as Tooltip, type TooltipAlign, type TooltipPopupProps, type TooltipRootProps, type TooltipSide, type TooltipState, type TooltipTriggerProps };
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import {
|
|
2
|
+
anchorNameFor,
|
|
3
|
+
anchoredDataAttributes,
|
|
4
|
+
anchoredPopupStyle
|
|
5
|
+
} from "../chunk-HCOCPQXS.js";
|
|
6
|
+
import {
|
|
7
|
+
useControlled
|
|
8
|
+
} from "../chunk-UEIB3I3T.js";
|
|
9
|
+
import {
|
|
10
|
+
__export,
|
|
11
|
+
useRender
|
|
12
|
+
} from "../chunk-OQTWWZ4B.js";
|
|
13
|
+
|
|
14
|
+
// src/tooltip/index.parts.ts
|
|
15
|
+
var index_parts_exports = {};
|
|
16
|
+
__export(index_parts_exports, {
|
|
17
|
+
Popup: () => TooltipPopup,
|
|
18
|
+
Root: () => TooltipRoot,
|
|
19
|
+
Trigger: () => TooltipTrigger
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// src/tooltip/TooltipRoot.tsx
|
|
23
|
+
import { useCallback, useEffect, useId, useMemo, useRef } from "react";
|
|
24
|
+
|
|
25
|
+
// src/tooltip/TooltipRootContext.ts
|
|
26
|
+
import { createContext, useContext } from "react";
|
|
27
|
+
var TooltipRootContext = createContext(null);
|
|
28
|
+
function useTooltipRootContext(part) {
|
|
29
|
+
const context = useContext(TooltipRootContext);
|
|
30
|
+
if (context === null) {
|
|
31
|
+
throw new Error(`<Tooltip.${part}> must be rendered inside <Tooltip.Root>.`);
|
|
32
|
+
}
|
|
33
|
+
return context;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/tooltip/TooltipRoot.tsx
|
|
37
|
+
import { jsx } from "react/jsx-runtime";
|
|
38
|
+
var WARM_WINDOW = 300;
|
|
39
|
+
var openCount = 0;
|
|
40
|
+
var lastClosedAt = Number.NEGATIVE_INFINITY;
|
|
41
|
+
function isWarm() {
|
|
42
|
+
return openCount > 0 || Date.now() - lastClosedAt < WARM_WINDOW;
|
|
43
|
+
}
|
|
44
|
+
function TooltipRoot({
|
|
45
|
+
open: openProp,
|
|
46
|
+
defaultOpen,
|
|
47
|
+
onOpenChange,
|
|
48
|
+
delay = 600,
|
|
49
|
+
closeDelay = 100,
|
|
50
|
+
children
|
|
51
|
+
}) {
|
|
52
|
+
const [open, setOpenState] = useControlled({
|
|
53
|
+
controlled: openProp,
|
|
54
|
+
default: defaultOpen ?? false,
|
|
55
|
+
name: "Tooltip.Root",
|
|
56
|
+
state: "open"
|
|
57
|
+
});
|
|
58
|
+
const timer = useRef(void 0);
|
|
59
|
+
const cancelPending = useCallback(() => {
|
|
60
|
+
clearTimeout(timer.current);
|
|
61
|
+
timer.current = void 0;
|
|
62
|
+
}, []);
|
|
63
|
+
const setOpen = useCallback(
|
|
64
|
+
(next) => {
|
|
65
|
+
cancelPending();
|
|
66
|
+
if (next === open) return;
|
|
67
|
+
setOpenState(next);
|
|
68
|
+
onOpenChange?.(next);
|
|
69
|
+
},
|
|
70
|
+
[open, setOpenState, onOpenChange, cancelPending]
|
|
71
|
+
);
|
|
72
|
+
const openAfterDelay = useCallback(() => {
|
|
73
|
+
cancelPending();
|
|
74
|
+
if (open) return;
|
|
75
|
+
if (isWarm()) setOpen(true);
|
|
76
|
+
else timer.current = setTimeout(() => setOpen(true), delay);
|
|
77
|
+
}, [open, delay, setOpen, cancelPending]);
|
|
78
|
+
const closeAfterGrace = useCallback(() => {
|
|
79
|
+
cancelPending();
|
|
80
|
+
if (!open) return;
|
|
81
|
+
timer.current = setTimeout(() => setOpen(false), closeDelay);
|
|
82
|
+
}, [open, closeDelay, setOpen, cancelPending]);
|
|
83
|
+
useEffect(() => cancelPending, [cancelPending]);
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (!open) return;
|
|
86
|
+
openCount += 1;
|
|
87
|
+
return () => {
|
|
88
|
+
openCount -= 1;
|
|
89
|
+
lastClosedAt = Date.now();
|
|
90
|
+
};
|
|
91
|
+
}, [open]);
|
|
92
|
+
const id = useId();
|
|
93
|
+
const context = useMemo(
|
|
94
|
+
() => ({
|
|
95
|
+
open,
|
|
96
|
+
setOpen,
|
|
97
|
+
openAfterDelay,
|
|
98
|
+
closeAfterGrace,
|
|
99
|
+
cancelPending,
|
|
100
|
+
popupId: id,
|
|
101
|
+
anchorName: anchorNameFor(id)
|
|
102
|
+
}),
|
|
103
|
+
[open, setOpen, openAfterDelay, closeAfterGrace, cancelPending, id]
|
|
104
|
+
);
|
|
105
|
+
return /* @__PURE__ */ jsx(TooltipRootContext.Provider, { value: context, children });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/tooltip/tooltipDataAttributes.ts
|
|
109
|
+
function tooltipDataAttributes({ open }) {
|
|
110
|
+
return {
|
|
111
|
+
"data-open": open ? "" : void 0,
|
|
112
|
+
"data-closed": open ? void 0 : ""
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/tooltip/TooltipTrigger.tsx
|
|
117
|
+
function TooltipTrigger({
|
|
118
|
+
className,
|
|
119
|
+
children,
|
|
120
|
+
render,
|
|
121
|
+
"aria-describedby": describedBy,
|
|
122
|
+
...rest
|
|
123
|
+
}) {
|
|
124
|
+
const { open, setOpen, openAfterDelay, closeAfterGrace, popupId, anchorName } = useTooltipRootContext("Trigger");
|
|
125
|
+
return useRender({
|
|
126
|
+
render,
|
|
127
|
+
defaultTagName: "button",
|
|
128
|
+
props: {
|
|
129
|
+
type: "button",
|
|
130
|
+
"aria-describedby": describedBy ? `${describedBy} ${popupId}` : popupId,
|
|
131
|
+
...tooltipDataAttributes({ open }),
|
|
132
|
+
style: { anchorName },
|
|
133
|
+
className,
|
|
134
|
+
children,
|
|
135
|
+
onPointerEnter(event) {
|
|
136
|
+
if (event.pointerType !== "touch") openAfterDelay();
|
|
137
|
+
},
|
|
138
|
+
onPointerLeave(event) {
|
|
139
|
+
if (event.pointerType !== "touch") closeAfterGrace();
|
|
140
|
+
},
|
|
141
|
+
onPointerDown() {
|
|
142
|
+
setOpen(false);
|
|
143
|
+
},
|
|
144
|
+
onFocus(event) {
|
|
145
|
+
if (event.currentTarget.matches(":focus-visible")) setOpen(true);
|
|
146
|
+
},
|
|
147
|
+
onBlur() {
|
|
148
|
+
setOpen(false);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
consumerProps: rest
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/tooltip/TooltipPopup.tsx
|
|
156
|
+
import { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
157
|
+
function TooltipPopup({
|
|
158
|
+
side = "top",
|
|
159
|
+
align = "center",
|
|
160
|
+
className,
|
|
161
|
+
children,
|
|
162
|
+
render,
|
|
163
|
+
...rest
|
|
164
|
+
}) {
|
|
165
|
+
const { open, setOpen, cancelPending, closeAfterGrace, popupId, anchorName } = useTooltipRootContext("Popup");
|
|
166
|
+
const elementRef = useRef2(null);
|
|
167
|
+
useEffect2(() => {
|
|
168
|
+
const popup = elementRef.current;
|
|
169
|
+
if (!popup || typeof popup.showPopover !== "function") return;
|
|
170
|
+
const shown = popup.matches(":popover-open");
|
|
171
|
+
if (open && !shown) popup.showPopover();
|
|
172
|
+
else if (!open && shown) popup.hidePopover();
|
|
173
|
+
});
|
|
174
|
+
useEffect2(() => {
|
|
175
|
+
if (!open) return;
|
|
176
|
+
function onKeyDown(event) {
|
|
177
|
+
if (event.key !== "Escape") return;
|
|
178
|
+
event.preventDefault();
|
|
179
|
+
event.stopPropagation();
|
|
180
|
+
setOpen(false);
|
|
181
|
+
}
|
|
182
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
183
|
+
return () => document.removeEventListener("keydown", onKeyDown, true);
|
|
184
|
+
}, [open, setOpen]);
|
|
185
|
+
return useRender({
|
|
186
|
+
render,
|
|
187
|
+
defaultTagName: "div",
|
|
188
|
+
props: {
|
|
189
|
+
id: popupId,
|
|
190
|
+
popover: "manual",
|
|
191
|
+
role: "tooltip",
|
|
192
|
+
...anchoredDataAttributes(side, align),
|
|
193
|
+
...tooltipDataAttributes({ open }),
|
|
194
|
+
style: anchoredPopupStyle(anchorName, side, align),
|
|
195
|
+
className,
|
|
196
|
+
children,
|
|
197
|
+
ref: elementRef,
|
|
198
|
+
onPointerEnter: cancelPending,
|
|
199
|
+
onPointerLeave: closeAfterGrace
|
|
200
|
+
},
|
|
201
|
+
consumerProps: rest
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
export {
|
|
205
|
+
index_parts_exports as Tooltip
|
|
206
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arun-dev/headless",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.0",
|
|
4
4
|
"description": "Unstyled React behaviour primitives — render engine, controlled/uncontrolled state, and data-* state projection. Ships no CSS and no class names.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -93,6 +93,16 @@
|
|
|
93
93
|
"types": "./dist/switch/index.d.cts",
|
|
94
94
|
"default": "./dist/switch/index.cjs"
|
|
95
95
|
}
|
|
96
|
+
},
|
|
97
|
+
"./tooltip": {
|
|
98
|
+
"import": {
|
|
99
|
+
"types": "./dist/tooltip/index.d.ts",
|
|
100
|
+
"default": "./dist/tooltip/index.js"
|
|
101
|
+
},
|
|
102
|
+
"require": {
|
|
103
|
+
"types": "./dist/tooltip/index.d.cts",
|
|
104
|
+
"default": "./dist/tooltip/index.cjs"
|
|
105
|
+
}
|
|
96
106
|
}
|
|
97
107
|
},
|
|
98
108
|
"publishConfig": {
|