@reopt-ai/opt-ui-primitives 1.5.1 → 1.5.2

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.cjs CHANGED
@@ -176,6 +176,32 @@ function removeGlobalListeners() {
176
176
  document.removeEventListener("touchstart", handlePointerDown, true);
177
177
  }
178
178
  }
179
+ /**
180
+ * Subscribe to the shared modality tracking and read it on demand.
181
+ *
182
+ * Returns a stable predicate answering "should this focus show as visible?".
183
+ * Widgets that must react ONLY to keyboard focus (a tooltip must not pop open
184
+ * because the user clicked its trigger) gate their focus handler on it — the
185
+ * `visibleOnly` behavior.
186
+ *
187
+ * The browser's own `:focus-visible` is the authority whenever the element and
188
+ * the engine support it: it knows about text inputs (which show a ring even
189
+ * after a click), programmatic focus, and platform conventions the modality
190
+ * heuristic cannot infer. The document-level heuristic is the fallback for
191
+ * engines and test environments where matching `:focus-visible` throws.
192
+ */
193
+ function useKeyboardModality() {
194
+ (0, react.useEffect)(() => {
195
+ addGlobalListeners();
196
+ return removeGlobalListeners;
197
+ }, []);
198
+ return (0, react.useCallback)((element) => {
199
+ if (element) try {
200
+ if (element.matches(":focus-visible")) return true;
201
+ } catch (_unused) {}
202
+ return hadKeyboardEvent;
203
+ }, []);
204
+ }
179
205
  /** Returns `focusVisibleProps` to spread on a focusable element. */
180
206
  function useFocusVisible() {
181
207
  (0, react.useEffect)(() => {
@@ -192,6 +218,89 @@ function useFocusVisible() {
192
218
  } };
193
219
  }
194
220
  //#endregion
221
+ //#region src/hooks/use-item-registry.ts
222
+ const DOCUMENT_POSITION_FOLLOWING = 4;
223
+ function resolveElement(entry) {
224
+ if (entry.element) return entry.element;
225
+ if (typeof document === "undefined") return null;
226
+ return document.getElementById(entry.id);
227
+ }
228
+ /** Insert `entry` at its document position instead of appending it. */
229
+ function insertByDomOrder(list, entry) {
230
+ const element = resolveElement(entry);
231
+ const lastElement = list.length > 0 ? resolveElement(list[list.length - 1]) : null;
232
+ if (!element || !lastElement || lastElement.compareDocumentPosition(element) & DOCUMENT_POSITION_FOLLOWING) {
233
+ list.push(entry);
234
+ return;
235
+ }
236
+ for (let i = 0; i < list.length; i += 1) {
237
+ const other = resolveElement(list[i]);
238
+ if (other && element.compareDocumentPosition(other) & DOCUMENT_POSITION_FOLLOWING) {
239
+ list.splice(i, 0, entry);
240
+ return;
241
+ }
242
+ }
243
+ list.push(entry);
244
+ }
245
+ /** Id of the first item that is not disabled. */
246
+ function firstEnabledId(items) {
247
+ var _items$find$id, _items$find;
248
+ return (_items$find$id = (_items$find = items.find((item) => !item.disabled)) === null || _items$find === void 0 ? void 0 : _items$find.id) !== null && _items$find$id !== void 0 ? _items$find$id : null;
249
+ }
250
+ /** Id of the last item that is not disabled. */
251
+ function lastEnabledId(items) {
252
+ for (let i = items.length - 1; i >= 0; i -= 1) if (!items[i].disabled) return items[i].id;
253
+ return null;
254
+ }
255
+ /**
256
+ * Id `delta` enabled items away from `activeId` (disabled skipped).
257
+ *
258
+ * Clamps at the ends by default — the listbox/menu behavior. Pass
259
+ * `{ wrap: true }` for the WAI-ARIA tabs behavior, where the arrow keys cycle.
260
+ * With no active item, a forward move lands on the first enabled item and a
261
+ * backward move on the last.
262
+ */
263
+ function moveActiveId(items, activeId, delta, options) {
264
+ const enabled = items.filter((item) => !item.disabled);
265
+ if (enabled.length === 0) return null;
266
+ const index = enabled.findIndex((item) => item.id === activeId);
267
+ if (index < 0) {
268
+ if (options === null || options === void 0 ? void 0 : options.wrap) return enabled[0].id;
269
+ return delta > 0 ? enabled[0].id : enabled[enabled.length - 1].id;
270
+ }
271
+ const count = enabled.length;
272
+ return enabled[(options === null || options === void 0 ? void 0 : options.wrap) ? ((index + delta) % count + count) % count : Math.min(count - 1, Math.max(0, index + delta))].id;
273
+ }
274
+ /**
275
+ * Id of the enabled item nearest to `index`: forward first (the item that took
276
+ * the removed slot), then backward. `null` when the list has none left.
277
+ *
278
+ * This is what keeps the navigation POSITION when the active item is removed.
279
+ */
280
+ function nearestEnabledId(items, index) {
281
+ for (let i = Math.max(0, index); i < items.length; i += 1) if (!items[i].disabled) return items[i].id;
282
+ for (let i = Math.min(index, items.length) - 1; i >= 0; i -= 1) if (!items[i].disabled) return items[i].id;
283
+ return null;
284
+ }
285
+ /** Creates a document-ordered item registry. Stable across renders. */
286
+ function useItemRegistry() {
287
+ const items = (0, react.useRef)([]);
288
+ return {
289
+ items,
290
+ registerItem: (0, react.useCallback)((entry) => {
291
+ const list = items.current;
292
+ const existing = list.findIndex((item) => item.id === entry.id);
293
+ if (existing >= 0) list.splice(existing, 1);
294
+ insertByDomOrder(list, entry);
295
+ }, []),
296
+ unregisterItem: (0, react.useCallback)((id) => {
297
+ const index = items.current.findIndex((item) => item.id === id);
298
+ if (index >= 0) items.current.splice(index, 1);
299
+ return index;
300
+ }, [])
301
+ };
302
+ }
303
+ //#endregion
195
304
  //#region src/hooks/use-roving-tabindex.ts
196
305
  /**
197
306
  * Implements roving tabindex pattern for keyboard navigation.
@@ -203,7 +312,7 @@ function useFocusVisible() {
203
312
  */
204
313
  function useRovingTabindex(options = {}) {
205
314
  const { orientation = "horizontal", loop = false, rtl = false, columns } = options;
206
- const itemsRef = (0, react.useRef)([]);
315
+ const { items: itemsRef, registerItem: registerEntry, unregisterItem: removeItem } = useItemRegistry();
207
316
  const [activeId, setActiveId] = (0, react.useState)(null);
208
317
  const containerRef = (0, react.useRef)(null);
209
318
  const seededRef = (0, react.useRef)(false);
@@ -211,14 +320,8 @@ function useRovingTabindex(options = {}) {
211
320
  return itemsRef.current.filter((item) => !item.disabled);
212
321
  }, []);
213
322
  const register = (0, react.useCallback)((id, element, disabled) => {
214
- const existing = itemsRef.current.findIndex((item) => item.id === id);
215
- const activeBecameDisabled = existing >= 0 && id === activeId && !!disabled;
216
- if (existing >= 0) itemsRef.current[existing] = {
217
- id,
218
- element,
219
- disabled
220
- };
221
- else itemsRef.current.push({
323
+ const activeBecameDisabled = itemsRef.current.findIndex((item) => item.id === id) >= 0 && id === activeId && !!disabled;
324
+ registerEntry({
222
325
  id,
223
326
  element,
224
327
  disabled
@@ -227,21 +330,28 @@ function useRovingTabindex(options = {}) {
227
330
  seededRef.current = true;
228
331
  setActiveId(id);
229
332
  } else if (activeBecameDisabled) {
230
- var _itemsRef$current$fin, _itemsRef$current$fin2;
231
- const next = (_itemsRef$current$fin = (_itemsRef$current$fin2 = itemsRef.current.find((item) => !item.disabled)) === null || _itemsRef$current$fin2 === void 0 ? void 0 : _itemsRef$current$fin2.id) !== null && _itemsRef$current$fin !== void 0 ? _itemsRef$current$fin : null;
333
+ const next = firstEnabledId(itemsRef.current);
232
334
  if (next === null) seededRef.current = false;
233
335
  setActiveId(next);
234
336
  }
235
- }, [activeId]);
337
+ }, [
338
+ activeId,
339
+ itemsRef,
340
+ registerEntry
341
+ ]);
236
342
  const unregister = (0, react.useCallback)((id) => {
237
- itemsRef.current = itemsRef.current.filter((item) => item.id !== id);
343
+ const removedIndex = removeItem(id);
344
+ if (removedIndex < 0) return;
238
345
  if (activeId === id) {
239
- var _getEnabledItems$0$id, _getEnabledItems$;
240
- const next = (_getEnabledItems$0$id = (_getEnabledItems$ = getEnabledItems()[0]) === null || _getEnabledItems$ === void 0 ? void 0 : _getEnabledItems$.id) !== null && _getEnabledItems$0$id !== void 0 ? _getEnabledItems$0$id : null;
346
+ const next = nearestEnabledId(itemsRef.current, removedIndex);
241
347
  if (next === null) seededRef.current = false;
242
348
  setActiveId(next);
243
349
  }
244
- }, [activeId, getEnabledItems]);
350
+ }, [
351
+ activeId,
352
+ itemsRef,
353
+ removeItem
354
+ ]);
245
355
  const moveTo = (0, react.useCallback)((id) => {
246
356
  setActiveId(id);
247
357
  const item = itemsRef.current.find((i) => i.id === id);
@@ -279,12 +389,12 @@ function useRovingTabindex(options = {}) {
279
389
  moveByOffset(rtl ? 1 : -1);
280
390
  } else if (e.key === "Home") {
281
391
  e.preventDefault();
282
- const enabled = getEnabledItems();
283
- if (enabled.length > 0) moveTo(enabled[0].id);
392
+ const first = firstEnabledId(itemsRef.current);
393
+ if (first) moveTo(first);
284
394
  } else if (e.key === "End") {
285
395
  e.preventDefault();
286
- const enabled = getEnabledItems();
287
- if (enabled.length > 0) moveTo(enabled[enabled.length - 1].id);
396
+ const last = lastEnabledId(itemsRef.current);
397
+ if (last) moveTo(last);
288
398
  }
289
399
  }, [
290
400
  orientation,
@@ -441,6 +551,28 @@ function useFloating(options = {}) {
441
551
  Object.assign(elements.floating.style, styles);
442
552
  }
443
553
  }));
554
+ m.push({
555
+ name: "optTransformOrigin",
556
+ fn(state) {
557
+ var _ref2, _middlewareData$shift, _middlewareData$shift2;
558
+ const { elements, middlewareData, placement: resolved, rects } = state;
559
+ const [resolvedSide, resolvedAlign] = resolved.split("-");
560
+ const isVertical = resolvedSide === "top" || resolvedSide === "bottom";
561
+ const so = sideOffsetRef.current;
562
+ const sideOffsetValue = (_ref2 = typeof so === "function" ? so({
563
+ rects,
564
+ placement: resolved
565
+ }) : so) !== null && _ref2 !== void 0 ? _ref2 : 0;
566
+ const shiftAmount = isVertical ? ((_middlewareData$shift = middlewareData.shift) === null || _middlewareData$shift === void 0 ? void 0 : _middlewareData$shift.x) || 0 : ((_middlewareData$shift2 = middlewareData.shift) === null || _middlewareData$shift2 === void 0 ? void 0 : _middlewareData$shift2.y) || 0;
567
+ let crossOrigin;
568
+ if (!resolvedAlign) crossOrigin = "50%";
569
+ else if (Math.abs(shiftAmount) <= 1) crossOrigin = resolvedAlign === "start" ? "0%" : "100%";
570
+ else crossOrigin = isVertical ? `${rects.reference.x + rects.reference.width / 2 - state.x}px` : `${rects.reference.y + rects.reference.height / 2 - state.y}px`;
571
+ const sideOrigin = resolvedSide === "top" || resolvedSide === "left" ? `calc(100% + ${sideOffsetValue}px)` : `${-sideOffsetValue}px`;
572
+ elements.floating.style.setProperty("--opt-transform-origin", isVertical ? `${crossOrigin} ${sideOrigin}` : `${sideOrigin} ${crossOrigin}`);
573
+ return {};
574
+ }
575
+ });
444
576
  m.push((0, _floating_ui_react.hide)({ padding: overflowPadding }));
445
577
  return m;
446
578
  }, [
@@ -495,7 +627,8 @@ function useFloating(options = {}) {
495
627
  ref: floating.refs.setFloating,
496
628
  style: _objectSpread2(_objectSpread2({
497
629
  "--opt-available-width": "100vw",
498
- "--opt-available-height": "100vh"
630
+ "--opt-available-height": "100vh",
631
+ "--opt-transform-origin": "center"
499
632
  }, floating.floatingStyles), isPositioned ? null : { opacity: 0 })
500
633
  }),
