@enigmax/primitives 0.27.0 → 0.28.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.
Files changed (36) hide show
  1. package/dist/{chunk-VKL3DEIQ.js → chunk-6WIGVIQF.js} +58 -37
  2. package/dist/{chunk-O2UJ5ZIX.js → chunk-CB7K6VW4.js} +1 -1
  3. package/dist/{chunk-2RIO4VDV.js → chunk-IQMHDRJF.js} +2 -2
  4. package/dist/{chunk-PVDU6AZU.js → chunk-JPBVKXOL.js} +1 -1
  5. package/dist/{chunk-CPJJ3FPW.js → chunk-RRPWJAIJ.js} +26 -12
  6. package/dist/chunk-RXEO4WPI.js +111 -0
  7. package/dist/{chunk-RMNXVWQ4.js → chunk-YHH5U2NW.js} +1 -1
  8. package/dist/{color-T63FLJNH.js → color-WBNVPFT5.js} +157 -158
  9. package/dist/{menu-52PXC5PL.js → menu-5JZMGNOT.js} +2 -1
  10. package/dist/{menu-YIGMPFUP.js → menu-O3G4LM5O.js} +3 -2
  11. package/dist/next/index.js +6 -5
  12. package/dist/react/context-menu.d.ts +1 -1
  13. package/dist/react/context-menu.js +2 -1
  14. package/dist/react/image.js +1 -1
  15. package/dist/react/index.js +6 -5
  16. package/dist/react/input.js +1 -1
  17. package/dist/react/select.d.ts +6 -1
  18. package/dist/react/select.js +2 -1
  19. package/dist/react/video.js +1 -1
  20. package/dist/react-router/index.js +6 -5
  21. package/dist/viewer-C323RYNZ.js +3 -0
  22. package/package.json +1 -1
  23. package/recipes/color/styles.css +12 -7
  24. package/recipes/context-menu/styles.css +9 -2
  25. package/recipes/select/styles.css +24 -7
  26. package/registry.json +98 -5
  27. package/src/core/floating.ts +71 -0
  28. package/src/react/context-menu/root.tsx +30 -15
  29. package/src/react/context-menu/styles.ts +9 -2
  30. package/src/react/floating.ts +185 -0
  31. package/src/react/input/color-styles.ts +12 -7
  32. package/src/react/input/color.tsx +30 -27
  33. package/src/react/select/context.ts +5 -0
  34. package/src/react/select/root.tsx +33 -21
  35. package/src/react/select/styles.ts +24 -7
  36. package/dist/viewer-N5WLFB24.js +0 -3
@@ -1,8 +1,10 @@
1
+ import { tabOut, useFloating } from './chunk-RXEO4WPI.js';
1
2
  import { createSelect } from './chunk-NONGREXC.js';
2
3
  import { Slot } from './chunk-HS3X3XCW.js';
3
4
  import { groupRows } from './chunk-DTWZDONY.js';
4
5
  import { shortenQuery } from './chunk-4ZPMP47J.js';
5
- import { createContext, useContext, useLayoutEffect, useId, useMemo, useRef, useState, useEffect, useCallback } from 'react';
6
+ import { createPortal } from 'react-dom';
7
+ import { createContext, useContext, useLayoutEffect, useId, useMemo, useRef, useCallback, useState, useEffect } from 'react';
6
8
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
9
 
8
10
  // src/react/select/styles.ts
@@ -21,12 +23,17 @@ var SELECT_STYLES = `
21
23
  --enigma-select-panel-border: #333333;
22
24
  --enigma-select-panel-radius: 0.625rem;
23
25
  --enigma-select-panel-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
24
- --enigma-select-panel-width: 100%;
25
26
  --enigma-select-list-height: 15rem;
26
27
 
27
28
  --enigma-select-active-bg: #2a2a2a;
28
29
  --enigma-select-accent: #fbbf24;
29
30
  --enigma-select-tag-bg: #2a2a2a;
31
+
32
+ /* The layer every portaled popup shares: above the lightbox (9999), because a popup is
33
+ opened FROM whatever is on top. The select, the colour picker and the context menu
34
+ each declare it with the same value, so overriding it once moves all three. Here and
35
+ not in the light-scheme block, or half the readers get an invalid z-index. */
36
+ --enigma-floating-z: 10000;
30
37
  }
31
38
 
32
39
  @media (prefers-color-scheme: light) {
@@ -131,11 +138,24 @@ var SELECT_STYLES = `
131
138
  [data-enigma-select-trigger][data-clearable]:focus [data-enigma-select-caret] { opacity: 0; }
