@slithy/base-ui 0.1.0 → 0.2.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/CHANGELOG.md +18 -0
- package/README.md +103 -128
- package/dist/index.d.ts +118 -35
- package/dist/index.js +911 -424
- package/package.json +3 -2
- package/src/Dropdown/Dropdown.test.tsx +361 -186
- package/src/Dropdown/Dropdown.tsx +353 -349
- package/src/Dropdown/DropdownRenderer.tsx +118 -0
- package/src/Dropdown/DropdownStore.ts +147 -0
- package/src/Dropdown/index.ts +1 -0
- package/src/Tooltip/Tooltip.test.tsx +221 -212
- package/src/Tooltip/Tooltip.tsx +274 -201
- package/src/Tooltip/TooltipRenderer.tsx +137 -0
- package/src/Tooltip/TooltipStore.ts +142 -0
- package/src/Tooltip/index.ts +2 -1
- package/src/index.ts +2 -2
- package/src/useCloseCleanup.ts +60 -0
- package/src/useSafePolygon.ts +144 -0
package/dist/index.js
CHANGED
|
@@ -1,296 +1,451 @@
|
|
|
1
1
|
// src/Dropdown/Dropdown.tsx
|
|
2
|
-
import { Menu } from "@base-ui/react/menu";
|
|
3
2
|
import { Tooltip as BaseTooltip } from "@base-ui/react/tooltip";
|
|
3
|
+
import { Menu } from "@base-ui/react/menu";
|
|
4
4
|
import {
|
|
5
5
|
cloneElement,
|
|
6
6
|
createContext,
|
|
7
7
|
isValidElement,
|
|
8
|
-
useCallback,
|
|
8
|
+
useCallback as useCallback3,
|
|
9
9
|
useContext,
|
|
10
10
|
useEffect,
|
|
11
|
-
useRef
|
|
12
|
-
useState
|
|
11
|
+
useRef as useRef3
|
|
13
12
|
} from "react";
|
|
14
13
|
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (style.styleSheet) {
|
|
31
|
-
style.styleSheet.cssText = css;
|
|
32
|
-
} else {
|
|
33
|
-
style.appendChild(document.createTextNode(css));
|
|
34
|
-
}
|
|
14
|
+
// src/Dropdown/DropdownStore.ts
|
|
15
|
+
import { useCallback, useRef, useSyncExternalStore } from "react";
|
|
16
|
+
function shallowEqual(a, b) {
|
|
17
|
+
if (Object.is(a, b)) return true;
|
|
18
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null)
|
|
19
|
+
return false;
|
|
20
|
+
const keysA = Object.keys(a);
|
|
21
|
+
const keysB = Object.keys(b);
|
|
22
|
+
if (keysA.length !== keysB.length) return false;
|
|
23
|
+
return keysA.every(
|
|
24
|
+
(k) => Object.is(
|
|
25
|
+
a[k],
|
|
26
|
+
b[k]
|
|
27
|
+
)
|
|
28
|
+
);
|
|
35
29
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// src/Dropdown/Dropdown.tsx
|
|
41
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
42
|
-
var DeferredContext = createContext(null);
|
|
43
|
-
function InnerRoot({
|
|
44
|
-
children,
|
|
45
|
-
defaultOpen,
|
|
46
|
-
shouldOpenRef,
|
|
47
|
-
unmountOnClose,
|
|
48
|
-
onDeactivate,
|
|
49
|
-
onMenuOpenChange,
|
|
50
|
-
onOpenChange,
|
|
51
|
-
onOpenChangeComplete,
|
|
52
|
-
...props
|
|
53
|
-
}) {
|
|
54
|
-
const shouldOpen = useRef(false);
|
|
55
|
-
const [deferredOpen, setDeferredOpen] = useState(false);
|
|
56
|
-
if (!shouldOpen.current && shouldOpenRef.current) {
|
|
57
|
-
shouldOpenRef.current = false;
|
|
58
|
-
shouldOpen.current = true;
|
|
30
|
+
function createDropdownStore() {
|
|
31
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
32
|
+
function notify() {
|
|
33
|
+
listeners.forEach((l) => l());
|
|
59
34
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
35
|
+
let state = {
|
|
36
|
+
open: false,
|
|
37
|
+
content: null,
|
|
38
|
+
anchor: null,
|
|
39
|
+
keyboardOpen: false,
|
|
40
|
+
callbacks: {},
|
|
41
|
+
menuConfig: {},
|
|
42
|
+
positionConfig: {},
|
|
43
|
+
openGeneration: 0,
|
|
44
|
+
openDropdown(content, anchor, options) {
|
|
45
|
+
state = {
|
|
46
|
+
...state,
|
|
47
|
+
open: true,
|
|
48
|
+
content,
|
|
49
|
+
anchor,
|
|
50
|
+
keyboardOpen: options?.keyboard ?? false,
|
|
51
|
+
callbacks: options?.callbacks ?? {},
|
|
52
|
+
menuConfig: options?.menuConfig ?? {},
|
|
53
|
+
positionConfig: options?.positionConfig ?? {},
|
|
54
|
+
openGeneration: state.openGeneration + 1
|
|
55
|
+
};
|
|
56
|
+
notify();
|
|
57
|
+
},
|
|
58
|
+
closeDropdown(generation) {
|
|
59
|
+
if (generation !== void 0 && generation !== state.openGeneration) return;
|
|
60
|
+
state = {
|
|
61
|
+
...state,
|
|
62
|
+
open: false,
|
|
63
|
+
content: null,
|
|
64
|
+
anchor: null,
|
|
65
|
+
keyboardOpen: false
|
|
66
|
+
};
|
|
67
|
+
notify();
|
|
68
|
+
},
|
|
69
|
+
updateContent(content) {
|
|
70
|
+
if (!state.open || state.content === content) return;
|
|
71
|
+
state = { ...state, content };
|
|
72
|
+
notify();
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
return {
|
|
76
|
+
getState: () => state,
|
|
77
|
+
subscribe: (listener) => {
|
|
78
|
+
listeners.add(listener);
|
|
79
|
+
return () => listeners.delete(listener);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
var store = createDropdownStore();
|
|
84
|
+
function useDropdownStore(selector) {
|
|
85
|
+
const selectorRef = useRef(selector);
|
|
86
|
+
selectorRef.current = selector;
|
|
87
|
+
const prevRef = useRef(void 0);
|
|
88
|
+
const getSnapshot = useCallback(() => {
|
|
89
|
+
const result = selectorRef.current(store.getState());
|
|
90
|
+
const prev = prevRef.current;
|
|
91
|
+
if (prev !== void 0 && shallowEqual(prev, result)) {
|
|
92
|
+
return prev;
|
|
93
|
+
}
|
|
94
|
+
prevRef.current = result;
|
|
95
|
+
return result;
|
|
66
96
|
}, []);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
97
|
+
return useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
|
|
98
|
+
}
|
|
99
|
+
useDropdownStore.getState = store.getState;
|
|
100
|
+
|
|
101
|
+
// src/Tooltip/TooltipStore.ts
|
|
102
|
+
import { useCallback as useCallback2, useRef as useRef2, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
103
|
+
function shallowEqual2(a, b) {
|
|
104
|
+
if (Object.is(a, b)) return true;
|
|
105
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null)
|
|
106
|
+
return false;
|
|
107
|
+
const keysA = Object.keys(a);
|
|
108
|
+
const keysB = Object.keys(b);
|
|
109
|
+
if (keysA.length !== keysB.length) return false;
|
|
110
|
+
return keysA.every(
|
|
111
|
+
(k) => Object.is(
|
|
112
|
+
a[k],
|
|
113
|
+
b[k]
|
|
114
|
+
)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
function createTooltipStore() {
|
|
118
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
119
|
+
function notify() {
|
|
120
|
+
listeners.forEach((l) => l());
|
|
75
121
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
122
|
+
let state = {
|
|
123
|
+
open: false,
|
|
124
|
+
content: null,
|
|
125
|
+
anchor: null,
|
|
126
|
+
positionConfig: {},
|
|
127
|
+
hoverable: false,
|
|
128
|
+
closeDelay: 300,
|
|
129
|
+
popupId: null,
|
|
130
|
+
lastCloseTime: 0,
|
|
131
|
+
setPopupId(id) {
|
|
132
|
+
if (state.popupId === id) return;
|
|
133
|
+
state = { ...state, popupId: id };
|
|
134
|
+
notify();
|
|
135
|
+
},
|
|
136
|
+
openTooltip(content, anchor, options) {
|
|
137
|
+
state = {
|
|
138
|
+
...state,
|
|
139
|
+
open: true,
|
|
140
|
+
content,
|
|
141
|
+
anchor,
|
|
142
|
+
positionConfig: options?.positionConfig ?? {},
|
|
143
|
+
hoverable: options?.hoverable ?? false,
|
|
144
|
+
closeDelay: options?.closeDelay ?? 300
|
|
145
|
+
};
|
|
146
|
+
notify();
|
|
147
|
+
},
|
|
148
|
+
closeTooltip(options) {
|
|
149
|
+
if (!state.open) return;
|
|
150
|
+
state = {
|
|
151
|
+
...state,
|
|
152
|
+
open: false,
|
|
153
|
+
content: null,
|
|
154
|
+
anchor: null,
|
|
155
|
+
lastCloseTime: options?.skipWarmUp ? 0 : Date.now()
|
|
156
|
+
};
|
|
157
|
+
notify();
|
|
158
|
+
},
|
|
159
|
+
updateContent(content) {
|
|
160
|
+
if (!state.open || state.content === content) return;
|
|
161
|
+
state = { ...state, content };
|
|
162
|
+
notify();
|
|
91
163
|
}
|
|
92
|
-
|
|
164
|
+
};
|
|
165
|
+
return {
|
|
166
|
+
getState: () => state,
|
|
167
|
+
subscribe: (listener) => {
|
|
168
|
+
listeners.add(listener);
|
|
169
|
+
return () => listeners.delete(listener);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
93
172
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const activated = wasActivated || !!open || !!defaultOpen;
|
|
108
|
-
const activate = useCallback(() => {
|
|
109
|
-
if (!disabled) {
|
|
110
|
-
if (shouldOpenRef.current) setMenuOpen(true);
|
|
111
|
-
setWasActivated(true);
|
|
112
|
-
}
|
|
113
|
-
}, [disabled]);
|
|
114
|
-
const deactivate = useCallback(() => {
|
|
115
|
-
setMenuOpen(false);
|
|
116
|
-
setWasActivated(false);
|
|
173
|
+
var store2 = createTooltipStore();
|
|
174
|
+
function useTooltipStore(selector) {
|
|
175
|
+
const selectorRef = useRef2(selector);
|
|
176
|
+
selectorRef.current = selector;
|
|
177
|
+
const prevRef = useRef2(void 0);
|
|
178
|
+
const getSnapshot = useCallback2(() => {
|
|
179
|
+
const result = selectorRef.current(store2.getState());
|
|
180
|
+
const prev = prevRef.current;
|
|
181
|
+
if (prev !== void 0 && shallowEqual2(prev, result)) {
|
|
182
|
+
return prev;
|
|
183
|
+
}
|
|
184
|
+
prevRef.current = result;
|
|
185
|
+
return result;
|
|
117
186
|
}, []);
|
|
187
|
+
return useSyncExternalStore2(store2.subscribe, getSnapshot, getSnapshot);
|
|
188
|
+
}
|
|
189
|
+
useTooltipStore.getState = store2.getState;
|
|
190
|
+
|
|
191
|
+
// src/Dropdown/Dropdown.tsx
|
|
192
|
+
import { jsx } from "react/jsx-runtime";
|
|
193
|
+
var DropdownCtx = createContext(null);
|
|
194
|
+
function Root({ children, open, defaultOpen, disabled = false, modal, openOn = "click", loopFocus, highlightItemOnHover, orientation, side, sideOffset, align, alignOffset, collisionPadding, onOpenChange, onOpenChangeComplete }) {
|
|
195
|
+
const contentRef = useRef3(null);
|
|
196
|
+
const triggerRef = useRef3(null);
|
|
197
|
+
const callbacks = { onOpenChange, onOpenChangeComplete };
|
|
198
|
+
const menuConfig = { modal, loopFocus, highlightItemOnHover, orientation };
|
|
199
|
+
const positionConfig = { side, sideOffset, align, alignOffset, collisionPadding };
|
|
200
|
+
const initializedRef = useRef3(false);
|
|
201
|
+
useEffect(() => {
|
|
202
|
+
if (open === void 0) return;
|
|
203
|
+
const store3 = useDropdownStore.getState();
|
|
204
|
+
if (open && !store3.open) {
|
|
205
|
+
const content = contentRef.current;
|
|
206
|
+
const anchor = triggerRef.current;
|
|
207
|
+
if (content && anchor) {
|
|
208
|
+
store3.openDropdown(content, anchor, { callbacks, menuConfig, positionConfig });
|
|
209
|
+
}
|
|
210
|
+
} else if (!open && store3.open && store3.anchor === triggerRef.current) {
|
|
211
|
+
store3.closeDropdown();
|
|
212
|
+
}
|
|
213
|
+
}, [open]);
|
|
214
|
+
useEffect(() => {
|
|
215
|
+
if (initializedRef.current || defaultOpen === void 0 || !defaultOpen) return;
|
|
216
|
+
initializedRef.current = true;
|
|
217
|
+
requestAnimationFrame(() => {
|
|
218
|
+
const content = contentRef.current;
|
|
219
|
+
const anchor = triggerRef.current;
|
|
220
|
+
if (content && anchor) {
|
|
221
|
+
useDropdownStore.getState().openDropdown(content, anchor, { callbacks, menuConfig, positionConfig });
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}, [defaultOpen]);
|
|
118
225
|
const ctx = {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
226
|
+
contentRef,
|
|
227
|
+
triggerRef,
|
|
228
|
+
disabled,
|
|
229
|
+
openOn,
|
|
230
|
+
callbacks,
|
|
231
|
+
menuConfig,
|
|
232
|
+
positionConfig
|
|
124
233
|
};
|
|
125
|
-
|
|
126
|
-
return /* @__PURE__ */ jsx(DeferredContext.Provider, { value: ctx, children });
|
|
127
|
-
}
|
|
128
|
-
return /* @__PURE__ */ jsx(DeferredContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx(
|
|
129
|
-
InnerRoot,
|
|
130
|
-
{
|
|
131
|
-
open,
|
|
132
|
-
defaultOpen,
|
|
133
|
-
disabled,
|
|
134
|
-
shouldOpenRef,
|
|
135
|
-
unmountOnClose,
|
|
136
|
-
onDeactivate: deactivate,
|
|
137
|
-
onMenuOpenChange: setMenuOpen,
|
|
138
|
-
onOpenChangeComplete,
|
|
139
|
-
...props,
|
|
140
|
-
children
|
|
141
|
-
}
|
|
142
|
-
) });
|
|
143
|
-
}
|
|
144
|
-
var BASE_UI_KEYS = /* @__PURE__ */ new Set([
|
|
145
|
-
"handle",
|
|
146
|
-
"payload",
|
|
147
|
-
"delay",
|
|
148
|
-
"closeDelay",
|
|
149
|
-
"openOnHover",
|
|
150
|
-
"render",
|
|
151
|
-
"tooltip"
|
|
152
|
-
]);
|
|
153
|
-
function pickHtmlProps(props) {
|
|
154
|
-
const html = {};
|
|
155
|
-
for (const key in props) {
|
|
156
|
-
if (!BASE_UI_KEYS.has(key)) html[key] = props[key];
|
|
157
|
-
}
|
|
158
|
-
return html;
|
|
234
|
+
return /* @__PURE__ */ jsx(DropdownCtx.Provider, { value: ctx, children });
|
|
159
235
|
}
|
|
160
|
-
function Trigger({
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
tooltip,
|
|
166
|
-
...rest
|
|
167
|
-
}) {
|
|
168
|
-
const deferred = useContext(DeferredContext);
|
|
169
|
-
const triggerElRef = useRef(null);
|
|
170
|
-
const [tooltipActivated, setTooltipActivated] = useState(false);
|
|
171
|
-
const isHoveringRef = useRef(false);
|
|
172
|
-
const pendingRef = useRef(0);
|
|
173
|
-
const lastPointerTypeRef = useRef("");
|
|
174
|
-
const shouldRefocusRef = deferred?.shouldRefocusRef;
|
|
175
|
-
const triggerRef = useCallback(
|
|
236
|
+
function Trigger({ children, onClick, onPointerDown, onPointerEnter, onPointerLeave, onFocus, onBlur, onKeyDown, render, tooltip, tooltipDelay = 600, ...props }) {
|
|
237
|
+
const ctx = useContext(DropdownCtx);
|
|
238
|
+
const triggerRef = useRef3(null);
|
|
239
|
+
const openOn = ctx?.openOn ?? "click";
|
|
240
|
+
const setTriggerRef = useCallback3(
|
|
176
241
|
(el) => {
|
|
177
|
-
|
|
178
|
-
if (
|
|
179
|
-
shouldRefocusRef.current = false;
|
|
180
|
-
el.focus();
|
|
181
|
-
}
|
|
242
|
+
triggerRef.current = el;
|
|
243
|
+
if (ctx) ctx.triggerRef.current = el;
|
|
182
244
|
},
|
|
183
|
-
[
|
|
245
|
+
[ctx]
|
|
184
246
|
);
|
|
247
|
+
const storeState = useDropdownStore((s) => ({
|
|
248
|
+
open: s.open,
|
|
249
|
+
anchor: s.anchor
|
|
250
|
+
}));
|
|
251
|
+
const isActive = storeState.open && storeState.anchor === triggerRef.current;
|
|
252
|
+
const tooltipOpenTimerRef = useRef3(0);
|
|
253
|
+
const tooltipCloseTimerRef = useRef3(0);
|
|
254
|
+
const lastPointerTypeRef = useRef3("");
|
|
255
|
+
const tooltipState = useTooltipStore((s) => ({
|
|
256
|
+
open: s.open,
|
|
257
|
+
anchor: s.anchor,
|
|
258
|
+
popupId: s.popupId
|
|
259
|
+
}));
|
|
260
|
+
const isTooltipActive = tooltip != null && tooltipState.open && tooltipState.anchor === triggerRef.current;
|
|
261
|
+
const wasActiveRef = useRef3(false);
|
|
262
|
+
const skipFocusTooltipRef = useRef3(false);
|
|
185
263
|
useEffect(() => {
|
|
186
|
-
if (
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (!isHoveringRef.current) return;
|
|
191
|
-
el.dispatchEvent(
|
|
192
|
-
new PointerEvent("pointerenter", {
|
|
193
|
-
bubbles: false,
|
|
194
|
-
pointerType: "mouse"
|
|
195
|
-
})
|
|
196
|
-
);
|
|
197
|
-
el.dispatchEvent(new MouseEvent("mouseenter", { bubbles: false }));
|
|
198
|
-
el.dispatchEvent(new MouseEvent("mousemove", { bubbles: true }));
|
|
199
|
-
});
|
|
200
|
-
return () => cancelAnimationFrame(frame);
|
|
201
|
-
}, [tooltipActivated]);
|
|
202
|
-
const tooltipHoverHandlers = tooltip ? {
|
|
203
|
-
onMouseEnter: () => {
|
|
204
|
-
if (lastPointerTypeRef.current === "touch") return;
|
|
205
|
-
isHoveringRef.current = true;
|
|
206
|
-
pendingRef.current = requestAnimationFrame(() => {
|
|
207
|
-
if (isHoveringRef.current) setTooltipActivated(true);
|
|
264
|
+
if (wasActiveRef.current && !isActive) {
|
|
265
|
+
skipFocusTooltipRef.current = true;
|
|
266
|
+
const id = requestAnimationFrame(() => {
|
|
267
|
+
skipFocusTooltipRef.current = false;
|
|
208
268
|
});
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
},
|
|
244
|
-
onClick: (e) => {
|
|
245
|
-
callHandler(htmlProps.onClick, e);
|
|
246
|
-
deferred.shouldOpenRef.current = true;
|
|
247
|
-
deferred.shouldRefocusRef.current = true;
|
|
248
|
-
deferred.activate();
|
|
249
|
-
},
|
|
250
|
-
onKeyDown: (e) => {
|
|
251
|
-
callHandler(htmlProps.onKeyDown, e);
|
|
252
|
-
if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
253
|
-
deferred.shouldOpenRef.current = true;
|
|
254
|
-
deferred.shouldRefocusRef.current = true;
|
|
255
|
-
deferred.activate();
|
|
269
|
+
return () => cancelAnimationFrame(id);
|
|
270
|
+
}
|
|
271
|
+
wasActiveRef.current = isActive;
|
|
272
|
+
});
|
|
273
|
+
const dismissTooltip = useCallback3(() => {
|
|
274
|
+
clearTimeout(tooltipOpenTimerRef.current);
|
|
275
|
+
clearTimeout(tooltipCloseTimerRef.current);
|
|
276
|
+
const ts = useTooltipStore.getState();
|
|
277
|
+
if (ts.open && ts.anchor === triggerRef.current) {
|
|
278
|
+
ts.closeTooltip({ skipWarmUp: true });
|
|
279
|
+
}
|
|
280
|
+
}, []);
|
|
281
|
+
const scheduleTooltip = useCallback3(() => {
|
|
282
|
+
if (!tooltip || !triggerRef.current) return;
|
|
283
|
+
const ds = useDropdownStore.getState();
|
|
284
|
+
if (ds.open) return;
|
|
285
|
+
clearTimeout(tooltipOpenTimerRef.current);
|
|
286
|
+
clearTimeout(tooltipCloseTimerRef.current);
|
|
287
|
+
const ts = useTooltipStore.getState();
|
|
288
|
+
if (ts.open && ts.anchor === triggerRef.current) return;
|
|
289
|
+
const tooltipContent = /* @__PURE__ */ jsx(BaseTooltip.Popup, { className: "slithy-tooltip-popup", children: tooltip });
|
|
290
|
+
const isSwitch = ts.open && ts.anchor !== triggerRef.current;
|
|
291
|
+
const elapsed = Date.now() - ts.lastCloseTime;
|
|
292
|
+
const isWarmUp = !isSwitch && elapsed < 300;
|
|
293
|
+
const effectiveDelay = isSwitch || isWarmUp ? 0 : tooltipDelay;
|
|
294
|
+
if (effectiveDelay === 0) {
|
|
295
|
+
ts.openTooltip(tooltipContent, triggerRef.current);
|
|
296
|
+
} else {
|
|
297
|
+
tooltipOpenTimerRef.current = window.setTimeout(() => {
|
|
298
|
+
const dds = useDropdownStore.getState();
|
|
299
|
+
if (dds.open) return;
|
|
300
|
+
if (triggerRef.current) {
|
|
301
|
+
const content = /* @__PURE__ */ jsx(BaseTooltip.Popup, { className: "slithy-tooltip-popup", children: tooltip });
|
|
302
|
+
useTooltipStore.getState().openTooltip(content, triggerRef.current);
|
|
256
303
|
}
|
|
304
|
+
}, effectiveDelay);
|
|
305
|
+
}
|
|
306
|
+
}, [tooltip, tooltipDelay]);
|
|
307
|
+
const scheduleTooltipClose = useCallback3(() => {
|
|
308
|
+
clearTimeout(tooltipOpenTimerRef.current);
|
|
309
|
+
const ts = useTooltipStore.getState();
|
|
310
|
+
if (!ts.open || ts.anchor !== triggerRef.current) return;
|
|
311
|
+
tooltipCloseTimerRef.current = window.setTimeout(() => {
|
|
312
|
+
const s = useTooltipStore.getState();
|
|
313
|
+
if (s.open && s.anchor === triggerRef.current) {
|
|
314
|
+
s.closeTooltip();
|
|
257
315
|
}
|
|
316
|
+
}, 300);
|
|
317
|
+
}, []);
|
|
318
|
+
useEffect(() => {
|
|
319
|
+
return () => {
|
|
320
|
+
clearTimeout(tooltipOpenTimerRef.current);
|
|
321
|
+
clearTimeout(tooltipCloseTimerRef.current);
|
|
258
322
|
};
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
323
|
+
}, []);
|
|
324
|
+
const toggleDropdown = useCallback3(
|
|
325
|
+
(options) => {
|
|
326
|
+
if (ctx?.disabled) return;
|
|
327
|
+
dismissTooltip();
|
|
328
|
+
const store3 = useDropdownStore.getState();
|
|
329
|
+
if (store3.open && store3.anchor === triggerRef.current) {
|
|
330
|
+
store3.closeDropdown();
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const content = ctx?.contentRef.current;
|
|
334
|
+
if (content && triggerRef.current) {
|
|
335
|
+
store3.openDropdown(content, triggerRef.current, {
|
|
336
|
+
keyboard: options?.keyboard,
|
|
337
|
+
callbacks: ctx?.callbacks,
|
|
338
|
+
menuConfig: ctx?.menuConfig,
|
|
339
|
+
positionConfig: ctx?.positionConfig
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
[ctx, dismissTooltip]
|
|
344
|
+
);
|
|
345
|
+
const handlePointerDown = useCallback3(
|
|
346
|
+
(e) => {
|
|
347
|
+
lastPointerTypeRef.current = e.pointerType;
|
|
348
|
+
onPointerDown?.(e);
|
|
349
|
+
if (e.defaultPrevented || openOn !== "pointerdown") return;
|
|
350
|
+
toggleDropdown();
|
|
351
|
+
},
|
|
352
|
+
[onPointerDown, openOn, toggleDropdown]
|
|
353
|
+
);
|
|
354
|
+
const handleClick = useCallback3(
|
|
355
|
+
(e) => {
|
|
356
|
+
onClick?.(e);
|
|
357
|
+
if (e.defaultPrevented) return;
|
|
358
|
+
if (openOn === "click") {
|
|
359
|
+
toggleDropdown();
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
[onClick, openOn, toggleDropdown]
|
|
283
363
|
);
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
364
|
+
const handlePointerEnter = useCallback3(
|
|
365
|
+
(e) => {
|
|
366
|
+
onPointerEnter?.(e);
|
|
367
|
+
if (e.defaultPrevented) return;
|
|
368
|
+
if (lastPointerTypeRef.current === "touch") return;
|
|
369
|
+
scheduleTooltip();
|
|
370
|
+
},
|
|
371
|
+
[onPointerEnter, scheduleTooltip]
|
|
372
|
+
);
|
|
373
|
+
const handlePointerLeave = useCallback3(
|
|
374
|
+
(e) => {
|
|
375
|
+
onPointerLeave?.(e);
|
|
376
|
+
if (e.defaultPrevented) return;
|
|
377
|
+
scheduleTooltipClose();
|
|
378
|
+
},
|
|
379
|
+
[onPointerLeave, scheduleTooltipClose]
|
|
380
|
+
);
|
|
381
|
+
const handleFocus = useCallback3(
|
|
382
|
+
(e) => {
|
|
383
|
+
onFocus?.(e);
|
|
384
|
+
if (e.defaultPrevented) return;
|
|
385
|
+
if (skipFocusTooltipRef.current) {
|
|
386
|
+
skipFocusTooltipRef.current = false;
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (lastPointerTypeRef.current === "touch") return;
|
|
390
|
+
scheduleTooltip();
|
|
391
|
+
},
|
|
392
|
+
[onFocus, scheduleTooltip]
|
|
393
|
+
);
|
|
394
|
+
const handleBlur = useCallback3(
|
|
395
|
+
(e) => {
|
|
396
|
+
onBlur?.(e);
|
|
397
|
+
if (e.defaultPrevented) return;
|
|
398
|
+
dismissTooltip();
|
|
399
|
+
},
|
|
400
|
+
[onBlur, dismissTooltip]
|
|
401
|
+
);
|
|
402
|
+
const handleKeyDown = useCallback3(
|
|
403
|
+
(e) => {
|
|
404
|
+
onKeyDown?.(e);
|
|
405
|
+
if (ctx?.disabled || e.defaultPrevented) return;
|
|
406
|
+
if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
407
|
+
e.preventDefault();
|
|
408
|
+
toggleDropdown({ keyboard: true });
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
[ctx, onKeyDown, toggleDropdown]
|
|
412
|
+
);
|
|
413
|
+
const triggerProps = {
|
|
414
|
+
ref: setTriggerRef,
|
|
415
|
+
type: "button",
|
|
416
|
+
"aria-haspopup": "menu",
|
|
417
|
+
"aria-expanded": isActive,
|
|
418
|
+
"aria-describedby": isTooltipActive && tooltipState.popupId ? tooltipState.popupId : void 0,
|
|
419
|
+
onPointerDown: handlePointerDown,
|
|
420
|
+
onClick: handleClick,
|
|
421
|
+
onPointerEnter: handlePointerEnter,
|
|
422
|
+
onPointerLeave: handlePointerLeave,
|
|
423
|
+
onFocus: handleFocus,
|
|
424
|
+
onBlur: handleBlur,
|
|
425
|
+
onKeyDown: handleKeyDown,
|
|
426
|
+
...props
|
|
427
|
+
};
|
|
428
|
+
if (isValidElement(render)) {
|
|
429
|
+
return cloneElement(render, triggerProps, children);
|
|
430
|
+
}
|
|
431
|
+
if (typeof render === "function") {
|
|
432
|
+
return render({ ...triggerProps, children });
|
|
433
|
+
}
|
|
434
|
+
return /* @__PURE__ */ jsx("button", { ...triggerProps, children });
|
|
289
435
|
}
|
|
290
|
-
function Portal(
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
436
|
+
function Portal({ children }) {
|
|
437
|
+
const ctx = useContext(DropdownCtx);
|
|
438
|
+
const storeAnchor = useDropdownStore((s) => s.anchor);
|
|
439
|
+
if (ctx) {
|
|
440
|
+
ctx.contentRef.current = children;
|
|
441
|
+
}
|
|
442
|
+
const isActive = !!(ctx && storeAnchor && storeAnchor === ctx.triggerRef.current);
|
|
443
|
+
useEffect(() => {
|
|
444
|
+
if (isActive) {
|
|
445
|
+
useDropdownStore.getState().updateContent(children);
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
return null;
|
|
294
449
|
}
|
|
295
450
|
function Positioner({ className, ...props }) {
|
|
296
451
|
return /* @__PURE__ */ jsx(
|
|
@@ -397,199 +552,351 @@ var Dropdown = {
|
|
|
397
552
|
RadioItemIndicator
|
|
398
553
|
};
|
|
399
554
|
|
|
555
|
+
// src/Dropdown/DropdownRenderer.tsx
|
|
556
|
+
import { Menu as Menu2 } from "@base-ui/react/menu";
|
|
557
|
+
import { useEffect as useEffect2, useRef as useRef4, useState } from "react";
|
|
558
|
+
|
|
559
|
+
// #style-inject:#style-inject
|
|
560
|
+
function styleInject(css, { insertAt } = {}) {
|
|
561
|
+
if (!css || typeof document === "undefined") return;
|
|
562
|
+
const head = document.head || document.getElementsByTagName("head")[0];
|
|
563
|
+
const style = document.createElement("style");
|
|
564
|
+
style.type = "text/css";
|
|
565
|
+
if (insertAt === "top") {
|
|
566
|
+
if (head.firstChild) {
|
|
567
|
+
head.insertBefore(style, head.firstChild);
|
|
568
|
+
} else {
|
|
569
|
+
head.appendChild(style);
|
|
570
|
+
}
|
|
571
|
+
} else {
|
|
572
|
+
head.appendChild(style);
|
|
573
|
+
}
|
|
574
|
+
if (style.styleSheet) {
|
|
575
|
+
style.styleSheet.cssText = css;
|
|
576
|
+
} else {
|
|
577
|
+
style.appendChild(document.createTextNode(css));
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// src/Dropdown/Dropdown.css
|
|
582
|
+
styleInject(".slithy-dropdown-positioner {\n z-index: 1000;\n}\n.slithy-dropdown-popup {\n background: var(--slithy-dropdown-bg, #fff);\n color: var(--slithy-dropdown-color, #222);\n font-size: var(--slithy-dropdown-font-size, 0.875rem);\n line-height: 1.4;\n padding: var(--slithy-dropdown-padding, 4px 0);\n border-radius: var(--slithy-dropdown-radius, 6px);\n min-width: var(--slithy-dropdown-min-width, 160px);\n box-shadow: var( --slithy-dropdown-shadow, 0 4px 16px rgba(0, 0, 0, 0.12), 0 1px 3px rgba(0, 0, 0, 0.08) );\n border: var(--slithy-dropdown-border, 1px solid rgba(0, 0, 0, 0.08));\n transform-origin: var(--transform-origin);\n transition-property: opacity, transform;\n transition-duration: 150ms;\n transition-timing-function: ease;\n outline: none;\n}\n.slithy-dropdown-popup[data-open] {\n opacity: 1;\n transform: scale(1);\n}\n.slithy-dropdown-popup[data-starting-style],\n.slithy-dropdown-popup[data-ending-style] {\n opacity: 0;\n transform: scale(0.95);\n}\n.slithy-dropdown-popup[data-instant] {\n transition-duration: 0ms;\n}\n.slithy-dropdown-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: var(--slithy-dropdown-item-padding, 6px 12px);\n cursor: default;\n outline: none;\n user-select: none;\n}\n.slithy-dropdown-item[data-highlighted] {\n background: var(--slithy-dropdown-item-highlighted-bg, rgba(0, 0, 0, 0.06));\n}\n.slithy-dropdown-item[data-disabled] {\n opacity: 0.4;\n pointer-events: none;\n}\n.slithy-dropdown-separator {\n height: 1px;\n margin: 4px 0;\n background: var(--slithy-dropdown-separator-color, rgba(0, 0, 0, 0.1));\n}\n.slithy-dropdown-group-label {\n padding: var(--slithy-dropdown-group-label-padding, 6px 12px 2px);\n font-size: 0.75rem;\n font-weight: 600;\n color: var(--slithy-dropdown-group-label-color, rgba(0, 0, 0, 0.5));\n user-select: none;\n}\n.slithy-dropdown-item-indicator {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n}\n.slithy-dropdown-arrow {\n width: 8px;\n height: 8px;\n transform: rotate(45deg);\n background: var(--slithy-dropdown-bg, #fff);\n border: var(--slithy-dropdown-border, 1px solid rgba(0, 0, 0, 0.08));\n}\n.slithy-dropdown-arrow[data-side=top] {\n bottom: -4px;\n border-top: none;\n border-left: none;\n}\n.slithy-dropdown-arrow[data-side=bottom] {\n top: -4px;\n border-bottom: none;\n border-right: none;\n}\n.slithy-dropdown-arrow[data-side=left] {\n right: -4px;\n border-bottom: none;\n border-left: none;\n}\n.slithy-dropdown-arrow[data-side=right] {\n left: -4px;\n border-top: none;\n border-right: none;\n}\n");
|
|
583
|
+
|
|
584
|
+
// src/Dropdown/DropdownRenderer.tsx
|
|
585
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
586
|
+
function DropdownRenderer() {
|
|
587
|
+
const open = useDropdownStore((s) => s.open);
|
|
588
|
+
const content = useDropdownStore((s) => s.content);
|
|
589
|
+
const anchor = useDropdownStore((s) => s.anchor);
|
|
590
|
+
const keyboardOpen = useDropdownStore((s) => s.keyboardOpen);
|
|
591
|
+
const generation = useDropdownStore((s) => s.openGeneration);
|
|
592
|
+
const lastContentRef = useRef4(content);
|
|
593
|
+
const lastAnchorRef = useRef4(anchor);
|
|
594
|
+
if (content) lastContentRef.current = content;
|
|
595
|
+
if (anchor) lastAnchorRef.current = anchor;
|
|
596
|
+
const activeContent = content ?? lastContentRef.current;
|
|
597
|
+
const activeAnchor = anchor ?? lastAnchorRef.current;
|
|
598
|
+
const [hasOpened, setHasOpened] = useState(false);
|
|
599
|
+
const [deferredOpen, setDeferredOpen] = useState(false);
|
|
600
|
+
useEffect2(() => {
|
|
601
|
+
if (open && !hasOpened) {
|
|
602
|
+
setHasOpened(true);
|
|
603
|
+
requestAnimationFrame(() => setDeferredOpen(true));
|
|
604
|
+
} else {
|
|
605
|
+
setDeferredOpen(open);
|
|
606
|
+
}
|
|
607
|
+
}, [open, hasOpened]);
|
|
608
|
+
const prevOpenRef = useRef4(false);
|
|
609
|
+
useEffect2(() => {
|
|
610
|
+
if (prevOpenRef.current && !open && lastAnchorRef.current) {
|
|
611
|
+
const el = lastAnchorRef.current;
|
|
612
|
+
requestAnimationFrame(() => el.focus());
|
|
613
|
+
}
|
|
614
|
+
prevOpenRef.current = open;
|
|
615
|
+
}, [open]);
|
|
616
|
+
const callbacks = useDropdownStore((s) => s.callbacks);
|
|
617
|
+
const menuConfig = useDropdownStore((s) => s.menuConfig);
|
|
618
|
+
const positionConfig = useDropdownStore((s) => s.positionConfig);
|
|
619
|
+
useEffect2(() => {
|
|
620
|
+
if (!open || !anchor || menuConfig.modal !== false) return;
|
|
621
|
+
const observer = new IntersectionObserver(
|
|
622
|
+
([entry]) => {
|
|
623
|
+
if (!entry.isIntersecting) {
|
|
624
|
+
useDropdownStore.getState().closeDropdown();
|
|
625
|
+
}
|
|
626
|
+
},
|
|
627
|
+
{ threshold: 0 }
|
|
628
|
+
);
|
|
629
|
+
observer.observe(anchor);
|
|
630
|
+
return () => observer.disconnect();
|
|
631
|
+
}, [open, anchor, menuConfig.modal]);
|
|
632
|
+
const lastCallbacksRef = useRef4(callbacks);
|
|
633
|
+
if (callbacks.onOpenChange) lastCallbacksRef.current = callbacks;
|
|
634
|
+
const handleOpenChangeComplete = (isOpen) => {
|
|
635
|
+
lastCallbacksRef.current.onOpenChangeComplete?.(isOpen);
|
|
636
|
+
if (!isOpen) {
|
|
637
|
+
lastContentRef.current = null;
|
|
638
|
+
lastAnchorRef.current = null;
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
if (!hasOpened) return null;
|
|
642
|
+
return /* @__PURE__ */ jsx2(
|
|
643
|
+
Menu2.Root,
|
|
644
|
+
{
|
|
645
|
+
open: deferredOpen,
|
|
646
|
+
modal: menuConfig.modal,
|
|
647
|
+
loopFocus: menuConfig.loopFocus,
|
|
648
|
+
highlightItemOnHover: menuConfig.highlightItemOnHover,
|
|
649
|
+
orientation: menuConfig.orientation,
|
|
650
|
+
onOpenChange: (isOpen, eventDetails) => {
|
|
651
|
+
if (!isOpen && eventDetails.reason !== "trigger-hover") {
|
|
652
|
+
lastCallbacksRef.current.onOpenChange?.(false);
|
|
653
|
+
useDropdownStore.getState().closeDropdown(generation);
|
|
654
|
+
}
|
|
655
|
+
},
|
|
656
|
+
onOpenChangeComplete: handleOpenChangeComplete,
|
|
657
|
+
children: /* @__PURE__ */ jsx2(Menu2.Portal, { children: /* @__PURE__ */ jsx2(
|
|
658
|
+
Menu2.Positioner,
|
|
659
|
+
{
|
|
660
|
+
anchor: activeAnchor,
|
|
661
|
+
className: "slithy-dropdown-positioner",
|
|
662
|
+
side: positionConfig.side,
|
|
663
|
+
sideOffset: positionConfig.sideOffset ?? 4,
|
|
664
|
+
align: positionConfig.align,
|
|
665
|
+
alignOffset: positionConfig.alignOffset,
|
|
666
|
+
collisionPadding: positionConfig.collisionPadding,
|
|
667
|
+
children: activeContent
|
|
668
|
+
}
|
|
669
|
+
) })
|
|
670
|
+
}
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
|
|
400
674
|
// src/Tooltip/Tooltip.tsx
|
|
401
675
|
import { Tooltip as BaseTooltip2 } from "@base-ui/react/tooltip";
|
|
402
676
|
import {
|
|
403
677
|
cloneElement as cloneElement2,
|
|
404
678
|
createContext as createContext2,
|
|
405
679
|
isValidElement as isValidElement2,
|
|
406
|
-
useCallback as
|
|
680
|
+
useCallback as useCallback4,
|
|
407
681
|
useContext as useContext2,
|
|
408
|
-
useEffect as
|
|
409
|
-
useRef as
|
|
410
|
-
useState as useState2
|
|
682
|
+
useEffect as useEffect3,
|
|
683
|
+
useRef as useRef5
|
|
411
684
|
} from "react";
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
styleInject(".slithy-tooltip-positioner {\n z-index: 1000;\n}\n.slithy-tooltip-popup {\n background: var(--slithy-tooltip-bg, #222);\n color: var(--slithy-tooltip-color, #fff);\n font-size: var(--slithy-tooltip-font-size, 0.8125rem);\n line-height: 1.4;\n padding: var(--slithy-tooltip-padding, 4px 8px);\n border-radius: var(--slithy-tooltip-radius, 4px);\n max-width: var(--slithy-tooltip-max-width, 300px);\n transform-origin: var(--transform-origin);\n transition-property: opacity, transform;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n}\n.slithy-tooltip-popup[data-open] {\n opacity: 1;\n transform: scale(1);\n}\n.slithy-tooltip-popup[data-starting-style],\n.slithy-tooltip-popup[data-ending-style] {\n opacity: 0;\n transform: scale(0.95);\n}\n.slithy-tooltip-popup[data-instant] {\n transition-duration: 0ms;\n}\n.slithy-tooltip-arrow {\n width: 8px;\n height: 8px;\n transform: rotate(45deg);\n background: var(--slithy-tooltip-bg, #222);\n}\n.slithy-tooltip-arrow[data-side=top] {\n bottom: -4px;\n}\n.slithy-tooltip-arrow[data-side=bottom] {\n top: -4px;\n}\n.slithy-tooltip-arrow[data-side=left] {\n right: -4px;\n}\n.slithy-tooltip-arrow[data-side=right] {\n left: -4px;\n}\n");
|
|
415
|
-
|
|
416
|
-
// src/Tooltip/Tooltip.tsx
|
|
417
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
418
|
-
var DeferredContext2 = createContext2(null);
|
|
419
|
-
var Provider = BaseTooltip2.Provider;
|
|
685
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
686
|
+
var TooltipCtx = createContext2(null);
|
|
420
687
|
function Root2({
|
|
421
688
|
children,
|
|
422
689
|
open,
|
|
423
690
|
defaultOpen,
|
|
424
|
-
disabled,
|
|
691
|
+
disabled = false,
|
|
425
692
|
touchDisabled = true,
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
693
|
+
hoverable = false,
|
|
694
|
+
delay = 600,
|
|
695
|
+
closeDelay = 300,
|
|
696
|
+
warmUpDelay = 300,
|
|
697
|
+
side,
|
|
698
|
+
sideOffset,
|
|
699
|
+
align,
|
|
700
|
+
alignOffset,
|
|
701
|
+
collisionPadding
|
|
429
702
|
}) {
|
|
430
|
-
const
|
|
431
|
-
const
|
|
432
|
-
const
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
if (
|
|
436
|
-
|
|
703
|
+
const contentRef = useRef5(null);
|
|
704
|
+
const triggerRef = useRef5(null);
|
|
705
|
+
const positionConfig = { side, sideOffset, align, alignOffset, collisionPadding };
|
|
706
|
+
const initializedRef = useRef5(false);
|
|
707
|
+
useEffect3(() => {
|
|
708
|
+
if (open === void 0) return;
|
|
709
|
+
const store3 = useTooltipStore.getState();
|
|
710
|
+
if (open && !store3.open) {
|
|
711
|
+
const content = contentRef.current;
|
|
712
|
+
const anchor = triggerRef.current;
|
|
713
|
+
if (content && anchor) {
|
|
714
|
+
store3.openTooltip(content, anchor, { positionConfig, hoverable, closeDelay });
|
|
715
|
+
}
|
|
716
|
+
} else if (!open && store3.open && store3.anchor === triggerRef.current) {
|
|
717
|
+
store3.closeTooltip();
|
|
718
|
+
}
|
|
719
|
+
}, [open]);
|
|
720
|
+
useEffect3(() => {
|
|
721
|
+
if (initializedRef.current || defaultOpen === void 0 || !defaultOpen) return;
|
|
722
|
+
initializedRef.current = true;
|
|
723
|
+
requestAnimationFrame(() => {
|
|
724
|
+
const content = contentRef.current;
|
|
725
|
+
const anchor = triggerRef.current;
|
|
726
|
+
if (content && anchor) {
|
|
727
|
+
useTooltipStore.getState().openTooltip(content, anchor, { positionConfig, hoverable, closeDelay });
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
}, [defaultOpen]);
|
|
437
731
|
const ctx = {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
732
|
+
contentRef,
|
|
733
|
+
triggerRef,
|
|
734
|
+
disabled,
|
|
735
|
+
touchDisabled,
|
|
736
|
+
hoverable,
|
|
737
|
+
delay,
|
|
738
|
+
closeDelay,
|
|
739
|
+
warmUpDelay,
|
|
740
|
+
positionConfig
|
|
443
741
|
};
|
|
444
|
-
|
|
445
|
-
return /* @__PURE__ */ jsx2(DeferredContext2.Provider, { value: ctx, children });
|
|
446
|
-
}
|
|
447
|
-
return /* @__PURE__ */ jsx2(DeferredContext2.Provider, { value: ctx, children: /* @__PURE__ */ jsx2(
|
|
448
|
-
BaseTooltip2.Root,
|
|
449
|
-
{
|
|
450
|
-
open,
|
|
451
|
-
defaultOpen,
|
|
452
|
-
disabled,
|
|
453
|
-
onOpenChangeComplete: (isOpen) => {
|
|
454
|
-
onOpenChangeComplete?.(isOpen);
|
|
455
|
-
if (!isOpen && unmountOnClose) setWasActivated(false);
|
|
456
|
-
},
|
|
457
|
-
...props,
|
|
458
|
-
children
|
|
459
|
-
}
|
|
460
|
-
) });
|
|
461
|
-
}
|
|
462
|
-
var BASE_UI_KEYS2 = /* @__PURE__ */ new Set([
|
|
463
|
-
"closeOnClick",
|
|
464
|
-
"handle",
|
|
465
|
-
"payload",
|
|
466
|
-
"delay",
|
|
467
|
-
"closeDelay",
|
|
468
|
-
"render"
|
|
469
|
-
]);
|
|
470
|
-
function pickHtmlProps2(props) {
|
|
471
|
-
const html = {};
|
|
472
|
-
for (const key in props) {
|
|
473
|
-
if (!BASE_UI_KEYS2.has(key)) html[key] = props[key];
|
|
474
|
-
}
|
|
475
|
-
return html;
|
|
742
|
+
return /* @__PURE__ */ jsx3(TooltipCtx.Provider, { value: ctx, children });
|
|
476
743
|
}
|
|
477
|
-
function Trigger2({
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
const deferred = useContext2(DeferredContext2);
|
|
485
|
-
const triggerElRef = useRef2(null);
|
|
486
|
-
const pendingRef = useRef2(0);
|
|
487
|
-
const lastPointerTypeRef = useRef2("");
|
|
488
|
-
const shouldRefocusRef = deferred?.shouldRefocusRef;
|
|
489
|
-
const triggerRef = useCallback2(
|
|
744
|
+
function Trigger2({ children, render, ...props }) {
|
|
745
|
+
const ctx = useContext2(TooltipCtx);
|
|
746
|
+
const triggerRef = useRef5(null);
|
|
747
|
+
const openTimerRef = useRef5(0);
|
|
748
|
+
const closeTimerRef = useRef5(0);
|
|
749
|
+
const lastPointerTypeRef = useRef5("");
|
|
750
|
+
const setTriggerRef = useCallback4(
|
|
490
751
|
(el) => {
|
|
491
|
-
|
|
492
|
-
if (
|
|
493
|
-
shouldRefocusRef.current = false;
|
|
494
|
-
el.focus();
|
|
495
|
-
}
|
|
752
|
+
triggerRef.current = el;
|
|
753
|
+
if (ctx) ctx.triggerRef.current = el;
|
|
496
754
|
},
|
|
497
|
-
[
|
|
755
|
+
[ctx]
|
|
498
756
|
);
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
if (!el) return;
|
|
504
|
-
const frame = requestAnimationFrame(() => {
|
|
505
|
-
if (!deferred.isHoveringRef.current) return;
|
|
506
|
-
el.dispatchEvent(
|
|
507
|
-
new PointerEvent("pointerenter", {
|
|
508
|
-
bubbles: false,
|
|
509
|
-
pointerType: "mouse"
|
|
510
|
-
})
|
|
511
|
-
);
|
|
512
|
-
el.dispatchEvent(new MouseEvent("mouseenter", { bubbles: false }));
|
|
513
|
-
el.dispatchEvent(new MouseEvent("mousemove", { bubbles: true }));
|
|
514
|
-
});
|
|
515
|
-
return () => cancelAnimationFrame(frame);
|
|
516
|
-
}, [activated]);
|
|
517
|
-
if (deferred && !deferred.activated) {
|
|
518
|
-
const { render: renderProp } = rest;
|
|
519
|
-
const htmlProps = pickHtmlProps2(rest);
|
|
520
|
-
const preProps = {
|
|
521
|
-
...htmlProps,
|
|
522
|
-
ref: triggerRef,
|
|
523
|
-
className: typeof className === "function" ? void 0 : className,
|
|
524
|
-
style: typeof style === "function" ? void 0 : style,
|
|
525
|
-
disabled,
|
|
526
|
-
onPointerDown: (e) => {
|
|
527
|
-
lastPointerTypeRef.current = e.pointerType;
|
|
528
|
-
},
|
|
529
|
-
onMouseEnter: () => {
|
|
530
|
-
if (deferred.touchDisabled && lastPointerTypeRef.current === "touch")
|
|
531
|
-
return;
|
|
532
|
-
deferred.isHoveringRef.current = true;
|
|
533
|
-
pendingRef.current = requestAnimationFrame(() => {
|
|
534
|
-
if (deferred.isHoveringRef.current) deferred.activate();
|
|
535
|
-
});
|
|
536
|
-
},
|
|
537
|
-
onMouseLeave: () => {
|
|
538
|
-
deferred.isHoveringRef.current = false;
|
|
539
|
-
cancelAnimationFrame(pendingRef.current);
|
|
540
|
-
},
|
|
541
|
-
onFocus: () => {
|
|
542
|
-
if (deferred.touchDisabled && lastPointerTypeRef.current === "touch")
|
|
543
|
-
return;
|
|
544
|
-
deferred.shouldRefocusRef.current = true;
|
|
545
|
-
deferred.activate();
|
|
546
|
-
}
|
|
757
|
+
useEffect3(() => {
|
|
758
|
+
return () => {
|
|
759
|
+
clearTimeout(openTimerRef.current);
|
|
760
|
+
clearTimeout(closeTimerRef.current);
|
|
547
761
|
};
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
762
|
+
}, []);
|
|
763
|
+
const storeState = useTooltipStore((s) => ({
|
|
764
|
+
open: s.open,
|
|
765
|
+
anchor: s.anchor,
|
|
766
|
+
popupId: s.popupId
|
|
767
|
+
}));
|
|
768
|
+
const isActive = storeState.open && storeState.anchor === triggerRef.current;
|
|
769
|
+
const scheduleOpen = useCallback4(() => {
|
|
770
|
+
if (ctx?.disabled || !triggerRef.current) return;
|
|
771
|
+
clearTimeout(closeTimerRef.current);
|
|
772
|
+
const content = ctx?.contentRef.current;
|
|
773
|
+
if (!content) return;
|
|
774
|
+
const store3 = useTooltipStore.getState();
|
|
775
|
+
if (store3.open && store3.anchor === triggerRef.current) return;
|
|
776
|
+
const isSwitch = store3.open && store3.anchor !== triggerRef.current;
|
|
777
|
+
const elapsed = Date.now() - store3.lastCloseTime;
|
|
778
|
+
const isWarmUp = !isSwitch && elapsed < (ctx?.warmUpDelay ?? 300);
|
|
779
|
+
const effectiveDelay = isSwitch || isWarmUp ? 0 : ctx?.delay ?? 600;
|
|
780
|
+
const openOptions = { positionConfig: ctx?.positionConfig, hoverable: ctx?.hoverable, closeDelay: ctx?.closeDelay };
|
|
781
|
+
if (effectiveDelay === 0) {
|
|
782
|
+
store3.openTooltip(content, triggerRef.current, openOptions);
|
|
783
|
+
} else {
|
|
784
|
+
openTimerRef.current = window.setTimeout(() => {
|
|
785
|
+
const currentContent = ctx?.contentRef.current;
|
|
786
|
+
if (currentContent && triggerRef.current) {
|
|
787
|
+
useTooltipStore.getState().openTooltip(currentContent, triggerRef.current, openOptions);
|
|
788
|
+
}
|
|
789
|
+
}, effectiveDelay);
|
|
553
790
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
791
|
+
}, [ctx]);
|
|
792
|
+
const scheduleClose = useCallback4(() => {
|
|
793
|
+
clearTimeout(openTimerRef.current);
|
|
794
|
+
if (ctx?.hoverable) return;
|
|
795
|
+
const store3 = useTooltipStore.getState();
|
|
796
|
+
if (!store3.open || store3.anchor !== triggerRef.current) return;
|
|
797
|
+
const closeDelay = ctx?.closeDelay ?? 300;
|
|
798
|
+
if (closeDelay === 0) {
|
|
799
|
+
store3.closeTooltip();
|
|
800
|
+
} else {
|
|
801
|
+
closeTimerRef.current = window.setTimeout(() => {
|
|
802
|
+
const s = useTooltipStore.getState();
|
|
803
|
+
if (s.open && s.anchor === triggerRef.current) {
|
|
804
|
+
s.closeTooltip();
|
|
805
|
+
}
|
|
806
|
+
}, closeDelay);
|
|
565
807
|
}
|
|
808
|
+
}, [ctx]);
|
|
809
|
+
const handlePointerDown = useCallback4(
|
|
810
|
+
(e) => {
|
|
811
|
+
lastPointerTypeRef.current = e.pointerType;
|
|
812
|
+
props.onPointerDown?.(e);
|
|
813
|
+
},
|
|
814
|
+
[props.onPointerDown]
|
|
566
815
|
);
|
|
816
|
+
const handlePointerEnter = useCallback4(
|
|
817
|
+
(e) => {
|
|
818
|
+
props.onPointerEnter?.(e);
|
|
819
|
+
if (e.defaultPrevented) return;
|
|
820
|
+
if (ctx?.touchDisabled && lastPointerTypeRef.current === "touch") return;
|
|
821
|
+
scheduleOpen();
|
|
822
|
+
},
|
|
823
|
+
[ctx, props.onPointerEnter, scheduleOpen]
|
|
824
|
+
);
|
|
825
|
+
const handlePointerLeave = useCallback4(
|
|
826
|
+
(e) => {
|
|
827
|
+
props.onPointerLeave?.(e);
|
|
828
|
+
if (e.defaultPrevented) return;
|
|
829
|
+
scheduleClose();
|
|
830
|
+
},
|
|
831
|
+
[props.onPointerLeave, scheduleClose]
|
|
832
|
+
);
|
|
833
|
+
const handleFocus = useCallback4(
|
|
834
|
+
(e) => {
|
|
835
|
+
props.onFocus?.(e);
|
|
836
|
+
if (e.defaultPrevented) return;
|
|
837
|
+
if (ctx?.touchDisabled && lastPointerTypeRef.current === "touch") return;
|
|
838
|
+
scheduleOpen();
|
|
839
|
+
},
|
|
840
|
+
[ctx, props.onFocus, scheduleOpen]
|
|
841
|
+
);
|
|
842
|
+
const handleBlur = useCallback4(
|
|
843
|
+
(e) => {
|
|
844
|
+
props.onBlur?.(e);
|
|
845
|
+
if (e.defaultPrevented) return;
|
|
846
|
+
clearTimeout(openTimerRef.current);
|
|
847
|
+
const store3 = useTooltipStore.getState();
|
|
848
|
+
if (store3.open && store3.anchor === triggerRef.current) {
|
|
849
|
+
store3.closeTooltip();
|
|
850
|
+
}
|
|
851
|
+
},
|
|
852
|
+
[props.onBlur]
|
|
853
|
+
);
|
|
854
|
+
const triggerProps = {
|
|
855
|
+
ref: setTriggerRef,
|
|
856
|
+
type: "button",
|
|
857
|
+
"aria-describedby": isActive && storeState.popupId ? storeState.popupId : void 0,
|
|
858
|
+
onPointerDown: handlePointerDown,
|
|
859
|
+
onPointerEnter: handlePointerEnter,
|
|
860
|
+
onPointerLeave: handlePointerLeave,
|
|
861
|
+
onFocus: handleFocus,
|
|
862
|
+
onBlur: handleBlur,
|
|
863
|
+
...props
|
|
864
|
+
};
|
|
865
|
+
if (isValidElement2(render)) {
|
|
866
|
+
return cloneElement2(render, triggerProps, children);
|
|
867
|
+
}
|
|
868
|
+
if (typeof render === "function") {
|
|
869
|
+
return render({ ...triggerProps, children });
|
|
870
|
+
}
|
|
871
|
+
return /* @__PURE__ */ jsx3("button", { ...triggerProps, children });
|
|
567
872
|
}
|
|
568
|
-
function Portal2(
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
{
|
|
577
|
-
|
|
578
|
-
...props
|
|
873
|
+
function Portal2({ children }) {
|
|
874
|
+
const ctx = useContext2(TooltipCtx);
|
|
875
|
+
const storeAnchor = useTooltipStore((s) => s.anchor);
|
|
876
|
+
if (ctx) {
|
|
877
|
+
ctx.contentRef.current = children;
|
|
878
|
+
}
|
|
879
|
+
const isActive = !!(ctx && storeAnchor && storeAnchor === ctx.triggerRef.current);
|
|
880
|
+
useEffect3(() => {
|
|
881
|
+
if (isActive) {
|
|
882
|
+
useTooltipStore.getState().updateContent(children);
|
|
579
883
|
}
|
|
580
|
-
);
|
|
884
|
+
});
|
|
885
|
+
return null;
|
|
581
886
|
}
|
|
582
|
-
function Popup2({ className, ...props }) {
|
|
583
|
-
|
|
887
|
+
function Popup2({ className, id, ...props }) {
|
|
888
|
+
const popupId = useTooltipStore((s) => s.popupId);
|
|
889
|
+
return /* @__PURE__ */ jsx3(
|
|
584
890
|
BaseTooltip2.Popup,
|
|
585
891
|
{
|
|
892
|
+
id: id ?? popupId ?? void 0,
|
|
586
893
|
className: className ?? "slithy-tooltip-popup",
|
|
587
894
|
...props
|
|
588
895
|
}
|
|
589
896
|
);
|
|
590
897
|
}
|
|
591
898
|
function Arrow2({ className, ...props }) {
|
|
592
|
-
return /* @__PURE__ */
|
|
899
|
+
return /* @__PURE__ */ jsx3(
|
|
593
900
|
BaseTooltip2.Arrow,
|
|
594
901
|
{
|
|
595
902
|
className: className ?? "slithy-tooltip-arrow",
|
|
@@ -598,15 +905,195 @@ function Arrow2({ className, ...props }) {
|
|
|
598
905
|
);
|
|
599
906
|
}
|
|
600
907
|
var Tooltip = {
|
|
601
|
-
Provider,
|
|
602
908
|
Root: Root2,
|
|
603
909
|
Trigger: Trigger2,
|
|
604
910
|
Portal: Portal2,
|
|
605
|
-
Positioner: Positioner2,
|
|
606
911
|
Popup: Popup2,
|
|
607
912
|
Arrow: Arrow2
|
|
608
913
|
};
|
|
914
|
+
|
|
915
|
+
// src/Tooltip/TooltipRenderer.tsx
|
|
916
|
+
import { Tooltip as BaseTooltip3 } from "@base-ui/react/tooltip";
|
|
917
|
+
import { useCallback as useCallback5, useEffect as useEffect5, useId, useRef as useRef7, useState as useState2 } from "react";
|
|
918
|
+
|
|
919
|
+
// src/useSafePolygon.ts
|
|
920
|
+
import { useEffect as useEffect4, useRef as useRef6 } from "react";
|
|
921
|
+
function useSafePolygon({
|
|
922
|
+
enabled,
|
|
923
|
+
anchor,
|
|
924
|
+
popupRef,
|
|
925
|
+
onClose
|
|
926
|
+
}) {
|
|
927
|
+
const exitPointRef = useRef6(null);
|
|
928
|
+
useEffect4(() => {
|
|
929
|
+
if (!enabled || !anchor) return;
|
|
930
|
+
const handlePointerLeave = (e) => {
|
|
931
|
+
exitPointRef.current = { x: e.clientX, y: e.clientY };
|
|
932
|
+
};
|
|
933
|
+
anchor.addEventListener("pointerleave", handlePointerLeave);
|
|
934
|
+
return () => anchor.removeEventListener("pointerleave", handlePointerLeave);
|
|
935
|
+
}, [enabled, anchor]);
|
|
936
|
+
useEffect4(() => {
|
|
937
|
+
if (!enabled || !anchor) return;
|
|
938
|
+
const handlePointerMove = (e) => {
|
|
939
|
+
const exit = exitPointRef.current;
|
|
940
|
+
if (!exit) return;
|
|
941
|
+
const popup = popupRef.current;
|
|
942
|
+
if (!popup) return;
|
|
943
|
+
const popupRect = popup.getBoundingClientRect();
|
|
944
|
+
if (e.clientX >= popupRect.left && e.clientX <= popupRect.right && e.clientY >= popupRect.top && e.clientY <= popupRect.bottom) {
|
|
945
|
+
exitPointRef.current = null;
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
const triangle = getTriangle(exit, popupRect);
|
|
949
|
+
if (!pointInTriangle({ x: e.clientX, y: e.clientY }, triangle)) {
|
|
950
|
+
exitPointRef.current = null;
|
|
951
|
+
onClose();
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
document.addEventListener("pointermove", handlePointerMove);
|
|
955
|
+
return () => document.removeEventListener("pointermove", handlePointerMove);
|
|
956
|
+
}, [enabled, anchor, popupRef, onClose]);
|
|
957
|
+
useEffect4(() => {
|
|
958
|
+
if (!enabled) {
|
|
959
|
+
exitPointRef.current = null;
|
|
960
|
+
}
|
|
961
|
+
}, [enabled]);
|
|
962
|
+
}
|
|
963
|
+
function getTriangle(exit, rect) {
|
|
964
|
+
const cx = rect.left + rect.width / 2;
|
|
965
|
+
const cy = rect.top + rect.height / 2;
|
|
966
|
+
const dx = cx - exit.x;
|
|
967
|
+
const dy = cy - exit.y;
|
|
968
|
+
if (Math.abs(dy) >= Math.abs(dx)) {
|
|
969
|
+
if (dy > 0) {
|
|
970
|
+
return [exit, { x: rect.left, y: rect.top }, { x: rect.right, y: rect.top }];
|
|
971
|
+
}
|
|
972
|
+
return [exit, { x: rect.left, y: rect.bottom }, { x: rect.right, y: rect.bottom }];
|
|
973
|
+
}
|
|
974
|
+
if (dx > 0) {
|
|
975
|
+
return [exit, { x: rect.left, y: rect.top }, { x: rect.left, y: rect.bottom }];
|
|
976
|
+
}
|
|
977
|
+
return [exit, { x: rect.right, y: rect.top }, { x: rect.right, y: rect.bottom }];
|
|
978
|
+
}
|
|
979
|
+
function pointInTriangle(p, [a, b, c]) {
|
|
980
|
+
const d1 = sign(p, a, b);
|
|
981
|
+
const d2 = sign(p, b, c);
|
|
982
|
+
const d3 = sign(p, c, a);
|
|
983
|
+
const hasNeg = d1 < 0 || d2 < 0 || d3 < 0;
|
|
984
|
+
const hasPos = d1 > 0 || d2 > 0 || d3 > 0;
|
|
985
|
+
return !(hasNeg && hasPos);
|
|
986
|
+
}
|
|
987
|
+
function sign(p1, p2, p3) {
|
|
988
|
+
return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// src/Tooltip/Tooltip.css
|
|
992
|
+
styleInject(".slithy-tooltip-positioner {\n z-index: 1000;\n}\n.slithy-tooltip-popup {\n background: var(--slithy-tooltip-bg, #222);\n color: var(--slithy-tooltip-color, #fff);\n font-size: var(--slithy-tooltip-font-size, 0.8125rem);\n line-height: 1.4;\n padding: var(--slithy-tooltip-padding, 4px 8px);\n border-radius: var(--slithy-tooltip-radius, 4px);\n max-width: var(--slithy-tooltip-max-width, 300px);\n transform-origin: var(--transform-origin);\n transition-property: opacity, transform;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n}\n.slithy-tooltip-popup[data-open] {\n opacity: 1;\n transform: scale(1);\n}\n.slithy-tooltip-popup[data-starting-style],\n.slithy-tooltip-popup[data-ending-style] {\n opacity: 0;\n transform: scale(0.95);\n}\n.slithy-tooltip-popup[data-instant] {\n transition-duration: 0ms;\n}\n.slithy-tooltip-arrow {\n width: 8px;\n height: 8px;\n transform: rotate(45deg);\n background: var(--slithy-tooltip-bg, #222);\n}\n.slithy-tooltip-arrow[data-side=top] {\n bottom: -4px;\n}\n.slithy-tooltip-arrow[data-side=bottom] {\n top: -4px;\n}\n.slithy-tooltip-arrow[data-side=left] {\n right: -4px;\n}\n.slithy-tooltip-arrow[data-side=right] {\n left: -4px;\n}\n");
|
|
993
|
+
|
|
994
|
+
// src/Tooltip/TooltipRenderer.tsx
|
|
995
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
996
|
+
function TooltipRenderer() {
|
|
997
|
+
const popupId = useId();
|
|
998
|
+
useEffect5(() => {
|
|
999
|
+
useTooltipStore.getState().setPopupId(popupId);
|
|
1000
|
+
}, [popupId]);
|
|
1001
|
+
const open = useTooltipStore((s) => s.open);
|
|
1002
|
+
const content = useTooltipStore((s) => s.content);
|
|
1003
|
+
const anchor = useTooltipStore((s) => s.anchor);
|
|
1004
|
+
const positionConfig = useTooltipStore((s) => s.positionConfig);
|
|
1005
|
+
const lastContentRef = useRef7(content);
|
|
1006
|
+
const lastAnchorRef = useRef7(anchor);
|
|
1007
|
+
if (content) lastContentRef.current = content;
|
|
1008
|
+
if (anchor) lastAnchorRef.current = anchor;
|
|
1009
|
+
const activeContent = content ?? lastContentRef.current;
|
|
1010
|
+
const activeAnchor = anchor ?? lastAnchorRef.current;
|
|
1011
|
+
const [hasOpened, setHasOpened] = useState2(false);
|
|
1012
|
+
const [deferredOpen, setDeferredOpen] = useState2(false);
|
|
1013
|
+
useEffect5(() => {
|
|
1014
|
+
if (open && !hasOpened) {
|
|
1015
|
+
setHasOpened(true);
|
|
1016
|
+
requestAnimationFrame(() => setDeferredOpen(true));
|
|
1017
|
+
} else {
|
|
1018
|
+
setDeferredOpen(open);
|
|
1019
|
+
}
|
|
1020
|
+
}, [open, hasOpened]);
|
|
1021
|
+
useEffect5(() => {
|
|
1022
|
+
if (!open) return;
|
|
1023
|
+
const handleKeyDown = (e) => {
|
|
1024
|
+
if (e.key === "Escape") {
|
|
1025
|
+
useTooltipStore.getState().closeTooltip();
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
1029
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
1030
|
+
}, [open]);
|
|
1031
|
+
const hoverable = useTooltipStore((s) => s.hoverable);
|
|
1032
|
+
const storeCloseDelay = useTooltipStore((s) => s.closeDelay);
|
|
1033
|
+
const popupRef = useRef7(null);
|
|
1034
|
+
const popupCloseTimerRef = useRef7(0);
|
|
1035
|
+
const handleSafePolygonClose = useCallback5(() => {
|
|
1036
|
+
useTooltipStore.getState().closeTooltip();
|
|
1037
|
+
}, []);
|
|
1038
|
+
useSafePolygon({
|
|
1039
|
+
enabled: open && hoverable,
|
|
1040
|
+
anchor,
|
|
1041
|
+
popupRef,
|
|
1042
|
+
onClose: handleSafePolygonClose
|
|
1043
|
+
});
|
|
1044
|
+
const handlePopupPointerEnter = useCallback5(() => {
|
|
1045
|
+
if (!hoverable) return;
|
|
1046
|
+
clearTimeout(popupCloseTimerRef.current);
|
|
1047
|
+
}, [hoverable]);
|
|
1048
|
+
const handlePopupPointerLeave = useCallback5(() => {
|
|
1049
|
+
if (!hoverable) return;
|
|
1050
|
+
popupCloseTimerRef.current = window.setTimeout(() => {
|
|
1051
|
+
useTooltipStore.getState().closeTooltip();
|
|
1052
|
+
}, storeCloseDelay);
|
|
1053
|
+
}, [hoverable, storeCloseDelay]);
|
|
1054
|
+
useEffect5(() => {
|
|
1055
|
+
if (!open) clearTimeout(popupCloseTimerRef.current);
|
|
1056
|
+
return () => clearTimeout(popupCloseTimerRef.current);
|
|
1057
|
+
}, [open]);
|
|
1058
|
+
const handleOpenChangeComplete = (isOpen) => {
|
|
1059
|
+
if (!isOpen) {
|
|
1060
|
+
lastContentRef.current = null;
|
|
1061
|
+
lastAnchorRef.current = null;
|
|
1062
|
+
}
|
|
1063
|
+
};
|
|
1064
|
+
if (!hasOpened) return null;
|
|
1065
|
+
return /* @__PURE__ */ jsx4(
|
|
1066
|
+
BaseTooltip3.Root,
|
|
1067
|
+
{
|
|
1068
|
+
open: deferredOpen,
|
|
1069
|
+
onOpenChange: (isOpen) => {
|
|
1070
|
+
if (!isOpen) {
|
|
1071
|
+
useTooltipStore.getState().closeTooltip();
|
|
1072
|
+
}
|
|
1073
|
+
},
|
|
1074
|
+
onOpenChangeComplete: handleOpenChangeComplete,
|
|
1075
|
+
children: /* @__PURE__ */ jsx4(BaseTooltip3.Portal, { children: /* @__PURE__ */ jsx4(
|
|
1076
|
+
BaseTooltip3.Positioner,
|
|
1077
|
+
{
|
|
1078
|
+
ref: popupRef,
|
|
1079
|
+
anchor: activeAnchor,
|
|
1080
|
+
className: "slithy-tooltip-positioner",
|
|
1081
|
+
side: positionConfig.side ?? "top",
|
|
1082
|
+
sideOffset: positionConfig.sideOffset ?? 6,
|
|
1083
|
+
align: positionConfig.align,
|
|
1084
|
+
alignOffset: positionConfig.alignOffset,
|
|
1085
|
+
collisionPadding: positionConfig.collisionPadding,
|
|
1086
|
+
onPointerEnter: handlePopupPointerEnter,
|
|
1087
|
+
onPointerLeave: handlePopupPointerLeave,
|
|
1088
|
+
children: activeContent
|
|
1089
|
+
}
|
|
1090
|
+
) })
|
|
1091
|
+
}
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
609
1094
|
export {
|
|
610
1095
|
Dropdown,
|
|
611
|
-
|
|
1096
|
+
DropdownRenderer,
|
|
1097
|
+
Tooltip,
|
|
1098
|
+
TooltipRenderer
|
|
612
1099
|
};
|