501
634
  /**
@@ -565,6 +698,137 @@ function useScrollActiveDescendantIntoView(activeId) {
565
698
  }, [activeId]);
566
699
  }
567
700
  //#endregion
701
+ //#region src/hooks/use-hidden-until-found.ts
702
+ /**
703
+ * Keeps collapsed content reachable by browser find-in-page.
704
+ *
705
+ * The content stays MOUNTED and toggles `hidden="until-found"` instead of
706
+ * unmounting, so Chromium can match text inside it; when it does, the
707
+ * `beforematch` event fires and `onReveal` opens the owning disclosure so the
708
+ * component state stays consistent with what the browser just revealed.
709
+ *
710
+ * React coerces the `hidden` prop to a boolean attribute, so the "until-found"
711
+ * string has to be written imperatively — hence the ref rather than a prop.
712
+ *
713
+ * Progressive enhancement: browsers without `hidden="until-found"` treat it as
714
+ * plain `hidden`, which is the correct fallback.
715
+ */
716
+ function useHiddenUntilFound(ref, { enabled, open, onReveal }) {
717
+ (0, react.useEffect)(() => {
718
+ const el = ref.current;
719
+ if (!el || !enabled) return;
720
+ const handleBeforeMatch = () => onReveal();
721
+ el.addEventListener("beforematch", handleBeforeMatch);
722
+ return () => el.removeEventListener("beforematch", handleBeforeMatch);
723
+ }, [
724
+ ref,
725
+ enabled,
726
+ onReveal
727
+ ]);
728
+ (0, react.useEffect)(() => {
729
+ const el = ref.current;
730
+ if (!el || !enabled) return;
731
+ if (open) el.removeAttribute("hidden");
732
+ else el.setAttribute("hidden", "until-found");
733
+ }, [
734
+ ref,
735
+ enabled,
736
+ open
737
+ ]);
738
+ }
739
+ //#endregion
740
+ //#region src/internal/merge-props.ts
741
+ const EVENT_HANDLER = /^on[A-Z]/;
742
+ function isSyntheticEvent(value) {
743
+ return typeof value === "object" && value !== null && "nativeEvent" in value;
744
+ }
745
+ function makePreventable(event) {
746
+ if (typeof event.preventOptHandler === "function") return;
747
+ event.optHandlerPrevented = false;
748
+ event.preventOptHandler = () => {
749
+ event.optHandlerPrevented = true;
750
+ };
751
+ }
752
+ function chainHandlers(a, b) {
753
+ return (...args) => {
754
+ const event = args[0];
755
+ if (isSyntheticEvent(event)) {
756
+ makePreventable(event);
757
+ a(...args);
758
+ if (event.optHandlerPrevented) return void 0;
759
+ return b(...args);
760
+ }
761
+ a(...args);
762
+ return b(...args);
763
+ };
764
+ }
765
+ /** Merge prop objects left→right with handler/className/style composition. */
766
+ function mergeProps(...parts) {
767
+ const result = {};
768
+ for (const part of parts) {
769
+ if (!part) continue;
770
+ for (const key in part) {
771
+ if (!Object.prototype.hasOwnProperty.call(part, key)) continue;
772
+ const value = part[key];
773
+ const existing = result[key];
774
+ if (EVENT_HANDLER.test(key)) {
775
+ if (typeof value === "function" && typeof existing === "function") result[key] = chainHandlers(existing, value);
776
+ else if (value === void 0 && typeof existing === "function") continue;
777
+ else result[key] = value;
778
+ } else if (key === "className") result[key] = [existing, value].filter(Boolean).join(" ") || void 0;
779
+ else if (key === "style" && existing && typeof existing === "object" && value && typeof value === "object") result[key] = _objectSpread2(_objectSpread2({}, existing), value);
780
+ else result[key] = value;
781
+ }
782
+ }
783
+ return result;
784
+ }
785
+ /**
786
+ * Merge multiple refs (callback or object) into one callback ref.
787
+ *
788
+ * Returns a React 19 ref CLEANUP function, so every merged ref is detached at
789
+ * exactly the right time: callback refs that themselves return a cleanup have
790
+ * it invoked (previously the returned cleanup was dropped on the floor, leaking
791
+ * whatever it was meant to release), and the rest are detached by being called
792
+ * with `null` / having their `.current` reset. Cleanups run in reverse
793
+ * attachment order, mirroring React's own teardown order.
794
+ */
795
+ function mergeRefs(...refs) {
796
+ return (value) => {
797
+ const cleanups = [];
798
+ for (const ref of refs) if (typeof ref === "function") {
799
+ const cleanup = ref(value);
800
+ cleanups.push(typeof cleanup === "function" ? cleanup : () => ref(null));
801
+ } else if (ref != null) {
802
+ const objectRef = ref;
803
+ objectRef.current = value;
804
+ cleanups.push(() => {
805
+ objectRef.current = null;
806
+ });
807
+ }
808
+ return () => {
809
+ for (let i = cleanups.length - 1; i >= 0; i -= 1) cleanups[i]();
810
+ };
811
+ };
812
+ }
813
+ //#endregion
814
+ //#region src/hooks/use-merged-ref.ts
815
+ /**
816
+ * A STABLE merged ref callback.
817
+ *
818
+ * `mergeRefs` returns a fresh function on every call, and React re-attaches a
819
+ * callback ref whenever its identity changes: it detaches first (nulling every
820
+ * ref in the chain), then attaches again. Building the merged ref inline during
821
+ * render therefore made every render null out the element the animation and
822
+ * measurement code reads — which is how the collapsible content on the explore
823
+ * detail pages stopped opening.
824
+ *
825
+ * Memoizing on the ref identities keeps the callback stable across renders, so
826
+ * the refs are attached exactly once.
827
+ */
828
+ function useMergedRef(...refs) {
829
+ return (0, react.useMemo)(() => mergeRefs(...refs), refs);
830
+ }
831
+ //#endregion
568
832
  //#region \0@oxc-project+runtime@0.146.0/helpers/esm/objectWithoutPropertiesLoose.js
569
833
  function _objectWithoutPropertiesLoose(r, e) {
570
834
  if (null == r) return {};
@@ -639,24 +903,14 @@ function DisclosureContent(_ref2) {
639
903
  const { open, setOpen, contentId, triggerId, animated } = useDisclosureContext();
640
904
  const { ref, mounted, dataAttributes } = useEnterLeave(open, { animated: animated && !hiddenUntilFound });
641
905
  const innerRef = (0, react.useRef)(null);
642
- (0, react.useEffect)(() => {
643
- if (innerRef.current) ref.current = innerRef.current;
644
- }, [ref]);
645
- (0, react.useEffect)(() => {
646
- const el = innerRef.current;
647
- if (!el || !hiddenUntilFound) return;
648
- const onBeforeMatch = () => setOpen(true);
649
- el.addEventListener("beforematch", onBeforeMatch);
650
- return () => el.removeEventListener("beforematch", onBeforeMatch);
651
- }, [hiddenUntilFound, setOpen]);
652
- (0, react.useEffect)(() => {
653
- const el = innerRef.current;
654
- if (!el || !hiddenUntilFound) return;
655
- if (open) el.removeAttribute("hidden");
656
- else el.setAttribute("hidden", "until-found");
657
- }, [open, hiddenUntilFound]);
906
+ const contentRef = useMergedRef(innerRef, ref);
907
+ useHiddenUntilFound(innerRef, {
908
+ enabled: hiddenUntilFound,
909
+ open,
910
+ onReveal: (0, react.useCallback)(() => setOpen(true), [setOpen])
911
+ });
658
912
  if (hiddenUntilFound) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", _objectSpread2({
659
- ref: innerRef,
913
+ ref: contentRef,
660
914
  id: contentId,
661
915
  role: "region",
662
916
  "aria-labelledby": triggerId,
@@ -665,7 +919,7 @@ function DisclosureContent(_ref2) {
665
919
  }, props));
666
920
  if (!mounted) return null;
667
921
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", _objectSpread2(_objectSpread2({
668
- ref: innerRef,
922
+ ref: contentRef,
669
923
  id: contentId,
670
924
  role: "region",
671
925
  "aria-labelledby": triggerId,
@@ -811,12 +1065,7 @@ const DialogPanel = (0, react.forwardRef)((_ref2, forwardedRef) => {
811
1065
  lastFocusedRef.current = null;
812
1066
  }
813
1067
  }, [mounted]);
