@structyl/core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1469 @@
1
+ import * as React17 from 'react';
2
+ import * as ReactDOM from 'react-dom';
3
+ import { offset, shift, limitShift, flip, size, arrow, hide, useFloating, autoUpdate } from '@floating-ui/react';
4
+
5
+ var __defProp = Object.defineProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var Slot = React17.forwardRef((props, forwardedRef) => {
11
+ const { children, ...slotProps } = props;
12
+ const childrenArray = React17.Children.toArray(children);
13
+ const slottable = childrenArray.find(isSlottable);
14
+ if (slottable) {
15
+ const newElement = slottable.props.children;
16
+ const newChildren = childrenArray.map((child) => {
17
+ if (child === slottable) {
18
+ if (React17.Children.count(newElement) > 1) return React17.Children.only(null);
19
+ return React17.isValidElement(newElement) ? newElement.props.children : null;
20
+ }
21
+ return child;
22
+ });
23
+ return /* @__PURE__ */ React17.createElement(SlotClone, { ...slotProps, ref: forwardedRef }, React17.isValidElement(newElement) ? React17.cloneElement(newElement, void 0, newChildren) : null);
24
+ }
25
+ const singleChild = childrenArray.length === 1 ? childrenArray[0] : children;
26
+ return /* @__PURE__ */ React17.createElement(SlotClone, { ...slotProps, ref: forwardedRef }, singleChild);
27
+ });
28
+ Slot.displayName = "Slot";
29
+ var SlotClone = React17.forwardRef((props, forwardedRef) => {
30
+ const { children, ...slotProps } = props;
31
+ if (React17.isValidElement(children)) {
32
+ const childRef = getElementRef(children);
33
+ const ref = forwardedRef ? composeRefs(forwardedRef, childRef) : childRef;
34
+ return React17.cloneElement(
35
+ children,
36
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
+ { ...mergeProps(slotProps, children.props), ref }
38
+ );
39
+ }
40
+ return React17.Children.count(children) > 1 ? React17.Children.only(null) : null;
41
+ });
42
+ SlotClone.displayName = "SlotClone";
43
+ var Slottable = ({ children }) => /* @__PURE__ */ React17.createElement(React17.Fragment, null, children);
44
+ function isSlottable(child) {
45
+ return React17.isValidElement(child) && child.type === Slottable;
46
+ }
47
+ function mergeProps(slotProps, childProps) {
48
+ const overrideProps = { ...childProps };
49
+ for (const propName in childProps) {
50
+ const slotPropValue = slotProps[propName];
51
+ const childPropValue = childProps[propName];
52
+ const isHandler = /^on[A-Z]/.test(propName);
53
+ if (isHandler) {
54
+ if (slotPropValue && childPropValue) {
55
+ overrideProps[propName] = (...args) => {
56
+ childPropValue(...args);
57
+ slotPropValue(...args);
58
+ };
59
+ } else if (slotPropValue) {
60
+ overrideProps[propName] = slotPropValue;
61
+ }
62
+ } else if (propName === "style") {
63
+ overrideProps[propName] = { ...slotPropValue, ...childPropValue };
64
+ } else if (propName === "className") {
65
+ overrideProps[propName] = [slotPropValue, childPropValue].filter(Boolean).join(" ");
66
+ }
67
+ }
68
+ return { ...slotProps, ...overrideProps };
69
+ }
70
+ function composeRefs(...refs) {
71
+ return (node) => refs.forEach((ref) => {
72
+ if (typeof ref === "function") ref(node);
73
+ else if (ref != null) ref.current = node;
74
+ });
75
+ }
76
+ function getElementRef(element) {
77
+ let getter = Object.getOwnPropertyDescriptor(element.props, "ref")?.get;
78
+ let mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
79
+ if (mayWarn) {
80
+ return element.ref;
81
+ }
82
+ getter = Object.getOwnPropertyDescriptor(element, "ref")?.get;
83
+ mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
84
+ if (mayWarn) {
85
+ return element.props.ref;
86
+ }
87
+ return element.props.ref || element.ref;
88
+ }
89
+
90
+ // src/primitive.tsx
91
+ var NODES = [
92
+ "a",
93
+ "button",
94
+ "div",
95
+ "form",
96
+ "h2",
97
+ "h3",
98
+ "img",
99
+ "input",
100
+ "label",
101
+ "li",
102
+ "nav",
103
+ "ol",
104
+ "p",
105
+ "span",
106
+ "svg",
107
+ "ul"
108
+ ];
109
+ var Primitive = NODES.reduce((primitive, node) => {
110
+ const Node = React17.forwardRef(
111
+ (props, forwardedRef) => {
112
+ const { asChild, ...rest } = props;
113
+ const Comp = asChild ? Slot : node;
114
+ return /* @__PURE__ */ React17.createElement(Comp, { ...rest, ref: forwardedRef });
115
+ }
116
+ );
117
+ Node.displayName = `Primitive.${node}`;
118
+ return { ...primitive, [node]: Node };
119
+ }, {});
120
+ function createContext2(rootComponentName, defaultContext) {
121
+ const Context = React17.createContext(defaultContext);
122
+ function Provider(props) {
123
+ const { children, ...context } = props;
124
+ const value = React17.useMemo(() => context, Object.values(context));
125
+ return /* @__PURE__ */ React17.createElement(Context.Provider, { value }, children);
126
+ }
127
+ Provider.displayName = `${rootComponentName}Provider`;
128
+ function useContextHook(consumerName) {
129
+ const ctx = React17.useContext(Context);
130
+ if (ctx !== void 0) return ctx;
131
+ if (defaultContext !== void 0) return defaultContext;
132
+ throw new Error(`\`${consumerName}\` must be used within \`${rootComponentName}\``);
133
+ }
134
+ return [Provider, useContextHook];
135
+ }
136
+ function createContextScope(scopeName, createContextScopeDeps = []) {
137
+ let defaultContexts = [];
138
+ function createScopedContext(rootComponentName, defaultContext) {
139
+ const BaseContext = React17.createContext(defaultContext);
140
+ const index = defaultContexts.push(BaseContext) - 1;
141
+ function Provider(props) {
142
+ const { scope, children, ...context } = props;
143
+ const Context = scope?.[scopeName]?.[index] ?? BaseContext;
144
+ const value = React17.useMemo(() => context, Object.values(context));
145
+ return /* @__PURE__ */ React17.createElement(Context.Provider, { value }, children);
146
+ }
147
+ Provider.displayName = `${rootComponentName}Provider`;
148
+ function useScopedContext(consumerName, scope) {
149
+ const Context = scope?.[scopeName]?.[index] ?? BaseContext;
150
+ const ctx = React17.useContext(Context);
151
+ if (ctx !== void 0) return ctx;
152
+ if (defaultContext !== void 0) return defaultContext;
153
+ throw new Error(`\`${consumerName}\` must be used within \`${rootComponentName}\``);
154
+ }
155
+ return [Provider, useScopedContext];
156
+ }
157
+ const createScope = () => {
158
+ const scopeContexts = defaultContexts.map(
159
+ () => React17.createContext(void 0)
160
+ );
161
+ return function useScopeHook(parentScope) {
162
+ return {
163
+ [`__scope${scopeName}`]: { ...parentScope, [scopeName]: scopeContexts }
164
+ };
165
+ };
166
+ };
167
+ createScope.scopeName = scopeName;
168
+ return [
169
+ createScopedContext,
170
+ composeContextScopes(createScope, ...createContextScopeDeps)
171
+ ];
172
+ }
173
+ function composeContextScopes(...scopes) {
174
+ const composed = () => {
175
+ const scopeHooks = scopes.map((scope) => scope());
176
+ return function useComposedScopeHook(parentScope) {
177
+ return scopeHooks.reduce(
178
+ (nextScope, hook) => ({ ...nextScope, ...hook(parentScope) }),
179
+ {}
180
+ );
181
+ };
182
+ };
183
+ composed.scopeName = scopes.map((s) => s.scopeName).join(",");
184
+ return composed;
185
+ }
186
+ var Portal = React17.forwardRef(
187
+ (props, forwardedRef) => {
188
+ const { container: containerProp, ...portalProps } = props;
189
+ const [mounted, setMounted] = React17.useState(false);
190
+ React17.useEffect(() => setMounted(true), []);
191
+ const container = containerProp ?? (typeof document !== "undefined" ? document.body : null);
192
+ if (!mounted || !container) return null;
193
+ return ReactDOM.createPortal(
194
+ /* @__PURE__ */ React17.createElement(Primitive.div, { ...portalProps, ref: forwardedRef }),
195
+ container
196
+ );
197
+ }
198
+ );
199
+ Portal.displayName = "Portal";
200
+ var VISUALLY_HIDDEN_STYLES = {
201
+ position: "absolute",
202
+ border: 0,
203
+ width: 1,
204
+ height: 1,
205
+ padding: 0,
206
+ margin: -1,
207
+ overflow: "hidden",
208
+ clip: "rect(0, 0, 0, 0)",
209
+ whiteSpace: "nowrap",
210
+ wordWrap: "normal"
211
+ };
212
+ var VisuallyHidden = React17.forwardRef(
213
+ (props, forwardedRef) => /* @__PURE__ */ React17.createElement(
214
+ Primitive.span,
215
+ {
216
+ ...props,
217
+ ref: forwardedRef,
218
+ style: { ...VISUALLY_HIDDEN_STYLES, ...props.style }
219
+ }
220
+ )
221
+ );
222
+ VisuallyHidden.displayName = "VisuallyHidden";
223
+ var DirectionContext = React17.createContext(void 0);
224
+ var DirectionProvider = ({ dir, children }) => /* @__PURE__ */ React17.createElement(DirectionContext.Provider, { value: dir }, children);
225
+ function useDirection(localDir) {
226
+ const globalDir = React17.useContext(DirectionContext);
227
+ return localDir ?? globalDir ?? "ltr";
228
+ }
229
+ function setRef(ref, value) {
230
+ if (typeof ref === "function") ref(value);
231
+ else if (ref != null && typeof ref === "object")
232
+ ref.current = value;
233
+ }
234
+ function composeRefs2(...refs) {
235
+ return (node) => refs.forEach((ref) => setRef(ref, node));
236
+ }
237
+ function useComposedRefs(...refs) {
238
+ return React17.useCallback(composeRefs2(...refs), refs);
239
+ }
240
+ function useCallbackRef(callback) {
241
+ const callbackRef = React17.useRef(callback);
242
+ React17.useEffect(() => {
243
+ callbackRef.current = callback;
244
+ });
245
+ return React17.useMemo(
246
+ () => ((...args) => callbackRef.current?.(...args)),
247
+ []
248
+ );
249
+ }
250
+ function useControllableState({
251
+ prop,
252
+ defaultProp,
253
+ onChange = () => {
254
+ }
255
+ }) {
256
+ const [uncontrolled, setUncontrolled] = React17.useState(defaultProp);
257
+ const isControlled = prop !== void 0;
258
+ const value = isControlled ? prop : uncontrolled;
259
+ const handleChange = useCallbackRef(onChange);
260
+ const setValue = React17.useCallback(
261
+ (nextValue) => {
262
+ if (isControlled) {
263
+ const setter = nextValue;
264
+ const newValue = typeof setter === "function" ? setter(prop) : setter;
265
+ if (newValue !== prop) handleChange(newValue);
266
+ } else {
267
+ setUncontrolled((prev) => {
268
+ const setter = nextValue;
269
+ const newValue = typeof setter === "function" ? setter(prev) : setter;
270
+ if (newValue !== prev) handleChange(newValue);
271
+ return newValue;
272
+ });
273
+ }
274
+ },
275
+ [isControlled, prop, handleChange]
276
+ );
277
+ return [value, setValue];
278
+ }
279
+ var idCounter = 0;
280
+ function useReactId() {
281
+ return React17.useId?.() ?? "";
282
+ }
283
+ function useId2(prefix) {
284
+ const reactId = useReactId();
285
+ const [id, setId] = React17.useState(reactId);
286
+ React17.useEffect(() => {
287
+ if (!reactId) setId(`yl-${++idCounter}`);
288
+ }, [reactId]);
289
+ return id || reactId;
290
+ }
291
+ function composeEventHandlers(originalEventHandler, ourEventHandler, { checkForDefaultPrevented = true } = {}) {
292
+ return function handleEvent(event) {
293
+ originalEventHandler?.(event);
294
+ if (checkForDefaultPrevented === false || !event.defaultPrevented) {
295
+ ourEventHandler?.(event);
296
+ }
297
+ };
298
+ }
299
+
300
+ // src/presence.tsx
301
+ var machine = {
302
+ mounted: {
303
+ UNMOUNT: "unmounted",
304
+ ANIMATION_OUT: "unmountSuspended"
305
+ },
306
+ unmountSuspended: {
307
+ MOUNT: "mounted",
308
+ ANIMATION_END: "unmounted"
309
+ },
310
+ unmounted: {
311
+ MOUNT: "mounted"
312
+ }
313
+ };
314
+ function usePresence(present) {
315
+ const [node, setNode] = React17.useState(null);
316
+ const stylesRef = React17.useRef(null);
317
+ const prevPresentRef = React17.useRef(present);
318
+ const prevAnimationNameRef = React17.useRef("none");
319
+ const initialState = present ? "mounted" : "unmounted";
320
+ const [state, setState] = React17.useState(initialState);
321
+ const send = React17.useCallback((event) => {
322
+ setState((prev) => {
323
+ const next = machine[prev][event];
324
+ return next ?? prev;
325
+ });
326
+ }, []);
327
+ React17.useEffect(() => {
328
+ const currentAnimationName = getAnimationName(stylesRef.current);
329
+ prevAnimationNameRef.current = state === "mounted" ? currentAnimationName : "none";
330
+ }, [state]);
331
+ React17.useEffect(() => {
332
+ const styles = stylesRef.current;
333
+ const wasPresent = prevPresentRef.current;
334
+ const hasPresentChanged = wasPresent !== present;
335
+ if (hasPresentChanged) {
336
+ const prevAnimationName = prevAnimationNameRef.current;
337
+ const currentAnimationName = getAnimationName(styles);
338
+ if (present) {
339
+ send("MOUNT");
340
+ } else if (currentAnimationName === "none" || styles?.display === "none") {
341
+ send("UNMOUNT");
342
+ } else {
343
+ const isAnimating = prevAnimationName !== currentAnimationName;
344
+ if (wasPresent && isAnimating) {
345
+ send("ANIMATION_OUT");
346
+ } else {
347
+ send("UNMOUNT");
348
+ }
349
+ }
350
+ prevPresentRef.current = present;
351
+ }
352
+ }, [present, send]);
353
+ React17.useEffect(() => {
354
+ if (!node) {
355
+ send("ANIMATION_END");
356
+ return;
357
+ }
358
+ const handleAnimationEnd = (event) => {
359
+ const currentAnimationName = getAnimationName(stylesRef.current);
360
+ const animationName = "animationName" in event ? event.animationName : "";
361
+ const isCurrent = currentAnimationName.includes(animationName) || animationName === "";
362
+ if (event.target === node && isCurrent) {
363
+ send("ANIMATION_END");
364
+ }
365
+ };
366
+ const handleAnimationStart = (event) => {
367
+ if (event.target === node) {
368
+ prevAnimationNameRef.current = getAnimationName(stylesRef.current);
369
+ }
370
+ };
371
+ node.addEventListener("animationstart", handleAnimationStart);
372
+ node.addEventListener("animationcancel", handleAnimationEnd);
373
+ node.addEventListener("animationend", handleAnimationEnd);
374
+ node.addEventListener("transitioncancel", handleAnimationEnd);
375
+ node.addEventListener("transitionend", handleAnimationEnd);
376
+ return () => {
377
+ node.removeEventListener("animationstart", handleAnimationStart);
378
+ node.removeEventListener("animationcancel", handleAnimationEnd);
379
+ node.removeEventListener("animationend", handleAnimationEnd);
380
+ node.removeEventListener("transitioncancel", handleAnimationEnd);
381
+ node.removeEventListener("transitionend", handleAnimationEnd);
382
+ };
383
+ }, [node, send]);
384
+ return {
385
+ isPresent: state !== "unmounted",
386
+ ref: React17.useCallback((el) => {
387
+ stylesRef.current = el ? getComputedStyle(el) : null;
388
+ setNode(el);
389
+ }, [])
390
+ };
391
+ }
392
+ function getAnimationName(styles) {
393
+ return styles?.animationName || "none";
394
+ }
395
+ var Presence = (props) => {
396
+ const { present, children, forceMount } = props;
397
+ const presence = usePresence(present);
398
+ const isFunctionChild = typeof children === "function";
399
+ const child = isFunctionChild ? children({ present: presence.isPresent }) : React17.Children.only(children);
400
+ const childRef = getElementRef2(child);
401
+ const ref = useComposedRefs(presence.ref, childRef);
402
+ const shouldRender = forceMount || isFunctionChild || presence.isPresent;
403
+ if (!shouldRender) return null;
404
+ return React17.cloneElement(child, { ref });
405
+ };
406
+ Presence.displayName = "Presence";
407
+ function getElementRef2(element) {
408
+ const props = element.props;
409
+ const ref19 = props?.ref;
410
+ const ref18 = element.ref;
411
+ return ref19 ?? ref18;
412
+ }
413
+ var AUTOFOCUS_ON_MOUNT = "focusScope.autoFocusOnMount";
414
+ var AUTOFOCUS_ON_UNMOUNT = "focusScope.autoFocusOnUnmount";
415
+ var EVENT_OPTIONS = { bubbles: false, cancelable: true };
416
+ var FocusScope = React17.forwardRef(
417
+ (props, forwardedRef) => {
418
+ const {
419
+ loop = false,
420
+ trapped = false,
421
+ onMountAutoFocus: onMountAutoFocusProp,
422
+ onUnmountAutoFocus: onUnmountAutoFocusProp,
423
+ ...scopeProps
424
+ } = props;
425
+ const [container, setContainer] = React17.useState(null);
426
+ const onMountAutoFocus = useCallbackRef(onMountAutoFocusProp);
427
+ const onUnmountAutoFocus = useCallbackRef(onUnmountAutoFocusProp);
428
+ const lastFocusedElementRef = React17.useRef(null);
429
+ const composedRefs = useComposedRefs(forwardedRef, setContainer);
430
+ const scope = React17.useRef({
431
+ paused: false,
432
+ pause() {
433
+ this.paused = true;
434
+ },
435
+ resume() {
436
+ this.paused = false;
437
+ }
438
+ }).current;
439
+ React17.useEffect(() => {
440
+ if (!trapped) return;
441
+ const handleFocusIn = (event) => {
442
+ if (focusScopesStack.active !== scope) return;
443
+ const target = event.target;
444
+ if (container?.contains(target)) {
445
+ lastFocusedElementRef.current = target;
446
+ } else {
447
+ focus(lastFocusedElementRef.current, { select: true });
448
+ }
449
+ };
450
+ const handleFocusOut = (event) => {
451
+ if (focusScopesStack.active !== scope) return;
452
+ const relatedTarget = event.relatedTarget;
453
+ if (relatedTarget === null) return;
454
+ if (!container?.contains(relatedTarget)) {
455
+ focus(lastFocusedElementRef.current, { select: true });
456
+ }
457
+ };
458
+ const handleMutations = (mutations) => {
459
+ const focusedEl = document.activeElement;
460
+ if (focusedEl !== document.body) return;
461
+ for (const mutation of mutations) {
462
+ if (mutation.removedNodes.length > 0) {
463
+ focus(container, { select: true });
464
+ }
465
+ }
466
+ };
467
+ document.addEventListener("focusin", handleFocusIn);
468
+ document.addEventListener("focusout", handleFocusOut);
469
+ const mutationObserver = new MutationObserver(handleMutations);
470
+ if (container) {
471
+ mutationObserver.observe(container, { childList: true, subtree: true });
472
+ }
473
+ return () => {
474
+ document.removeEventListener("focusin", handleFocusIn);
475
+ document.removeEventListener("focusout", handleFocusOut);
476
+ mutationObserver.disconnect();
477
+ };
478
+ }, [trapped, container, scope]);
479
+ React17.useEffect(() => {
480
+ if (!container) return;
481
+ focusScopesStack.add(scope);
482
+ const previouslyFocusedElement = document.activeElement;
483
+ const hasFocusedCandidate = container.contains(previouslyFocusedElement);
484
+ if (!hasFocusedCandidate) {
485
+ const mountEvent = new CustomEvent(AUTOFOCUS_ON_MOUNT, EVENT_OPTIONS);
486
+ container.addEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
487
+ container.dispatchEvent(mountEvent);
488
+ if (!mountEvent.defaultPrevented) {
489
+ focusFirst(removeLinks(getTabbableCandidates(container)), { select: true });
490
+ if (document.activeElement === previouslyFocusedElement) {
491
+ focus(container);
492
+ }
493
+ }
494
+ }
495
+ return () => {
496
+ container.removeEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
497
+ setTimeout(() => {
498
+ const unmountEvent = new CustomEvent(AUTOFOCUS_ON_UNMOUNT, EVENT_OPTIONS);
499
+ container.addEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
500
+ container.dispatchEvent(unmountEvent);
501
+ if (!unmountEvent.defaultPrevented) {
502
+ focus(previouslyFocusedElement ?? document.body, { select: true });
503
+ }
504
+ container.removeEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
505
+ focusScopesStack.remove(scope);
506
+ }, 0);
507
+ };
508
+ }, [container, onMountAutoFocus, onUnmountAutoFocus, scope]);
509
+ const handleKeyDown = React17.useCallback(
510
+ (event) => {
511
+ if (!loop && !trapped) return;
512
+ if (scope.paused) return;
513
+ const isTabKey = event.key === "Tab" && !event.altKey && !event.ctrlKey && !event.metaKey;
514
+ const focusedElement = document.activeElement;
515
+ if (!isTabKey || !focusedElement) return;
516
+ const containerEl = event.currentTarget;
517
+ const [first, last] = getTabbableEdges(containerEl);
518
+ const hasTabbable = first && last;
519
+ if (!hasTabbable) {
520
+ if (focusedElement === containerEl) event.preventDefault();
521
+ } else {
522
+ if (!event.shiftKey && focusedElement === last) {
523
+ event.preventDefault();
524
+ if (loop) focus(first, { select: true });
525
+ } else if (event.shiftKey && focusedElement === first) {
526
+ event.preventDefault();
527
+ if (loop) focus(last, { select: true });
528
+ }
529
+ }
530
+ },
531
+ [loop, trapped, scope.paused]
532
+ );
533
+ return /* @__PURE__ */ React17.createElement(
534
+ Primitive.div,
535
+ {
536
+ tabIndex: -1,
537
+ ...scopeProps,
538
+ ref: composedRefs,
539
+ onKeyDown: handleKeyDown
540
+ }
541
+ );
542
+ }
543
+ );
544
+ FocusScope.displayName = "FocusScope";
545
+ function focus(element, { select = false } = {}) {
546
+ if (element && element.focus) {
547
+ const previouslyFocusedElement = document.activeElement;
548
+ element.focus({ preventScroll: true });
549
+ if (element !== previouslyFocusedElement && isSelectableInput(element) && select) {
550
+ element.select();
551
+ }
552
+ }
553
+ }
554
+ function focusFirst(candidates, { select = false } = {}) {
555
+ const previouslyFocusedElement = document.activeElement;
556
+ for (const candidate of candidates) {
557
+ focus(candidate, { select });
558
+ if (document.activeElement !== previouslyFocusedElement) return;
559
+ }
560
+ }
561
+ function getTabbableEdges(container) {
562
+ const candidates = getTabbableCandidates(container);
563
+ const first = findVisible(candidates, container);
564
+ const last = findVisible(candidates.reverse(), container);
565
+ return [first, last];
566
+ }
567
+ function getTabbableCandidates(container) {
568
+ const nodes = [];
569
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, {
570
+ acceptNode: (node) => {
571
+ const el = node;
572
+ const isHiddenInput = el.tagName === "INPUT" && el.type === "hidden";
573
+ if (el.hasAttribute("disabled") || el.hasAttribute("hidden") || isHiddenInput) {
574
+ return NodeFilter.FILTER_SKIP;
575
+ }
576
+ return el.tabIndex >= 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
577
+ }
578
+ });
579
+ while (walker.nextNode()) nodes.push(walker.currentNode);
580
+ return nodes;
581
+ }
582
+ function findVisible(elements, container) {
583
+ for (const element of elements) {
584
+ if (!isHidden(element, { upTo: container })) return element;
585
+ }
586
+ return void 0;
587
+ }
588
+ function isHidden(node, { upTo } = {}) {
589
+ if (getComputedStyle(node).visibility === "hidden") return true;
590
+ let current = node;
591
+ while (current) {
592
+ if (upTo !== void 0 && current === upTo) return false;
593
+ if (getComputedStyle(current).display === "none") return true;
594
+ current = current.parentElement;
595
+ }
596
+ return false;
597
+ }
598
+ function isSelectableInput(element) {
599
+ return element instanceof HTMLInputElement && "select" in element;
600
+ }
601
+ function removeLinks(items) {
602
+ return items.filter((item) => item.tagName !== "A");
603
+ }
604
+ var focusScopesStack = createFocusScopesStack();
605
+ function createFocusScopesStack() {
606
+ let stack = [];
607
+ return {
608
+ add(focusScope) {
609
+ const active = stack[0];
610
+ if (focusScope !== active) active?.pause();
611
+ stack = arrayRemove(stack, focusScope);
612
+ stack.unshift(focusScope);
613
+ },
614
+ remove(focusScope) {
615
+ stack = arrayRemove(stack, focusScope);
616
+ stack[0]?.resume();
617
+ },
618
+ get active() {
619
+ return stack[0];
620
+ }
621
+ };
622
+ }
623
+ function arrayRemove(arr, item) {
624
+ const newArr = [...arr];
625
+ const idx = newArr.indexOf(item);
626
+ if (idx !== -1) newArr.splice(idx, 1);
627
+ return newArr;
628
+ }
629
+ var count = 0;
630
+ function useFocusGuards() {
631
+ React17.useEffect(() => {
632
+ if (typeof document === "undefined") return;
633
+ const edgeGuards = document.querySelectorAll("[data-structyl-focus-guard]");
634
+ document.body.insertAdjacentElement("afterbegin", edgeGuards[0] ?? createFocusGuard());
635
+ document.body.insertAdjacentElement("beforeend", edgeGuards[1] ?? createFocusGuard());
636
+ count++;
637
+ return () => {
638
+ if (count === 1) {
639
+ document.querySelectorAll("[data-structyl-focus-guard]").forEach((node) => node.remove());
640
+ }
641
+ count--;
642
+ };
643
+ }, []);
644
+ }
645
+ function createFocusGuard() {
646
+ const element = document.createElement("span");
647
+ element.setAttribute("data-structyl-focus-guard", "");
648
+ element.tabIndex = 0;
649
+ element.style.cssText = "outline: none; opacity: 0; position: fixed; pointer-events: none";
650
+ return element;
651
+ }
652
+ var FocusGuards = ({ children }) => {
653
+ useFocusGuards();
654
+ return /* @__PURE__ */ React17.createElement(React17.Fragment, null, children);
655
+ };
656
+ FocusGuards.displayName = "FocusGuards";
657
+ var CONTEXT_UPDATE = "dismissableLayer.update";
658
+ var POINTER_DOWN_OUTSIDE = "dismissableLayer.pointerDownOutside";
659
+ var FOCUS_OUTSIDE = "dismissableLayer.focusOutside";
660
+ var originalBodyPointerEvents;
661
+ var DismissableLayerContext = React17.createContext({
662
+ layers: /* @__PURE__ */ new Set(),
663
+ layersWithOutsidePointerEventsDisabled: /* @__PURE__ */ new Set(),
664
+ branches: /* @__PURE__ */ new Set()
665
+ });
666
+ var DismissableLayer = React17.forwardRef(
667
+ (props, forwardedRef) => {
668
+ const {
669
+ disableOutsidePointerEvents = false,
670
+ onEscapeKeyDown,
671
+ onPointerDownOutside,
672
+ onFocusOutside,
673
+ onInteractOutside,
674
+ onDismiss,
675
+ ...layerProps
676
+ } = props;
677
+ const context = React17.useContext(DismissableLayerContext);
678
+ const [node, setNode] = React17.useState(null);
679
+ const [, forceUpdate] = React17.useState({});
680
+ const ownerDocument = node?.ownerDocument ?? (typeof document !== "undefined" ? document : null);
681
+ const layers = Array.from(context.layers);
682
+ const [highestLayerWithOutsidePointerEventsDisabled] = [...context.layersWithOutsidePointerEventsDisabled].slice(-1);
683
+ const highestLayerWithOutsidePointerEventsDisabledIndex = highestLayerWithOutsidePointerEventsDisabled ? layers.indexOf(highestLayerWithOutsidePointerEventsDisabled) : -1;
684
+ const index = node ? layers.indexOf(node) : -1;
685
+ const isBodyPointerEventsDisabled = context.layersWithOutsidePointerEventsDisabled.size > 0;
686
+ const isPointerEventsEnabled = index >= highestLayerWithOutsidePointerEventsDisabledIndex;
687
+ const pointerDownOutside = usePointerDownOutside((event) => {
688
+ const target = event.target;
689
+ const isPointerDownOnBranch = [...context.branches].some(
690
+ (branch) => branch.contains(target)
691
+ );
692
+ if (!isPointerEventsEnabled || isPointerDownOnBranch) return;
693
+ onPointerDownOutside?.(event);
694
+ onInteractOutside?.(event);
695
+ if (!event.defaultPrevented) onDismiss?.();
696
+ }, ownerDocument);
697
+ const focusOutside = useFocusOutside((event) => {
698
+ const target = event.target;
699
+ const isFocusOnBranch = [...context.branches].some(
700
+ (branch) => branch.contains(target)
701
+ );
702
+ if (isFocusOnBranch) return;
703
+ onFocusOutside?.(event);
704
+ onInteractOutside?.(event);
705
+ if (!event.defaultPrevented) onDismiss?.();
706
+ }, ownerDocument);
707
+ useEscapeKeydown((event) => {
708
+ const layers2 = Array.from(context.layers);
709
+ const index2 = node ? layers2.indexOf(node) : -1;
710
+ const isHighest = node != null && index2 === layers2.length - 1;
711
+ if (!isHighest) return;
712
+ onEscapeKeyDown?.(event);
713
+ if (!event.defaultPrevented && onDismiss) {
714
+ event.preventDefault();
715
+ onDismiss();
716
+ }
717
+ }, ownerDocument);
718
+ React17.useLayoutEffect(() => {
719
+ if (!node) return;
720
+ if (disableOutsidePointerEvents) {
721
+ if (context.layersWithOutsidePointerEventsDisabled.size === 0 && ownerDocument) {
722
+ originalBodyPointerEvents = ownerDocument.body.style.pointerEvents;
723
+ ownerDocument.body.style.pointerEvents = "none";
724
+ }
725
+ context.layersWithOutsidePointerEventsDisabled.add(node);
726
+ }
727
+ context.layers.add(node);
728
+ updatePointerEventsOnNode(node, context);
729
+ dispatchUpdate();
730
+ forceUpdate({});
731
+ return () => {
732
+ if (disableOutsidePointerEvents && context.layersWithOutsidePointerEventsDisabled.size === 1 && ownerDocument) {
733
+ ownerDocument.body.style.pointerEvents = originalBodyPointerEvents;
734
+ }
735
+ };
736
+ }, [node, ownerDocument, disableOutsidePointerEvents, context]);
737
+ React17.useLayoutEffect(() => {
738
+ return () => {
739
+ if (!node) return;
740
+ context.layers.delete(node);
741
+ context.layersWithOutsidePointerEventsDisabled.delete(node);
742
+ dispatchUpdate();
743
+ };
744
+ }, [node, context]);
745
+ React17.useEffect(() => {
746
+ const handleUpdate = () => {
747
+ if (node) updatePointerEventsOnNode(node, context);
748
+ forceUpdate({});
749
+ };
750
+ document.addEventListener(CONTEXT_UPDATE, handleUpdate);
751
+ return () => document.removeEventListener(CONTEXT_UPDATE, handleUpdate);
752
+ }, [node, context]);
753
+ const composedRefs = useComposedRefs(forwardedRef, setNode);
754
+ return /* @__PURE__ */ React17.createElement(
755
+ Primitive.div,
756
+ {
757
+ ...layerProps,
758
+ ref: composedRefs,
759
+ style: {
760
+ pointerEvents: isBodyPointerEventsDisabled ? isPointerEventsEnabled ? "auto" : "none" : void 0,
761
+ ...layerProps.style
762
+ },
763
+ onFocusCapture: composeEventHandlers(layerProps.onFocusCapture, focusOutside.onFocusCapture),
764
+ onBlurCapture: composeEventHandlers(layerProps.onBlurCapture, focusOutside.onBlurCapture),
765
+ onPointerDownCapture: composeEventHandlers(
766
+ layerProps.onPointerDownCapture,
767
+ pointerDownOutside.onPointerDownCapture
768
+ )
769
+ }
770
+ );
771
+ }
772
+ );
773
+ DismissableLayer.displayName = "DismissableLayer";
774
+ var DismissableLayerBranch = React17.forwardRef(
775
+ (props, forwardedRef) => {
776
+ const context = React17.useContext(DismissableLayerContext);
777
+ const ref = React17.useRef(null);
778
+ const composedRefs = useComposedRefs(forwardedRef, ref);
779
+ React17.useEffect(() => {
780
+ const node = ref.current;
781
+ if (!node) return;
782
+ context.branches.add(node);
783
+ return () => {
784
+ context.branches.delete(node);
785
+ };
786
+ }, [context]);
787
+ return /* @__PURE__ */ React17.createElement(Primitive.div, { ...props, ref: composedRefs });
788
+ }
789
+ );
790
+ DismissableLayerBranch.displayName = "DismissableLayerBranch";
791
+ function updatePointerEventsOnNode(node, context) {
792
+ const layers = Array.from(context.layers);
793
+ const [highest] = [...context.layersWithOutsidePointerEventsDisabled].slice(-1);
794
+ const highestIndex = highest ? layers.indexOf(highest) : -1;
795
+ const nodeIndex = layers.indexOf(node);
796
+ const isBodyDisabled = context.layersWithOutsidePointerEventsDisabled.size > 0;
797
+ const isEnabled = nodeIndex >= highestIndex;
798
+ if (isBodyDisabled) {
799
+ node.style.pointerEvents = isEnabled ? "auto" : "none";
800
+ } else {
801
+ node.style.removeProperty("pointer-events");
802
+ }
803
+ }
804
+ function dispatchUpdate() {
805
+ if (typeof document === "undefined") return;
806
+ const event = new CustomEvent(CONTEXT_UPDATE);
807
+ document.dispatchEvent(event);
808
+ }
809
+ function usePointerDownOutside(onPointerDownOutside, ownerDocument = typeof document !== "undefined" ? document : null) {
810
+ const handler = useCallbackRef(onPointerDownOutside);
811
+ const isPointerInsideReactTreeRef = React17.useRef(false);
812
+ const handleClickRef = React17.useRef(() => {
813
+ });
814
+ React17.useEffect(() => {
815
+ if (!ownerDocument) return;
816
+ const handlePointerDown = (event) => {
817
+ if (event.target && !isPointerInsideReactTreeRef.current) {
818
+ const eventDetail = { originalEvent: event };
819
+ const handleAndDispatchPointerDownOutsideEvent = () => {
820
+ const customEvent = new CustomEvent(POINTER_DOWN_OUTSIDE, {
821
+ bubbles: false,
822
+ cancelable: true,
823
+ detail: eventDetail
824
+ });
825
+ const targetEl = event.target;
826
+ targetEl.addEventListener(POINTER_DOWN_OUTSIDE, handler, { once: true });
827
+ targetEl.dispatchEvent(customEvent);
828
+ };
829
+ if (event.pointerType === "touch") {
830
+ ownerDocument.removeEventListener("click", handleClickRef.current);
831
+ handleClickRef.current = handleAndDispatchPointerDownOutsideEvent;
832
+ ownerDocument.addEventListener("click", handleClickRef.current, { once: true });
833
+ } else {
834
+ handleAndDispatchPointerDownOutsideEvent();
835
+ }
836
+ } else {
837
+ ownerDocument.removeEventListener("click", handleClickRef.current);
838
+ }
839
+ isPointerInsideReactTreeRef.current = false;
840
+ };
841
+ const timerId = window.setTimeout(() => {
842
+ ownerDocument.addEventListener("pointerdown", handlePointerDown);
843
+ }, 0);
844
+ return () => {
845
+ window.clearTimeout(timerId);
846
+ ownerDocument.removeEventListener("pointerdown", handlePointerDown);
847
+ ownerDocument.removeEventListener("click", handleClickRef.current);
848
+ };
849
+ }, [ownerDocument, handler]);
850
+ return {
851
+ onPointerDownCapture: () => {
852
+ isPointerInsideReactTreeRef.current = true;
853
+ }
854
+ };
855
+ }
856
+ function useFocusOutside(onFocusOutside, ownerDocument = typeof document !== "undefined" ? document : null) {
857
+ const handler = useCallbackRef(onFocusOutside);
858
+ const isFocusInsideReactTreeRef = React17.useRef(false);
859
+ React17.useEffect(() => {
860
+ if (!ownerDocument) return;
861
+ const handleFocus = (event) => {
862
+ if (event.target && !isFocusInsideReactTreeRef.current) {
863
+ const eventDetail = { originalEvent: event };
864
+ const customEvent = new CustomEvent(FOCUS_OUTSIDE, {
865
+ bubbles: false,
866
+ cancelable: true,
867
+ detail: eventDetail
868
+ });
869
+ const targetEl = event.target;
870
+ targetEl.addEventListener(FOCUS_OUTSIDE, handler, { once: true });
871
+ targetEl.dispatchEvent(customEvent);
872
+ }
873
+ };
874
+ ownerDocument.addEventListener("focusin", handleFocus);
875
+ return () => ownerDocument.removeEventListener("focusin", handleFocus);
876
+ }, [ownerDocument, handler]);
877
+ return {
878
+ onFocusCapture: () => {
879
+ isFocusInsideReactTreeRef.current = true;
880
+ },
881
+ onBlurCapture: () => {
882
+ isFocusInsideReactTreeRef.current = false;
883
+ }
884
+ };
885
+ }
886
+ function useEscapeKeydown(onEscapeKeyDown, ownerDocument = typeof document !== "undefined" ? document : null) {
887
+ const handler = useCallbackRef(onEscapeKeyDown);
888
+ React17.useEffect(() => {
889
+ if (!ownerDocument) return;
890
+ const handleKeyDown = (event) => {
891
+ if (event.key === "Escape") handler(event);
892
+ };
893
+ ownerDocument.addEventListener("keydown", handleKeyDown, { capture: true });
894
+ return () => ownerDocument.removeEventListener("keydown", handleKeyDown, { capture: true });
895
+ }, [handler, ownerDocument]);
896
+ }
897
+ function createCollection(name) {
898
+ const CollectionContext = React17.createContext({
899
+ collectionRef: { current: null },
900
+ itemMap: /* @__PURE__ */ new Map()
901
+ });
902
+ const CollectionProvider = ({ children }) => {
903
+ const collectionRef = React17.useRef(null);
904
+ const itemMap = React17.useRef(/* @__PURE__ */ new Map()).current;
905
+ const value = React17.useMemo(() => ({ collectionRef, itemMap }), [itemMap]);
906
+ return /* @__PURE__ */ React17.createElement(CollectionContext.Provider, { value }, children);
907
+ };
908
+ CollectionProvider.displayName = `${name}CollectionProvider`;
909
+ const CollectionSlot = React17.forwardRef(
910
+ ({ children }, forwardedRef) => {
911
+ const context = React17.useContext(CollectionContext);
912
+ const composedRefs = useComposedRefs(forwardedRef, context.collectionRef);
913
+ return /* @__PURE__ */ React17.createElement(Slot, { ref: composedRefs }, children);
914
+ }
915
+ );
916
+ CollectionSlot.displayName = `${name}CollectionSlot`;
917
+ const ITEM_DATA_ATTR = `data-${name.toLowerCase()}-collection-item`;
918
+ const CollectionItemSlot = React17.forwardRef(
919
+ (props, forwardedRef) => {
920
+ const { children, ...itemData } = props;
921
+ const ref = React17.useRef(null);
922
+ const composedRefs = useComposedRefs(forwardedRef, ref);
923
+ const context = React17.useContext(CollectionContext);
924
+ React17.useEffect(() => {
925
+ context.itemMap.set(ref, { ref, ...itemData });
926
+ return () => {
927
+ context.itemMap.delete(ref);
928
+ };
929
+ });
930
+ return /* @__PURE__ */ React17.createElement(Slot, { ...{ [ITEM_DATA_ATTR]: "" }, ref: composedRefs }, children);
931
+ }
932
+ );
933
+ CollectionItemSlot.displayName = `${name}CollectionItemSlot`;
934
+ function useCollection2() {
935
+ const context = React17.useContext(CollectionContext);
936
+ return React17.useCallback(() => {
937
+ const collectionNode = context.collectionRef.current;
938
+ if (!collectionNode) return [];
939
+ const orderedNodes = Array.from(
940
+ collectionNode.querySelectorAll(`[${ITEM_DATA_ATTR}]`)
941
+ );
942
+ const items = Array.from(context.itemMap.values());
943
+ items.sort(
944
+ (a, b) => orderedNodes.indexOf(a.ref.current) - orderedNodes.indexOf(b.ref.current)
945
+ );
946
+ return items;
947
+ }, [context.collectionRef, context.itemMap]);
948
+ }
949
+ return [
950
+ { Provider: CollectionProvider, Slot: CollectionSlot, ItemSlot: CollectionItemSlot },
951
+ useCollection2
952
+ ];
953
+ }
954
+
955
+ // src/roving-focus-group.tsx
956
+ var ENTRY_FOCUS = "rovingFocusGroup.onEntryFocus";
957
+ var EVENT_OPTIONS2 = { bubbles: false, cancelable: true };
958
+ var [Collection, useCollection] = createCollection("RovingFocus");
959
+ var [RovingFocusProvider, useRovingFocusContext] = createContext2("RovingFocusGroup");
960
+ var RovingFocusGroup = React17.forwardRef(
961
+ (props, forwardedRef) => {
962
+ const {
963
+ orientation,
964
+ dir: dirProp,
965
+ loop = false,
966
+ currentTabStopId: currentTabStopIdProp,
967
+ defaultCurrentTabStopId,
968
+ onCurrentTabStopIdChange,
969
+ onEntryFocus,
970
+ preventScrollOnEntryFocus = false,
971
+ ...groupProps
972
+ } = props;
973
+ const ref = React17.useRef(null);
974
+ const composedRefs = useComposedRefs(forwardedRef, ref);
975
+ const dir = useDirection(dirProp);
976
+ const [currentTabStopId = null, setCurrentTabStopId] = useControllableState({
977
+ prop: currentTabStopIdProp,
978
+ defaultProp: defaultCurrentTabStopId,
979
+ onChange: onCurrentTabStopIdChange
980
+ });
981
+ const [isTabbingBackOut, setIsTabbingBackOut] = React17.useState(false);
982
+ const handleEntryFocus = useCallbackRef(onEntryFocus);
983
+ const getItems = useCollection();
984
+ const isClickFocusRef = React17.useRef(false);
985
+ const [focusableItemsCount, setFocusableItemsCount] = React17.useState(0);
986
+ React17.useEffect(() => {
987
+ const node = ref.current;
988
+ if (!node) return;
989
+ const handler = (e) => handleEntryFocus(e);
990
+ node.addEventListener(ENTRY_FOCUS, handler);
991
+ return () => node.removeEventListener(ENTRY_FOCUS, handler);
992
+ }, [handleEntryFocus]);
993
+ return /* @__PURE__ */ React17.createElement(Collection.Provider, null, /* @__PURE__ */ React17.createElement(Collection.Slot, null, /* @__PURE__ */ React17.createElement(
994
+ RovingFocusProvider,
995
+ {
996
+ orientation,
997
+ dir,
998
+ loop,
999
+ currentTabStopId,
1000
+ onItemFocus: React17.useCallback(
1001
+ (id) => setCurrentTabStopId(id),
1002
+ [setCurrentTabStopId]
1003
+ ),
1004
+ onItemShiftTab: React17.useCallback(() => setIsTabbingBackOut(true), []),
1005
+ onFocusableItemAdd: React17.useCallback(
1006
+ () => setFocusableItemsCount((c) => c + 1),
1007
+ []
1008
+ ),
1009
+ onFocusableItemRemove: React17.useCallback(
1010
+ () => setFocusableItemsCount((c) => c - 1),
1011
+ []
1012
+ )
1013
+ },
1014
+ /* @__PURE__ */ React17.createElement(
1015
+ Primitive.div,
1016
+ {
1017
+ tabIndex: isTabbingBackOut || focusableItemsCount === 0 ? -1 : 0,
1018
+ "data-orientation": orientation,
1019
+ ...groupProps,
1020
+ ref: composedRefs,
1021
+ style: { outline: "none", ...groupProps.style },
1022
+ onMouseDown: composeEventHandlers(groupProps.onMouseDown, () => {
1023
+ isClickFocusRef.current = true;
1024
+ }),
1025
+ onFocus: composeEventHandlers(groupProps.onFocus, (event) => {
1026
+ const isKeyboardFocus = !isClickFocusRef.current;
1027
+ if (event.target === event.currentTarget && isKeyboardFocus && !isTabbingBackOut) {
1028
+ const entryEvent = new CustomEvent(ENTRY_FOCUS, EVENT_OPTIONS2);
1029
+ event.currentTarget.dispatchEvent(entryEvent);
1030
+ if (!entryEvent.defaultPrevented) {
1031
+ const items = getItems().filter((item) => item.focusable);
1032
+ const activeItem = items.find((item) => item.active);
1033
+ const currentItem = items.find((item) => item.id === currentTabStopId);
1034
+ const candidateItems = [activeItem, currentItem, ...items].filter(
1035
+ Boolean
1036
+ );
1037
+ focusFirst2(
1038
+ candidateItems.map((i) => i.ref.current).filter(Boolean),
1039
+ preventScrollOnEntryFocus
1040
+ );
1041
+ }
1042
+ }
1043
+ isClickFocusRef.current = false;
1044
+ }),
1045
+ onBlur: composeEventHandlers(groupProps.onBlur, () => setIsTabbingBackOut(false))
1046
+ }
1047
+ )
1048
+ )));
1049
+ }
1050
+ );
1051
+ RovingFocusGroup.displayName = "RovingFocusGroup";
1052
+ var RovingFocusItem = React17.forwardRef(
1053
+ (props, forwardedRef) => {
1054
+ const { focusable = true, active = false, tabStopId, ...itemProps } = props;
1055
+ const autoId = useId2();
1056
+ const id = tabStopId ?? autoId;
1057
+ const context = useRovingFocusContext("RovingFocusItem");
1058
+ const isCurrentTabStop = context.currentTabStopId === id;
1059
+ const getItems = useCollection();
1060
+ const { onFocusableItemAdd, onFocusableItemRemove } = context;
1061
+ React17.useEffect(() => {
1062
+ if (!focusable) return void 0;
1063
+ onFocusableItemAdd();
1064
+ return () => onFocusableItemRemove();
1065
+ }, [focusable, onFocusableItemAdd, onFocusableItemRemove]);
1066
+ return /* @__PURE__ */ React17.createElement(Collection.ItemSlot, { id, focusable, active }, /* @__PURE__ */ React17.createElement(
1067
+ Primitive.span,
1068
+ {
1069
+ tabIndex: isCurrentTabStop ? 0 : -1,
1070
+ "data-orientation": context.orientation,
1071
+ ...itemProps,
1072
+ ref: forwardedRef,
1073
+ onMouseDown: composeEventHandlers(itemProps.onMouseDown, (event) => {
1074
+ if (!focusable) event.preventDefault();
1075
+ else context.onItemFocus(id);
1076
+ }),
1077
+ onFocus: composeEventHandlers(itemProps.onFocus, () => context.onItemFocus(id)),
1078
+ onKeyDown: composeEventHandlers(itemProps.onKeyDown, (event) => {
1079
+ if (event.key === "Tab" && event.shiftKey) {
1080
+ context.onItemShiftTab();
1081
+ return;
1082
+ }
1083
+ if (event.target !== event.currentTarget) return;
1084
+ const focusIntent = getFocusIntent(event, context.orientation, context.dir);
1085
+ if (focusIntent !== void 0) {
1086
+ if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return;
1087
+ event.preventDefault();
1088
+ let candidateNodes = getItems().filter((item) => item.focusable).map((item) => item.ref.current).filter(Boolean);
1089
+ if (focusIntent === "last") candidateNodes.reverse();
1090
+ else if (focusIntent === "prev" || focusIntent === "next") {
1091
+ if (focusIntent === "prev") candidateNodes.reverse();
1092
+ const currentIndex = candidateNodes.indexOf(event.currentTarget);
1093
+ candidateNodes = context.loop ? wrapArray(candidateNodes, currentIndex + 1) : candidateNodes.slice(currentIndex + 1);
1094
+ }
1095
+ setTimeout(() => focusFirst2(candidateNodes));
1096
+ }
1097
+ })
1098
+ }
1099
+ ));
1100
+ }
1101
+ );
1102
+ RovingFocusItem.displayName = "RovingFocusItem";
1103
+ var MAP_KEY_TO_FOCUS_INTENT = {
1104
+ ArrowLeft: "prev",
1105
+ ArrowUp: "prev",
1106
+ ArrowRight: "next",
1107
+ ArrowDown: "next",
1108
+ PageUp: "first",
1109
+ Home: "first",
1110
+ PageDown: "last",
1111
+ End: "last"
1112
+ };
1113
+ function getDirectionAwareKey(key, dir) {
1114
+ if (dir !== "rtl") return key;
1115
+ if (key === "ArrowLeft") return "ArrowRight";
1116
+ if (key === "ArrowRight") return "ArrowLeft";
1117
+ return key;
1118
+ }
1119
+ function getFocusIntent(event, orientation, dir) {
1120
+ const key = getDirectionAwareKey(event.key, dir);
1121
+ if (orientation === "vertical" && ["ArrowLeft", "ArrowRight"].includes(key)) return void 0;
1122
+ if (orientation === "horizontal" && ["ArrowUp", "ArrowDown"].includes(key)) return void 0;
1123
+ return MAP_KEY_TO_FOCUS_INTENT[key];
1124
+ }
1125
+ function focusFirst2(candidates, preventScroll = false) {
1126
+ const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;
1127
+ for (const candidate of candidates) {
1128
+ if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;
1129
+ candidate.focus({ preventScroll });
1130
+ if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;
1131
+ }
1132
+ }
1133
+ function wrapArray(arr, startIdx) {
1134
+ return arr.map((_, index) => arr[(startIdx + index) % arr.length]);
1135
+ }
1136
+ var Arrow = React17.forwardRef((props, forwardedRef) => {
1137
+ const { width = 10, height = 5, ...arrowProps } = props;
1138
+ return /* @__PURE__ */ React17.createElement(
1139
+ Primitive.svg,
1140
+ {
1141
+ ...arrowProps,
1142
+ ref: forwardedRef,
1143
+ width,
1144
+ height,
1145
+ viewBox: "0 0 30 10",
1146
+ preserveAspectRatio: "none"
1147
+ },
1148
+ /* @__PURE__ */ React17.createElement("polygon", { points: "0,0 30,0 15,10", fill: "currentColor" })
1149
+ );
1150
+ });
1151
+ Arrow.displayName = "Arrow";
1152
+ var AccessibleIcon = ({ label, children }) => {
1153
+ const child = React17.Children.only(children);
1154
+ return /* @__PURE__ */ React17.createElement(React17.Fragment, null, React17.cloneElement(child, {
1155
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1156
+ ...child.props,
1157
+ "aria-hidden": "true",
1158
+ focusable: "false"
1159
+ }), /* @__PURE__ */ React17.createElement(VisuallyHidden, null, label));
1160
+ };
1161
+ AccessibleIcon.displayName = "AccessibleIcon";
1162
+ var lockCount = 0;
1163
+ var originalOverflow = "";
1164
+ var originalPaddingRight = "";
1165
+ function getScrollbarWidth() {
1166
+ if (typeof window === "undefined" || typeof document === "undefined") return 0;
1167
+ return window.innerWidth - document.documentElement.clientWidth;
1168
+ }
1169
+ function useScrollLock(enabled = true) {
1170
+ React17.useEffect(() => {
1171
+ if (!enabled || typeof document === "undefined") return;
1172
+ if (lockCount === 0) {
1173
+ const body = document.body;
1174
+ const scrollbarWidth = getScrollbarWidth();
1175
+ originalOverflow = body.style.overflow;
1176
+ originalPaddingRight = body.style.paddingRight;
1177
+ const existingPadding = parseInt(window.getComputedStyle(body).paddingRight, 10) || 0;
1178
+ body.style.overflow = "hidden";
1179
+ if (scrollbarWidth > 0) {
1180
+ body.style.paddingRight = `${existingPadding + scrollbarWidth}px`;
1181
+ }
1182
+ }
1183
+ lockCount++;
1184
+ return () => {
1185
+ lockCount--;
1186
+ if (lockCount === 0) {
1187
+ document.body.style.overflow = originalOverflow;
1188
+ document.body.style.paddingRight = originalPaddingRight;
1189
+ }
1190
+ };
1191
+ }, [enabled]);
1192
+ }
1193
+ var ScrollLock = ({
1194
+ children,
1195
+ enabled = true
1196
+ }) => {
1197
+ useScrollLock(enabled);
1198
+ return /* @__PURE__ */ React17.createElement(React17.Fragment, null, children);
1199
+ };
1200
+ ScrollLock.displayName = "ScrollLock";
1201
+
1202
+ // src/popper.tsx
1203
+ var popper_exports = {};
1204
+ __export(popper_exports, {
1205
+ Anchor: () => PopperAnchor,
1206
+ Arrow: () => PopperArrow,
1207
+ Content: () => PopperContent,
1208
+ Root: () => Popper
1209
+ });
1210
+ var [PopperProvider, usePopperContext] = createContext2("Popper");
1211
+ var Popper = ({ children }) => {
1212
+ const [anchor, setAnchor] = React17.useState(null);
1213
+ return /* @__PURE__ */ React17.createElement(PopperProvider, { anchor, onAnchorChange: setAnchor }, children);
1214
+ };
1215
+ Popper.displayName = "Popper";
1216
+ var PopperAnchor = React17.forwardRef((props, forwardedRef) => {
1217
+ const { virtualRef, ...anchorProps } = props;
1218
+ const ctx = usePopperContext("PopperAnchor");
1219
+ const ref = React17.useRef(null);
1220
+ const composedRefs = useComposedRefs(forwardedRef, ref);
1221
+ React17.useEffect(() => {
1222
+ ctx.onAnchorChange(virtualRef?.current ?? ref.current);
1223
+ });
1224
+ return virtualRef ? null : /* @__PURE__ */ React17.createElement(Primitive.div, { ...anchorProps, ref: composedRefs });
1225
+ });
1226
+ PopperAnchor.displayName = "PopperAnchor";
1227
+ var [PopperContentProvider, useContentContext] = createContext2("PopperContent");
1228
+ var PopperContent = React17.forwardRef(
1229
+ (props, forwardedRef) => {
1230
+ const {
1231
+ side = "bottom",
1232
+ sideOffset = 0,
1233
+ align = "center",
1234
+ alignOffset = 0,
1235
+ arrowPadding = 0,
1236
+ avoidCollisions = true,
1237
+ collisionBoundary = [],
1238
+ collisionPadding: collisionPaddingProp = 0,
1239
+ sticky = "partial",
1240
+ hideWhenDetached = false,
1241
+ updatePositionStrategy = "optimized",
1242
+ onPlaced,
1243
+ strategy = "fixed",
1244
+ ...contentProps
1245
+ } = props;
1246
+ const ctx = usePopperContext("PopperContent");
1247
+ const [content, setContent] = React17.useState(null);
1248
+ const composedRefs = useComposedRefs(forwardedRef, setContent);
1249
+ const [arrow$1, setArrow] = React17.useState(null);
1250
+ const arrowSize = useSize(arrow$1);
1251
+ const arrowWidth = arrowSize?.width ?? 0;
1252
+ const arrowHeight = arrowSize?.height ?? 0;
1253
+ const dir = useDirection();
1254
+ const desiredPlacement = side + (align !== "center" ? "-" + align : "");
1255
+ const collisionPadding = typeof collisionPaddingProp === "number" ? collisionPaddingProp : { top: 0, right: 0, bottom: 0, left: 0, ...collisionPaddingProp };
1256
+ const boundary = Array.isArray(collisionBoundary) ? collisionBoundary : [collisionBoundary].filter(Boolean);
1257
+ const detectOverflowOptions = {
1258
+ padding: collisionPadding,
1259
+ boundary: boundary.filter(isHTMLElement),
1260
+ altBoundary: boundary.length > 0
1261
+ };
1262
+ const middleware = [
1263
+ offset({ mainAxis: sideOffset + arrowHeight, alignmentAxis: alignOffset }),
1264
+ avoidCollisions && shift({
1265
+ mainAxis: true,
1266
+ crossAxis: true,
1267
+ limiter: sticky === "partial" ? limitShift() : void 0,
1268
+ ...detectOverflowOptions
1269
+ }),
1270
+ avoidCollisions && flip({ ...detectOverflowOptions }),
1271
+ size({
1272
+ ...detectOverflowOptions,
1273
+ apply: ({ elements, rects, availableWidth, availableHeight }) => {
1274
+ const { width: anchorWidth, height: anchorHeight } = rects.reference;
1275
+ const contentStyle = elements.floating.style;
1276
+ contentStyle.setProperty("--structyl-popper-available-width", `${availableWidth}px`);
1277
+ contentStyle.setProperty("--structyl-popper-available-height", `${availableHeight}px`);
1278
+ contentStyle.setProperty("--structyl-popper-anchor-width", `${anchorWidth}px`);
1279
+ contentStyle.setProperty("--structyl-popper-anchor-height", `${anchorHeight}px`);
1280
+ }
1281
+ }),
1282
+ arrow$1 && arrow({ element: arrow$1, padding: arrowPadding }),
1283
+ transformOrigin({ arrowWidth, arrowHeight }),
1284
+ hideWhenDetached && hide({ strategy: "referenceHidden" })
1285
+ ].filter(Boolean);
1286
+ const { refs, floatingStyles, placement, isPositioned, middlewareData } = useFloating({
1287
+ strategy,
1288
+ placement: desiredPlacement,
1289
+ whileElementsMounted: (...args) => autoUpdate(...args, {
1290
+ animationFrame: updatePositionStrategy === "always"
1291
+ }),
1292
+ middleware
1293
+ });
1294
+ React17.useLayoutEffect(() => {
1295
+ refs.setPositionReference(ctx.anchor);
1296
+ }, [ctx.anchor, refs]);
1297
+ const [placedSide, placedAlign] = getSideAndAlignFromPlacement(placement);
1298
+ const handlePlaced = useCallbackRef(onPlaced);
1299
+ React17.useLayoutEffect(() => {
1300
+ if (isPositioned) handlePlaced();
1301
+ }, [isPositioned, handlePlaced]);
1302
+ const arrowX = middlewareData.arrow?.x;
1303
+ const arrowY = middlewareData.arrow?.y;
1304
+ const cannotCenterArrow = middlewareData.arrow?.centerOffset !== 0;
1305
+ const contentStyleZIndex = contentProps.style?.zIndex;
1306
+ const [contentZIndex, setContentZIndex] = React17.useState(
1307
+ contentStyleZIndex ?? 50
1308
+ );
1309
+ React17.useLayoutEffect(() => {
1310
+ if (!content) {
1311
+ setContentZIndex(contentStyleZIndex ?? 50);
1312
+ return;
1313
+ }
1314
+ const computedZIndex = window.getComputedStyle(content).zIndex;
1315
+ setContentZIndex(computedZIndex === "auto" ? contentStyleZIndex ?? 50 : computedZIndex);
1316
+ }, [content, contentProps.className, contentStyleZIndex]);
1317
+ return /* @__PURE__ */ React17.createElement(
1318
+ "div",
1319
+ {
1320
+ ref: refs.setFloating,
1321
+ "data-structyl-popper-content-wrapper": "",
1322
+ style: {
1323
+ ...floatingStyles,
1324
+ transform: isPositioned ? floatingStyles.transform : "translate(0, -200%)",
1325
+ minWidth: "max-content",
1326
+ zIndex: contentZIndex,
1327
+ ["--structyl-popper-transform-origin"]: [middlewareData.transformOrigin?.x, middlewareData.transformOrigin?.y].join(" ") || void 0,
1328
+ ...middlewareData.hide?.referenceHidden && {
1329
+ visibility: "hidden",
1330
+ pointerEvents: "none"
1331
+ }
1332
+ },
1333
+ dir
1334
+ },
1335
+ /* @__PURE__ */ React17.createElement(
1336
+ PopperContentProvider,
1337
+ {
1338
+ placedSide,
1339
+ placedAlign,
1340
+ arrowX,
1341
+ arrowY,
1342
+ shouldHideArrow: cannotCenterArrow,
1343
+ onArrowChange: setArrow
1344
+ },
1345
+ /* @__PURE__ */ React17.createElement(
1346
+ Primitive.div,
1347
+ {
1348
+ "data-side": placedSide,
1349
+ "data-align": placedAlign,
1350
+ ...contentProps,
1351
+ ref: composedRefs,
1352
+ style: { ...contentProps.style, animation: !isPositioned ? "none" : void 0 }
1353
+ }
1354
+ )
1355
+ )
1356
+ );
1357
+ }
1358
+ );
1359
+ PopperContent.displayName = "PopperContent";
1360
+ var OPPOSITE_SIDE = {
1361
+ top: "bottom",
1362
+ right: "left",
1363
+ bottom: "top",
1364
+ left: "right"
1365
+ };
1366
+ var PopperArrow = React17.forwardRef((props, forwardedRef) => {
1367
+ const { ...arrowProps } = props;
1368
+ const contentContext = useContentContext("PopperArrow");
1369
+ const baseSide = OPPOSITE_SIDE[contentContext.placedSide];
1370
+ return /* @__PURE__ */ React17.createElement(
1371
+ "span",
1372
+ {
1373
+ ref: contentContext.onArrowChange,
1374
+ style: {
1375
+ position: "absolute",
1376
+ left: contentContext.arrowX,
1377
+ top: contentContext.arrowY,
1378
+ [baseSide]: 0,
1379
+ transformOrigin: {
1380
+ top: "",
1381
+ right: "0 0",
1382
+ bottom: "center 0",
1383
+ left: "100% 0"
1384
+ }[contentContext.placedSide],
1385
+ transform: {
1386
+ top: "translateY(100%)",
1387
+ right: "translateY(50%) rotate(90deg) translateX(-50%)",
1388
+ bottom: "rotate(180deg)",
1389
+ left: "translateY(50%) rotate(-90deg) translateX(50%)"
1390
+ }[contentContext.placedSide],
1391
+ visibility: contentContext.shouldHideArrow ? "hidden" : void 0
1392
+ }
1393
+ },
1394
+ /* @__PURE__ */ React17.createElement(
1395
+ Arrow,
1396
+ {
1397
+ ...arrowProps,
1398
+ ref: forwardedRef,
1399
+ style: { ...arrowProps.style, display: "block" }
1400
+ }
1401
+ )
1402
+ );
1403
+ });
1404
+ PopperArrow.displayName = "PopperArrow";
1405
+ function isHTMLElement(value) {
1406
+ return value instanceof Element;
1407
+ }
1408
+ function getSideAndAlignFromPlacement(placement) {
1409
+ const [side, align = "center"] = placement.split("-");
1410
+ return [side, align];
1411
+ }
1412
+ function transformOrigin(options) {
1413
+ return {
1414
+ name: "transformOrigin",
1415
+ options,
1416
+ fn(data) {
1417
+ const { placement, rects, middlewareData } = data;
1418
+ const cannotCenterArrow = middlewareData.arrow?.centerOffset !== 0;
1419
+ const isArrowHidden = cannotCenterArrow;
1420
+ const arrowWidth = isArrowHidden ? 0 : options.arrowWidth;
1421
+ const arrowHeight = isArrowHidden ? 0 : options.arrowHeight;
1422
+ const [placedSide, placedAlign] = getSideAndAlignFromPlacement(placement);
1423
+ const noArrowAlign = { start: "0%", center: "50%", end: "100%" }[placedAlign];
1424
+ const arrowXCenter = (middlewareData.arrow?.x ?? 0) + arrowWidth / 2;
1425
+ const arrowYCenter = (middlewareData.arrow?.y ?? 0) + arrowHeight / 2;
1426
+ let x = "";
1427
+ let y = "";
1428
+ if (placedSide === "bottom") {
1429
+ x = isArrowHidden ? noArrowAlign : `${arrowXCenter}px`;
1430
+ y = `${-arrowHeight}px`;
1431
+ } else if (placedSide === "top") {
1432
+ x = isArrowHidden ? noArrowAlign : `${arrowXCenter}px`;
1433
+ y = `${rects.floating.height + arrowHeight}px`;
1434
+ } else if (placedSide === "right") {
1435
+ x = `${-arrowHeight}px`;
1436
+ y = isArrowHidden ? noArrowAlign : `${arrowYCenter}px`;
1437
+ } else if (placedSide === "left") {
1438
+ x = `${rects.floating.width + arrowHeight}px`;
1439
+ y = isArrowHidden ? noArrowAlign : `${arrowYCenter}px`;
1440
+ }
1441
+ return { data: { x, y } };
1442
+ }
1443
+ };
1444
+ }
1445
+ function useSize(element) {
1446
+ const [size, setSize] = React17.useState(void 0);
1447
+ React17.useLayoutEffect(() => {
1448
+ if (!element) {
1449
+ setSize(void 0);
1450
+ return;
1451
+ }
1452
+ setSize({ width: element.offsetWidth, height: element.offsetHeight });
1453
+ const observer = new ResizeObserver((entries) => {
1454
+ const entry = entries[0];
1455
+ if (!entry) return;
1456
+ const borderBox = entry.borderBoxSize?.[0];
1457
+ const w = borderBox ? borderBox.inlineSize : entry.contentRect.width;
1458
+ const h = borderBox ? borderBox.blockSize : entry.contentRect.height;
1459
+ setSize({ width: w, height: h });
1460
+ });
1461
+ observer.observe(element, { box: "border-box" });
1462
+ return () => observer.disconnect();
1463
+ }, [element]);
1464
+ return size;
1465
+ }
1466
+
1467
+ export { AccessibleIcon, Arrow, DirectionProvider, DismissableLayer, DismissableLayerBranch, FocusGuards, FocusScope, popper_exports as Popper, Portal, Presence, Primitive, RovingFocusGroup, RovingFocusItem, ScrollLock, Slot, Slottable, VisuallyHidden, composeContextScopes, createCollection, createContext2 as createContext, createContextScope, useDirection, useFocusGuards, usePresence, useScrollLock };
1468
+ //# sourceMappingURL=index.mjs.map
1469
+ //# sourceMappingURL=index.mjs.map