132
139
  [data-enigma-select-clear]:hover { color: var(--enigma-select-text); background: var(--enigma-select-active-bg); }
133
140
 
141
+ /* Portaled to <body> and placed in viewport coordinates against the trigger (see
142
+ react/floating.ts), so no ancestor's overflow can clip it and no dialog can stack over it.
143
+ left, top, max-height and data-side are written by the placement: below the trigger,
144
+ flipped above when there is more room there, and capped to the room it has. The column
145
+ is what makes the cap shrink the LIST, which scrolls, instead of cutting off the panel. */
134
146
  [data-enigma-select-content] {
135
- position: absolute; z-index: 50;
136
- top: calc(100% + 0.25rem); left: 0;
137
- min-width: var(--enigma-select-panel-width); box-sizing: border-box;
147
+ position: fixed; z-index: var(--enigma-floating-z);
148
+ display: flex; flex-direction: column;
149
+ /* As wide as the trigger, measured - 100% would now be a percentage of the window - and
150
+ wider only for content that cannot shrink. min-content is what keeps the rest at the
151
+ trigger's width: fixed, the panel's room is the whole window, and a long label or a
152
+ long "Nothing matches" would otherwise widen it to max-width. --enigma-select-panel-width
153
+ overrides the trigger's width. */
154
+ width: min-content;
155
+ min-width: var(--enigma-select-panel-width, var(--enigma-anchor-width, auto)); box-sizing: border-box;
138
156
  padding: 0.25rem;
157
+ color: var(--enigma-select-text);
158
+ font-size: var(--enigma-select-font-size);
139
159
  background: var(--enigma-select-panel-bg);
140
160
  border: 1px solid var(--enigma-select-panel-border);
141
161
  border-radius: var(--enigma-select-panel-radius);
@@ -146,9 +166,6 @@ var SELECT_STYLES = `
146
166
  trigger wider than this, which is what it is for. */
147
167
  max-width: min(28rem, calc(100vw - 2rem));
148
168
  }
149
- /* Opening upwards is not a variant, it is the same panel measured against the window: near
150
- the bottom of the screen the list would otherwise be unreachable. */
151
- [data-enigma-select-content][data-side="top"] { top: auto; bottom: calc(100% + 0.25rem); }
152
169
  [data-enigma-select-content][data-state="closed"] { animation: enigma-select-out 120ms ease-in forwards; pointer-events: none; }
153
170
 
154
171
  @keyframes enigma-select-in {
@@ -180,6 +197,8 @@ var SELECT_STYLES = `
180
197
 
181
198
  [data-enigma-select-list] {
182
199
  max-height: var(--enigma-select-list-height);
200
+ /* Allowed below its content, so the panel's placement cap shrinks it and it scrolls. */
201
+ min-height: 0; flex: 0 1 auto;
183
202
  margin-top: 0.25rem;
184
203
  overflow-y: auto; overscroll-behavior: contain;
185
204
  }
@@ -295,6 +314,8 @@ function SelectRoot(props) {
295
314
  const triggerRef = useRef(null);
296
315
  const fieldRef = useRef(null);
297
316
  const rootRef = useRef(null);
317
+ const contentRef = useRef(null);
318
+ const owns = useCallback((node) => Boolean(node && (rootRef.current?.contains(node) || contentRef.current?.contains(node))), []);
298
319
  const latest = useRef(props);
299
320
  latest.current = props;
300
321
  const instance = useMemo(() => createSelect({
@@ -367,10 +388,10 @@ function SelectRoot(props) {
367
388
  const closedItself = wasOpen.current && !state.open;
368
389
  wasOpen.current = state.open;
369
390
  if (!closedItself || !open) return;
370
- const held = rootRef.current?.contains(document.activeElement);
391
+ const held = owns(document.activeElement);
371
392
  setOpen(false);
372
393
  if (held) triggerRef.current?.focus();
373
- }, [state.open, open, setOpen]);
394
+ }, [state.open, open, setOpen, owns]);
374
395
  const close = useCallback(() => {
375
396
  setOpen(false);
376
397
  triggerRef.current?.focus();
@@ -378,12 +399,12 @@ function SelectRoot(props) {
378
399
  useEffect(() => {
379
400
  if (!open) return;
380
401
  const onPointerDown = (event) => {
381
- if (rootRef.current?.contains(event.target)) return;
402
+ if (owns(event.target)) return;
382
403
  setOpen(false);
383
404
  };
384
405
  document.addEventListener("pointerdown", onPointerDown, true);
385
406
  return () => document.removeEventListener("pointerdown", onPointerDown, true);
386
- }, [open, setOpen]);
407
+ }, [open, setOpen, owns]);
387
408
  const onListKeyDown = useCallback((event) => {
388
409
  const move = MOVE_KEYS[event.key];
389
410
  if (move) {
@@ -413,6 +434,7 @@ function SelectRoot(props) {
413
434
  return;
414
435
  }
415
436
  if (event.key === "Tab" && open) {
437
+ if (contentRef.current?.contains(event.target)) tabOut(event, contentRef.current, triggerRef.current);
416
438
  setOpen(false);
417
439
  return;
418
440
  }
@@ -450,6 +472,7 @@ function SelectRoot(props) {
450
472
  optionId: (index) => `${id}-option-${index}`,
451
473
  triggerRef,
452
474
  fieldRef,
475
+ contentRef,
453
476
  close,
454
477
  onListKeyDown
455
478
  }), [instance, state, setOpen, disabled, clearable, multiple, isSearchable, isEmpty, loading, emptyLabel, loadingLabel, ids, id, close, onListKeyDown]);
@@ -608,9 +631,7 @@ function SelectValue({ placeholder = "Select", tags, maxTags = 3, children, ...p
608
631
  }
609
632
  function SelectContent({ closeDuration = 120, children, ...props }) {
610
633
  const select = useSelectContext("Select.Content");
611
- const ref = useRef(null);
612
634
  const [mounted, setMounted] = useState(select.state.open);
613
- const [side, setSide] = useState("bottom");
614
635
  useEffect(() => {
615
636
  if (select.state.open) {
616
637
  setMounted(true);
@@ -619,29 +640,29 @@ function SelectContent({ closeDuration = 120, children, ...props }) {
619
640
  const timer = setTimeout(() => setMounted(false), closeDuration);
620
641
  return () => clearTimeout(timer);
621
642
  }, [select.state.open, closeDuration]);
622
- useLayoutEffect(() => {
623
- if (!select.state.open || !ref.current) return;
624
- const trigger = select.triggerRef.current?.getBoundingClientRect();
625
- if (!trigger) return;
626
- const height = ref.current.offsetHeight;
627
- const below = window.innerHeight - trigger.bottom;
628
- setSide(below < height && trigger.top > below ? "top" : "bottom");
629
- }, [select.state.open, select.state.visible.length, select.triggerRef]);
630
- if (!mounted) return null;
631
- return /* @__PURE__ */ jsx(
632
- "div",
633
- {
634
- ...props,
635
- ref,
636
- "data-enigma-select-content": "",
637
- "data-state": select.state.open ? "open" : "closed",
638
- "data-side": side,
639
- onKeyDown: (event) => {
640
- props.onKeyDown?.(event);
641
- if (!event.defaultPrevented) select.onListKeyDown(event);
642
- },
643
- children
644
- }
643
+ const container = useFloating({
644
+ open: mounted && select.state.open,
645
+ anchorRef: select.triggerRef,
646
+ panelRef: select.contentRef,
647
+ deps: [select.state.visible.length]
648
+ });
649
+ if (!mounted || !container) return null;
650
+ return createPortal(
651
+ /* @__PURE__ */ jsx(
652
+ "div",
653
+ {
654
+ ...props,
655
+ ref: select.contentRef,
656
+ "data-enigma-select-content": "",
657
+ "data-state": select.state.open ? "open" : "closed",
658
+ onKeyDown: (event) => {
659
+ props.onKeyDown?.(event);
660
+ if (!event.defaultPrevented) select.onListKeyDown(event);
661
+ },
662
+ children
663
+ }
664
+ ),
665
+ container
645
666
  );
646
667
  }
647
668
  function SelectSearch({ placeholder = "Search", onKeyDown, ...props }) {
@@ -42,7 +42,7 @@ function NewTab() {
42
42
  /* @__PURE__ */ jsx("path", { d: "M18 14v5a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 4 19V7.5A1.5 1.5 0 0 1 5.5 6H10" })
43
43
  ] });
44
44
  }
45
- var ImageMenu = lazy(() => import('./menu-YIGMPFUP.js').then((module) => ({ default: module.ImageMenu })));
45
+ var ImageMenu = lazy(() => import('./menu-O3G4LM5O.js').then((module) => ({ default: module.ImageMenu })));
46
46
  function ImageViewer({
47
47
  items,
48
48
  index,
@@ -6,12 +6,12 @@ import { jsx, jsxs } from 'react/jsx-runtime';
6
6
  function toItem(source) {
7
7
  return typeof source === "string" ? { src: source } : source;
8
8
  }
9
- var ImageViewer = lazy(() => import('./viewer-N5WLFB24.js').then((module) => ({ default: module.ImageViewer })));
9
+ var ImageViewer = lazy(() => import('./viewer-C323RYNZ.js').then((module) => ({ default: module.ImageViewer })));
10
10
  var prefetched = false;
11
11
  function prefetchViewer() {
12
12
  if (prefetched) return;
13
13
  prefetched = true;
14
- void import('./viewer-N5WLFB24.js');
14
+ void import('./viewer-C323RYNZ.js');
15
15
  }
16
16
  var ZOOM_DEFAULTS = { min: 1, max: 8, wheel: true, doubleClick: true };
17
17
  function Image(props) {
@@ -351,7 +351,7 @@ var CONTROL_DEFAULTS = {
351
351
  fullscreen: true,
352
352
  download: false
353
353
  };
354
- var VideoMenu = lazy(() => import('./menu-52PXC5PL.js').then((module) => ({ default: module.VideoMenu })));
354
+ var VideoMenu = lazy(() => import('./menu-5JZMGNOT.js').then((module) => ({ default: module.VideoMenu })));
355
355
  var injected = false;
356
356
  function injectStyles() {
357
357
  if (injected || typeof document === "undefined") return;
@@ -1,4 +1,5 @@
1
1
  import { createContextMenu, clipboardAction, performClipboardAction, inspectClipboardTarget, CLIPBOARD_PREFIX, clipboardEntries, isAction, clipboardHasText } from './chunk-YQV5XZIA.js';
2
+ import { floatingContainer, measureFrame, placeFloating } from './chunk-RXEO4WPI.js';
2
3
  import { shortcutTokens } from './chunk-4VUHQFAT.js';
3
4
  import { Slot } from './chunk-HS3X3XCW.js';
4
5
  import { shortenQuery } from './chunk-4ZPMP47J.js';
@@ -28,8 +29,11 @@ var CONTEXT_MENU_STYLES = `
28
29
  top: the image viewer's own three-dot menu was portalled to the same body and painted
29
30
  underneath it, so its rows could be neither seen nor pressed. A menu is the topmost
30
31
  transient surface on a page, and this is the number that says so. It belongs here and
31
- not in the light-scheme block, or half the readers get an invalid z-index. */
32
- --enigma-menu-z: 10000;
32
+ not in the light-scheme block, or half the readers get an invalid z-index.
33
+ --enigma-floating-z is the layer the select and the colour picker share, declared with
34
+ the same value in their sheets, so overriding it once moves every popup together. */
35
+ --enigma-floating-z: 10000;
36
+ --enigma-menu-z: var(--enigma-floating-z);
33
37
  }
34
38
 
35
39
  @media (prefers-color-scheme: light) {
@@ -51,6 +55,9 @@ var CONTEXT_MENU_STYLES = `
51
55
  ancestor cannot drag it away from the place it was opened. */
52
56
  [data-enigma-menu-panel] {
53
57
  position: fixed; z-index: var(--enigma-menu-z);
58
+ /* A column, so the max-height the placement writes in a short window shrinks the LIST,
59
+ which scrolls, rather than letting rows spill past the panel's edge. */
60
+ display: flex; flex-direction: column;
54
61
  box-sizing: border-box;
55
62
  min-width: var(--enigma-menu-min-width);
56
63
  max-width: min(22rem, calc(100vw - 1rem));
@@ -105,6 +112,7 @@ var CONTEXT_MENU_STYLES = `
105
112
 
106
113
  [data-enigma-menu-list] {
107
114
  max-height: var(--enigma-menu-max-height);
115
+ min-height: 0; flex: 0 1 auto;
108
116
  overflow-y: auto; overscroll-behavior: contain;
109
117
  }
110
118
 
@@ -534,7 +542,7 @@ function ContextMenuContent({ chunk = 40, portal = true, ...props }) {
534
542
  const panels = /* @__PURE__ */ jsx(Fragment, { children: menu.state.levels.map((_, level) => /* @__PURE__ */ jsx(ContextMenuPanel, { level, chunk, ...props }, level)) });
535
543
  if (!portal) return panels;
536
544
  if (!mounted || typeof document === "undefined") return null;
537
- return createPortal(panels, document.body);
545
+ return createPortal(panels, floatingContainer(menu.triggerRef.current) ?? document.body);
538
546
  }
539
547
  function ContextMenuPanel({ level, chunk = 40, ...props }) {
540
548
  const menu = useContextMenuContext("ContextMenu.Panel");
@@ -547,27 +555,33 @@ function ContextMenuPanel({ level, chunk = 40, ...props }) {
547
555
  useLayoutEffect(() => {
548
556
  const panel = ref.current;
549
557
  if (!panel) return;
558
+ panel.style.maxHeight = "";
550
559
  const width = panel.offsetWidth;
551
560
  const height = panel.offsetHeight;
552
- const vw = window.innerWidth;
553
- const vh = window.innerHeight;
561
+ const frame = measureFrame(panel);
562
+ const { boundary } = frame;
554
563
  let left;
555
564
  let top;
556
565
  if (level === 0) {
557
- left = point?.x ?? MARGIN;
558
- top = point?.y ?? MARGIN;
559
- if (left + width > vw - MARGIN) left = Math.max(MARGIN, left - width);
560
- if (top + height > vh - MARGIN) top = Math.max(MARGIN, top - height);
566
+ const x = point?.x ?? MARGIN;
567
+ const y = point?.y ?? MARGIN;
568
+ const placedAt = placeFloating({ left: x, right: x, top: y, bottom: y }, { width, height }, { boundary, margin: MARGIN });
569
+ left = placedAt.left;
570
+ top = placedAt.top;
571
+ panel.style.maxHeight = `${placedAt.maxHeight}px`;
561
572
  } else {
562
573
  const parent = document.getElementById(menu.panelId(level - 1))?.getBoundingClientRect();
563
574
  const row = document.getElementById(menu.itemId(level - 1, menu.state.levels[level - 1]?.active ?? -1))?.getBoundingClientRect();
564
575
  const anchor = parent ?? { left: point?.x ?? MARGIN, right: point?.x ?? MARGIN, top: point?.y ?? MARGIN };
565
576
  left = anchor.right - 2;
566
- if (left + width > vw - MARGIN) left = Math.max(MARGIN, anchor.left - width + 2);
577
+ if (left + width > boundary.right - MARGIN) left = Math.max(boundary.left + MARGIN, anchor.left - width + 2);
567
578
  top = (row?.top ?? anchor.top) - 4;
568
- if (top + height > vh - MARGIN) top = Math.max(MARGIN, vh - MARGIN - height);
579
+ if (top + height > boundary.bottom - MARGIN) top = Math.max(boundary.top + MARGIN, boundary.bottom - MARGIN - height);
569
580
  }
570
- setPlaced({ left: Math.max(MARGIN, Math.round(left)), top: Math.max(MARGIN, Math.round(top)) });
581
+ setPlaced({
582
+ left: Math.max(boundary.left + MARGIN, Math.round(left)) - frame.x,
583
+ top: Math.max(boundary.top + MARGIN, Math.round(top)) - frame.y
584
+ });
571
585
  }, [level, point?.x, point?.y, rows, loading, menu, menu.state.levels.length]);
572
586
  const isPlaced = placed !== null;
573
587
  const isDeepest = menu.state.levels.length - 1 === level;
@@ -0,0 +1,111 @@
1
+ import { useState, useLayoutEffect, useCallback, useEffect } from 'react';
2
+
3
+ // src/core/floating.ts
4
+ function placeFloating(anchor, size, options) {
5
+ const { boundary, margin = 8, gap = 0, side = "auto" } = options;
6
+ const below = boundary.bottom - margin - anchor.bottom - gap;
7
+ const above = anchor.top - gap - boundary.top - margin;
8
+ const chosen = side !== "auto" ? side : size.height <= below || below >= above ? "bottom" : "top";
9
+ const maxHeight = Math.max(0, Math.floor(chosen === "bottom" ? below : above));
10
+ const height = Math.min(size.height, maxHeight);
11
+ const top = chosen === "bottom" ? anchor.bottom + gap : anchor.top - gap - height;
12
+ const maxLeft = boundary.right - margin - size.width;
13
+ let left = anchor.left;
14
+ if (left > maxLeft) left = anchor.right - size.width;
15
+ left = Math.max(boundary.left + margin, Math.min(left, maxLeft));
16
+ return { left: Math.round(left), top: Math.round(top), side: chosen, maxHeight };
17
+ }
18
+ function floatingContainer(anchor) {
19
+ if (typeof document === "undefined") return null;
20
+ const host = anchor?.closest("dialog[open], [popover], [role=dialog], [role=alertdialog], [aria-modal=true]");
21
+ return host ?? document.body;
22
+ }
23
+ function measureFrame(panel) {
24
+ const root = document.documentElement;
25
+ const viewport = { left: 0, top: 0, right: root.clientWidth || window.innerWidth, bottom: root.clientHeight || window.innerHeight };
26
+ const host = panel.parentElement;
27
+ if (!host || host === document.body) return { x: 0, y: 0, boundary: viewport };
28
+ const probe = document.createElement("div");
29
+ probe.style.cssText = "position:fixed;left:0;top:0;width:0;height:0;visibility:hidden;pointer-events:none";
30
+ host.appendChild(probe);
31
+ const origin = probe.getBoundingClientRect();
32
+ probe.remove();
33
+ const box = host.getBoundingClientRect();
34
+ const isBlock = Math.abs(origin.left - (box.left + host.clientLeft)) < 1 && Math.abs(origin.top - (box.top + host.clientTop)) < 1;
35
+ const style = getComputedStyle(host);
36
+ const clips = style.overflowX !== "visible" || style.overflowY !== "visible";
37
+ const boundary = isBlock && clips ? {
38
+ left: Math.max(viewport.left, box.left),
39
+ top: Math.max(viewport.top, box.top),
40
+ right: Math.min(viewport.right, box.right),
41
+ bottom: Math.min(viewport.bottom, box.bottom)
42
+ } : viewport;
43
+ return { x: origin.left, y: origin.top, boundary };
44
+ }
45
+ function useFloating({ open, anchorRef, panelRef, gap = 4, margin = 8, side = "auto", deps = [] }) {
46
+ const [container, setContainer] = useState(null);
47
+ useLayoutEffect(() => {
48
+ if (open) setContainer(floatingContainer(anchorRef.current));
49
+ }, [open, anchorRef]);
50
+ const place = useCallback(() => {
51
+ const anchor = anchorRef.current;
52
+ const panel = panelRef.current;
53
+ if (!anchor || !panel) return;
54
+ const rect = anchor.getBoundingClientRect();
55
+ panel.style.setProperty("--enigma-anchor-width", `${rect.width}px`);
56
+ panel.style.maxHeight = "";
57
+ const frame = measureFrame(panel);
58
+ const placed = placeFloating(rect, { width: panel.offsetWidth, height: panel.offsetHeight }, { boundary: frame.boundary, margin, gap, side });
59
+ panel.style.left = `${placed.left - frame.x}px`;
60
+ panel.style.top = `${placed.top - frame.y}px`;
61
+ panel.style.maxHeight = `${placed.maxHeight}px`;
62
+ panel.setAttribute("data-side", placed.side);
63
+ }, [anchorRef, panelRef, margin, gap, side]);
64
+ useLayoutEffect(() => {
65
+ if (open) place();
66
+ }, [open, container, place, ...deps]);
67
+ useEffect(() => {
68
+ if (!open || !container) return;
69
+ let frame = 0;
70
+ const schedule = () => {
71
+ if (frame) return;
72
+ frame = requestAnimationFrame(() => {
73
+ frame = 0;
74
+ place();
75
+ });
76
+ };
77
+ const onScroll = (event) => {
78
+ if (panelRef.current?.contains(event.target)) return;
79
+ schedule();
80
+ };
81
+ document.addEventListener("scroll", onScroll, true);
82
+ window.addEventListener("resize", schedule);
83
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(schedule);
84
+ if (anchorRef.current) observer?.observe(anchorRef.current);
85
+ if (panelRef.current) observer?.observe(panelRef.current);
86
+ return () => {
87
+ cancelAnimationFrame(frame);
88
+ document.removeEventListener("scroll", onScroll, true);
89
+ window.removeEventListener("resize", schedule);
90
+ observer?.disconnect();
91
+ };
92
+ }, [open, container, place, anchorRef, panelRef]);
93
+ return container;
94
+ }
95
+ var TABBABLE = "a[href], button:not(:disabled), input:not(:disabled):not([type=hidden]), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex='-1'])";
96
+ function tabOut(event, panel, trigger) {
97
+ if (event.key !== "Tab" || !panel || !trigger) return false;
98
+ const stops = Array.from(panel.querySelectorAll(TABBABLE));
99
+ const at = stops.indexOf(document.activeElement);
100
+ if (event.shiftKey) {
101
+ if (at > 0) return false;
102
+ event.preventDefault();
103
+ trigger.focus();
104
+ return true;
105
+ }
106
+ if (at !== -1 && at < stops.length - 1) return false;
107
+ trigger.focus();
108
+ return true;
109
+ }
110
+
111
+ export { floatingContainer, measureFrame, placeFloating, tabOut, useFloating };
@@ -25,7 +25,7 @@ var COLOR_FIELD_PROPS = {
25
25
  };
26
26
  var PasswordExtras = lazy(() => import('./password-3DRQYAYQ.js').then((module) => ({ default: module.PasswordExtras })));
27
27
  var SearchExtras = lazy(() => import('./search-PBZORZ7P.js').then((module) => ({ default: module.SearchExtras })));
28
- var ColorExtras = lazy(() => import('./color-T63FLJNH.js').then((module) => ({ default: module.ColorExtras })));
28
+ var ColorExtras = lazy(() => import('./color-WBNVPFT5.js').then((module) => ({ default: module.ColorExtras })));
29
29
  var Input = forwardRef(function Input2(props, forwardedRef) {
30
30
  const {
31
31
  reveal,