814
- const mergedRef = (0, react.useCallback)((node) => {
815
- dialogRef.current = node;
816
- enterLeaveRef.current = node;
817
- if (typeof forwardedRef === "function") forwardedRef(node);
818
- else if (forwardedRef) forwardedRef.current = node;
819
- }, [
1068
+ const mergedRef = (0, react.useMemo)(() => mergeRefs(dialogRef, enterLeaveRef, forwardedRef), [
820
1069
  dialogRef,
821
1070
  enterLeaveRef,
822
1071
  forwardedRef
@@ -833,7 +1082,8 @@ const DialogPanel = (0, react.forwardRef)((_ref2, forwardedRef) => {
833
1082
  }, [
834
1083
  dialogRef,
835
1084
  setOpen,
836
- dismissOnEscape
1085
+ dismissOnEscape,
1086
+ mounted
837
1087
  ]);
838
1088
  const handleClick = (0, react.useCallback)((e) => {
839
1089
  if (dismissOnBackdrop && e.target === e.currentTarget) setOpen(false);
@@ -898,13 +1148,9 @@ const _excluded2$12 = [
898
1148
  "onClick"
899
1149
  ];
900
1150
  const _excluded3$9 = ["tabId"];
1151
+ /** WAI-ARIA tabs wrap at both ends, unlike a listbox/menu (which clamps). */
901
1152
  function nextEnabledTabId(tabs, currentId, delta) {
902
- const enabled = tabs.filter((t) => !t.disabled);
903
- if (enabled.length === 0) return null;
904
- const idx = enabled.findIndex((t) => t.id === currentId);
905
- if (idx < 0) return enabled[0].id;
906
- const n = enabled.length;
907
- return enabled[((idx + delta) % n + n) % n].id;
1153
+ return moveActiveId(tabs, currentId, delta, { wrap: true });
908
1154
  }
909
1155
  const TabsContext = (0, react.createContext)(null);
910
1156
  function useTabsContext() {
@@ -916,37 +1162,54 @@ function useTabsContext() {
916
1162
  function TabsRoot({ children, selectedId: controlledId, defaultSelectedId = "", onSelectedIdChange, setSelectedId: setSelectedIdDeprecated, orientation = "horizontal" }) {
917
1163
  const [selectedId, setSelectedId] = useControllableState(defaultSelectedId, controlledId, onSelectedIdChange !== null && onSelectedIdChange !== void 0 ? onSelectedIdChange : setSelectedIdDeprecated);
918
1164
  const baseId = useId();
919
- const tabs = (0, react.useRef)([]);
920
- const registerTab = (0, react.useCallback)((id, disabled) => {
921
- const existing = tabs.current.findIndex((t) => t.id === id);
922
- if (existing >= 0) tabs.current[existing] = {
923
- id,
924
- disabled
925
- };
926
- else tabs.current.push({
927
- id,
928
- disabled
929
- });
930
- }, []);
1165
+ const { items: tabs, registerItem, unregisterItem } = useItemRegistry();
1166
+ const selectedIdRef = (0, react.useRef)(selectedId);
1167
+ selectedIdRef.current = selectedId;
1168
+ const isControlled = controlledId !== void 0;
1169
+ const isControlledRef = (0, react.useRef)(isControlled);
1170
+ isControlledRef.current = isControlled;
1171
+ const setSelectedIdRef = (0, react.useRef)(setSelectedId);
1172
+ setSelectedIdRef.current = setSelectedId;
1173
+ const registerTab = (0, react.useCallback)((id, disabled) => registerItem({
1174
+ id,
1175
+ disabled
1176
+ }), [registerItem]);
931
1177
  const unregisterTab = (0, react.useCallback)((id) => {
932
- tabs.current = tabs.current.filter((t) => t.id !== id);
933
- }, []);
1178
+ const removedIndex = unregisterItem(id);
1179
+ if (selectedIdRef.current !== id) return;
1180
+ if (isControlledRef.current) return;
1181
+ const next = nearestEnabledId(tabs.current, removedIndex);
1182
+ if (next) setSelectedIdRef.current(next);
1183
+ }, [tabs, unregisterItem]);
934
1184
  (0, react.useLayoutEffect)(() => {
935
1185
  if (!selectedId) {
936
- const first = tabs.current.find((t) => !t.disabled);
937
- if (first) setSelectedId(first.id);
1186
+ const first = firstEnabledId(tabs.current);
1187
+ if (first) setSelectedId(first);
938
1188
  }
939
- }, [selectedId, setSelectedId]);
1189
+ }, [
1190
+ selectedId,
1191
+ setSelectedId,
1192
+ tabs
1193
+ ]);
1194
+ const contextValue = (0, react.useMemo)(() => ({
1195
+ selectedId,
1196
+ setSelectedId,
1197
+ baseId,
1198
+ orientation,
1199
+ registerTab,
1200
+ unregisterTab,
1201
+ tabs
1202
+ }), [
1203
+ selectedId,
1204
+ setSelectedId,
1205
+ baseId,
1206
+ orientation,
1207
+ registerTab,
1208
+ unregisterTab,
1209
+ tabs
1210
+ ]);
940
1211
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TabsContext.Provider, {
941
- value: {
942
- selectedId,
943
- setSelectedId,
944
- baseId,
945
- orientation,
946
- registerTab,
947
- unregisterTab,
948
- tabs
949
- },
1212
+ value: contextValue,
950
1213
  children
951
1214
  });
952
1215
  }
@@ -1108,39 +1371,6 @@ const Radio = (0, react.forwardRef)((_ref2, ref) => {
1108
1371
  });
1109
1372
  Radio.displayName = "Radio";
1110
1373
  //#endregion
1111
- //#region src/internal/merge-props.ts
1112
- const EVENT_HANDLER = /^on[A-Z]/;
1113
- /** Merge prop objects left→right with handler/className/style composition. */
1114
- function mergeProps(...parts) {
1115
- const result = {};
1116
- for (const part of parts) {
1117
- if (!part) continue;
1118
- for (const key in part) {
1119
- if (!Object.prototype.hasOwnProperty.call(part, key)) continue;
1120
- const value = part[key];
1121
- const existing = result[key];
1122
- if (EVENT_HANDLER.test(key) && typeof value === "function" && typeof existing === "function") {
1123
- const a = existing;
1124
- const b = value;
1125
- result[key] = (...args) => {
1126
- a(...args);
1127
- return b(...args);
1128
- };
1129
- } else if (key === "className") result[key] = [existing, value].filter(Boolean).join(" ") || void 0;
1130
- else if (key === "style" && existing && typeof existing === "object" && value && typeof value === "object") result[key] = _objectSpread2(_objectSpread2({}, existing), value);
1131
- else result[key] = value;
1132
- }
1133
- }
1134
- return result;
1135
- }
1136
- /** Merge multiple refs (callback or object) into one callback ref. */
1137
- function mergeRefs(...refs) {
1138
- return (value) => {
1139
- for (const ref of refs) if (typeof ref === "function") ref(value);
1140
- else if (ref != null) ref.current = value;
1141
- };
1142
- }
1143
- //#endregion
1144
1374
  //#region src/internal/overlay-portal.tsx
1145
1375
  /**
1146
1376
  * Conditionally portals overlay content. When `portal` is false, children render
@@ -1166,6 +1396,32 @@ function OverlayPortal({ portal, portalRoot, children }) {
1166
1396
  });
1167
1397
  }
1168
1398
  //#endregion
1399
+ //#region src/internal/render-element.tsx
1400
+ /**
1401
+ * Renders one part of a primitive: merges prop objects, merges refs, and
1402
+ * honors a `render` prop.
1403
+ *
1404
+ * A plain function, not a hook, so parts can call it after an early return
1405
+ * (`if (!mounted) return null`) — which most overlay panels do.
1406
+ *
1407
+ * Every part used to hand-roll this — an inline `ref={(node) => {...}}` that
1408
+ * poked one ref object and then type-checked a floating-ui callback ref, plus
1409
+ * an effect copying an element into the enter/leave ref. Eight copies of the
1410
+ * same wiring meant a fix (React 19 ref cleanup, say) had to be applied eight
1411
+ * times, and only `Composite` ever supported `render`. Routing parts through
1412
+ * here fixes all of them at once and makes `render` universal.
1413
+ */
1414
+ function renderElement(tag, { render, refs, props }) {
1415
+ const mergedProps = mergeProps(...props !== null && props !== void 0 ? props : []);
1416
+ const mergedRef = mergeRefs(...refs !== null && refs !== void 0 ? refs : []);
1417
+ if (typeof render === "function") return render(_objectSpread2(_objectSpread2({}, mergedProps), {}, { ref: mergedRef }));
1418
+ if (render && (0, react.isValidElement)(render)) {
1419
+ const renderProps = render.props;
1420
+ return (0, react.cloneElement)(render, _objectSpread2(_objectSpread2({}, mergeProps(renderProps, mergedProps)), {}, { ref: mergeRefs(renderProps.ref, mergedRef) }));
1421
+ }
1422
+ return (0, react.createElement)(tag, _objectSpread2(_objectSpread2({}, mergedProps), {}, { ref: mergedRef }));
1423
+ }
1424
+ //#endregion
1169
1425
  //#region src/primitives/tooltip.tsx
1170
1426
  const _excluded$15 = [
1171
1427
  "children",
@@ -1176,7 +1432,8 @@ const _excluded2$10 = ["ref", "aria-describedby"];
1176
1432
  const _excluded3$8 = [
1177
1433
  "children",
1178
1434
  "onMouseEnter",
1179
- "onMouseLeave"
1435
+ "onMouseLeave",
1436
+ "render"
1180
1437
  ];
1181
1438
  function mergeAriaDescribedBy(...values) {
1182
1439
  const ids = values.flatMap((value) => typeof value === "string" ? value.trim().split(/\s+/) : []);
@@ -1186,53 +1443,98 @@ function mergeAriaDescribedBy(...values) {
1186
1443
  const TooltipContext = (0, react.createContext)(null);
1187
1444
  function useTooltipContext() {
1188
1445
  const ctx = (0, react.useContext)(TooltipContext);
1189
- if (!ctx) throw new Error("Tooltip components must be used within TooltipProvider");
1446
+ if (!ctx) throw new Error("Tooltip components must be used within TooltipRoot");
1190
1447
  return ctx;
1191
1448
  }
1192
- /** Renders the `TooltipProvider` component. */
1193
- function TooltipProvider({ children, timeout, showTimeout = 700, hideTimeout = 300, placement = "top", animated = true, portal = false, portalRoot = null }) {
1449
+ const TooltipGroupContext = (0, react.createContext)(null);
1450
+ /**
1451
+ * Groups sibling tooltips so moving between them does not re-pay the show
1452
+ * delay — the APG/desktop convention for toolbars and icon rows, where waiting
1453
+ * 700ms per button makes the row feel broken.
1454
+ *
1455
+ * Optional: a `TooltipProvider` outside any group keeps its own delay.
1456
+ */
1457
+ function TooltipGroup({ children, skipDelay = 300 }) {
1458
+ const warmUntilRef = (0, react.useRef)(0);
1459
+ const cooldownRef = (0, react.useRef)(void 0);
1460
+ const value = (0, react.useMemo)(() => ({
1461
+ isWarm: () => skipDelay > 0 && Date.now() < warmUntilRef.current,
1462
+ onOpen: () => {
1463
+ clearTimeout(cooldownRef.current);
1464
+ warmUntilRef.current = 0;
1465
+ },
1466
+ onClose: () => {
1467
+ if (skipDelay <= 0) return;
1468
+ warmUntilRef.current = Date.now() + skipDelay;
1469
+ clearTimeout(cooldownRef.current);
1470
+ cooldownRef.current = setTimeout(() => {
1471
+ warmUntilRef.current = 0;
1472
+ }, skipDelay);
1473
+ }
1474
+ }), [skipDelay]);
1475
+ (0, react.useEffect)(() => () => clearTimeout(cooldownRef.current), []);
1476
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipGroupContext.Provider, {
1477
+ value,
1478
+ children
1479
+ });
1480
+ }
1481
+ /**
1482
+ * Owns ONE tooltip: its open state, delays, placement and portal.
1483
+ *
1484
+ * Wrap several of these in a {@link TooltipGroup} to share a skip-delay window.
1485
+ */
1486
+ function TooltipRoot({ children, timeout, showTimeout = 700, hideTimeout = 300, placement = "top", animated = true, portal = false, portalRoot = null }) {
1194
1487
  const [open, setOpen] = (0, react.useState)(false);
1195
1488
  const tooltipId = useId();
1196
1489
  const showDelay = timeout !== null && timeout !== void 0 ? timeout : showTimeout;
1197
1490
  const showTimerRef = (0, react.useRef)(void 0);
1198
1491
  const hideTimerRef = (0, react.useRef)(void 0);
1492
+ const group = (0, react.useContext)(TooltipGroupContext);
1493
+ const isKeyboardModality = useKeyboardModality();
1494
+ const clearTimers = (0, react.useCallback)(() => {
1495
+ clearTimeout(showTimerRef.current);
1496
+ clearTimeout(hideTimerRef.current);
1497
+ }, []);
1199
1498
  const { getReferenceProps: getFloatRefProps, getFloatingProps } = useFloating({
1200
1499
  placement,
1201
1500
  gutter: 8,
1202
- open
1501
+ open,
1502
+ onOpenChange: (0, react.useCallback)((next) => {
1503
+ if (next) return;
1504
+ clearTimers();
1505
+ setOpen(false);
1506
+ }, [clearTimers]),
1507
+ dismiss: {
1508
+ escapeKey: true,
1509
+ outsidePress: false
1510
+ }
1203
1511
  });
1204
1512
  const show = (0, react.useCallback)(() => {
1205
1513
  clearTimeout(hideTimerRef.current);
1206
- showTimerRef.current = setTimeout(() => setOpen(true), showDelay);
1207
- }, [showDelay]);
1514
+ const delay = (group === null || group === void 0 ? void 0 : group.isWarm()) ? 0 : showDelay;
1515
+ showTimerRef.current = setTimeout(() => {
1516
+ group === null || group === void 0 || group.onOpen();
1517
+ setOpen(true);
1518
+ }, delay);
1519
+ }, [showDelay, group]);
1208
1520
  const hide = (0, react.useCallback)(() => {
1209
1521
  clearTimeout(showTimerRef.current);
1210
- hideTimerRef.current = setTimeout(() => setOpen(false), hideTimeout);
1211
- }, [hideTimeout]);
1212
- (0, react.useEffect)(() => {
1213
- return () => {
1214
- clearTimeout(showTimerRef.current);
1215
- clearTimeout(hideTimerRef.current);
1216
- };
1217
- }, []);
1218
- (0, react.useEffect)(() => {
1219
- if (!open) return;
1220
- const handler = (e) => {
1221
- if (e.key === "Escape") {
1222
- clearTimeout(showTimerRef.current);
1223
- clearTimeout(hideTimerRef.current);
1224
- setOpen(false);
1225
- }
1226
- };
1227
- document.addEventListener("keydown", handler);
1228
- return () => document.removeEventListener("keydown", handler);
1229
- }, [open]);
1522
+ hideTimerRef.current = setTimeout(() => {
1523
+ setOpen((wasOpen) => {
1524
+ if (wasOpen) group === null || group === void 0 || group.onClose();
1525
+ return false;
1526
+ });
1527
+ }, hideTimeout);
1528
+ }, [hideTimeout, group]);
1529
+ (0, react.useEffect)(() => clearTimers, [clearTimers]);
1230
1530
  const getReferenceProps = (0, react.useCallback)(() => {
1231
1531
  return _objectSpread2(_objectSpread2({}, getFloatRefProps()), {}, {
1232
1532
  "aria-describedby": open ? tooltipId : void 0,
1233
1533
  onMouseEnter: show,
1234
1534
  onMouseLeave: hide,
1235
- onFocus: show,
1535
+ onFocus: (event) => {
1536
+ if (isKeyboardModality(event.currentTarget)) show();
1537
+ },
1236
1538
  onBlur: hide
1237
1539
  });
1238
1540
  }, [
@@ -1240,24 +1542,46 @@ function TooltipProvider({ children, timeout, showTimeout = 700, hideTimeout = 3
1240
1542
  open,
1241
1543
  tooltipId,
1242
1544
  show,
1243
- hide
1545
+ hide,
1546
+ isKeyboardModality
1547
+ ]);
1548
+ const contextValue = (0, react.useMemo)(() => ({
1549
+ open,
1550
+ setOpen,
1551
+ tooltipId,
1552
+ show,
1553
+ hide,
1554
+ animated,
1555
+ portal,
1556
+ portalRoot,
1557
+ getReferenceProps,
1558
+ getFloatingProps
1559
+ }), [
1560
+ open,
1561
+ setOpen,
1562
+ tooltipId,
1563
+ show,
1564
+ hide,
1565
+ animated,
1566
+ portal,
1567
+ portalRoot,
1568
+ getReferenceProps,
1569
+ getFloatingProps
1244
1570
  ]);
1245
1571
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipContext.Provider, {
1246
- value: {
1247
- open,
1248
- setOpen,
1249
- tooltipId,
1250
- show,
1251
- hide,
1252
- animated,
1253
- portal,
1254
- portalRoot,
1255
- getReferenceProps,
1256
- getFloatingProps
1257
- },
1572
+ value: contextValue,
1258
1573
  children
1259
1574
  });
1260
1575
  }
1576
+ /**
1577
+ * A single tooltip.
1578
+ *
1579
+ * @deprecated Renamed to {@link TooltipRoot}. Despite the name this component
1580
+ * never provided anything to a subtree of tooltips — it IS one tooltip. The
1581
+ * delay-sharing provider is {@link TooltipGroup}. Kept as an alias for
1582
+ * back-compat; scheduled for removal in the next major.
1583
+ */
1584
+ const TooltipProvider = TooltipRoot;
1261
1585
  const TooltipAnchor = (0, react.forwardRef)((_ref, ref) => {
1262
1586
  let { children, render, "aria-describedby": ariaDescribedBy } = _ref, props = _objectWithoutProperties(_ref, _excluded$15);
1263
1587
  const { getReferenceProps } = useTooltipContext();
@@ -1280,28 +1604,32 @@ const TooltipAnchor = (0, react.forwardRef)((_ref, ref) => {
1280
1604
  TooltipAnchor.displayName = "TooltipAnchor";
1281
1605
  /** Renders the `Tooltip` component. */
1282
1606
  function Tooltip(_ref3) {
1283
- let { children, onMouseEnter, onMouseLeave } = _ref3, props = _objectWithoutProperties(_ref3, _excluded3$8);
1607
+ let { children, onMouseEnter, onMouseLeave, render } = _ref3, props = _objectWithoutProperties(_ref3, _excluded3$8);
1284
1608
  const { open, tooltipId, show, hide, animated, portal, portalRoot, getFloatingProps } = useTooltipContext();
1285
1609
  const { mounted, dataAttributes, ref } = useEnterLeave(open, { animated });
1286
1610
  const floatingProps = getFloatingProps();
1611
+ const panelRef = useMergedRef(ref, floatingProps.ref);
1287
1612
  if (!mounted) return null;
1288
- const node = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", _objectSpread2(_objectSpread2(_objectSpread2({
1289
- ref: (n) => {
1290
- ref.current = n;
1291
- if (floatingProps.ref && typeof floatingProps.ref === "function") floatingProps.ref(n);
1292
- },
1293
- id: tooltipId,
1294
- role: "tooltip",
1295
- style: floatingProps.style,
1296
- onMouseEnter: (e) => {
1297
- show();
1298
- onMouseEnter === null || onMouseEnter === void 0 || onMouseEnter(e);
1299
- },
1300
- onMouseLeave: (e) => {
1301
- hide();
1302
- onMouseLeave === null || onMouseLeave === void 0 || onMouseLeave(e);
1303
- }
1304
- }, dataAttributes), props), {}, { children }));
1613
+ const node = renderElement("div", {
1614
+ render,
1615
+ refs: [panelRef],
1616
+ props: [
1617
+ {
1618
+ id: tooltipId,
1619
+ role: "tooltip",
1620
+ style: floatingProps.style,
1621
+ onMouseEnter: show,
1622
+ onMouseLeave: hide
1623
+ },
1624
+ dataAttributes,
1625
+ {
1626
+ onMouseEnter,
1627
+ onMouseLeave
1628
+ },
1629
+ props,
1630
+ { children }
1631
+ ]
1632
+ });
1305
1633
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverlayPortal, {
1306
1634
  portal,
1307
1635
  portalRoot,
@@ -1311,7 +1639,7 @@ function Tooltip(_ref3) {
1311
1639
  //#endregion
1312
1640
  //#region src/primitives/popover.tsx
1313
1641
  const _excluded$14 = ["onClick"];
1314
- const _excluded2$9 = ["children"];
1642
+ const _excluded2$9 = ["children", "render"];
1315
1643
  const _excluded3$7 = ["onClick"];
1316
1644
  const PopoverContext = (0, react.createContext)(null);
1317
1645
  function usePopoverContext() {
@@ -1330,20 +1658,33 @@ function PopoverRoot({ children, open: controlledOpen, defaultOpen = false, onOp
1330
1658
  onOpenChange: setOpen,
1331
1659
  dismiss: true
1332
1660
  });
1661
+ const contextValue = (0, react.useMemo)(() => ({
1662
+ open,
1663
+ setOpen,
1664
+ popoverId,
1665
+ manageFocus,
1666
+ animated,
1667
+ portal,
1668
+ portalRoot,
1669
+ getReferenceProps,
1670
+ getFloatingProps,
1671
+ getPositionerStateProps,
1672
+ context
1673
+ }), [
1674
+ open,
1675
+ setOpen,
1676
+ popoverId,
1677
+ manageFocus,
1678
+ animated,
1679
+ portal,
1680
+ portalRoot,
1681
+ getReferenceProps,
1682
+ getFloatingProps,
1683
+ getPositionerStateProps,
1684
+ context
1685
+ ]);
1333
1686
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PopoverContext.Provider, {
1334
- value: {
1335
- open,
1336
- setOpen,
1337
- popoverId,
1338
- manageFocus,
1339
- animated,
1340
- portal,
1341
- portalRoot,
1342
- getReferenceProps,
1343
- getFloatingProps,
1344
- getPositionerStateProps,
1345
- context
1346
- },
1687
+ value: contextValue,
1347
1688
  children
1348
1689
  });
1349
1690
  }
@@ -1375,21 +1716,28 @@ const PopoverTrigger = (0, react.forwardRef)((_ref, ref) => {
1375
1716
  PopoverTrigger.displayName = "PopoverTrigger";
1376
1717
  /** Renders the `PopoverContent` component. */
1377
1718
  function PopoverContent(_ref2) {
1378
- let { children } = _ref2, props = _objectWithoutProperties(_ref2, _excluded2$9);
1719
+ let { children, render } = _ref2, props = _objectWithoutProperties(_ref2, _excluded2$9);
1379
1720
  const { open, popoverId, manageFocus, animated, portal, portalRoot, getFloatingProps, getPositionerStateProps, context } = usePopoverContext();
1380
1721
  const { ref, mounted, dataAttributes } = useEnterLeave(open, { animated });
1381
1722
  const floatingProps = getFloatingProps();
1723
+ const panelRef = useMergedRef(ref, floatingProps.ref);
1382
1724
  if (!mounted) return null;
1383
- const node = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({
1384
- ref: (n) => {
1385
- ref.current = n;
1386
- if (floatingProps.ref && typeof floatingProps.ref === "function") floatingProps.ref(n);
1387
- },
1388
- id: popoverId,
1389
- "data-popover-id": popoverId,
1390
- tabIndex: -1,
1391
- style: floatingProps.style
1392
- }, getPositionerStateProps(open)), dataAttributes), props), {}, { children }));
1725
+ const node = renderElement("div", {
1726
+ render,
1727
+ refs: [panelRef],
1728
+ props: [
1729
+ {
1730
+ id: popoverId,
1731
+ "data-popover-id": popoverId,
1732
+ tabIndex: -1,
1733
+ style: floatingProps.style
1734
+ },
1735
+ getPositionerStateProps(open),
1736
+ dataAttributes,
1737
+ props,
1738
+ { children }
1739
+ ]
1740
+ });
1393
1741
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverlayPortal, {
1394
1742
  portal,
1395
1743
  portalRoot,
@@ -1416,144 +1764,245 @@ const PopoverClose = (0, react.forwardRef)((_ref3, ref) => {
1416
1764
  });
1417
1765
  PopoverClose.displayName = "PopoverClose";
1418
1766
  //#endregion
1767
+ //#region src/internal/create-store.ts
1768
+ /** Creates a {@link Store} seeded with `initial`. */
1769
+ function createStore(initial) {
1770
+ let state = initial;
1771
+ const listeners = /* @__PURE__ */ new Set();
1772
+ return {
1773
+ getState: () => state,
1774
+ setState: (partial) => {
1775
+ let changed = false;
1776
+ for (const key in partial) if (!Object.is(state[key], partial[key])) {
1777
+ changed = true;
1778
+ break;
1779
+ }
1780
+ if (!changed) return;
1781
+ state = _objectSpread2(_objectSpread2({}, state), partial);
1782
+ for (const listener of listeners) listener();
1783
+ },
1784
+ subscribe: (listener) => {
1785
+ listeners.add(listener);
1786
+ return () => {
1787
+ listeners.delete(listener);
1788
+ };
1789
+ }
1790
+ };
1791
+ }
1792
+ /**
1793
+ * Subscribe to a slice of a {@link Store}.
1794
+ *
1795
+ * The selector MUST return a stable value (primitive, or a referentially stable
1796
+ * object) — `useSyncExternalStore` re-renders in a loop otherwise.
1797
+ */
1798
+ function useStoreSelector(store, selector) {
1799
+ return (0, react.useSyncExternalStore)(store.subscribe, () => selector(store.getState()), () => selector(store.getState()));
1800
+ }
1801
+ //#endregion
1419
1802
  //#region src/primitives/select.tsx
1420
1803
  const _excluded$13 = [
1421
1804
  "onClick",
1422
1805
  "onKeyDown",
1423
1806
  "children"
1424
1807
  ];
1425
- const _excluded2$8 = ["children"];
1808
+ const _excluded2$8 = ["children", "render"];
1426
1809
  const _excluded3$6 = [
1427
1810
  "value",
1428
1811
  "disabled",
1429
1812
  "onClick",
1430
1813
  "children"
1431
1814
  ];
1432
- function firstEnabledId$4(items) {
1433
- var _items$find$id, _items$find;
1434
- return (_items$find$id = (_items$find = items.find((i) => !i.disabled)) === null || _items$find === void 0 ? void 0 : _items$find.id) !== null && _items$find$id !== void 0 ? _items$find$id : null;
1815
+ /** Normalize the raw value (single `string` or multiple `string[]`). */
1816
+ function toValues(value) {
1817
+ if (Array.isArray(value)) return value;
1818
+ return value === "" ? [] : [value];
1819
+ }
1820
+ const SelectStoreContext = (0, react.createContext)(null);
1821
+ function useSelectStoreContext() {
1822
+ const ctx = (0, react.useContext)(SelectStoreContext);
1823
+ if (!ctx) throw new Error("Select components must be used within SelectRoot");
1824
+ return ctx;
1435
1825
  }
1436
- function moveActiveId$4(items, activeId, delta) {
1437
- const enabled = items.filter((i) => !i.disabled);
1438
- if (enabled.length === 0) return null;
1439
- const idx = enabled.findIndex((i) => i.id === activeId);
1440
- if (idx < 0) return delta > 0 ? enabled[0].id : enabled[enabled.length - 1].id;
1441
- return enabled[Math.min(enabled.length - 1, Math.max(0, idx + delta))].id;
1826
+ /** Subscribe to a slice of select state; re-renders only when it changes. */
1827
+ function useSelectSelector(selector) {
1828
+ const { store } = useSelectStoreContext();
1829
+ return useStoreSelector(store, selector);
1442
1830
  }
1443
- const SelectContext = (0, react.createContext)(null);
1444
- function useSelectContext() {
1445
- const ctx = (0, react.useContext)(SelectContext);
1831
+ const SelectFloatingContext = (0, react.createContext)(null);
1832
+ function useSelectFloating() {
1833
+ const ctx = (0, react.useContext)(SelectFloatingContext);
1446
1834
  if (!ctx) throw new Error("Select components must be used within SelectRoot");
1447
1835
  return ctx;
1448
1836
  }
1449
1837
  /** Renders the `SelectRoot` component. */
1450
- function SelectRoot({ children, value: controlledValue, defaultValue, onValueChange, setValue: setValueDeprecated, multiple = false, name, open: controlledOpen, onOpenChange, setOpen: setOpenDeprecated, animated = true, portal = false, portalRoot = null }) {
1838
+ function SelectRoot({ children, value: controlledValue, defaultValue, onValueChange, setValue: setValueDeprecated, multiple = false, readOnly = false, name, open: controlledOpen, onOpenChange, setOpen: setOpenDeprecated, animated = true, portal = false, portalRoot = null }) {
1451
1839
  var _selectedValues$;
1452
- const [value, setValue] = useControllableState(defaultValue !== null && defaultValue !== void 0 ? defaultValue : multiple ? [] : "", controlledValue, onValueChange !== null && onValueChange !== void 0 ? onValueChange : setValueDeprecated);
1453
- const [open, setOpenState] = (0, react.useState)(controlledOpen !== null && controlledOpen !== void 0 ? controlledOpen : false);
1454
- const onOpenChangeCb = onOpenChange !== null && onOpenChange !== void 0 ? onOpenChange : setOpenDeprecated;
1455
- const [activeId, setActiveId] = (0, react.useState)(null);
1456
1840
  const selectId = useId();
1457
1841
  const listboxId = `${selectId}-listbox`;
1458
- const items = (0, react.useRef)([]);
1459
- (0, react.useEffect)(() => {
1460
- if (controlledOpen !== void 0) setOpenState(controlledOpen);
1461
- }, [controlledOpen]);
1462
- const setOpen = (0, react.useCallback)((v) => {
1463
- if (controlledOpen === void 0) setOpenState(v);
1464
- onOpenChangeCb === null || onOpenChangeCb === void 0 || onOpenChangeCb(v);
1465
- if (!v) setActiveId(null);
1466
- }, [controlledOpen, onOpenChangeCb]);
1467
- const selectedValues = (0, react.useMemo)(() => Array.isArray(value) ? value : value === "" ? [] : [value], [value]);
1468
- const isSelected = (0, react.useCallback)((v) => selectedValues.includes(v), [selectedValues]);
1469
- const selectValue = (0, react.useCallback)((v) => {
1470
- if (multiple) {
1471
- const arr = Array.isArray(value) ? value : value ? [value] : [];
1472
- setValue(arr.includes(v) ? arr.filter((x) => x !== v) : [...arr, v]);
1473
- } else {
1474
- setValue(v);
1475
- setOpen(false);
1476
- }
1477
- }, [
1842
+ const { items, registerItem, unregisterItem: removeItem } = useItemRegistry();
1843
+ const [store] = (0, react.useState)(() => {
1844
+ var _ref;
1845
+ return createStore({
1846
+ value: (_ref = controlledValue !== null && controlledValue !== void 0 ? controlledValue : defaultValue) !== null && _ref !== void 0 ? _ref : multiple ? [] : "",
1847
+ activeId: null,
1848
+ open: controlledOpen !== null && controlledOpen !== void 0 ? controlledOpen : false
1849
+ });
1850
+ });
1851
+ const propsRef = (0, react.useRef)({
1852
+ controlledValue,
1853
+ controlledOpen,
1478
1854
  multiple,
1479
- value,
1480
- setValue,
1481
- setOpen
1482
- ]);
1483
- const registerItem = (0, react.useCallback)((entry) => {
1484
- const list = items.current;
1485
- const existing = list.findIndex((i) => i.id === entry.id);
1486
- if (existing >= 0) list[existing] = entry;
1487
- else list.push(entry);
1488
- }, []);
1855
+ readOnly,
1856
+ onValueChange: onValueChange !== null && onValueChange !== void 0 ? onValueChange : setValueDeprecated,
1857
+ onOpenChange: onOpenChange !== null && onOpenChange !== void 0 ? onOpenChange : setOpenDeprecated
1858
+ });
1859
+ propsRef.current = {
1860
+ controlledValue,
1861
+ controlledOpen,
1862
+ multiple,
1863
+ readOnly,
1864
+ onValueChange: onValueChange !== null && onValueChange !== void 0 ? onValueChange : setValueDeprecated,
1865
+ onOpenChange: onOpenChange !== null && onOpenChange !== void 0 ? onOpenChange : setOpenDeprecated
1866
+ };
1867
+ (0, react.useLayoutEffect)(() => {
1868
+ if (controlledValue !== void 0) store.setState({ value: controlledValue });
1869
+ }, [controlledValue, store]);
1870
+ (0, react.useLayoutEffect)(() => {
1871
+ if (controlledOpen !== void 0) store.setState({ open: controlledOpen });
1872
+ }, [controlledOpen, store]);
1873
+ const actions = (0, react.useMemo)(() => {
1874
+ const setValue = (v) => {
1875
+ var _p$onValueChange;
1876
+ const p = propsRef.current;
1877
+ if (p.controlledValue === void 0) store.setState({ value: v });
1878
+ (_p$onValueChange = p.onValueChange) === null || _p$onValueChange === void 0 || _p$onValueChange.call(p, v);
1879
+ };
1880
+ const setOpen = (v) => {
1881
+ var _p$onOpenChange;
1882
+ const p = propsRef.current;
1883
+ if (p.controlledOpen === void 0) store.setState({ open: v });
1884
+ (_p$onOpenChange = p.onOpenChange) === null || _p$onOpenChange === void 0 || _p$onOpenChange.call(p, v);
1885
+ if (!v) store.setState({ activeId: null });
1886
+ };
1887
+ return {
1888
+ setValue,
1889
+ setOpen,
1890
+ setActiveId: (updater) => {
1891
+ const cur = store.getState().activeId;
1892
+ const next = typeof updater === "function" ? updater(cur) : updater;
1893
+ store.setState({ activeId: next });
1894
+ },
1895
+ selectValue: (v) => {
1896
+ const p = propsRef.current;
1897
+ if (p.readOnly) return;
1898
+ if (p.multiple) {
1899
+ const arr = toValues(store.getState().value);
1900
+ setValue(arr.includes(v) ? arr.filter((x) => x !== v) : [...arr, v]);
1901
+ } else {
1902
+ setValue(v);
1903
+ setOpen(false);
1904
+ }
1905
+ }
1906
+ };
1907
+ }, [store]);
1489
1908
  const unregisterItem = (0, react.useCallback)((id) => {
1490
- items.current = items.current.filter((i) => i.id !== id);
1491
- }, []);
1909
+ const removedIndex = removeItem(id);
1910
+ if (store.getState().activeId !== id) return;
1911
+ store.setState({ activeId: nearestEnabledId(items.current, removedIndex) });
1912
+ }, [
1913
+ items,
1914
+ removeItem,
1915
+ store
1916
+ ]);
1917
+ const open = useStoreSelector(store, (state) => state.open);
1918
+ const value = useStoreSelector(store, (state) => state.value);
1919
+ const selectedValues = (0, react.useMemo)(() => toValues(value), [value]);
1492
1920
  const { getReferenceProps, getFloatingProps } = useFloating({
1493
1921
  placement: "bottom-start",
1494
1922
  gutter: 4,
1495
1923
  sameWidth: true,
1496
1924
  open,
1497
- onOpenChange: setOpen,
1925
+ onOpenChange: actions.setOpen,
1498
1926
  dismiss: {
1499
1927
  outsidePress: true,
1500
1928
  escapeKey: false
1501
1929
  }
1502
1930
  });
1503
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(SelectContext.Provider, {
1504
- value: {
1505
- open,
1506
- setOpen,
1507
- value,
1508
- selectedValues,
1509
- multiple,
1510
- isSelected,
1511
- selectValue,
1512
- activeId,
1513
- setActiveId,
1514
- selectId,
1515
- listboxId,
1516
- getReferenceProps,
1517
- getFloatingProps,
1518
- items,
1519
- registerItem,
1520
- unregisterItem,
1521
- animated,
1522
- portal,
1523
- portalRoot
1524
- },
1525
- children: [children, name && (multiple ? selectedValues.map((v) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1526
- type: "hidden",
1527
- name,
1528
- value: v
1529
- }, v)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1530
- type: "hidden",
1531
- name,
1532
- value: (_selectedValues$ = selectedValues[0]) !== null && _selectedValues$ !== void 0 ? _selectedValues$ : ""
1533
- }))]
1931
+ const storeContext = (0, react.useMemo)(() => ({
1932
+ store,
1933
+ actions,
1934
+ multiple,
1935
+ readOnly,
1936
+ selectId,
1937
+ listboxId,
1938
+ items,
1939
+ registerItem,
1940
+ unregisterItem
1941
+ }), [
1942
+ store,
1943
+ actions,
1944
+ multiple,
1945
+ readOnly,
1946
+ selectId,
1947
+ listboxId,
1948
+ registerItem,
1949
+ unregisterItem
1950
+ ]);
1951
+ const floatingContext = (0, react.useMemo)(() => ({
1952
+ getReferenceProps,
1953
+ getFloatingProps,
1954
+ animated,
1955
+ portal,
1956
+ portalRoot
1957
+ }), [
1958
+ getReferenceProps,
1959
+ getFloatingProps,
1960
+ animated,
1961
+ portal,
1962
+ portalRoot
1963
+ ]);
1964
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectStoreContext.Provider, {
1965
+ value: storeContext,
1966
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(SelectFloatingContext.Provider, {
1967
+ value: floatingContext,
1968
+ children: [children, name && (multiple ? selectedValues.map((v) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1969
+ type: "hidden",
1970
+ name,
1971
+ value: v
1972
+ }, v)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1973
+ type: "hidden",
1974
+ name,
1975
+ value: (_selectedValues$ = selectedValues[0]) !== null && _selectedValues$ !== void 0 ? _selectedValues$ : ""
1976
+ }))]
1977
+ })
1534
1978
  });
1535
1979
  }
1536
1980
  /** Renders the `SelectLabel` component. */
1537
1981
  function SelectLabel(props) {
1538
- const { selectId } = useSelectContext();
1982
+ const { selectId } = useSelectStoreContext();
1539
1983
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", _objectSpread2({ htmlFor: selectId }, props));
1540
1984
  }
1541
- const SelectTrigger = (0, react.forwardRef)((_ref, ref) => {
1985
+ const SelectTrigger = (0, react.forwardRef)((_ref2, ref) => {
1542
1986
  var _selectedValues$2;
1543
- let { onClick, onKeyDown, children } = _ref, props = _objectWithoutProperties(_ref, _excluded$13);
1544
- const { open, setOpen, selectedValues, multiple, selectValue, selectId, listboxId, getReferenceProps, items, activeId, setActiveId } = useSelectContext();
1987
+ let { onClick, onKeyDown, children } = _ref2, props = _objectWithoutProperties(_ref2, _excluded$13);
1988
+ const { actions, multiple, readOnly, selectId, listboxId, items, store } = useSelectStoreContext();
1989
+ const { getReferenceProps } = useSelectFloating();
1990
+ const open = useSelectSelector((state) => state.open);
1991
+ const activeId = useSelectSelector((state) => state.activeId);
1992
+ const value = useSelectSelector((state) => state.value);
1993
+ const selectedValues = (0, react.useMemo)(() => toValues(value), [value]);
1545
1994
  const refProps = getReferenceProps();
1546
1995
  const typeahead = (0, react.useRef)({
1547
1996
  buffer: "",
1548
1997
  timer: 0
1549
1998
  });
1550
1999
  const selectActive = (0, react.useCallback)(() => {
1551
- const item = items.current.find((i) => i.id === activeId);
1552
- if (item && !item.disabled) selectValue(item.value);
2000
+ const item = items.current.find((i) => i.id === store.getState().activeId);
2001
+ if (item && !item.disabled) actions.selectValue(item.value);
1553
2002
  }, [
1554
2003
  items,
1555
- activeId,
1556
- selectValue
2004
+ store,
2005
+ actions
1557
2006
  ]);
1558
2007
  const runTypeahead = (0, react.useCallback)((char) => {
1559
2008
  const t = typeahead.current;
@@ -1566,52 +2015,51 @@ const SelectTrigger = (0, react.forwardRef)((_ref, ref) => {
1566
2015
  var _document$getElementB;
1567
2016
  return (_document$getElementB = document.getElementById(i.id)) === null || _document$getElementB === void 0 || (_document$getElementB = _document$getElementB.textContent) === null || _document$getElementB === void 0 ? void 0 : _document$getElementB.trim().toLowerCase().startsWith(t.buffer);
1568
2017
  });
1569
- if (match) setActiveId(match.id);
1570
- }, [items, setActiveId]);
2018
+ if (match) actions.setActiveId(match.id);
2019
+ }, [items, actions]);
1571
2020
  const handleClick = (0, react.useCallback)((e) => {
1572
- setOpen(!open);
2021
+ actions.setOpen(!open);
1573
2022
  onClick === null || onClick === void 0 || onClick(e);
1574
2023
  }, [
1575
2024
  open,
1576
- setOpen,
2025
+ actions,
1577
2026
  onClick
1578
2027
  ]);
1579
2028
  const handleKeyDown = (0, react.useCallback)((e) => {
2029
+ const currentActiveId = store.getState().activeId;
1580
2030
  switch (e.key) {
1581
2031
  case "ArrowDown":
1582
2032
  e.preventDefault();
1583
- if (!open) setOpen(true);
1584
- else setActiveId(moveActiveId$4(items.current, activeId, 1));
2033
+ if (!open) actions.setOpen(true);
2034
+ else actions.setActiveId(moveActiveId(items.current, currentActiveId, 1));
1585
2035
  break;
1586
2036
  case "ArrowUp":
1587
2037
  e.preventDefault();
1588
- if (!open) setOpen(true);
1589
- else setActiveId(moveActiveId$4(items.current, activeId, -1));
2038
+ if (!open) actions.setOpen(true);
2039
+ else actions.setActiveId(moveActiveId(items.current, currentActiveId, -1));
1590
2040
  break;
1591
2041
  case "Home":
1592
2042
  if (open) {
1593
2043
  e.preventDefault();
1594
- setActiveId(firstEnabledId$4(items.current));
2044
+ actions.setActiveId(firstEnabledId(items.current));
1595
2045
  }
1596
2046
  break;
1597
2047
  case "End":
1598
2048
  if (open) {
1599
- var _enabled$id, _enabled;
1600
2049
  e.preventDefault();
1601
- const enabled = items.current.filter((i) => !i.disabled);
1602
- setActiveId((_enabled$id = (_enabled = enabled[enabled.length - 1]) === null || _enabled === void 0 ? void 0 : _enabled.id) !== null && _enabled$id !== void 0 ? _enabled$id : null);
2050
+ actions.setActiveId(lastEnabledId(items.current));
1603
2051
  }
1604
2052
  break;
1605
2053
  case "Enter":
1606
2054
  case " ":
1607
2055
  e.preventDefault();
1608
2056
  if (open) selectActive();
1609
- else setOpen(true);
2057
+ else actions.setOpen(true);
1610
2058
  break;
1611
2059
  case "Escape":
1612
2060
  if (open) {
1613
2061
  e.preventDefault();
1614
- setOpen(false);
2062
+ actions.setOpen(false);
1615
2063
  }
1616
2064
  break;
1617
2065
  default: if (open && e.key.length === 1 && e.key !== " " && !e.metaKey && !e.ctrlKey && !e.altKey && !e.nativeEvent.isComposing) runTypeahead(e.key);
@@ -1620,9 +2068,8 @@ const SelectTrigger = (0, react.forwardRef)((_ref, ref) => {
1620
2068
  }, [
1621
2069
  open,
1622
2070
  items,
1623
- activeId,
1624
- setActiveId,
1625
- setOpen,
2071
+ store,
2072
+ actions,
1626
2073
  selectActive,
1627
2074
  runTypeahead,
1628
2075
  onKeyDown
@@ -1640,6 +2087,8 @@ const SelectTrigger = (0, react.forwardRef)((_ref, ref) => {
1640
2087
  "aria-haspopup": "listbox",
1641
2088
  "aria-controls": open ? listboxId : void 0,
1642
2089
  "aria-activedescendant": open ? activeId !== null && activeId !== void 0 ? activeId : void 0 : void 0,
2090
+ "aria-readonly": readOnly || void 0,
2091
+ "data-readonly": readOnly ? "" : void 0,
1643
2092
  "data-select-trigger": selectId,
1644
2093
  onClick: handleClick,
1645
2094
  onKeyDown: handleKeyDown
@@ -1647,38 +2096,50 @@ const SelectTrigger = (0, react.forwardRef)((_ref, ref) => {
1647
2096
  });
1648
2097
  SelectTrigger.displayName = "SelectTrigger";
1649
2098
  /** Renders the `SelectPopover` component. */
1650
- function SelectPopover(_ref2) {
1651
- let { children } = _ref2, props = _objectWithoutProperties(_ref2, _excluded2$8);
1652
- const { open, multiple, selectedValues, listboxId, selectId, getFloatingProps, items, activeId, setActiveId, animated, portal, portalRoot } = useSelectContext();
2099
+ function SelectPopover(_ref3) {
2100
+ let { children, render } = _ref3, props = _objectWithoutProperties(_ref3, _excluded2$8);
2101
+ const { actions, multiple, readOnly, selectId, listboxId, items, store } = useSelectStoreContext();
2102
+ const { getFloatingProps, animated, portal, portalRoot } = useSelectFloating();
2103
+ const open = useSelectSelector((state) => state.open);
2104
+ const activeId = useSelectSelector((state) => state.activeId);
1653
2105
  const { ref, mounted, dataAttributes } = useEnterLeave(open, { animated });
1654
2106
  const floatingProps = getFloatingProps();
2107
+ const panelRef = useMergedRef(ref, floatingProps.ref);
1655
2108
  (0, react.useLayoutEffect)(() => {
1656
- if (mounted) setActiveId((prev) => {
2109
+ if (!mounted) return;
2110
+ actions.setActiveId((prev) => {
1657
2111
  var _selected$id;
1658
2112
  if (prev) return prev;
2113
+ const selectedValues = toValues(store.getState().value);
1659
2114
  const selected = items.current.find((i) => selectedValues.includes(i.value));
1660
- return (_selected$id = selected === null || selected === void 0 ? void 0 : selected.id) !== null && _selected$id !== void 0 ? _selected$id : firstEnabledId$4(items.current);
2115
+ return (_selected$id = selected === null || selected === void 0 ? void 0 : selected.id) !== null && _selected$id !== void 0 ? _selected$id : firstEnabledId(items.current);
1661
2116
  });
1662
2117
  }, [
1663
2118
  mounted,
1664
2119
  items,
1665
- selectedValues,
1666
- setActiveId
2120
+ store,
2121
+ actions
1667
2122
  ]);
1668
2123
  useScrollActiveDescendantIntoView(activeId);
1669
2124
  if (!mounted) return null;
1670
- const node = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", _objectSpread2(_objectSpread2(_objectSpread2({
1671
- ref: (n) => {
1672
- ref.current = n;
1673
- if (floatingProps.ref && typeof floatingProps.ref === "function") floatingProps.ref(n);
1674
- },
1675
- id: listboxId,
1676
- role: "listbox",
1677
- "aria-multiselectable": multiple || void 0,
1678
- "aria-activedescendant": activeId !== null && activeId !== void 0 ? activeId : void 0,
1679
- "data-select-id": selectId,
1680
- style: floatingProps.style
1681
- }, dataAttributes), props), {}, { children }));
2125
+ const node = renderElement("div", {
2126
+ render,
2127
+ refs: [panelRef],
2128
+ props: [
2129
+ {
2130
+ id: listboxId,
2131
+ role: "listbox",
2132
+ "aria-multiselectable": multiple || void 0,
2133
+ "aria-readonly": readOnly || void 0,
2134
+ "aria-activedescendant": activeId !== null && activeId !== void 0 ? activeId : void 0,
2135
+ "data-select-id": selectId,
2136
+ style: floatingProps.style
2137
+ },
2138
+ dataAttributes,
2139
+ props,
2140
+ { children }
2141
+ ]
2142
+ });
1682
2143
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverlayPortal, {
1683
2144
  portal,
1684
2145
  portalRoot,
@@ -1686,12 +2147,12 @@ function SelectPopover(_ref2) {
1686
2147
  });
1687
2148
  }
1688
2149
  /** Renders the `SelectItem` component. */
1689
- function SelectItem(_ref3) {
1690
- let { value: itemValue, disabled, onClick, children } = _ref3, props = _objectWithoutProperties(_ref3, _excluded3$6);
1691
- const { isSelected: isValueSelected, selectValue, activeId, setActiveId, registerItem, unregisterItem } = useSelectContext();
2150
+ function SelectItem(_ref4) {
2151
+ let { value: itemValue, disabled, onClick, children } = _ref4, props = _objectWithoutProperties(_ref4, _excluded3$6);
2152
+ const { actions, registerItem, unregisterItem } = useSelectStoreContext();
1692
2153
  const itemId = useId();
1693
- const isSelected = isValueSelected(itemValue);
1694
- const isActive = activeId === itemId;
2154
+ const isSelected = useSelectSelector((state) => toValues(state.value).includes(itemValue));
2155
+ const isActive = useSelectSelector((state) => state.activeId === itemId);
1695
2156
  (0, react.useLayoutEffect)(() => {
1696
2157
  registerItem({
1697
2158
  id: itemId,
@@ -1707,12 +2168,12 @@ function SelectItem(_ref3) {
1707
2168
  unregisterItem
1708
2169
  ]);
1709
2170
  const handleClick = (0, react.useCallback)((e) => {
1710
- if (!disabled) selectValue(itemValue);
2171
+ if (!disabled) actions.selectValue(itemValue);
1711
2172
  onClick === null || onClick === void 0 || onClick(e);
1712
2173
  }, [
1713
2174
  disabled,
1714
2175
  itemValue,
1715
- selectValue,
2176
+ actions,
1716
2177
  onClick
1717
2178
  ]);
1718
2179
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", _objectSpread2(_objectSpread2({
@@ -1724,14 +2185,14 @@ function SelectItem(_ref3) {
1724
2185
  "data-disabled": disabled ? "" : void 0,
1725
2186
  onClick: handleClick,
1726
2187
  onMouseEnter: () => {
1727
- if (!disabled) setActiveId(itemId);
2188
+ if (!disabled) actions.setActiveId(itemId);
1728
2189
  }
1729
2190
  }, props), {}, { children: children !== null && children !== void 0 ? children : itemValue }));
1730
2191
  }
1731
2192
  //#endregion
1732
2193
  //#region src/primitives/combobox.tsx
1733
2194
  const _excluded$12 = ["onKeyDown"];
1734
- const _excluded2$7 = ["children"];
2195
+ const _excluded2$7 = ["children", "render"];
1735
2196
  const _excluded3$5 = [
1736
2197
  "value",
1737
2198
  "disabled",
@@ -1739,36 +2200,6 @@ const _excluded3$5 = [
1739
2200
  "children"
1740
2201
  ];
1741
2202
  const _excluded4$3 = ["children"];
1742
- function firstEnabledId$3(items) {
1743
- var _items$find$id, _items$find;
1744
- return (_items$find$id = (_items$find = items.find((i) => !i.disabled)) === null || _items$find === void 0 ? void 0 : _items$find.id) !== null && _items$find$id !== void 0 ? _items$find$id : null;
1745
- }
1746
- function moveActiveId$3(items, activeId, delta) {
1747
- const enabled = items.filter((i) => !i.disabled);
1748
- if (enabled.length === 0) return null;
1749
- const idx = enabled.findIndex((i) => i.id === activeId);
1750
- if (idx < 0) return delta > 0 ? enabled[0].id : enabled[enabled.length - 1].id;
1751
- return enabled[Math.min(enabled.length - 1, Math.max(0, idx + delta))].id;
1752
- }
1753
- function createComboboxStore(initial) {
1754
- let state = initial;
1755
- const listeners = /* @__PURE__ */ new Set();
1756
- return {
1757
- getState: () => state,
1758
- setState: (partial) => {
1759
- const next = _objectSpread2(_objectSpread2({}, state), partial);
1760
- if (next.value === state.value && next.searchValue === state.searchValue && next.activeId === state.activeId && next.open === state.open) return;
1761
- state = next;
1762
- listeners.forEach((l) => l());
1763
- },
1764
- subscribe: (listener) => {
1765
- listeners.add(listener);
1766
- return () => {
1767
- listeners.delete(listener);
1768
- };
1769
- }
1770
- };
1771
- }
1772
2203
  const ComboboxStoreContext = (0, react.createContext)(null);
1773
2204
  function useComboboxStoreContext() {
1774
2205
  const ctx = (0, react.useContext)(ComboboxStoreContext);
@@ -1778,7 +2209,7 @@ function useComboboxStoreContext() {
1778
2209
  /** Subscribe to a slice of combobox state; re-renders only when it changes. */
1779
2210
  function useComboboxSelector(selector) {
1780
2211
  const { store } = useComboboxStoreContext();
1781
- return (0, react.useSyncExternalStore)(store.subscribe, () => selector(store.getState()), () => selector(store.getState()));
2212
+ return useStoreSelector(store, selector);
1782
2213
  }
1783
2214
  const ComboboxFloatingContext = (0, react.createContext)(null);
1784
2215
  function useComboboxFloating() {
@@ -1787,11 +2218,11 @@ function useComboboxFloating() {
1787
2218
  return ctx;
1788
2219
  }
1789
2220
  /** Renders the `ComboboxRoot` component. */
1790
- function ComboboxRoot({ children, value: controlledValue, defaultValue = "", onValueChange, setValue: setValueDeprecated, open: controlledOpen, onOpenChange, setOpen: setOpenDeprecated, animated = true, portal = false, portalRoot = null }) {
2221
+ function ComboboxRoot({ children, value: controlledValue, defaultValue = "", onValueChange, setValue: setValueDeprecated, readOnly = false, open: controlledOpen, onOpenChange, setOpen: setOpenDeprecated, animated = true, portal = false, portalRoot = null }) {
1791
2222
  const comboboxId = useId();
1792
2223
  const listboxId = `${comboboxId}-listbox`;
1793
- const items = (0, react.useRef)([]);
1794
- const [store] = (0, react.useState)(() => createComboboxStore({
2224
+ const { items, registerItem, unregisterItem: removeItem } = useItemRegistry();
2225
+ const [store] = (0, react.useState)(() => createStore({
1795
2226
  value: controlledValue !== null && controlledValue !== void 0 ? controlledValue : defaultValue,
1796
2227
  searchValue: "",
1797
2228
  activeId: null,
@@ -1809,10 +2240,10 @@ function ComboboxRoot({ children, value: controlledValue, defaultValue = "", onV
1809
2240
  onValueChange: onValueChange !== null && onValueChange !== void 0 ? onValueChange : setValueDeprecated,
1810
2241
  onOpenChange: onOpenChange !== null && onOpenChange !== void 0 ? onOpenChange : setOpenDeprecated
1811
2242
  };
1812
- (0, react.useEffect)(() => {
2243
+ (0, react.useLayoutEffect)(() => {
1813
2244
  if (controlledValue !== void 0) store.setState({ value: controlledValue });
1814
2245
  }, [controlledValue, store]);
1815
- (0, react.useEffect)(() => {
2246
+ (0, react.useLayoutEffect)(() => {
1816
2247
  if (controlledOpen !== void 0) store.setState({ open: controlledOpen });
1817
2248
  }, [controlledOpen, store]);
1818
2249
  const actions = (0, react.useMemo)(() => ({
@@ -1836,21 +2267,21 @@ function ComboboxRoot({ children, value: controlledValue, defaultValue = "", onV
1836
2267
  store.setState({ activeId: next });
1837
2268
  }
1838
2269
  }), [store]);
1839
- const registerItem = (0, react.useCallback)((entry) => {
1840
- const list = items.current;
1841
- const existing = list.findIndex((i) => i.id === entry.id);
1842
- if (existing >= 0) list[existing] = entry;
1843
- else list.push(entry);
1844
- }, []);
1845
2270
  const unregisterItem = (0, react.useCallback)((id) => {
1846
- items.current = items.current.filter((i) => i.id !== id);
1847
- }, []);
2271
+ const removedIndex = removeItem(id);
2272
+ if (store.getState().activeId !== id) return;
2273
+ store.setState({ activeId: nearestEnabledId(items.current, removedIndex) });
2274
+ }, [
2275
+ items,
2276
+ removeItem,
2277
+ store
2278
+ ]);
1848
2279
  const { getReferenceProps, getFloatingProps } = useFloating({
1849
2280
  placement: "bottom-start",
1850
2281
  gutter: 4,
1851
2282
  sameWidth: true,
1852
2283
  lazyFlip: true,
1853
- open: (0, react.useSyncExternalStore)(store.subscribe, () => store.getState().open, () => store.getState().open),
2284
+ open: useStoreSelector(store, (state) => state.open),
1854
2285
  onOpenChange: actions.setOpen,
1855
2286
  dismiss: {
1856
2287
  outsidePress: true,
@@ -1860,6 +2291,7 @@ function ComboboxRoot({ children, value: controlledValue, defaultValue = "", onV
1860
2291
  const storeContext = (0, react.useMemo)(() => ({
1861
2292
  store,
1862
2293
  actions,
2294
+ readOnly,
1863
2295
  comboboxId,
1864
2296
  listboxId,
1865
2297
  items,
@@ -1868,6 +2300,7 @@ function ComboboxRoot({ children, value: controlledValue, defaultValue = "", onV
1868
2300
  }), [
1869
2301
  store,
1870
2302
  actions,
2303
+ readOnly,
1871
2304
  comboboxId,
1872
2305
  listboxId,
1873
2306
  registerItem,
@@ -1896,7 +2329,7 @@ function ComboboxRoot({ children, value: controlledValue, defaultValue = "", onV
1896
2329
  }
1897
2330
  const ComboboxInput = (0, react.forwardRef)((_ref, ref) => {
1898
2331
  let { onKeyDown } = _ref, props = _objectWithoutProperties(_ref, _excluded$12);
1899
- const { actions, comboboxId, listboxId, items } = useComboboxStoreContext();
2332
+ const { actions, readOnly, comboboxId, listboxId, items } = useComboboxStoreContext();
1900
2333
  const { getReferenceProps } = useComboboxFloating();
1901
2334
  const open = useComboboxSelector((s) => s.open);
1902
2335
  const searchValue = useComboboxSelector((s) => s.searchValue);
@@ -1909,6 +2342,7 @@ const ComboboxInput = (0, react.forwardRef)((_ref, ref) => {
1909
2342
  actions.setActiveId(null);
1910
2343
  }, [actions, open]);
1911
2344
  const selectActive = (0, react.useCallback)(() => {
2345
+ if (readOnly) return;
1912
2346
  const item = items.current.find((i) => i.id === activeId);
1913
2347
  if (item && !item.disabled) {
1914
2348
  actions.setValue(item.value);
@@ -1918,7 +2352,8 @@ const ComboboxInput = (0, react.forwardRef)((_ref, ref) => {
1918
2352
  }, [
1919
2353
  items,
1920
2354
  activeId,
1921
- actions
2355
+ actions,
2356
+ readOnly
1922
2357
  ]);
1923
2358
  const handleKeyDown = (0, react.useCallback)((e) => {
1924
2359
  if (e.nativeEvent.isComposing) {
@@ -1929,12 +2364,12 @@ const ComboboxInput = (0, react.forwardRef)((_ref, ref) => {
1929
2364
  case "ArrowDown":
1930
2365
  e.preventDefault();
1931
2366
  if (!open) actions.setOpen(true);
1932
- actions.setActiveId(moveActiveId$3(items.current, activeId, 1));
2367
+ actions.setActiveId(moveActiveId(items.current, activeId, 1));
1933
2368
  break;
1934
2369
  case "ArrowUp":
1935
2370
  e.preventDefault();
1936
2371
  if (!open) actions.setOpen(true);
1937
- actions.setActiveId(moveActiveId$3(items.current, activeId, -1));
2372
+ actions.setActiveId(moveActiveId(items.current, activeId, -1));
1938
2373
  break;
1939
2374
  case "Enter":
1940
2375
  if (open && activeId) {
@@ -1969,6 +2404,9 @@ const ComboboxInput = (0, react.forwardRef)((_ref, ref) => {
1969
2404
  "aria-autocomplete": "list",
1970
2405
  "aria-controls": open ? listboxId : void 0,
1971
2406
  "aria-activedescendant": open ? activeId !== null && activeId !== void 0 ? activeId : void 0 : void 0,
2407
+ "aria-readonly": readOnly || void 0,
2408
+ readOnly,
2409
+ "data-readonly": readOnly ? "" : void 0,
1972
2410
  "data-combobox-input": comboboxId,
1973
2411
  value: searchValue,
1974
2412
  onChange: handleChange,
@@ -1978,29 +2416,35 @@ const ComboboxInput = (0, react.forwardRef)((_ref, ref) => {
1978
2416
  ComboboxInput.displayName = "ComboboxInput";
1979
2417
  /** Renders the `ComboboxPopover` component. */
1980
2418
  function ComboboxPopover(_ref2) {
1981
- let { children } = _ref2, props = _objectWithoutProperties(_ref2, _excluded2$7);
2419
+ let { children, render } = _ref2, props = _objectWithoutProperties(_ref2, _excluded2$7);
1982
2420
  const { actions, comboboxId, listboxId, items } = useComboboxStoreContext();
1983
2421
  const { getFloatingProps, animated, portal, portalRoot } = useComboboxFloating();
1984
2422
  const { ref, mounted, dataAttributes } = useEnterLeave(useComboboxSelector((s) => s.open), { animated });
1985
2423
  const floatingProps = getFloatingProps();
2424
+ const panelRef = useMergedRef(ref, floatingProps.ref);
1986
2425
  (0, react.useLayoutEffect)(() => {
1987
- if (mounted) actions.setActiveId((prev) => prev !== null && prev !== void 0 ? prev : firstEnabledId$3(items.current));
2426
+ if (mounted) actions.setActiveId((prev) => prev !== null && prev !== void 0 ? prev : firstEnabledId(items.current));
1988
2427
  }, [
1989
2428
  mounted,
1990
2429
  items,
1991
2430
  actions
1992
2431
  ]);
1993
2432
  if (!mounted) return null;
1994
- const node = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", _objectSpread2(_objectSpread2(_objectSpread2({
1995
- ref: (n) => {
1996
- ref.current = n;
1997
- if (floatingProps.ref && typeof floatingProps.ref === "function") floatingProps.ref(n);
1998
- },
1999
- id: listboxId,
2000
- role: "listbox",
2001
- "data-combobox-id": comboboxId,
2002
- style: floatingProps.style
2003
- }, dataAttributes), props), {}, { children }));
2433
+ const node = renderElement("div", {
2434
+ render,
2435
+ refs: [panelRef],
2436
+ props: [
2437
+ {
2438
+ id: listboxId,
2439
+ role: "listbox",
2440
+ "data-combobox-id": comboboxId,
2441
+ style: floatingProps.style
2442
+ },
2443
+ dataAttributes,
2444
+ props,
2445
+ { children }
2446
+ ]
2447
+ });
2004
2448
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverlayPortal, {
2005
2449
  portal,
2006
2450
  portalRoot,
@@ -2010,7 +2454,7 @@ function ComboboxPopover(_ref2) {
2010
2454
  /** Renders the `ComboboxItem` component. */
2011
2455
  function ComboboxItem(_ref3) {
2012
2456
  let { value: itemValue, disabled, onClick, children } = _ref3, props = _objectWithoutProperties(_ref3, _excluded3$5);
2013
- const { actions, registerItem, unregisterItem } = useComboboxStoreContext();
2457
+ const { actions, readOnly, registerItem, unregisterItem } = useComboboxStoreContext();
2014
2458
  const itemId = useId();
2015
2459
  const isSelected = useComboboxSelector((s) => s.value === itemValue);
2016
2460
  const isActive = useComboboxSelector((s) => s.activeId === itemId);
@@ -2029,7 +2473,7 @@ function ComboboxItem(_ref3) {
2029
2473
  unregisterItem
2030
2474
  ]);
2031
2475
  const handleClick = (0, react.useCallback)((e) => {
2032
- if (!disabled) {
2476
+ if (!disabled && !readOnly) {
2033
2477
  actions.setValue(itemValue);
2034
2478
  actions.setSearchValue(itemValue);
2035
2479
  actions.setOpen(false);
@@ -2037,6 +2481,7 @@ function ComboboxItem(_ref3) {
2037
2481
  onClick === null || onClick === void 0 || onClick(e);
2038
2482
  }, [
2039
2483
  disabled,
2484
+ readOnly,
2040
2485
  itemValue,
2041
2486
  actions,
2042
2487
  onClick
@@ -2308,10 +2753,18 @@ function CommandGroup(_ref5) {
2308
2753
  children
2309
2754
  })] }));
2310
2755
  }
2311
- /** Renders the `CommandSeparator` component. */
2756
+ /**
2757
+ * Renders the `CommandSeparator` component.
2758
+ *
2759
+ * Rendered with `role="presentation"`: the separator is a child of
2760
+ * `CommandList` (`role="listbox"`), whose only permitted children are `option`
2761
+ * and `group`. A real `role="separator"` there fails
2762
+ * `aria-required-children` and corrupts the option count announced by screen
2763
+ * readers. (Menu separators keep `role="separator"` — `menu` permits it.)
2764
+ */
2312
2765
  function CommandSeparator(props) {
2313
2766
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", _objectSpread2({
2314
- role: "separator",
2767
+ role: "presentation",
2315
2768
  "data-slot": "command-separator"
2316
2769
  }, props));
2317
2770
  }
@@ -2394,7 +2847,11 @@ const _excluded2$5 = [
2394
2847
  "onKeyDown",
2395
2848
  "role"
2396
2849
  ];
2397
- const _excluded3$3 = ["children", "onKeyDown"];
2850
+ const _excluded3$3 = [
2851
+ "children",
2852
+ "onKeyDown",
2853
+ "render"
2854
+ ];
2398
2855
  const _excluded4$1 = [
2399
2856
  "disabled",
2400
2857
  "hideOnClick",
@@ -2415,19 +2872,6 @@ const _excluded6$1 = [
2415
2872
  "onClick",
2416
2873
  "children"
2417
2874
  ];
2418
- /** First enabled item id, or null. */
2419
- function firstEnabledId$2(items) {
2420
- var _items$find$id, _items$find;
2421
- return (_items$find$id = (_items$find = items.find((i) => !i.disabled)) === null || _items$find === void 0 ? void 0 : _items$find.id) !== null && _items$find$id !== void 0 ? _items$find$id : null;
2422
- }
2423
- /** Move the active id by `delta` over the enabled items (clamped, no wrap). */
2424
- function moveActiveId$2(items, activeId, delta) {
2425
- const enabled = items.filter((i) => !i.disabled);
2426
- if (enabled.length === 0) return null;
2427
- const idx = enabled.findIndex((i) => i.id === activeId);
2428
- if (idx < 0) return delta > 0 ? enabled[0].id : enabled[enabled.length - 1].id;
2429
- return enabled[Math.min(enabled.length - 1, Math.max(0, idx + delta))].id;
2430
- }
2431
2875
  const MenuContext = (0, react.createContext)(null);
2432
2876
  function useMenuContext() {
2433
2877
  const ctx = (0, react.useContext)(MenuContext);
@@ -2478,20 +2922,19 @@ function MenubarContainer(_ref) {
2478
2922
  }
2479
2923
  /** Renders the `MenuRoot` component. */
2480
2924
  function MenuRoot({ children, open: controlledOpen, onOpenChange, setOpen: setOpenDeprecated, animated = true, portal = false, portalRoot = null }) {
2481
- const [open, setOpenState] = (0, react.useState)(controlledOpen !== null && controlledOpen !== void 0 ? controlledOpen : false);
2925
+ const [open, setOpenState] = useControllableState(false, controlledOpen, onOpenChange !== null && onOpenChange !== void 0 ? onOpenChange : setOpenDeprecated);
2482
2926
  const [activeId, setActiveId] = (0, react.useState)(null);
2483
2927
  const menuId = useId();
2484
2928
  const triggerId = `${menuId}-trigger`;
2485
- const items = (0, react.useRef)([]);
2486
- const onOpenChangeCb = onOpenChange !== null && onOpenChange !== void 0 ? onOpenChange : setOpenDeprecated;
2487
- (0, react.useEffect)(() => {
2488
- if (controlledOpen !== void 0) setOpenState(controlledOpen);
2489
- }, [controlledOpen]);
2929
+ const { items, registerItem: registerItemEntry, unregisterItem: removeItem } = useItemRegistry();
2930
+ const registerItem = (0, react.useCallback)((id, disabled) => registerItemEntry({
2931
+ id,
2932
+ disabled
2933
+ }), [registerItemEntry]);
2490
2934
  const setOpen = (0, react.useCallback)((v) => {
2491
- if (controlledOpen === void 0) setOpenState(v);
2492
- onOpenChangeCb === null || onOpenChangeCb === void 0 || onOpenChangeCb(v);
2935
+ setOpenState(v);
2493
2936
  if (!v) setActiveId(null);
2494
- }, [controlledOpen, onOpenChangeCb]);
2937
+ }, [setOpenState]);
2495
2938
  const focusTrigger = (0, react.useCallback)(() => {
2496
2939
  var _document$getElementB;
2497
2940
  (_document$getElementB = document.getElementById(triggerId)) === null || _document$getElementB === void 0 || _document$getElementB.focus();
@@ -2506,39 +2949,45 @@ function MenuRoot({ children, open: controlledOpen, onOpenChange, setOpen: setOp
2506
2949
  escapeKey: false
2507
2950
  }
2508
2951
  });
2509
- const registerItem = (0, react.useCallback)((id, disabled) => {
2510
- const list = items.current;
2511
- const existing = list.findIndex((i) => i.id === id);
2512
- if (existing >= 0) list[existing] = {
2513
- id,
2514
- disabled
2515
- };
2516
- else list.push({
2517
- id,
2518
- disabled
2519
- });
2520
- }, []);
2521
2952
  const unregisterItem = (0, react.useCallback)((id) => {
2522
- items.current = items.current.filter((i) => i.id !== id);
2523
- }, []);
2953
+ const removedIndex = removeItem(id);
2954
+ setActiveId((prev) => prev === id ? nearestEnabledId(items.current, removedIndex) : prev);
2955
+ }, [items, removeItem]);
2956
+ const contextValue = (0, react.useMemo)(() => ({
2957
+ open,
2958
+ setOpen,
2959
+ menuId,
2960
+ triggerId,
2961
+ getReferenceProps,
2962
+ getFloatingProps,
2963
+ activeId,
2964
+ setActiveId,
2965
+ items,
2966
+ registerItem,
2967
+ unregisterItem,
2968
+ focusTrigger,
2969
+ animated,
2970
+ portal,
2971
+ portalRoot
2972
+ }), [
2973
+ open,
2974
+ setOpen,
2975
+ menuId,
2976
+ triggerId,
2977
+ getReferenceProps,
2978
+ getFloatingProps,
2979
+ activeId,
2980
+ setActiveId,
2981
+ items,
2982
+ registerItem,
2983
+ unregisterItem,
2984
+ focusTrigger,
2985
+ animated,
2986
+ portal,
2987
+ portalRoot
2988
+ ]);
2524
2989
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuContext.Provider, {
2525
- value: {
2526
- open,
2527
- setOpen,
2528
- menuId,
2529
- triggerId,
2530
- getReferenceProps,
2531
- getFloatingProps,
2532
- activeId,
2533
- setActiveId,
2534
- items,
2535
- registerItem,
2536
- unregisterItem,
2537
- focusTrigger,
2538
- animated,
2539
- portal,
2540
- portalRoot
2541
- },
2990
+ value: contextValue,
2542
2991
  children
2543
2992
  });
2544
2993
  }
@@ -2582,11 +3031,12 @@ const MenuTrigger = (0, react.forwardRef)((_ref2, ref) => {
2582
3031
  MenuTrigger.displayName = "MenuTrigger";
2583
3032
  /** Renders the `MenuPopover` component. */
2584
3033
  function MenuPopover(_ref3) {
2585
- let { children, onKeyDown } = _ref3, props = _objectWithoutProperties(_ref3, _excluded3$3);
3034
+ let { children, onKeyDown, render } = _ref3, props = _objectWithoutProperties(_ref3, _excluded3$3);
2586
3035
  const { open, setOpen, menuId, triggerId, getFloatingProps, items, activeId, setActiveId, focusTrigger, animated, portal, portalRoot } = useMenuContext();
2587
3036
  const { ref, mounted, dataAttributes } = useEnterLeave(open, { animated });
2588
3037
  const floatingProps = getFloatingProps();
2589
3038
  const menuRef = (0, react.useRef)(null);
3039
+ const panelRef = useMergedRef(menuRef, ref, floatingProps.ref);
2590
3040
  const typeahead = (0, react.useRef)({
2591
3041
  buffer: "",
2592
3042
  timer: 0
@@ -2612,23 +3062,20 @@ function MenuPopover(_ref3) {
2612
3062
  switch (e.key) {
2613
3063
  case "ArrowDown":
2614
3064
  e.preventDefault();
2615
- setActiveId(moveActiveId$2(items.current, activeId, 1));
3065
+ setActiveId(moveActiveId(items.current, activeId, 1));
2616
3066
  break;
2617
3067
  case "ArrowUp":
2618
3068
  e.preventDefault();
2619
- setActiveId(moveActiveId$2(items.current, activeId, -1));
3069
+ setActiveId(moveActiveId(items.current, activeId, -1));
2620
3070
  break;
2621
3071
  case "Home":
2622
3072
  e.preventDefault();
2623
- setActiveId(firstEnabledId$2(items.current));
3073
+ setActiveId(firstEnabledId(items.current));
2624
3074
  break;
2625
- case "End": {
2626
- var _enabled$id, _enabled;
3075
+ case "End":
2627
3076
  e.preventDefault();
2628
- const enabled = items.current.filter((i) => !i.disabled);
2629
- setActiveId((_enabled$id = (_enabled = enabled[enabled.length - 1]) === null || _enabled === void 0 ? void 0 : _enabled.id) !== null && _enabled$id !== void 0 ? _enabled$id : null);
3077
+ setActiveId(lastEnabledId(items.current));
2630
3078
  break;
2631
- }
2632
3079
  case "Enter":
2633
3080
  case " ":
2634
3081
  e.preventDefault();
@@ -2659,7 +3106,7 @@ function MenuPopover(_ref3) {
2659
3106
  if (mounted) {
2660
3107
  var _menuRef$current;
2661
3108
  (_menuRef$current = menuRef.current) === null || _menuRef$current === void 0 || _menuRef$current.focus();
2662
- setActiveId((prev) => prev !== null && prev !== void 0 ? prev : firstEnabledId$2(items.current));
3109
+ setActiveId((prev) => prev !== null && prev !== void 0 ? prev : firstEnabledId(items.current));
2663
3110
  }
2664
3111
  }, [
2665
3112
  mounted,
@@ -2668,21 +3115,25 @@ function MenuPopover(_ref3) {
2668
3115
  ]);
2669
3116
  useScrollActiveDescendantIntoView(activeId);
2670
3117
  if (!mounted) return null;
2671
- const node = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", _objectSpread2(_objectSpread2(_objectSpread2({
2672
- ref: (n) => {
2673
- menuRef.current = n;
2674
- ref.current = n;
2675
- if (floatingProps.ref && typeof floatingProps.ref === "function") floatingProps.ref(n);
2676
- },
2677
- id: menuId,
2678
- role: "menu",
2679
- "aria-labelledby": triggerId,
2680
- "aria-activedescendant": activeId !== null && activeId !== void 0 ? activeId : void 0,
2681
- "data-menu-id": menuId,
2682
- style: floatingProps.style,
2683
- tabIndex: -1,
2684
- onKeyDown: handleKeyDown
2685
- }, dataAttributes), props), {}, { children }));
3118
+ const node = renderElement("div", {
3119
+ render,
3120
+ refs: [panelRef],
3121
+ props: [
3122
+ {
3123
+ id: menuId,
3124
+ role: "menu",
3125
+ "aria-labelledby": triggerId,
3126
+ "aria-activedescendant": activeId !== null && activeId !== void 0 ? activeId : void 0,
3127
+ "data-menu-id": menuId,
3128
+ style: floatingProps.style,
3129
+ tabIndex: -1,
3130
+ onKeyDown: handleKeyDown
3131
+ },
3132
+ dataAttributes,
3133
+ props,
3134
+ { children }
3135
+ ]
3136
+ });
2686
3137
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverlayPortal, {
2687
3138
  portal,
2688
3139
  portalRoot,
@@ -2801,17 +3252,6 @@ function MenuButtonArrow(props) {
2801
3252
  //#region src/primitives/toolbar.tsx
2802
3253
  const _excluded$9 = ["onKeyDown"];
2803
3254
  const _excluded2$4 = ["disabled", "onFocus"];
2804
- function firstEnabledId$1(items) {
2805
- var _items$find$id, _items$find;
2806
- return (_items$find$id = (_items$find = items.find((i) => !i.disabled)) === null || _items$find === void 0 ? void 0 : _items$find.id) !== null && _items$find$id !== void 0 ? _items$find$id : null;
2807
- }
2808
- function moveActiveId$1(items, activeId, delta) {
2809
- const enabled = items.filter((i) => !i.disabled);
2810
- if (enabled.length === 0) return null;
2811
- const idx = enabled.findIndex((i) => i.id === activeId);
2812
- if (idx < 0) return delta > 0 ? enabled[0].id : enabled[enabled.length - 1].id;
2813
- return enabled[Math.min(enabled.length - 1, Math.max(0, idx + delta))].id;
2814
- }
2815
3255
  const ToolbarContext = (0, react.createContext)(null);
2816
3256
  function useToolbarContext() {
2817
3257
  const ctx = (0, react.useContext)(ToolbarContext);
@@ -2821,29 +3261,31 @@ function useToolbarContext() {
2821
3261
  /** Renders the `ToolbarRoot` component. */
2822
3262
  function ToolbarRoot({ children, orientation = "horizontal" }) {
2823
3263
  const [activeId, setActiveId] = (0, react.useState)(null);
2824
- const items = (0, react.useRef)([]);
2825
- const registerItem = (0, react.useCallback)((entry) => {
2826
- const list = items.current;
2827
- const existing = list.findIndex((i) => i.id === entry.id);
2828
- if (existing >= 0) list[existing] = entry;
2829
- else list.push(entry);
2830
- }, []);
3264
+ const { items, registerItem, unregisterItem: removeItem } = useItemRegistry();
2831
3265
  const unregisterItem = (0, react.useCallback)((id) => {
2832
- items.current = items.current.filter((i) => i.id !== id);
2833
- setActiveId((prev) => prev === id ? firstEnabledId$1(items.current) : prev);
2834
- }, []);
3266
+ const removedIndex = removeItem(id);
3267
+ setActiveId((prev) => prev === id ? nearestEnabledId(items.current, removedIndex) : prev);
3268
+ }, [items, removeItem]);
2835
3269
  (0, react.useLayoutEffect)(() => {
2836
- setActiveId((prev) => prev !== null && prev !== void 0 ? prev : firstEnabledId$1(items.current));
3270
+ setActiveId((prev) => prev !== null && prev !== void 0 ? prev : firstEnabledId(items.current));
2837
3271
  }, [activeId]);
3272
+ const contextValue = (0, react.useMemo)(() => ({
3273
+ orientation,
3274
+ activeId,
3275
+ setActiveId,
3276
+ registerItem,
3277
+ unregisterItem,
3278
+ items
3279
+ }), [
3280
+ orientation,
3281
+ activeId,
3282
+ setActiveId,
3283
+ registerItem,
3284
+ unregisterItem,
3285
+ items
3286
+ ]);
2838
3287
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarContext.Provider, {
2839
- value: {
2840
- orientation,
2841
- activeId,
2842
- setActiveId,
2843
- registerItem,
2844
- unregisterItem,
2845
- items
2846
- },
3288
+ value: contextValue,
2847
3289
  children
2848
3290
  });
2849
3291
  }
@@ -2862,18 +3304,16 @@ function ToolbarContainer(_ref) {
2862
3304
  let nextId;
2863
3305
  if (e.key === nextKey) {
2864
3306
  e.preventDefault();
2865
- nextId = moveActiveId$1(items.current, activeId, 1);
3307
+ nextId = moveActiveId(items.current, activeId, 1);
2866
3308
  } else if (e.key === prevKey) {
2867
3309
  e.preventDefault();
2868
- nextId = moveActiveId$1(items.current, activeId, -1);
3310
+ nextId = moveActiveId(items.current, activeId, -1);
2869
3311
  } else if (e.key === "Home") {
2870
3312
  e.preventDefault();
2871
- nextId = firstEnabledId$1(items.current);
3313
+ nextId = firstEnabledId(items.current);
2872
3314
  } else if (e.key === "End") {
2873
- var _enabled$id, _enabled;
2874
3315
  e.preventDefault();
2875
- const enabled = items.current.filter((i) => !i.disabled);
2876
- nextId = (_enabled$id = (_enabled = enabled[enabled.length - 1]) === null || _enabled === void 0 ? void 0 : _enabled.id) !== null && _enabled$id !== void 0 ? _enabled$id : null;
3316
+ nextId = lastEnabledId(items.current);
2877
3317
  }
2878
3318
  if (nextId) {
2879
3319
  setActiveId(nextId);
@@ -2947,6 +3387,10 @@ const _excluded3$2 = [
2947
3387
  /**
2948
3388
  * From `start`, return the index of the first enabled item walking in `dir`
2949
3389
  * (+1/-1). Honors `loop`. Returns -1 if none.
3390
+ *
3391
+ * Composite navigates by INDEX (grid rows/columns, wrap and loop modes), which
3392
+ * is why it keeps this directional scan instead of the registry's id-based
3393
+ * `moveActiveId`.
2950
3394
  */
2951
3395
  function firstEnabledFrom(list, start, dir, loop) {
2952
3396
  const n = list.length;
@@ -2969,7 +3413,7 @@ function useCompositeContext() {
2969
3413
  /** Renders the `CompositeProvider` component. */
2970
3414
  function CompositeProvider({ children, focusLoop = false, focusWrap = false, orientation = "both", activeId: controlledActiveId, onActiveIdChange, setActiveId: setActiveIdDeprecated }) {
2971
3415
  const [internalActiveId, setInternalActiveId] = (0, react.useState)(null);
2972
- const items = (0, react.useRef)([]);
3416
+ const { items, registerItem: registerEntry, unregisterItem: removeItem } = useItemRegistry();
2973
3417
  const onActiveIdChangeCb = onActiveIdChange !== null && onActiveIdChange !== void 0 ? onActiveIdChange : setActiveIdDeprecated;
2974
3418
  const activeId = controlledActiveId !== void 0 ? controlledActiveId : internalActiveId;
2975
3419
  const activeIdRef = (0, react.useRef)(activeId);
@@ -2981,16 +3425,8 @@ function CompositeProvider({ children, focusLoop = false, focusWrap = false, ori
2981
3425
  }, [controlledActiveId, onActiveIdChangeCb]);
2982
3426
  const registerItem = (0, react.useCallback)((id, element, row = 0, col = 0, disabled = false) => {
2983
3427
  const list = items.current;
2984
- const existing = list.findIndex((i) => i.id === id);
2985
- const activeBecameDisabled = existing >= 0 && id === activeIdRef.current && disabled;
2986
- if (existing >= 0) list[existing] = {
2987
- id,
2988
- element,
2989
- row,
2990
- col,
2991
- disabled
2992
- };
2993
- else list.push({
3428
+ const activeBecameDisabled = list.findIndex((i) => i.id === id) >= 0 && id === activeIdRef.current && disabled;
3429
+ registerEntry({
2994
3430
  id,
2995
3431
  element,
2996
3432
  row,
@@ -3011,29 +3447,48 @@ function CompositeProvider({ children, focusLoop = false, focusWrap = false, ori
3011
3447
  setInternalActiveId(null);
3012
3448
  } else setActiveId(nextId);
3013
3449
  }
3014
- }, [controlledActiveId, setActiveId]);
3450
+ }, [
3451
+ controlledActiveId,
3452
+ setActiveId,
3453
+ items,
3454
+ registerEntry
3455
+ ]);
3015
3456
  const unregisterItem = (0, react.useCallback)((id) => {
3016
- items.current = items.current.filter((i) => i.id !== id);
3457
+ const removedIndex = removeItem(id);
3458
+ if (removedIndex < 0) return;
3017
3459
  if (controlledActiveId === void 0) setInternalActiveId((prev) => {
3018
3460
  if (prev !== id) return prev;
3019
- const idx = firstEnabledFrom(items.current, 0, 1, false);
3020
- const nextId = idx >= 0 ? items.current[idx].id : null;
3461
+ const nextId = nearestEnabledId(items.current, removedIndex);
3021
3462
  activeIdRef.current = nextId;
3022
3463
  if (nextId === null) seededRef.current = false;
3023
3464
  return nextId;
3024
3465
  });
3025
- }, [controlledActiveId]);
3466
+ }, [
3467
+ controlledActiveId,
3468
+ items,
3469
+ removeItem
3470
+ ]);
3471
+ const contextValue = (0, react.useMemo)(() => ({
3472
+ activeId,
3473
+ setActiveId,
3474
+ registerItem,
3475
+ unregisterItem,
3476
+ items,
3477
+ focusLoop,
3478
+ focusWrap,
3479
+ orientation
3480
+ }), [
3481
+ activeId,
3482
+ setActiveId,
3483
+ registerItem,
3484
+ unregisterItem,
3485
+ items,
3486
+ focusLoop,
3487
+ focusWrap,
3488
+ orientation
3489
+ ]);
3026
3490
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompositeContext.Provider, {
3027
- value: {
3028
- activeId,
3029
- setActiveId,
3030
- registerItem,
3031
- unregisterItem,
3032
- items,
3033
- focusLoop,
3034
- focusWrap,
3035
- orientation
3036
- },
3491
+ value: contextValue,
3037
3492
  children
3038
3493
  });
3039
3494
  }
@@ -3200,17 +3655,6 @@ const _excluded2$2 = [
3200
3655
  "onClick",
3201
3656
  "onFocus"
3202
3657
  ];
3203
- function firstEnabledId(items) {
3204
- var _items$find$id, _items$find;
3205
- return (_items$find$id = (_items$find = items.find((i) => !i.disabled)) === null || _items$find === void 0 ? void 0 : _items$find.id) !== null && _items$find$id !== void 0 ? _items$find$id : null;
3206
- }
3207
- function moveActiveId(items, activeId, delta) {
3208
- const enabled = items.filter((i) => !i.disabled);
3209
- if (enabled.length === 0) return null;
3210
- const idx = enabled.findIndex((i) => i.id === activeId);
3211
- if (idx < 0) return delta > 0 ? enabled[0].id : enabled[enabled.length - 1].id;
3212
- return enabled[Math.min(enabled.length - 1, Math.max(0, idx + delta))].id;
3213
- }
3214
3658
  const ToggleGroupContext = (0, react.createContext)(null);
3215
3659
  /**
3216
3660
  * Groups related `Toggle`s with roving-tabindex keyboard navigation and shared
@@ -3238,17 +3682,11 @@ function ToggleGroup(_ref) {
3238
3682
  setValue
3239
3683
  ]);
3240
3684
  const [activeId, setActiveId] = (0, react.useState)(null);
3241
- const items = (0, react.useRef)([]);
3242
- const registerItem = (0, react.useCallback)((entry) => {
3243
- const list = items.current;
3244
- const existing = list.findIndex((i) => i.id === entry.id);
3245
- if (existing >= 0) list[existing] = entry;
3246
- else list.push(entry);
3247
- }, []);
3685
+ const { items, registerItem, unregisterItem: removeItem } = useItemRegistry();
3248
3686
  const unregisterItem = (0, react.useCallback)((id) => {
3249
- items.current = items.current.filter((i) => i.id !== id);
3250
- setActiveId((prev) => prev === id ? firstEnabledId(items.current) : prev);
3251
- }, []);
3687
+ const removedIndex = removeItem(id);
3688
+ setActiveId((prev) => prev === id ? nearestEnabledId(items.current, removedIndex) : prev);
3689
+ }, [items, removeItem]);
3252
3690
  (0, react.useLayoutEffect)(() => {
3253
3691
  setActiveId((prev) => prev !== null && prev !== void 0 ? prev : firstEnabledId(items.current));
3254
3692
  }, [activeId]);
@@ -3271,10 +3709,8 @@ function ToggleGroup(_ref) {
3271
3709
  e.preventDefault();
3272
3710
  nextId = firstEnabledId(items.current);
3273
3711
  } else if (e.key === "End") {
3274
- var _enabled$id, _enabled;
3275
3712
  e.preventDefault();
3276
- const enabled = items.current.filter((i) => !i.disabled);
3277
- nextId = (_enabled$id = (_enabled = enabled[enabled.length - 1]) === null || _enabled === void 0 ? void 0 : _enabled.id) !== null && _enabled$id !== void 0 ? _enabled$id : null;
3713
+ nextId = lastEnabledId(items.current);
3278
3714
  }
3279
3715
  if (nextId) {
3280
3716
  setActiveId(nextId);
@@ -3287,18 +3723,28 @@ function ToggleGroup(_ref) {
3287
3723
  focusActive,
3288
3724
  onKeyDown
3289
3725
  ]);
3726
+ const contextValue = (0, react.useMemo)(() => ({
3727
+ isPressed,
3728
+ toggle,
3729
+ disabled,
3730
+ orientation,
3731
+ activeId,
3732
+ setActiveId,
3733
+ registerItem,
3734
+ unregisterItem,
3735
+ items
3736
+ }), [
3737
+ isPressed,
3738
+ toggle,
3739
+ disabled,
3740
+ orientation,
3741
+ activeId,
3742
+ registerItem,
3743
+ unregisterItem,
3744
+ items
3745
+ ]);
3290
3746
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleGroupContext.Provider, {
3291
- value: {
3292
- isPressed,
3293
- toggle,
3294
- disabled,
3295
- orientation,
3296
- activeId,
3297
- setActiveId,
3298
- registerItem,
3299
- unregisterItem,
3300
- items
3301
- },
3747
+ value: contextValue,
3302
3748
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", _objectSpread2({
3303
3749
  role: "group",
3304
3750
  onKeyDown: handleKeyDown
@@ -3318,17 +3764,19 @@ function Toggle(_ref2) {
3318
3764
  const [standalonePressed, setStandalonePressed] = useControllableState(defaultPressed, pressedProp, onPressedChange);
3319
3765
  const inGroup = group !== null;
3320
3766
  const isDisabled = disabled || inGroup && group.disabled || false;
3767
+ const registerItem = group === null || group === void 0 ? void 0 : group.registerItem;
3768
+ const unregisterItem = group === null || group === void 0 ? void 0 : group.unregisterItem;
3321
3769
  (0, react.useLayoutEffect)(() => {
3322
- if (!inGroup) return void 0;
3323
- group.registerItem({
3770
+ if (!registerItem || !unregisterItem) return void 0;
3771
+ registerItem({
3324
3772
  id: itemId,
3325
3773
  element: buttonRef.current,
3326
3774
  disabled: isDisabled
3327
3775
  });
3328
- return () => group.unregisterItem(itemId);
3776
+ return () => unregisterItem(itemId);
3329
3777
  }, [
3330
- inGroup,
3331
- group,
3778
+ registerItem,
3779
+ unregisterItem,
3332
3780
  itemId,
3333
3781
  isDisabled
3334
3782
  ]);
@@ -3556,7 +4004,7 @@ const Meter = (0, react.forwardRef)(function Meter(_ref, ref) {
3556
4004
  //#region src/primitives/accordion.tsx
3557
4005
  const _excluded$1 = ["value", "disabled"];
3558
4006
  const _excluded2$1 = ["onClick", "disabled"];
3559
- const _excluded3$1 = ["style"];
4007
+ const _excluded3$1 = ["hiddenUntilFound", "style"];
3560
4008
  const AccordionRootContext = (0, react.createContext)(null);
3561
4009
  function useAccordionRoot() {
3562
4010
  const ctx = (0, react.useContext)(AccordionRootContext);
@@ -3643,17 +4091,31 @@ function AccordionTrigger(_ref2) {
3643
4091
  }
3644
4092
  /** Renders the `AccordionContent` component. */
3645
4093
  function AccordionContent(_ref3) {
3646
- let { style } = _ref3, props = _objectWithoutProperties(_ref3, _excluded3$1);
4094
+ let { hiddenUntilFound = false, style } = _ref3, props = _objectWithoutProperties(_ref3, _excluded3$1);
3647
4095
  const root = useAccordionRoot();
3648
4096
  const item = useAccordionItem();
3649
- const { ref, mounted, dataAttributes } = useEnterLeave(root.isExpanded(item.value), { animated: root.animated });
4097
+ const expanded = root.isExpanded(item.value);
4098
+ const { ref, mounted, dataAttributes } = useEnterLeave(expanded, { animated: root.animated && !hiddenUntilFound });
3650
4099
  const innerRef = (0, react.useRef)(null);
3651
- (0, react.useEffect)(() => {
3652
- if (innerRef.current) ref.current = innerRef.current;
3653
- }, [ref]);
4100
+ const contentRef = useMergedRef(innerRef, ref);
4101
+ useHiddenUntilFound(innerRef, {
4102
+ enabled: hiddenUntilFound,
4103
+ open: expanded,
4104
+ onReveal: (0, react.useCallback)(() => {
4105
+ if (!root.isExpanded(item.value)) root.toggle(item.value);
4106
+ }, [root, item.value])
4107
+ });
4108
+ if (hiddenUntilFound) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", _objectSpread2({
4109
+ ref: contentRef,
4110
+ id: item.contentId,
4111
+ role: "region",
4112
+ "aria-labelledby": item.triggerId,
4113
+ "data-state": expanded ? "open" : "closed",
4114
+ style
4115
+ }, props));
3654
4116
  if (!mounted) return null;
3655
4117
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", _objectSpread2(_objectSpread2({
3656
- ref: innerRef,
4118
+ ref: contentRef,
3657
4119
  id: item.contentId,
3658
4120
  role: "region",
3659
4121
  "aria-labelledby": item.triggerId,
@@ -4005,18 +4467,21 @@ const _excluded4 = [
4005
4467
  "name",
4006
4468
  "id",
4007
4469
  "onChange",
4470
+ "onBlur",
4008
4471
  "aria-describedby"
4009
4472
  ];
4010
4473
  const _excluded5 = [
4011
4474
  "name",
4012
4475
  "id",
4013
4476
  "onChange",
4477
+ "onBlur",
4014
4478
  "aria-describedby"
4015
4479
  ];
4016
4480
  const _excluded6 = [
4017
4481
  "name",
4018
4482
  "id",
4019
4483
  "onChange",
4484
+ "onBlur",
4020
4485
  "aria-describedby",
4021
4486
  "children"
4022
4487
  ];
@@ -4127,7 +4592,7 @@ function FormLabel(_ref3) {
4127
4592
  }
4128
4593
  const FormInput = (0, react.forwardRef)((_ref4, ref) => {
4129
4594
  var _useFieldValueMaybe;
4130
- let { name, id, onChange, "aria-describedby": ariaDescribedBy } = _ref4, props = _objectWithoutProperties(_ref4, _excluded4);
4595
+ let { name, id, onChange, onBlur, "aria-describedby": ariaDescribedBy } = _ref4, props = _objectWithoutProperties(_ref4, _excluded4);
4131
4596
  const form = useFormContext();
4132
4597
  const field = useFormField();
4133
4598
  const value = (_useFieldValueMaybe = useFieldValueMaybe(form, name)) !== null && _useFieldValueMaybe !== void 0 ? _useFieldValueMaybe : "";
@@ -4146,13 +4611,21 @@ const FormInput = (0, react.forwardRef)((_ref4, ref) => {
4146
4611
  name,
4147
4612
  onChange
4148
4613
  ]);
4614
+ const handleBlur = (0, react.useCallback)((e) => {
4615
+ form === null || form === void 0 || form.setFieldTouched(name, true);
4616
+ onBlur === null || onBlur === void 0 || onBlur(e);
4617
+ }, [
4618
+ form,
4619
+ name,
4620
+ onBlur
4621
+ ]);
4149
4622
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", _objectSpread2({
4150
4623
  ref: setRef,
4151
4624
  id: id !== null && id !== void 0 ? id : field === null || field === void 0 ? void 0 : field.inputId,
4152
4625
  name,
4153
4626
  value,
4154
4627
  onChange: handleChange,
4155
- onBlur: () => form === null || form === void 0 ? void 0 : form.setFieldTouched(name, true),
4628
+ onBlur: handleBlur,
4156
4629
  "aria-invalid": hasError || void 0,
4157
4630
  "aria-describedby": composeDescribedBy(field, hasError, ariaDescribedBy)
4158
4631
  }, props));
@@ -4160,7 +4633,7 @@ const FormInput = (0, react.forwardRef)((_ref4, ref) => {
4160
4633
  FormInput.displayName = "FormInput";
4161
4634
  const FormTextarea = (0, react.forwardRef)((_ref5, ref) => {
4162
4635
  var _useFieldValueMaybe2;
4163
- let { name, id, onChange, "aria-describedby": ariaDescribedBy } = _ref5, props = _objectWithoutProperties(_ref5, _excluded5);
4636
+ let { name, id, onChange, onBlur, "aria-describedby": ariaDescribedBy } = _ref5, props = _objectWithoutProperties(_ref5, _excluded5);
4164
4637
  const form = useFormContext();
4165
4638
  const field = useFormField();
4166
4639
  const value = (_useFieldValueMaybe2 = useFieldValueMaybe(form, name)) !== null && _useFieldValueMaybe2 !== void 0 ? _useFieldValueMaybe2 : "";
@@ -4176,13 +4649,21 @@ const FormTextarea = (0, react.forwardRef)((_ref5, ref) => {
4176
4649
  name,
4177
4650
  onChange
4178
4651
  ]);
4652
+ const handleBlur = (0, react.useCallback)((e) => {
4653
+ form === null || form === void 0 || form.setFieldTouched(name, true);
4654
+ onBlur === null || onBlur === void 0 || onBlur(e);
4655
+ }, [
4656
+ form,
4657
+ name,
4658
+ onBlur
4659
+ ]);
4179
4660
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", _objectSpread2({
4180
4661
  ref: setRef,
4181
4662
  id: id !== null && id !== void 0 ? id : field === null || field === void 0 ? void 0 : field.inputId,
4182
4663
  name,
4183
4664
  value,
4184
4665
  onChange: handleChange,
4185
- onBlur: () => form === null || form === void 0 ? void 0 : form.setFieldTouched(name, true),
4666
+ onBlur: handleBlur,
4186
4667
  "aria-invalid": hasError || void 0,
4187
4668
  "aria-describedby": composeDescribedBy(field, hasError, ariaDescribedBy)
4188
4669
  }, props));
@@ -4190,7 +4671,7 @@ const FormTextarea = (0, react.forwardRef)((_ref5, ref) => {
4190
4671
  FormTextarea.displayName = "FormTextarea";
4191
4672
  const FormSelect = (0, react.forwardRef)((_ref6, ref) => {
4192
4673
  var _useFieldValueMaybe3;
4193
- let { name, id, onChange, "aria-describedby": ariaDescribedBy, children } = _ref6, props = _objectWithoutProperties(_ref6, _excluded6);
4674
+ let { name, id, onChange, onBlur, "aria-describedby": ariaDescribedBy, children } = _ref6, props = _objectWithoutProperties(_ref6, _excluded6);
4194
4675
  const form = useFormContext();
4195
4676
  const field = useFormField();
4196
4677
  const value = (_useFieldValueMaybe3 = useFieldValueMaybe(form, name)) !== null && _useFieldValueMaybe3 !== void 0 ? _useFieldValueMaybe3 : "";
@@ -4206,13 +4687,21 @@ const FormSelect = (0, react.forwardRef)((_ref6, ref) => {
4206
4687
  name,
4207
4688
  onChange
4208
4689
  ]);
4690
+ const handleBlur = (0, react.useCallback)((e) => {
4691
+ form === null || form === void 0 || form.setFieldTouched(name, true);
4692
+ onBlur === null || onBlur === void 0 || onBlur(e);
4693
+ }, [
4694
+ form,
4695
+ name,
4696
+ onBlur
4697
+ ]);
4209
4698
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", _objectSpread2(_objectSpread2({
4210
4699
  ref: setRef,
4211
4700
  id: id !== null && id !== void 0 ? id : field === null || field === void 0 ? void 0 : field.inputId,
4212
4701
  name,
4213
4702
  value,
4214
4703
  onChange: handleChange,
4215
- onBlur: () => form === null || form === void 0 ? void 0 : form.setFieldTouched(name, true),
4704
+ onBlur: handleBlur,
4216
4705
  "aria-invalid": hasError || void 0,
4217
4706
  "aria-describedby": composeDescribedBy(field, hasError, ariaDescribedBy)
4218
4707
  }, props), {}, { children }));
@@ -4510,7 +4999,9 @@ exports.ToolbarRoot = ToolbarRoot;
4510
4999
  exports.ToolbarSeparator = ToolbarSeparator;
4511
5000
  exports.Tooltip = Tooltip;
4512
5001
  exports.TooltipAnchor = TooltipAnchor;
5002
+ exports.TooltipGroup = TooltipGroup;
4513
5003
  exports.TooltipProvider = TooltipProvider;
5004
+ exports.TooltipRoot = TooltipRoot;
4514
5005
  exports.useCommandState = useCommandState;
4515
5006
  exports.useControllableState = useControllableState;
4516
5007
  exports.useDialogClose = useDialogClose;