@vellira-ui/react-native 2.58.0 → 2.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { Children, cloneElement, createContext, forwardRef, isValidElement, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
1
+ import { Children, cloneElement, createContext, forwardRef, isValidElement, useCallback, useContext, useEffect, useId, useMemo, useRef, useState, useSyncExternalStore } from "react";
2
2
  import { Check, ChevronDown, Close, Search } from "@vellira-ui/icons";
3
3
  import { AccessibilityInfo, ActivityIndicator, Animated, BackHandler, Dimensions, Easing, FlatList, Modal as Modal$1, Platform, Pressable, ScrollView, StyleSheet, Text, TextInput, View, findNodeHandle, useWindowDimensions } from "react-native";
4
- import { createConsoleOverlayDiagnostics, createOverlayZIndexPolicy, resolveOverlayZIndex } from "@vellira-ui/core";
4
+ import { createConsoleOverlayDiagnostics, createOverlayManagerStore, createOverlayZIndexPolicy, createRetainedResourceRegistry, deferOverlayFocusRestore, getCompoundSlot, markCompoundSlot, resolveOverlayPresentation } from "@vellira-ui/core";
5
5
  import { darkTheme, highContrastTheme, lightTheme } from "@vellira-ui/tokens";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
7
  //#region src/theme/fontWeight.ts
@@ -31,38 +31,54 @@ const nativeOverlayZIndexPolicy = createOverlayZIndexPolicy({
31
31
  });
32
32
  const nativeOverlayDiagnostics = createConsoleOverlayDiagnostics("NativeOverlayManager");
33
33
  const createNativeOverlayManager = () => {
34
- let stack = [];
34
+ const store = createOverlayManagerStore({
35
+ diagnostics: nativeOverlayDiagnostics,
36
+ policy: nativeOverlayZIndexPolicy
37
+ });
35
38
  const dismissHandlers = /* @__PURE__ */ new Map();
36
39
  const outsidePressHandlers = /* @__PURE__ */ new Map();
40
+ const toNativeEntry = (id) => ({
41
+ id,
42
+ zIndex: store.getZIndex(id) ?? nativeOverlayZIndexPolicy.levels.modal
43
+ });
44
+ let cachedStoreSnapshot;
45
+ let cachedNativeSnapshot;
46
+ const getNativeSnapshot = () => {
47
+ const snapshot = store.getSnapshot();
48
+ if (cachedStoreSnapshot === snapshot && cachedNativeSnapshot) return cachedNativeSnapshot;
49
+ const stack = snapshot.stack.map((entry) => toNativeEntry(entry.id));
50
+ cachedStoreSnapshot = snapshot;
51
+ cachedNativeSnapshot = {
52
+ registry: new Map(stack.map((entry) => [entry.id, entry])),
53
+ stack,
54
+ topmost: snapshot.topmost ? toNativeEntry(snapshot.topmost.id) : void 0
55
+ };
56
+ return cachedNativeSnapshot;
57
+ };
37
58
  return {
38
59
  register(id) {
39
- if (stack.some((item) => item.id === id)) nativeOverlayDiagnostics.duplicateRegistration?.(id);
40
- stack = stack.filter((item) => item.id !== id);
41
- const entry = {
42
- id,
43
- zIndex: resolveOverlayZIndex({
44
- level: nativeOverlayZIndexPolicy.defaultLevel,
45
- order: stack.length,
46
- policy: nativeOverlayZIndexPolicy
47
- })
48
- };
49
- stack.push(entry);
50
- return entry;
60
+ const entry = store.register({ id });
61
+ return toNativeEntry(entry.id);
51
62
  },
52
63
  unregister(id) {
53
- if (!stack.some((item) => item.id === id)) nativeOverlayDiagnostics.unknownUnregister?.(id);
54
64
  dismissHandlers.delete(id);
55
65
  outsidePressHandlers.delete(id);
56
- stack = stack.filter((item) => item.id !== id);
66
+ store.unregister(id);
67
+ },
68
+ getSnapshot() {
69
+ return getNativeSnapshot();
57
70
  },
58
71
  isTop(id) {
59
- return stack.at(-1)?.id === id;
72
+ return store.isTopmost(id);
60
73
  },
61
74
  getTop() {
62
- return stack.at(-1);
75
+ return getNativeSnapshot().topmost;
63
76
  },
64
77
  getZIndex(id) {
65
- return stack.find((item) => item.id === id)?.zIndex ?? nativeOverlayZIndexPolicy.levels[nativeOverlayZIndexPolicy.defaultLevel];
78
+ return store.getZIndex(id) ?? nativeOverlayZIndexPolicy.levels[nativeOverlayZIndexPolicy.defaultLevel];
79
+ },
80
+ subscribe(listener) {
81
+ return store.subscribe(listener);
66
82
  },
67
83
  registerDismissHandler(id, handler) {
68
84
  dismissHandlers.set(id, handler);
@@ -93,9 +109,9 @@ const createNativeOverlayManager = () => {
93
109
  return handler();
94
110
  },
95
111
  clear() {
96
- stack = [];
97
112
  dismissHandlers.clear();
98
113
  outsidePressHandlers.clear();
114
+ store.clear();
99
115
  }
100
116
  };
101
117
  };
@@ -108,11 +124,10 @@ const useNativeOverlayManager = () => useContext(NativeOverlayManagerContext) ??
108
124
  //#region src/hooks/behavior/overlay/useOverlayRegistration.ts
109
125
  const useOverlayRegistration = ({ active, id }) => {
110
126
  const nativeOverlayManager = useNativeOverlayManager();
111
- const [zIndex, setZIndex] = useState(() => nativeOverlayManager.getZIndex(id));
127
+ const snapshot = useSyncExternalStore(nativeOverlayManager.subscribe, nativeOverlayManager.getSnapshot, nativeOverlayManager.getSnapshot);
112
128
  useEffect(() => {
113
129
  if (!active) return;
114
- const entry = nativeOverlayManager.register(id);
115
- setZIndex(entry.zIndex);
130
+ nativeOverlayManager.register(id);
116
131
  return () => {
117
132
  nativeOverlayManager.unregister(id);
118
133
  };
@@ -121,52 +136,33 @@ const useOverlayRegistration = ({ active, id }) => {
121
136
  id,
122
137
  nativeOverlayManager
123
138
  ]);
139
+ const isTopOverlay = useCallback(() => nativeOverlayManager.isTop(id), [id, nativeOverlayManager]);
124
140
  return {
125
- zIndex,
126
- isTopOverlay: useCallback(() => nativeOverlayManager.isTop(id), [id, nativeOverlayManager])
141
+ zIndex: snapshot.registry.get(id)?.zIndex ?? nativeOverlayManager.getZIndex(id),
142
+ isTopmost: snapshot.topmost?.id === id,
143
+ isTopOverlay
127
144
  };
128
145
  };
129
146
  //#endregion
130
147
  //#region src/hooks/behavior/overlay/useOverlayDismiss.ts
131
- const dismissListeners = /* @__PURE__ */ new Map();
132
- function attachDismissListener(manager) {
133
- if (dismissListeners.has(manager)) return;
148
+ const dismissListeners = createRetainedResourceRegistry((manager) => {
134
149
  if (Platform.OS === "web") {
135
150
  const handleKeyDown = (event) => {
136
151
  if (event.key !== "Escape") return;
137
152
  manager.dispatchTopDismiss();
138
153
  };
139
154
  document.addEventListener("keydown", handleKeyDown);
140
- dismissListeners.set(manager, {
141
- count: 0,
142
- detach: () => {
143
- document.removeEventListener("keydown", handleKeyDown);
144
- dismissListeners.delete(manager);
145
- }
146
- });
147
- return;
155
+ return () => {
156
+ document.removeEventListener("keydown", handleKeyDown);
157
+ };
148
158
  }
149
159
  const subscription = BackHandler.addEventListener("hardwareBackPress", () => manager.dispatchTopDismiss());
150
- dismissListeners.set(manager, {
151
- count: 0,
152
- detach: () => {
153
- subscription.remove();
154
- dismissListeners.delete(manager);
155
- }
156
- });
157
- }
158
- function retainDismissListener(manager) {
159
- attachDismissListener(manager);
160
- const retained = dismissListeners.get(manager);
161
- if (!retained) return () => void 0;
162
- retained.count += 1;
163
160
  return () => {
164
- const current = dismissListeners.get(manager);
165
- if (!current) return;
166
- current.count = Math.max(0, current.count - 1);
167
- if (current.count > 0) return;
168
- current.detach();
161
+ subscription.remove();
169
162
  };
163
+ });
164
+ function retainDismissListener(manager) {
165
+ return dismissListeners.retain(manager);
170
166
  }
171
167
  const useOverlayDismiss = ({ active, closeOnEscape = true, closeOnOutsidePress = true, id, requestClose, requestOutsideClose }) => {
172
168
  const nativeOverlayManager = useNativeOverlayManager();
@@ -182,6 +178,22 @@ const useOverlayDismiss = ({ active, closeOnEscape = true, closeOnOutsidePress =
182
178
  const requestOutsideTopClose = useCallback(() => {
183
179
  nativeOverlayManager.dispatchTopOutsidePress();
184
180
  }, [nativeOverlayManager]);
181
+ const getOutsidePressProps = useCallback(({ accessibilityLabel = "Dismiss overlay" } = {}) => {
182
+ if (!active || !closeOnOutsidePress) return {
183
+ accessibilityLabel: void 0,
184
+ accessibilityRole: void 0,
185
+ onPress: void 0
186
+ };
187
+ return {
188
+ accessibilityLabel,
189
+ accessibilityRole: "button",
190
+ onPress: requestOutsideTopClose
191
+ };
192
+ }, [
193
+ active,
194
+ closeOnOutsidePress,
195
+ requestOutsideTopClose
196
+ ]);
185
197
  useEffect(() => {
186
198
  if (!active) return;
187
199
  return nativeOverlayManager.registerOutsidePressHandler(id, () => {
@@ -225,6 +237,7 @@ const useOverlayDismiss = ({ active, closeOnEscape = true, closeOnOutsidePress =
225
237
  return {
226
238
  zIndex: registration.zIndex,
227
239
  isTopOverlay,
240
+ getOutsidePressProps,
228
241
  requestClose: requestTopClose,
229
242
  requestOutsideClose: requestOutsideTopClose
230
243
  };
@@ -262,7 +275,7 @@ const useOverlayFocusRestore = ({ active = false, enabled = true, finalFocus, tr
262
275
  ]);
263
276
  const restoreFocusAfterClose = useCallback(() => {
264
277
  if (!enabled) return;
265
- requestAnimationFrame(restoreFocus);
278
+ deferOverlayFocusRestore(restoreFocus, requestAnimationFrame);
266
279
  }, [enabled, restoreFocus]);
267
280
  useEffect(() => {
268
281
  if (!active) return;
@@ -278,8 +291,11 @@ const useOverlayFocusRestore = ({ active = false, enabled = true, finalFocus, tr
278
291
  //#region src/hooks/behavior/overlay/useOverlayPresentation.ts
279
292
  function useOverlayPresentation(presentation = "auto", breakpoint = 768) {
280
293
  const { width } = useWindowDimensions();
281
- if (presentation === "auto") return width >= breakpoint ? "popover" : "sheet";
282
- return presentation;
294
+ return resolveOverlayPresentation({
295
+ presentation,
296
+ defaultPresentation: "sheet",
297
+ autoPresentation: width >= breakpoint ? "popover" : "sheet"
298
+ });
283
299
  }
284
300
  //#endregion
285
301
  //#region src/hooks/useControllableState.ts
@@ -531,11 +547,23 @@ const useModal = ({ open, defaultOpen = false, onOpenChange, closeOnEscape, clos
531
547
  };
532
548
  //#endregion
533
549
  //#region src/hooks/useSelect.ts
534
- const useSelect = ({ value, defaultValue, onValueChange, onChange, options, multiple = false, maxSelected, closeOnSelect = !multiple, disabled = false, open, defaultOpen = false, onOpenChange }) => {
550
+ const useSelect = ({ value, defaultValue, onValueChange, options, multiple = false, maxSelected, closeOnSelect = !multiple, disabled = false, open, defaultOpen = false, onOpenChange }) => {
551
+ const handleValueChange = useCallback((nextValue) => {
552
+ if (!onValueChange) return;
553
+ if (multiple) {
554
+ if (Array.isArray(nextValue)) {
555
+ onValueChange(nextValue);
556
+ return;
557
+ }
558
+ onValueChange(nextValue ? [nextValue] : []);
559
+ return;
560
+ }
561
+ onValueChange(Array.isArray(nextValue) ? nextValue[0] ?? "" : nextValue);
562
+ }, [multiple, onValueChange]);
535
563
  const [selectedValue, setSelectedValue] = useControllableState({
536
564
  value,
537
565
  defaultValue: defaultValue ?? (multiple ? [] : ""),
538
- onChange: onValueChange ?? onChange
566
+ onChange: handleValueChange
539
567
  });
540
568
  const [isOpen, setIsOpen] = useControllableState({
541
569
  value: open,
@@ -808,7 +836,7 @@ const createStyles$21 = (theme) => StyleSheet.create({
808
836
  const nativePointerEventsBoxNone$1 = Platform.OS === "web" ? void 0 : { pointerEvents: "box-none" };
809
837
  const webPointerEventsBoxNone$1 = Platform.OS === "web" ? { pointerEvents: "box-none" } : void 0;
810
838
  function DropdownContent({ children, contentStyle, accessibilityLabel }) {
811
- const { open, color, presentation, zIndex, position, searchable, searchValue, searchPlaceholder, searchAccessibilityLabel, requestClose, requestOutsideClose, onSearchChange, onFloatingLayout } = useDropdownContext();
839
+ const { open, color, presentation, zIndex, position, searchable, searchValue, searchPlaceholder, searchAccessibilityLabel, requestClose, getOutsidePressProps, onSearchChange, onFloatingLayout } = useDropdownContext();
812
840
  const { theme } = useTheme();
813
841
  const styles = useThemeStyles(createStyles$21);
814
842
  const colorPalette = theme.components.dropdown[color];
@@ -881,10 +909,8 @@ function DropdownContent({ children, contentStyle, accessibilityLabel }) {
881
909
  webPointerEventsBoxNone$1
882
910
  ],
883
911
  children: /* @__PURE__ */ jsx(Pressable, {
884
- accessibilityRole: "button",
885
- accessibilityLabel: "Close menu",
886
- style: StyleSheet.absoluteFill,
887
- onPress: requestOutsideClose
912
+ ...getOutsidePressProps({ accessibilityLabel: "Close menu" }),
913
+ style: StyleSheet.absoluteFill
888
914
  })
889
915
  }), /* @__PURE__ */ jsxs(Animated.View, {
890
916
  accessibilityRole: "menu",
@@ -932,7 +958,7 @@ DropdownContent.displayName = "DropdownContent";
932
958
  //#region src/components/Dropdown/internal/DropdownCollection.ts
933
959
  function createDropdownSlot(name, displayName) {
934
960
  const Slot = () => null;
935
- Slot.__velliraDropdownPart = name;
961
+ markCompoundSlot(Slot, name);
936
962
  Slot.displayName = displayName;
937
963
  return Slot;
938
964
  }
@@ -960,11 +986,12 @@ function parseDropdownChildren(children) {
960
986
  Children.forEach(node, (child) => {
961
987
  if (!isValidElement(child)) return;
962
988
  const type = child.type;
989
+ const slot = getCompoundSlot(type);
963
990
  if (type.__velliraPortal) {
964
991
  visit(child.props.children);
965
992
  return;
966
993
  }
967
- switch (type.__velliraDropdownPart) {
994
+ switch (slot) {
968
995
  case "trigger":
969
996
  triggerProps = child.props;
970
997
  trigger = triggerProps.children;
@@ -1340,6 +1367,11 @@ function DropdownGroup({ label }) {
1340
1367
  }
1341
1368
  DropdownGroup.displayName = "DropdownGroup";
1342
1369
  //#endregion
1370
+ //#region src/utils/devWarning.ts
1371
+ const devWarning = (condition, message) => {
1372
+ if ((typeof __DEV__ === "undefined" || __DEV__) && !condition) console.warn(message);
1373
+ };
1374
+ //#endregion
1343
1375
  //#region src/components/Dropdown/Item/DropdownItem.styles.ts
1344
1376
  const createStyles$18 = (theme) => StyleSheet.create({
1345
1377
  item: {
@@ -1364,7 +1396,7 @@ const createStyles$18 = (theme) => StyleSheet.create({
1364
1396
  });
1365
1397
  //#endregion
1366
1398
  //#region src/components/Dropdown/Item/DropdownItem.tsx
1367
- function DropdownItem({ label, value, color = "default", icon, disabled = false, textWrap = "truncate", onSelect }) {
1399
+ function DropdownItem({ label, value, asChild = false, children, color = "default", icon, disabled = false, textWrap = "truncate", onSelect }) {
1368
1400
  const { color: rootColor, itemStyle, textStyle } = useDropdownContext();
1369
1401
  const { theme } = useTheme();
1370
1402
  const styles = useThemeStyles(createStyles$18);
@@ -1389,6 +1421,24 @@ function DropdownItem({ label, value, color = "default", icon, disabled = false,
1389
1421
  const accessibilityLabel = typeof label === "string" ? label : value;
1390
1422
  const numberOfLines = textWrap === "wrap" ? void 0 : 1;
1391
1423
  const ellipsizeMode = textWrap === "truncate" ? "tail" : "clip";
1424
+ const child = asChild && isValidElement(children) ? children : void 0;
1425
+ const getItemStyle = (pressed) => [
1426
+ styles.item,
1427
+ { backgroundColor: getBackgroundColor(pressed) },
1428
+ itemStyle
1429
+ ];
1430
+ devWarning(!asChild || Boolean(child), "Dropdown.Item: asChild requires a single valid React element child.");
1431
+ if (child) return cloneElement(child, {
1432
+ accessibilityRole: "menuitem",
1433
+ accessibilityLabel,
1434
+ accessibilityState: { disabled },
1435
+ disabled,
1436
+ onPress: (event) => {
1437
+ child.props.onPress?.(event);
1438
+ if (!event.defaultPrevented && !disabled) onSelect(value);
1439
+ },
1440
+ style: [getItemStyle(false), child.props.style]
1441
+ });
1392
1442
  return /* @__PURE__ */ jsx(Pressable, {
1393
1443
  disabled,
1394
1444
  accessibilityRole: "menuitem",
@@ -1398,11 +1448,7 @@ function DropdownItem({ label, value, color = "default", icon, disabled = false,
1398
1448
  if (disabled) return;
1399
1449
  onSelect(value);
1400
1450
  },
1401
- style: ({ pressed }) => [
1402
- styles.item,
1403
- { backgroundColor: getBackgroundColor(pressed) },
1404
- itemStyle
1405
- ],
1451
+ style: ({ pressed }) => getItemStyle(pressed),
1406
1452
  children: ({ pressed }) => {
1407
1453
  const contentColor = getContentColor(pressed);
1408
1454
  return /* @__PURE__ */ jsxs(Fragment, { children: [icon ? renderColoredNode(icon, contentColor) : null, /* @__PURE__ */ jsx(Text, {
@@ -1736,13 +1782,15 @@ function DropdownRoot({ children, label = "Menu", trigger, icon, arrowIcon, show
1736
1782
  children: item.props.children
1737
1783
  });
1738
1784
  return /* @__PURE__ */ jsx(DropdownItem, {
1785
+ asChild: item.props.asChild,
1739
1786
  label: item.props.children,
1740
1787
  value: item.props.value ?? item.id,
1741
1788
  color: item.props.color,
1742
1789
  icon: item.props.icon,
1743
1790
  disabled: item.props.disabled,
1744
1791
  textWrap: item.props.textWrap,
1745
- onSelect: () => handleSelect(item)
1792
+ onSelect: () => handleSelect(item),
1793
+ children: item.props.children
1746
1794
  });
1747
1795
  }, [handleSelect, styles.emptyText]);
1748
1796
  const resolvedSearchPlaceholder = parsed.searchProps?.placeholder ?? searchPlaceholder ?? (command || contentCommand ? "Type a command..." : "Search actions...");
@@ -1764,7 +1812,7 @@ function DropdownRoot({ children, label = "Menu", trigger, icon, arrowIcon, show
1764
1812
  itemStyle,
1765
1813
  textStyle,
1766
1814
  requestClose: dismiss.requestClose,
1767
- requestOutsideClose: dismiss.requestOutsideClose,
1815
+ getOutsidePressProps: dismiss.getOutsidePressProps,
1768
1816
  toggle: handleTriggerPress,
1769
1817
  onSearchChange: handleSearchChange,
1770
1818
  onFloatingLayout
@@ -1773,8 +1821,8 @@ function DropdownRoot({ children, label = "Menu", trigger, icon, arrowIcon, show
1773
1821
  contentPresentation,
1774
1822
  disabled,
1775
1823
  dismiss.zIndex,
1824
+ dismiss.getOutsidePressProps,
1776
1825
  dismiss.requestClose,
1777
- dismiss.requestOutsideClose,
1778
1826
  handleSearchChange,
1779
1827
  handleTriggerPress,
1780
1828
  isOpen,
@@ -1885,13 +1933,23 @@ const createStyles$14 = (theme) => StyleSheet.create({
1885
1933
  justifyContent: "space-between",
1886
1934
  paddingBottom: theme.components.modal.header.paddingBottom
1887
1935
  },
1888
- title: {
1936
+ headerContent: {
1889
1937
  flex: 1,
1938
+ gap: theme.tokens.spacing["1"]
1939
+ },
1940
+ title: {
1890
1941
  color: theme.components.modal.title.fg,
1891
1942
  fontFamily: theme.tokens.typography.family.semibold,
1892
1943
  fontSize: theme.tokens.typography.size.lg,
1893
1944
  lineHeight: theme.tokens.typography.lineHeight.md
1894
1945
  },
1946
+ plainTitle: { flex: 1 },
1947
+ description: {
1948
+ color: theme.components.modal.description.fg,
1949
+ fontFamily: theme.tokens.typography.family.regular,
1950
+ fontSize: theme.tokens.typography.size.sm,
1951
+ lineHeight: theme.tokens.typography.lineHeight.sm
1952
+ },
1895
1953
  closeButton: {
1896
1954
  width: theme.components.modal.closeButton.size,
1897
1955
  height: theme.components.modal.closeButton.size,
@@ -1915,11 +1973,11 @@ const useModalContext = () => {
1915
1973
  ModalContext.displayName = "ModalContext";
1916
1974
  //#endregion
1917
1975
  //#region src/components/Modal/Close/ModalClose.tsx
1918
- const ModalClose = ({ children, accessibilityLabel, style }) => {
1976
+ const ModalClose = ({ asChild = false, children, accessibilityLabel, style }) => {
1919
1977
  const { theme } = useTheme();
1920
1978
  const styles = useThemeStyles(createStyles$14);
1921
1979
  const { onClose } = useModalContext();
1922
- if (isValidElement(children)) return cloneElement(children, {
1980
+ if ((asChild || children !== void 0) && isValidElement(children)) return cloneElement(children, {
1923
1981
  accessibilityLabel: children.props.accessibilityLabel ?? accessibilityLabel,
1924
1982
  onPress: (event) => {
1925
1983
  children.props.onPress?.(event);
@@ -2012,19 +2070,46 @@ const ModalFooter = ({ children, style }) => {
2012
2070
  };
2013
2071
  ModalFooter.displayName = "ModalFooter";
2014
2072
  //#endregion
2073
+ //#region src/components/Modal/Header/ModalDescription.tsx
2074
+ const ModalDescription = ({ children, style }) => {
2075
+ return /* @__PURE__ */ jsx(Text, {
2076
+ style: [useThemeStyles(createStyles$14).description, style],
2077
+ children
2078
+ });
2079
+ };
2080
+ ModalDescription.displayName = "Modal.Description";
2081
+ //#endregion
2015
2082
  //#region src/components/Modal/Header/ModalHeader.tsx
2016
2083
  const ModalHeader = ({ children, style, textStyle }) => {
2017
2084
  const styles = useThemeStyles(createStyles$14);
2085
+ const isPlainTitle = typeof children === "string" || typeof children === "number";
2018
2086
  return /* @__PURE__ */ jsxs(View, {
2019
2087
  style: [styles.header, style],
2020
- children: [/* @__PURE__ */ jsx(Text, {
2021
- style: [styles.title, textStyle],
2088
+ children: [isPlainTitle ? /* @__PURE__ */ jsx(Text, {
2089
+ style: [
2090
+ styles.title,
2091
+ styles.plainTitle,
2092
+ textStyle
2093
+ ],
2094
+ children
2095
+ }) : /* @__PURE__ */ jsx(View, {
2096
+ style: styles.headerContent,
2022
2097
  children
2023
2098
  }), /* @__PURE__ */ jsx(ModalClose, {})]
2024
2099
  });
2025
2100
  };
2026
2101
  ModalHeader.displayName = "ModalHeader";
2027
2102
  //#endregion
2103
+ //#region src/components/Modal/Header/ModalTitle.tsx
2104
+ const ModalTitle = ({ children, style }) => {
2105
+ return /* @__PURE__ */ jsx(Text, {
2106
+ accessibilityRole: "header",
2107
+ style: [useThemeStyles(createStyles$14).title, style],
2108
+ children
2109
+ });
2110
+ };
2111
+ ModalTitle.displayName = "Modal.Title";
2112
+ //#endregion
2028
2113
  //#region src/components/Modal/Modal.styles.ts
2029
2114
  const createStyles$11 = (theme) => StyleSheet.create({
2030
2115
  overlay: {
@@ -2042,7 +2127,7 @@ const createStyles$11 = (theme) => StyleSheet.create({
2042
2127
  //#region src/components/Modal/Overlay/ModalOverlay.tsx
2043
2128
  const ModalOverlay = ({ children, overlayStyle }) => {
2044
2129
  const styles = useThemeStyles(createStyles$11);
2045
- const { animation, animationProgress, closeOnOutsidePress, zIndex, onClose, onOutsideClose, shouldRender } = useModalContext();
2130
+ const { animation, animationProgress, zIndex, onClose, getOutsidePressProps, shouldRender } = useModalContext();
2046
2131
  const backdropStyle = animation === "none" ? void 0 : { opacity: animationProgress };
2047
2132
  return /* @__PURE__ */ jsx(Modal$1, {
2048
2133
  visible: shouldRender,
@@ -2059,17 +2144,15 @@ const ModalOverlay = ({ children, overlayStyle }) => {
2059
2144
  style: [styles.backdrop, backdropStyle],
2060
2145
  children: /* @__PURE__ */ jsx(Pressable, {
2061
2146
  testID: "modal-backdrop",
2062
- accessibilityRole: closeOnOutsidePress ? "button" : void 0,
2063
- accessibilityLabel: closeOnOutsidePress ? "Close modal" : void 0,
2064
- style: StyleSheet.absoluteFill,
2065
- onPress: closeOnOutsidePress ? onOutsideClose : void 0
2147
+ ...getOutsidePressProps({ accessibilityLabel: "Close modal" }),
2148
+ style: StyleSheet.absoluteFill
2066
2149
  })
2067
2150
  }), children]
2068
2151
  })
2069
2152
  });
2070
2153
  };
2071
2154
  //#endregion
2072
- //#region src/components/Modal/Root/ModalRoot.tsx
2155
+ //#region src/components/Modal/Root/useModalRootAnimation.ts
2073
2156
  const linearEasing = (value) => value;
2074
2157
  const easingMap = {
2075
2158
  standard: Easing?.bezier?.(.22, 1, .36, 1) ?? linearEasing,
@@ -2090,33 +2173,10 @@ const resolveDuration = (duration) => {
2090
2173
  open: duration?.open ?? parseDuration(nativeThemes.light.components.modal.motion.openDuration)
2091
2174
  };
2092
2175
  };
2093
- const ModalRoot = ({ open, defaultOpen = false, onOpenChange, animation = "scale", duration, easing = "standard", closeOnOutsidePress = true, children }) => {
2094
- const initialOpen = open ?? defaultOpen;
2095
- const animationProgress = useRef(new Animated.Value(initialOpen ? 1 : 0));
2096
- const triggerRef = useRef(null);
2097
- const [shouldRender, setShouldRender] = useState(initialOpen);
2176
+ function useModalRootAnimation({ animation, defaultOpen, duration, easing, open }) {
2177
+ const animationProgress = useRef(new Animated.Value(defaultOpen ? 1 : 0));
2178
+ const [shouldRender, setShouldRender] = useState(defaultOpen);
2098
2179
  const [reduceMotion, setReduceMotion] = useState(false);
2099
- const modal = useModal({
2100
- open,
2101
- defaultOpen,
2102
- onOpenChange,
2103
- closeOnOutsidePress
2104
- });
2105
- const { restoreFocusAfterClose } = useOverlayFocusRestore({
2106
- active: modal.open,
2107
- triggerRef
2108
- });
2109
- const previousOpenRef = useRef(modal.open);
2110
- useEffect(() => {
2111
- if (previousOpenRef.current && !modal.open) restoreFocusAfterClose();
2112
- previousOpenRef.current = modal.open;
2113
- }, [modal.open, restoreFocusAfterClose]);
2114
- const dismiss = useOverlayDismiss({
2115
- id: modal.contentId,
2116
- active: modal.open,
2117
- closeOnOutsidePress: modal.closeOnOutsidePress,
2118
- requestClose: modal.requestClose
2119
- });
2120
2180
  const animationDuration = resolveDuration(duration);
2121
2181
  const shouldAnimate = animation !== "none" && !reduceMotion;
2122
2182
  useEffect(() => {
@@ -2128,7 +2188,7 @@ const ModalRoot = ({ open, defaultOpen = false, onOpenChange, animation = "scale
2128
2188
  }, []);
2129
2189
  useEffect(() => {
2130
2190
  const progress = animationProgress.current;
2131
- if (modal.open) {
2191
+ if (open) {
2132
2192
  setShouldRender(true);
2133
2193
  if (!shouldAnimate) {
2134
2194
  progress.setValue(1);
@@ -2160,17 +2220,57 @@ const ModalRoot = ({ open, defaultOpen = false, onOpenChange, animation = "scale
2160
2220
  animationDuration.close,
2161
2221
  animationDuration.open,
2162
2222
  easing,
2163
- modal.open,
2223
+ open,
2164
2224
  shouldAnimate
2165
2225
  ]);
2226
+ return {
2227
+ animationProgress: animationProgress.current,
2228
+ shouldRender
2229
+ };
2230
+ }
2231
+ //#endregion
2232
+ //#region src/components/Modal/Root/ModalRoot.tsx
2233
+ const ModalRoot = ({ open, defaultOpen = false, onOpenChange, animation = "scale", duration, easing = "standard", closeOnEscape = true, closeOnOutsidePress = true, restoreFocus = true, children }) => {
2234
+ const initialOpen = open ?? defaultOpen;
2235
+ const triggerRef = useRef(null);
2236
+ const modal = useModal({
2237
+ open,
2238
+ defaultOpen,
2239
+ onOpenChange,
2240
+ closeOnEscape,
2241
+ closeOnOutsidePress
2242
+ });
2243
+ const { animationProgress, shouldRender } = useModalRootAnimation({
2244
+ animation,
2245
+ defaultOpen: initialOpen,
2246
+ duration,
2247
+ easing,
2248
+ open: modal.open
2249
+ });
2250
+ const { restoreFocusAfterClose } = useOverlayFocusRestore({
2251
+ active: modal.open,
2252
+ enabled: restoreFocus,
2253
+ triggerRef
2254
+ });
2255
+ const previousOpenRef = useRef(modal.open);
2256
+ useEffect(() => {
2257
+ if (previousOpenRef.current && !modal.open) restoreFocusAfterClose();
2258
+ previousOpenRef.current = modal.open;
2259
+ }, [modal.open, restoreFocusAfterClose]);
2260
+ const dismiss = useOverlayDismiss({
2261
+ id: modal.contentId,
2262
+ active: modal.open,
2263
+ closeOnEscape: modal.closeOnEscape,
2264
+ closeOnOutsidePress: modal.closeOnOutsidePress,
2265
+ requestClose: modal.requestClose
2266
+ });
2166
2267
  return /* @__PURE__ */ jsx(ModalProvider, {
2167
2268
  value: {
2168
2269
  animation,
2169
- animationProgress: animationProgress.current,
2170
- closeOnOutsidePress: modal.closeOnOutsidePress,
2270
+ animationProgress,
2171
2271
  zIndex: dismiss.zIndex,
2172
2272
  onClose: dismiss.requestClose,
2173
- onOutsideClose: dismiss.requestOutsideClose,
2273
+ getOutsidePressProps: dismiss.getOutsidePressProps,
2174
2274
  open: modal.open,
2175
2275
  setOpen: modal.setOpen,
2176
2276
  shouldRender,
@@ -2236,6 +2336,8 @@ const Modal = Object.assign(ModalRoot, {
2236
2336
  Overlay: ModalOverlay,
2237
2337
  Content: ModalContent,
2238
2338
  Header: ModalHeader,
2339
+ Title: ModalTitle,
2340
+ Description: ModalDescription,
2239
2341
  Body: ModalBody,
2240
2342
  Footer: ModalFooter,
2241
2343
  Close: ModalClose
@@ -2416,7 +2518,7 @@ function PopoverContent({ children, style, ...contentProps }) {
2416
2518
  const { theme } = useTheme();
2417
2519
  const layerRef = useRef(null);
2418
2520
  const themedStyles = useMemo(() => createPopoverContentStyles(theme), [theme]);
2419
- const { open, zIndex, position, onFloatingLayout, updatePosition, requestClose, requestOutsideClose, closeOnOutsidePress } = usePopoverContext("Popover.Content");
2521
+ const { open, zIndex, position, onFloatingLayout, updatePosition, requestClose, getOutsidePressProps } = usePopoverContext("Popover.Content");
2420
2522
  useEffect(() => {
2421
2523
  if (!open) return;
2422
2524
  requestAnimationFrame(() => {
@@ -2432,9 +2534,7 @@ function PopoverContent({ children, style, ...contentProps }) {
2432
2534
  style: [styles.root, { zIndex }],
2433
2535
  children: [/* @__PURE__ */ jsx(Pressable, {
2434
2536
  testID: "popover-backdrop",
2435
- accessibilityLabel: closeOnOutsidePress ? "Close popover" : void 0,
2436
- accessibilityRole: closeOnOutsidePress ? "button" : void 0,
2437
- onPress: closeOnOutsidePress ? requestOutsideClose : void 0,
2537
+ ...getOutsidePressProps({ accessibilityLabel: "Close popover" }),
2438
2538
  style: styles.backdrop
2439
2539
  }), /* @__PURE__ */ jsx(View, {
2440
2540
  ...contentProps,
@@ -2517,7 +2617,6 @@ function PopoverRoot({ children, open: openProp, defaultOpen = false, onOpenChan
2517
2617
  return /* @__PURE__ */ jsx(PopoverProvider, {
2518
2618
  value: {
2519
2619
  open,
2520
- closeOnOutsidePress,
2521
2620
  triggerRef,
2522
2621
  anchorRef,
2523
2622
  side,
@@ -2527,7 +2626,7 @@ function PopoverRoot({ children, open: openProp, defaultOpen = false, onOpenChan
2527
2626
  position,
2528
2627
  arrowPosition,
2529
2628
  requestClose: dismiss.requestClose,
2530
- requestOutsideClose: dismiss.requestOutsideClose,
2629
+ getOutsidePressProps: dismiss.getOutsidePressProps,
2531
2630
  onFloatingLayout,
2532
2631
  updatePosition,
2533
2632
  setOpen
@@ -3231,17 +3330,14 @@ const createContentStyles = (theme) => StyleSheet.create({
3231
3330
  }
3232
3331
  });
3233
3332
  //#endregion
3234
- //#region src/components/Select/internal/types.ts
3235
- const selectSlotName = Symbol("VelliraNativeSelectSlot");
3236
- //#endregion
3237
3333
  //#region src/components/Select/internal/SelectCollection.ts
3238
3334
  const createSelectSlot = (name, displayName) => {
3239
3335
  const Slot = (_props) => null;
3240
- Slot[selectSlotName] = name;
3336
+ markCompoundSlot(Slot, name);
3241
3337
  Slot.displayName = displayName;
3242
3338
  return Slot;
3243
3339
  };
3244
- const getSelectSlot = (type) => type?.[selectSlotName];
3340
+ const getSelectSlot = (type) => getCompoundSlot(type);
3245
3341
  const defaultSelectFilter = (option, query) => option.label.toLowerCase().includes(query.trim().toLowerCase());
3246
3342
  const getTextFromNode = (node) => {
3247
3343
  if (typeof node === "string" || typeof node === "number") return String(node);
@@ -3330,6 +3426,8 @@ const parseSelectChildren = (children) => {
3330
3426
  const option = {
3331
3427
  value: props.value,
3332
3428
  label: props.label,
3429
+ asChild: props.asChild,
3430
+ children: props.children,
3333
3431
  disabled: props.disabled,
3334
3432
  description: props.description,
3335
3433
  icon: props.icon,
@@ -3537,17 +3635,54 @@ const renderNodeOrText = (node, textStyle, fallback) => {
3537
3635
  }) : null);
3538
3636
  };
3539
3637
  const SelectItem = createSelectSlot("item", "Select.Item");
3540
- const SelectItemRow = ({ option, isSelected, isDisabled, optionStyle, onSelect }) => {
3638
+ const SelectItemRow = ({ option, isSelected, isDisabled, itemIndex, selectedValues, multiple, optionStyle, onSelect }) => {
3541
3639
  const { theme } = useTheme();
3542
3640
  const styles = useThemeStyles(createItemStyles);
3543
3641
  const { color, variant, renderOption } = useSelectContext();
3544
3642
  const [isHovered, setIsHovered] = useState(false);
3643
+ const child = option.asChild && isValidElement(option.children) ? option.children : void 0;
3545
3644
  const optionPalette = theme.components.select[option.color ?? color][variant].option;
3546
3645
  const getOptionState = (pressed) => {
3547
3646
  if (isDisabled) return theme.components.select.option.disabled;
3548
3647
  if (isSelected) return pressed ? optionPalette.selectedPressed : isHovered ? optionPalette.selectedHover : optionPalette.selected;
3549
3648
  return pressed ? optionPalette.pressed : isHovered ? optionPalette.hover : theme.components.select.option.default;
3550
3649
  };
3650
+ const getOptionStyle = (pressed) => {
3651
+ const optionState = getOptionState(pressed);
3652
+ return [
3653
+ styles.option,
3654
+ {
3655
+ backgroundColor: optionState.bg,
3656
+ borderColor: optionState.border
3657
+ },
3658
+ isDisabled && styles.optionDisabled,
3659
+ optionStyle
3660
+ ];
3661
+ };
3662
+ devWarning(!option.asChild || Boolean(child), "Select.Item: asChild requires a single valid React element child.");
3663
+ if (child) return cloneElement(child, {
3664
+ accessibilityRole: "button",
3665
+ accessibilityLabel: option.accessibilityLabel ?? option.label,
3666
+ accessibilityHint: option.accessibilityHint,
3667
+ accessibilityState: {
3668
+ selected: isSelected,
3669
+ disabled: isDisabled
3670
+ },
3671
+ disabled: isDisabled,
3672
+ onPress: (event) => {
3673
+ child.props.onPress?.(event);
3674
+ if (!event.defaultPrevented && !isDisabled) onSelect(option);
3675
+ },
3676
+ onHoverIn: () => {
3677
+ child.props.onHoverIn?.();
3678
+ setIsHovered(true);
3679
+ },
3680
+ onHoverOut: () => {
3681
+ child.props.onHoverOut?.();
3682
+ setIsHovered(false);
3683
+ },
3684
+ style: [getOptionStyle(false), child.props.style]
3685
+ });
3551
3686
  return /* @__PURE__ */ jsx(Pressable, {
3552
3687
  disabled: isDisabled,
3553
3688
  accessibilityRole: "button",
@@ -3560,24 +3695,19 @@ const SelectItemRow = ({ option, isSelected, isDisabled, optionStyle, onSelect }
3560
3695
  onPress: () => onSelect(option),
3561
3696
  onHoverIn: () => setIsHovered(true),
3562
3697
  onHoverOut: () => setIsHovered(false),
3563
- style: ({ pressed }) => {
3564
- const optionState = getOptionState(pressed);
3565
- return [
3566
- styles.option,
3567
- {
3568
- backgroundColor: optionState.bg,
3569
- borderColor: optionState.border
3570
- },
3571
- isDisabled && styles.optionDisabled,
3572
- optionStyle
3573
- ];
3574
- },
3698
+ style: ({ pressed }) => getOptionStyle(pressed),
3575
3699
  children: ({ pressed }) => {
3576
3700
  const optionFg = getOptionState(pressed).fg;
3577
3701
  const descriptionFg = isSelected || pressed ? optionFg : theme.components.select.option.description.fg;
3578
- return renderOption ? renderNodeOrText(renderOption(option, {
3702
+ return renderOption ? renderNodeOrText(renderOption({
3703
+ option,
3579
3704
  selected: isSelected,
3580
- disabled: isDisabled
3705
+ disabled: isDisabled,
3706
+ active: isHovered,
3707
+ index: itemIndex,
3708
+ values: selectedValues,
3709
+ multiple,
3710
+ pressed
3581
3711
  }), [styles.optionLabel, { color: optionFg }]) : /* @__PURE__ */ jsxs(Fragment, { children: [
3582
3712
  option.icon && /* @__PURE__ */ jsx(View, {
3583
3713
  style: styles.optionIcon,
@@ -3693,12 +3823,11 @@ const createPresentationStyles = (theme) => StyleSheet.create({
3693
3823
  });
3694
3824
  //#endregion
3695
3825
  //#region src/components/Select/Presentation/SelectBackdrop.tsx
3696
- const SelectBackdrop = ({ onClose, dismissOnBackdropPress }) => {
3826
+ const SelectBackdrop = ({ outsidePressProps }) => {
3827
+ const styles = useThemeStyles(createPresentationStyles);
3697
3828
  return /* @__PURE__ */ jsx(Pressable, {
3698
- style: useThemeStyles(createPresentationStyles).backdrop,
3699
- onPress: dismissOnBackdropPress ? onClose : void 0,
3700
- accessibilityRole: "button",
3701
- accessibilityLabel: "Dismiss select"
3829
+ ...outsidePressProps,
3830
+ style: styles.backdrop
3702
3831
  });
3703
3832
  };
3704
3833
  SelectBackdrop.displayName = "Select.Backdrop";
@@ -3715,7 +3844,7 @@ const SelectHandle = () => {
3715
3844
  SelectHandle.displayName = "Select.Handle";
3716
3845
  //#endregion
3717
3846
  //#region src/components/Select/Presentation/SelectModal.tsx
3718
- const SelectModal = ({ visible, onClose, dismissOnBackdropPress, zIndex, contentStyle, children }) => {
3847
+ const SelectModal = ({ visible, onClose, outsidePressProps, zIndex, contentStyle, children }) => {
3719
3848
  const styles = useThemeStyles(createPresentationStyles);
3720
3849
  return /* @__PURE__ */ jsx(Modal$1, {
3721
3850
  transparent: true,
@@ -3729,10 +3858,7 @@ const SelectModal = ({ visible, onClose, dismissOnBackdropPress, zIndex, content
3729
3858
  Platform.OS === "web" && { zIndex }
3730
3859
  ],
3731
3860
  testID: "select-content-root",
3732
- children: [/* @__PURE__ */ jsx(SelectBackdrop, {
3733
- onClose,
3734
- dismissOnBackdropPress
3735
- }), /* @__PURE__ */ jsx(View, {
3861
+ children: [/* @__PURE__ */ jsx(SelectBackdrop, { outsidePressProps }), /* @__PURE__ */ jsx(View, {
3736
3862
  style: [
3737
3863
  styles.content,
3738
3864
  styles.modalPresentation,
@@ -3747,7 +3873,7 @@ const SelectModal = ({ visible, onClose, dismissOnBackdropPress, zIndex, content
3747
3873
  SelectModal.displayName = "Select.Modal";
3748
3874
  //#endregion
3749
3875
  //#region src/components/Select/Presentation/SelectPopover.tsx
3750
- const SelectPopover = ({ visible, onClose, dismissOnBackdropPress, zIndex, position, onFloatingLayout, matchTriggerWidth, triggerWidth, contentStyle, children }) => {
3876
+ const SelectPopover = ({ visible, onClose, outsidePressProps, zIndex, position, onFloatingLayout, matchTriggerWidth, triggerWidth, contentStyle, children }) => {
3751
3877
  const styles = useThemeStyles(createPresentationStyles);
3752
3878
  return /* @__PURE__ */ jsx(Modal$1, {
3753
3879
  transparent: true,
@@ -3757,10 +3883,7 @@ const SelectPopover = ({ visible, onClose, dismissOnBackdropPress, zIndex, posit
3757
3883
  children: /* @__PURE__ */ jsxs(View, {
3758
3884
  style: [styles.modalRoot, Platform.OS === "web" && { zIndex }],
3759
3885
  testID: "select-content-root",
3760
- children: [/* @__PURE__ */ jsx(SelectBackdrop, {
3761
- onClose,
3762
- dismissOnBackdropPress
3763
- }), /* @__PURE__ */ jsx(View, {
3886
+ children: [/* @__PURE__ */ jsx(SelectBackdrop, { outsidePressProps }), /* @__PURE__ */ jsx(View, {
3764
3887
  onLayout: onFloatingLayout,
3765
3888
  style: [
3766
3889
  styles.content,
@@ -3782,7 +3905,7 @@ const SelectPopover = ({ visible, onClose, dismissOnBackdropPress, zIndex, posit
3782
3905
  SelectPopover.displayName = "Select.Popover";
3783
3906
  //#endregion
3784
3907
  //#region src/components/Select/Presentation/SelectSheet.tsx
3785
- const SelectSheet = ({ visible, onClose, dismissOnBackdropPress, zIndex, contentStyle, children }) => {
3908
+ const SelectSheet = ({ visible, onClose, outsidePressProps, zIndex, contentStyle, children }) => {
3786
3909
  const styles = useThemeStyles(createPresentationStyles);
3787
3910
  return /* @__PURE__ */ jsx(Modal$1, {
3788
3911
  transparent: true,
@@ -3796,10 +3919,7 @@ const SelectSheet = ({ visible, onClose, dismissOnBackdropPress, zIndex, content
3796
3919
  Platform.OS === "web" && { zIndex }
3797
3920
  ],
3798
3921
  testID: "select-content-root",
3799
- children: [/* @__PURE__ */ jsx(SelectBackdrop, {
3800
- onClose,
3801
- dismissOnBackdropPress
3802
- }), /* @__PURE__ */ jsxs(View, {
3922
+ children: [/* @__PURE__ */ jsx(SelectBackdrop, { outsidePressProps }), /* @__PURE__ */ jsxs(View, {
3803
3923
  style: [
3804
3924
  styles.content,
3805
3925
  styles.sheet,
@@ -3924,13 +4044,13 @@ const SelectContentSurface = () => {
3924
4044
  const wasOpenRef = useRef(false);
3925
4045
  const [openCycle, setOpenCycle] = useState(0);
3926
4046
  const context = useSelectContext();
3927
- const { isOpen, resolvedPresentation, zIndex, position, onFloatingLayout, dismissOnBackdropPress, contentStyle, matchTriggerWidth, triggerWidth, resolvedLabel, closeContent, searchable, loading, filteredRows, selectedValues, selectedOptions, maxSelected, optionStyle, selectOption, selectGroup, itemHeight, selectedRowIndex, query } = context;
4047
+ const { isOpen, resolvedPresentation, zIndex, position, onFloatingLayout, contentStyle, matchTriggerWidth, triggerWidth, resolvedLabel, closeContent, getOutsidePressProps, searchable, loading, filteredRows, selectedValues, selectedOptions, maxSelected, optionStyle, selectOption, selectGroup, itemHeight, selectedRowIndex, query } = context;
3928
4048
  const initialScrollIndex = Boolean(context.virtual) && selectedRowIndex > 0 && query === "" ? selectedRowIndex : void 0;
3929
4049
  useEffect(() => {
3930
4050
  if (isOpen && !wasOpenRef.current) setOpenCycle((cycle) => cycle + 1);
3931
4051
  wasOpenRef.current = isOpen;
3932
4052
  }, [isOpen]);
3933
- const renderRow = ({ item }) => {
4053
+ const renderRow = ({ item, index }) => {
3934
4054
  if (item.type === "group") {
3935
4055
  if (item.selectable && context.multiple) {
3936
4056
  const enabledGroupValues = item.itemValues.filter((value) => context.optionsByValue.get(value));
@@ -3952,6 +4072,9 @@ const SelectContentSurface = () => {
3952
4072
  option: item.option,
3953
4073
  isSelected,
3954
4074
  isDisabled: Boolean(item.option.disabled || maxReached),
4075
+ itemIndex: index,
4076
+ selectedValues,
4077
+ multiple: context.multiple,
3955
4078
  optionStyle,
3956
4079
  onSelect: selectOption
3957
4080
  });
@@ -4014,7 +4137,7 @@ const SelectContentSurface = () => {
4014
4137
  if (resolvedPresentation === "sheet") return /* @__PURE__ */ jsx(SelectSheet, {
4015
4138
  visible: isOpen,
4016
4139
  onClose: closeContent,
4017
- dismissOnBackdropPress,
4140
+ outsidePressProps: getOutsidePressProps({ accessibilityLabel: "Dismiss select" }),
4018
4141
  zIndex,
4019
4142
  contentStyle,
4020
4143
  children: body
@@ -4022,7 +4145,7 @@ const SelectContentSurface = () => {
4022
4145
  if (resolvedPresentation === "popover") return /* @__PURE__ */ jsx(SelectPopover, {
4023
4146
  visible: isOpen,
4024
4147
  onClose: closeContent,
4025
- dismissOnBackdropPress,
4148
+ outsidePressProps: getOutsidePressProps({ accessibilityLabel: "Dismiss select" }),
4026
4149
  position,
4027
4150
  zIndex,
4028
4151
  onFloatingLayout,
@@ -4034,7 +4157,7 @@ const SelectContentSurface = () => {
4034
4157
  return /* @__PURE__ */ jsx(SelectModal, {
4035
4158
  visible: isOpen,
4036
4159
  onClose: closeContent,
4037
- dismissOnBackdropPress,
4160
+ outsidePressProps: getOutsidePressProps({ accessibilityLabel: "Dismiss select" }),
4038
4161
  zIndex,
4039
4162
  contentStyle,
4040
4163
  children: body
@@ -4057,95 +4180,6 @@ const SelectItemIcon = createSelectSlot("itemIcon", "Select.ItemIcon");
4057
4180
  //#region src/components/Select/Label/SelectLabel.tsx
4058
4181
  const SelectLabel = createSelectSlot("label", "Select.Label");
4059
4182
  //#endregion
4060
- //#region src/hooks/behavior/select/useSelectCollection.ts
4061
- const useSelectCollection = (children, optionsProp) => {
4062
- const parsedChildren = useMemo(() => parseSelectChildren(children), [children]);
4063
- const options = useMemo(() => [...optionsProp ?? [], ...parsedChildren.options], [optionsProp, parsedChildren.options]);
4064
- return {
4065
- options,
4066
- rows: useMemo(() => {
4067
- if (parsedChildren.rows.length > 0) return parsedChildren.rows;
4068
- return options.map((option) => ({
4069
- type: "item",
4070
- key: `item-${option.value}`,
4071
- option
4072
- }));
4073
- }, [options, parsedChildren.rows]),
4074
- searchableFromChildren: parsedChildren.searchable,
4075
- searchPlaceholderFromChildren: parsedChildren.searchPlaceholder,
4076
- emptyFromChildren: parsedChildren.empty,
4077
- loadingFromChildren: parsedChildren.loading
4078
- };
4079
- };
4080
- //#endregion
4081
- //#region src/hooks/behavior/select/useSelectSearch.ts
4082
- const useSelectSearch = ({ rows, isOpen, searchable, searchableFromChildren, onSearch, filterOptions, filter = defaultSelectFilter }) => {
4083
- const [query, setQuery] = useState("");
4084
- const shouldSearch = searchable ?? searchableFromChildren ?? Boolean(onSearch);
4085
- const shouldFilter = filterOptions ?? !onSearch;
4086
- const filteredRows = useMemo(() => {
4087
- if (!query || !shouldFilter) return rows;
4088
- const visibleRows = [];
4089
- let pendingGroup;
4090
- rows.forEach((row) => {
4091
- if (row.type === "group") {
4092
- pendingGroup = row;
4093
- return;
4094
- }
4095
- if (row.type === "separator") {
4096
- if (visibleRows.length > 0 && visibleRows[visibleRows.length - 1]?.type !== "separator") visibleRows.push(row);
4097
- return;
4098
- }
4099
- if (!filter(row.option, query)) return;
4100
- if (pendingGroup) {
4101
- visibleRows.push(pendingGroup);
4102
- pendingGroup = void 0;
4103
- }
4104
- visibleRows.push(row);
4105
- });
4106
- return visibleRows.filter((row, index, collection) => {
4107
- if (row.type !== "separator") return true;
4108
- return index > 0 && index < collection.length - 1 && collection[index - 1]?.type !== "separator";
4109
- });
4110
- }, [
4111
- filter,
4112
- query,
4113
- rows,
4114
- shouldFilter
4115
- ]);
4116
- useEffect(() => {
4117
- if (!isOpen) {
4118
- setQuery("");
4119
- return;
4120
- }
4121
- if (shouldSearch) onSearch?.(query);
4122
- }, [
4123
- isOpen,
4124
- onSearch,
4125
- query,
4126
- shouldSearch
4127
- ]);
4128
- return {
4129
- query,
4130
- setQuery,
4131
- shouldSearch,
4132
- filteredRows
4133
- };
4134
- };
4135
- //#endregion
4136
- //#region src/components/Select/internal/resolveSelectAccessibility.ts
4137
- const resolveSelectAccessibility = ({ accessibilityLabel, accessibilityHint, label, description, error, invalid, placeholder, selectedLabel, hasFieldContext, fieldDescribedBy }) => {
4138
- const descriptionText = typeof description === "string" || typeof description === "number" ? String(description) : void 0;
4139
- const errorText = typeof error === "string" || typeof error === "number" ? String(error) : void 0;
4140
- return {
4141
- resolvedLabel: accessibilityLabel ?? label ?? selectedLabel ?? placeholder ?? "Select",
4142
- resolvedHint: accessibilityHint ?? (invalid && errorText ? errorText : descriptionText ? descriptionText : hasFieldContext && fieldDescribedBy ? "Opens a list of options" : void 0),
4143
- announce: (message) => {
4144
- AccessibilityInfo.announceForAccessibility?.(message);
4145
- }
4146
- };
4147
- };
4148
- //#endregion
4149
4183
  //#region src/components/Select/Trigger/SelectTrigger.styles.ts
4150
4184
  const createTriggerStyles = (theme) => StyleSheet.create({
4151
4185
  container: {
@@ -4401,174 +4435,174 @@ function SelectTrigger({ displayText, isPlaceholder, isOpen, size = "md", color
4401
4435
  }
4402
4436
  SelectTrigger.displayName = "SelectTrigger";
4403
4437
  //#endregion
4404
- //#region src/components/Select/Root/SelectRoot.tsx
4405
- function SelectRoot(props) {
4406
- const { label, description, error, invalid = false, required = false, disabled = false, placeholder = "Select...", color = "primary", variant = "outline", size, open, defaultOpen, onOpenChange, clearable = false, searchable: searchableProp, searchPlaceholder, loading = false, loadingText = "Loading...", onSearch, filterOptions, filter, empty, startIcon, endIcon, prefix, suffix, renderValue, renderOption, closeOnSelect, maxSelected, presentation = "auto", placement = "bottom-start", offset = 8, matchTriggerWidth = false, dismissOnBackdropPress = true, virtual, options: optionsProp, children, style, triggerStyle, textStyle, contentStyle, optionStyle, searchStyle, accessibilityLabel, accessibilityHint, testID } = props;
4407
- const field = useFormFieldContext();
4408
- const overlayId = useId();
4409
- const hasOwnField = Boolean(label || description || error);
4410
- const [triggerWidth, setTriggerWidth] = useState();
4411
- const triggerRef = useRef(null);
4412
- const { position, onFloatingLayout } = useNativeFloatingPosition(placement, offset);
4413
- const searchInputRef = useRef(null);
4414
- const selectedFocusValueRef = useRef(void 0);
4415
- const resolvedPresentation = useOverlayPresentation(presentation);
4416
- const { options, rows, searchableFromChildren, searchPlaceholderFromChildren, emptyFromChildren, loadingFromChildren } = useSelectCollection(children, optionsProp);
4417
- const resolvedSize = size ?? field?.size ?? "md";
4418
- const isInvalid = invalid || Boolean(error) || !hasOwnField && Boolean(field?.invalid);
4419
- const isDisabled = disabled || !hasOwnField && Boolean(field?.disabled);
4420
- const isRequired = required || !hasOwnField && Boolean(field?.required);
4421
- const { selectedValue, setSelectedValue, isOpen, openDropdown, closeDropdown, selectValue } = useSelect({
4422
- value: props.multiple ? props.value : props.value === null ? "" : props.value,
4423
- defaultValue: props.multiple ? props.defaultValue : props.defaultValue === null ? "" : props.defaultValue,
4424
- onValueChange: (nextValue) => {
4425
- if (props.multiple) {
4426
- props.onValueChange?.(nextValue);
4438
+ //#region src/hooks/behavior/select/useSelectCollection.ts
4439
+ const useSelectCollection = (children, optionsProp) => {
4440
+ const parsedChildren = useMemo(() => parseSelectChildren(children), [children]);
4441
+ const options = useMemo(() => [...optionsProp ?? [], ...parsedChildren.options], [optionsProp, parsedChildren.options]);
4442
+ return {
4443
+ options,
4444
+ rows: useMemo(() => {
4445
+ if (parsedChildren.rows.length > 0) return parsedChildren.rows;
4446
+ return options.map((option) => ({
4447
+ type: "item",
4448
+ key: `item-${option.value}`,
4449
+ option
4450
+ }));
4451
+ }, [options, parsedChildren.rows]),
4452
+ searchableFromChildren: parsedChildren.searchable,
4453
+ searchPlaceholderFromChildren: parsedChildren.searchPlaceholder,
4454
+ emptyFromChildren: parsedChildren.empty,
4455
+ loadingFromChildren: parsedChildren.loading
4456
+ };
4457
+ };
4458
+ //#endregion
4459
+ //#region src/hooks/behavior/select/useSelectSearch.ts
4460
+ const useSelectSearch = ({ rows, isOpen, searchable, searchableFromChildren, onSearch, filterOptions, filter = defaultSelectFilter }) => {
4461
+ const [query, setQuery] = useState("");
4462
+ const shouldSearch = searchable ?? searchableFromChildren ?? Boolean(onSearch);
4463
+ const shouldFilter = filterOptions ?? !onSearch;
4464
+ const filteredRows = useMemo(() => {
4465
+ if (!query || !shouldFilter) return rows;
4466
+ const visibleRows = [];
4467
+ let pendingGroup;
4468
+ rows.forEach((row) => {
4469
+ if (row.type === "group") {
4470
+ pendingGroup = row;
4427
4471
  return;
4428
4472
  }
4429
- props.onValueChange?.(nextValue === "" ? null : nextValue);
4430
- },
4431
- options,
4432
- multiple: props.multiple,
4433
- maxSelected,
4434
- closeOnSelect,
4435
- disabled: isDisabled,
4436
- open,
4437
- defaultOpen,
4438
- onOpenChange
4439
- });
4440
- const { restoreFocusAfterClose } = useOverlayFocusRestore({
4441
- active: isOpen,
4442
- triggerRef
4443
- });
4444
- const closeAndFocusTrigger = useCallback(() => {
4445
- closeDropdown();
4446
- restoreFocusAfterClose();
4447
- }, [closeDropdown, restoreFocusAfterClose]);
4448
- const selectedValues = useMemo(() => {
4449
- if (props.multiple) return Array.isArray(selectedValue) ? selectedValue : [];
4450
- return typeof selectedValue === "string" && selectedValue ? [selectedValue] : [];
4451
- }, [props.multiple, selectedValue]);
4452
- const selectedOption = options.find((option) => selectedValues.includes(option.value));
4453
- const selectedOptions = options.filter((option) => selectedValues.includes(option.value));
4454
- const optionsByValue = useMemo(() => new Map(options.filter((option) => !option.disabled).map((option) => [option.value, option])), [options]);
4455
- const { query, setQuery, shouldSearch, filteredRows } = useSelectSearch({
4456
- rows,
4457
- isOpen,
4458
- searchable: searchableProp,
4459
- searchableFromChildren,
4460
- onSearch,
4461
- filterOptions,
4462
- filter
4463
- });
4464
- const selectedFocusValue = selectedValues.includes(selectedFocusValueRef.current ?? "") ? selectedFocusValueRef.current : selectedValues[0];
4465
- const selectedRowIndex = Math.max(0, filteredRows.findIndex((row) => row.type === "item" && row.option.value === selectedFocusValue));
4466
- const itemHeight = typeof virtual === "object" ? virtual.estimatedItemSize ?? 46 : 46;
4467
- const displayValue = useMemo(() => {
4468
- if (renderValue) return renderValue(props.multiple ? selectedOptions : selectedOption ?? null, {
4469
- placeholder,
4470
- multiple: Boolean(props.multiple)
4473
+ if (row.type === "separator") {
4474
+ if (visibleRows.length > 0 && visibleRows[visibleRows.length - 1]?.type !== "separator") visibleRows.push(row);
4475
+ return;
4476
+ }
4477
+ if (!filter(row.option, query)) return;
4478
+ if (pendingGroup) {
4479
+ visibleRows.push(pendingGroup);
4480
+ pendingGroup = void 0;
4481
+ }
4482
+ visibleRows.push(row);
4483
+ });
4484
+ return visibleRows.filter((row, index, collection) => {
4485
+ if (row.type !== "separator") return true;
4486
+ return index > 0 && index < collection.length - 1 && collection[index - 1]?.type !== "separator";
4471
4487
  });
4472
- if (props.multiple && selectedOptions.length > 0) {
4473
- const visibleLabels = selectedOptions.slice(0, 2).map((option) => option.label);
4474
- return selectedOptions.length > 2 ? `${visibleLabels.join(", ")} +${selectedOptions.length - 2}` : visibleLabels.join(", ");
4475
- }
4476
- return selectedOption?.label ?? placeholder;
4477
- }, [
4478
- placeholder,
4479
- props.multiple,
4480
- renderValue,
4481
- selectedOption,
4482
- selectedOptions
4483
- ]);
4484
- const { resolvedLabel, resolvedHint, announce } = resolveSelectAccessibility({
4485
- accessibilityLabel,
4486
- accessibilityHint,
4487
- label: !hasOwnField ? void 0 : label,
4488
- description: !hasOwnField ? field?.description : description,
4489
- error: !hasOwnField ? field?.error : error,
4490
- invalid: isInvalid,
4491
- placeholder,
4492
- selectedLabel: selectedOption?.label,
4493
- hasFieldContext: !hasOwnField && Boolean(field),
4494
- fieldDescribedBy: field?.ariaDescribedBy
4495
- });
4496
- const hasValue = selectedValues.length > 0;
4497
- const dismiss = useOverlayDismiss({
4498
- id: overlayId,
4499
- active: isOpen,
4500
- closeOnOutsidePress: dismissOnBackdropPress,
4501
- requestClose: closeAndFocusTrigger
4502
- });
4503
- const clearValue = () => {
4504
- selectedFocusValueRef.current = void 0;
4505
- selectValue("");
4506
- announce("Selection cleared");
4507
- };
4508
- const selectOption = useCallback((option) => {
4509
- if (option.disabled) return;
4510
- const selectedBefore = selectedValues.includes(option.value);
4511
- if (Boolean(props.multiple) && !selectedBefore && typeof maxSelected === "number" && selectedValues.length >= maxSelected) return;
4512
- selectedFocusValueRef.current = option.value;
4513
- selectValue(option.value);
4514
- announce(`${option.label} selected`);
4515
4488
  }, [
4516
- announce,
4517
- maxSelected,
4518
- props.multiple,
4519
- selectValue,
4520
- selectedValues
4489
+ filter,
4490
+ query,
4491
+ rows,
4492
+ shouldFilter
4521
4493
  ]);
4522
- const selectGroup = useCallback((values) => {
4523
- if (!props.multiple || values.length === 0) return;
4524
- const enabledValues = values.filter((value) => optionsByValue.has(value));
4525
- const selectedGroupValues = enabledValues.filter((value) => selectedValues.includes(value));
4526
- const outsideSelectedCount = selectedValues.filter((value) => !enabledValues.includes(value)).length;
4527
- const maxSelectableGroupCount = typeof maxSelected === "number" ? Math.max(0, Math.min(enabledValues.length, maxSelected - outsideSelectedCount)) : enabledValues.length;
4528
- if (selectedGroupValues.length > 0 && selectedGroupValues.length >= maxSelectableGroupCount) {
4529
- selectedFocusValueRef.current = void 0;
4530
- setSelectedValue(selectedValues.filter((value) => !enabledValues.includes(value)));
4531
- announce("Group selection cleared");
4494
+ useEffect(() => {
4495
+ if (!isOpen) {
4496
+ setQuery("");
4532
4497
  return;
4533
4498
  }
4534
- const nextValues = [...selectedValues];
4535
- for (const value of enabledValues) {
4536
- if (nextValues.includes(value)) continue;
4537
- if (typeof maxSelected === "number" && nextValues.length >= maxSelected) break;
4538
- nextValues.push(value);
4539
- }
4540
- setSelectedValue(nextValues);
4541
- selectedFocusValueRef.current = nextValues.at(-1);
4542
- announce("Group selected");
4543
- if (closeOnSelect) closeAndFocusTrigger();
4499
+ if (shouldSearch) onSearch?.(query);
4544
4500
  }, [
4545
- announce,
4546
- closeAndFocusTrigger,
4547
- closeOnSelect,
4548
- maxSelected,
4549
- optionsByValue,
4550
- props.multiple,
4551
- selectedValues,
4552
- setSelectedValue
4501
+ isOpen,
4502
+ onSearch,
4503
+ query,
4504
+ shouldSearch
4553
4505
  ]);
4554
- const resolvedSearchPlaceholder = searchPlaceholder ?? searchPlaceholderFromChildren ?? "Search...";
4555
- const resolvedEmpty = empty ?? emptyFromChildren ?? "Nothing found";
4556
- const resolvedLoadingContent = loadingFromChildren ?? loadingText;
4557
- const contextValue = useMemo(() => ({
4506
+ return {
4507
+ query,
4508
+ setQuery,
4509
+ shouldSearch,
4510
+ filteredRows
4511
+ };
4512
+ };
4513
+ //#endregion
4514
+ //#region src/components/Select/internal/resolveSelectAccessibility.ts
4515
+ const resolveSelectAccessibility = ({ accessibilityLabel, accessibilityHint, label, description, error, invalid, placeholder, selectedLabel, hasFieldContext, fieldDescribedBy }) => {
4516
+ const descriptionText = typeof description === "string" || typeof description === "number" ? String(description) : void 0;
4517
+ const errorText = typeof error === "string" || typeof error === "number" ? String(error) : void 0;
4518
+ return {
4519
+ resolvedLabel: accessibilityLabel ?? label ?? selectedLabel ?? placeholder ?? "Select",
4520
+ resolvedHint: accessibilityHint ?? (invalid && errorText ? errorText : descriptionText ? descriptionText : hasFieldContext && fieldDescribedBy ? "Opens a list of options" : void 0),
4521
+ announce: (message) => {
4522
+ AccessibilityInfo.announceForAccessibility?.(message);
4523
+ }
4524
+ };
4525
+ };
4526
+ //#endregion
4527
+ //#region src/components/Select/Root/useSelectRootActions.ts
4528
+ function useSelectRootActions({ multiple, maxSelected, closeOnSelect, selectedValues, optionsByValue, selectedFocusValueRef, selectValue, setSelectedValue, announce, closeAndFocusTrigger }) {
4529
+ return {
4530
+ clearValue: useCallback(() => {
4531
+ selectedFocusValueRef.current = void 0;
4532
+ selectValue("");
4533
+ announce("Selection cleared");
4534
+ }, [
4535
+ announce,
4536
+ selectValue,
4537
+ selectedFocusValueRef
4538
+ ]),
4539
+ selectOption: useCallback((option) => {
4540
+ if (option.disabled) return;
4541
+ const selectedBefore = selectedValues.includes(option.value);
4542
+ if (multiple && !selectedBefore && typeof maxSelected === "number" && selectedValues.length >= maxSelected) return;
4543
+ selectedFocusValueRef.current = option.value;
4544
+ selectValue(option.value);
4545
+ announce(`${option.label} selected`);
4546
+ }, [
4547
+ announce,
4548
+ maxSelected,
4549
+ multiple,
4550
+ selectValue,
4551
+ selectedFocusValueRef,
4552
+ selectedValues
4553
+ ]),
4554
+ selectGroup: useCallback((values) => {
4555
+ if (!multiple || values.length === 0) return;
4556
+ const enabledValues = values.filter((value) => optionsByValue.has(value));
4557
+ const selectedGroupValues = enabledValues.filter((value) => selectedValues.includes(value));
4558
+ const outsideSelectedCount = selectedValues.filter((value) => !enabledValues.includes(value)).length;
4559
+ const maxSelectableGroupCount = typeof maxSelected === "number" ? Math.max(0, Math.min(enabledValues.length, maxSelected - outsideSelectedCount)) : enabledValues.length;
4560
+ if (selectedGroupValues.length > 0 && selectedGroupValues.length >= maxSelectableGroupCount) {
4561
+ selectedFocusValueRef.current = void 0;
4562
+ setSelectedValue(selectedValues.filter((value) => !enabledValues.includes(value)));
4563
+ announce("Group selection cleared");
4564
+ return;
4565
+ }
4566
+ const nextValues = [...selectedValues];
4567
+ for (const value of enabledValues) {
4568
+ if (nextValues.includes(value)) continue;
4569
+ if (typeof maxSelected === "number" && nextValues.length >= maxSelected) break;
4570
+ nextValues.push(value);
4571
+ }
4572
+ setSelectedValue(nextValues);
4573
+ selectedFocusValueRef.current = nextValues.at(-1);
4574
+ announce("Group selected");
4575
+ if (closeOnSelect) closeAndFocusTrigger();
4576
+ }, [
4577
+ announce,
4578
+ closeAndFocusTrigger,
4579
+ closeOnSelect,
4580
+ maxSelected,
4581
+ multiple,
4582
+ optionsByValue,
4583
+ selectedFocusValueRef,
4584
+ selectedValues,
4585
+ setSelectedValue
4586
+ ])
4587
+ };
4588
+ }
4589
+ //#endregion
4590
+ //#region src/components/Select/Root/useSelectRootContextValue.ts
4591
+ function useSelectRootContextValue({ color, variant, isOpen, loading, searchable, multiple, maxSelected, virtual, resolvedLabel, resolvedPresentation, zIndex, position, onFloatingLayout, matchTriggerWidth, triggerWidth, selectedValues, selectedOptions, optionsByValue, filteredRows, selectedRowIndex, itemHeight, query, searchPlaceholder, searchInputRef, empty, loadingContent, closeContent, getOutsidePressProps, selectOption, selectGroup, setQuery, renderOption, contentStyle, optionStyle, searchStyle }) {
4592
+ return useMemo(() => ({
4558
4593
  color,
4559
4594
  variant,
4560
4595
  isOpen,
4561
4596
  loading,
4562
- searchable: shouldSearch,
4563
- multiple: Boolean(props.multiple),
4597
+ searchable,
4598
+ multiple,
4564
4599
  maxSelected,
4565
4600
  virtual,
4566
4601
  resolvedLabel,
4567
4602
  resolvedPresentation,
4568
- zIndex: dismiss.zIndex,
4603
+ zIndex,
4569
4604
  position,
4570
4605
  onFloatingLayout,
4571
- dismissOnBackdropPress,
4572
4606
  matchTriggerWidth,
4573
4607
  triggerWidth,
4574
4608
  selectedValues,
@@ -4578,11 +4612,12 @@ function SelectRoot(props) {
4578
4612
  selectedRowIndex,
4579
4613
  itemHeight,
4580
4614
  query,
4581
- searchPlaceholder: resolvedSearchPlaceholder,
4615
+ searchPlaceholder,
4582
4616
  searchInputRef,
4583
- empty: resolvedEmpty,
4584
- loadingContent: resolvedLoadingContent,
4585
- closeContent: dismiss.requestClose,
4617
+ empty,
4618
+ loadingContent,
4619
+ closeContent,
4620
+ getOutsidePressProps,
4586
4621
  selectOption,
4587
4622
  selectGroup,
4588
4623
  setQuery,
@@ -4595,16 +4630,15 @@ function SelectRoot(props) {
4595
4630
  variant,
4596
4631
  isOpen,
4597
4632
  loading,
4598
- shouldSearch,
4599
- props.multiple,
4633
+ searchable,
4634
+ multiple,
4600
4635
  maxSelected,
4601
4636
  virtual,
4602
4637
  resolvedLabel,
4603
4638
  resolvedPresentation,
4604
- dismiss.zIndex,
4639
+ zIndex,
4605
4640
  position,
4606
4641
  onFloatingLayout,
4607
- dismissOnBackdropPress,
4608
4642
  matchTriggerWidth,
4609
4643
  triggerWidth,
4610
4644
  selectedValues,
@@ -4614,11 +4648,12 @@ function SelectRoot(props) {
4614
4648
  selectedRowIndex,
4615
4649
  itemHeight,
4616
4650
  query,
4617
- resolvedSearchPlaceholder,
4651
+ searchPlaceholder,
4618
4652
  searchInputRef,
4619
- resolvedEmpty,
4620
- resolvedLoadingContent,
4621
- dismiss.requestClose,
4653
+ empty,
4654
+ loadingContent,
4655
+ closeContent,
4656
+ getOutsidePressProps,
4622
4657
  selectOption,
4623
4658
  selectGroup,
4624
4659
  setQuery,
@@ -4627,11 +4662,229 @@ function SelectRoot(props) {
4627
4662
  optionStyle,
4628
4663
  searchStyle
4629
4664
  ]);
4665
+ }
4666
+ //#endregion
4667
+ //#region src/components/Select/Root/useSelectRootDisplayValue.ts
4668
+ function useSelectRootDisplayValue({ multiple, placeholder, renderValue, selectedOption, selectedOptions, selectedValues }) {
4669
+ return useMemo(() => {
4670
+ if (renderValue) return renderValue({
4671
+ option: selectedOption,
4672
+ options: selectedOptions,
4673
+ value: selectedValues[0] ?? "",
4674
+ values: selectedValues,
4675
+ placeholder,
4676
+ multiple
4677
+ });
4678
+ if (multiple && selectedOptions.length > 0) {
4679
+ const visibleLabels = selectedOptions.slice(0, 2).map((option) => option.label);
4680
+ return selectedOptions.length > 2 ? `${visibleLabels.join(", ")} +${selectedOptions.length - 2}` : visibleLabels.join(", ");
4681
+ }
4682
+ return selectedOption?.label ?? placeholder;
4683
+ }, [
4684
+ multiple,
4685
+ placeholder,
4686
+ renderValue,
4687
+ selectedOption,
4688
+ selectedOptions,
4689
+ selectedValues
4690
+ ]);
4691
+ }
4692
+ //#endregion
4693
+ //#region src/components/Select/Root/useSelectRootSelection.ts
4694
+ function useSelectRootSelection({ props, options, isDisabled }) {
4695
+ const selection = useSelect({
4696
+ value: props.multiple ? props.value : props.value === null ? "" : props.value,
4697
+ defaultValue: props.multiple ? props.defaultValue : props.defaultValue === null ? "" : props.defaultValue,
4698
+ onValueChange: (nextValue) => {
4699
+ if (props.multiple) {
4700
+ props.onValueChange?.(Array.isArray(nextValue) ? nextValue : nextValue ? [nextValue] : []);
4701
+ return;
4702
+ }
4703
+ props.onValueChange?.(Array.isArray(nextValue) ? nextValue[0] ?? null : nextValue === "" ? null : nextValue);
4704
+ },
4705
+ options,
4706
+ multiple: props.multiple,
4707
+ maxSelected: props.maxSelected,
4708
+ closeOnSelect: props.closeOnSelect,
4709
+ disabled: isDisabled,
4710
+ open: props.open,
4711
+ defaultOpen: props.defaultOpen,
4712
+ onOpenChange: props.onOpenChange
4713
+ });
4714
+ const optionsByValue = useMemo(() => new Map(options.filter((option) => !option.disabled).map((option) => [option.value, option])), [options]);
4715
+ return {
4716
+ ...selection,
4717
+ optionsByValue
4718
+ };
4719
+ }
4720
+ //#endregion
4721
+ //#region src/components/Select/Root/useSelectRootState.tsx
4722
+ function useSelectRootState(props) {
4723
+ const { label, description, error, invalid = false, required = false, disabled = false, placeholder = "Select...", color = "primary", variant = "outline", size, clearable = false, searchable: searchableProp, searchPlaceholder, loading = false, loadingText = "Loading...", onSearch, filterOptions, filter, empty, startIcon, endIcon, prefix, suffix, renderValue, renderOption, closeOnSelect, maxSelected, presentation = "auto", placement = "bottom-start", offset = 8, matchTriggerWidth = false, dismissOnBackdropPress = true, virtual, options: optionsProp, children, style, triggerStyle, textStyle, contentStyle, optionStyle, searchStyle, accessibilityLabel, accessibilityHint, testID } = props;
4724
+ const field = useFormFieldContext();
4725
+ const overlayId = useId();
4726
+ const hasOwnField = Boolean(label || description || error);
4727
+ const [triggerWidth, setTriggerWidth] = useState();
4728
+ const triggerRef = useRef(null);
4729
+ const searchInputRef = useRef(null);
4730
+ const selectedFocusValueRef = useRef(void 0);
4731
+ const resolvedPresentation = useOverlayPresentation(presentation);
4732
+ const { position, onFloatingLayout } = useNativeFloatingPosition(placement, offset);
4733
+ const { options, rows, searchableFromChildren, searchPlaceholderFromChildren, emptyFromChildren, loadingFromChildren } = useSelectCollection(children, optionsProp);
4734
+ const resolvedSize = size ?? field?.size ?? "md";
4735
+ const isInvalid = invalid || Boolean(error) || !hasOwnField && Boolean(field?.invalid);
4736
+ const isDisabled = disabled || !hasOwnField && Boolean(field?.disabled);
4737
+ const isRequired = required || !hasOwnField && Boolean(field?.required);
4738
+ const { setSelectedValue, selectedValues, selectedOption, selectedOptions, optionsByValue, isOpen, openDropdown, closeDropdown, selectValue } = useSelectRootSelection({
4739
+ props,
4740
+ options,
4741
+ isDisabled
4742
+ });
4743
+ const { restoreFocusAfterClose } = useOverlayFocusRestore({
4744
+ active: isOpen,
4745
+ triggerRef
4746
+ });
4747
+ const closeAndFocusTrigger = useCallback(() => {
4748
+ closeDropdown();
4749
+ restoreFocusAfterClose();
4750
+ }, [closeDropdown, restoreFocusAfterClose]);
4751
+ const { query, setQuery, shouldSearch, filteredRows } = useSelectSearch({
4752
+ rows,
4753
+ isOpen,
4754
+ searchable: searchableProp,
4755
+ searchableFromChildren,
4756
+ onSearch,
4757
+ filterOptions,
4758
+ filter
4759
+ });
4760
+ const selectedFocusValue = selectedValues.includes(selectedFocusValueRef.current ?? "") ? selectedFocusValueRef.current : selectedValues[0];
4761
+ const selectedRowIndex = Math.max(0, filteredRows.findIndex((row) => row.type === "item" && row.option.value === selectedFocusValue));
4762
+ const itemHeight = typeof virtual === "object" ? virtual.estimatedItemSize ?? 46 : 46;
4763
+ const displayValue = useSelectRootDisplayValue({
4764
+ multiple: Boolean(props.multiple),
4765
+ placeholder,
4766
+ renderValue,
4767
+ selectedOption,
4768
+ selectedOptions,
4769
+ selectedValues
4770
+ });
4771
+ const { resolvedLabel, resolvedHint, announce } = resolveSelectAccessibility({
4772
+ accessibilityLabel,
4773
+ accessibilityHint,
4774
+ label: !hasOwnField ? void 0 : label,
4775
+ description: !hasOwnField ? field?.description : description,
4776
+ error: !hasOwnField ? field?.error : error,
4777
+ invalid: isInvalid,
4778
+ placeholder,
4779
+ selectedLabel: selectedOption?.label,
4780
+ hasFieldContext: !hasOwnField && Boolean(field),
4781
+ fieldDescribedBy: field?.ariaDescribedBy
4782
+ });
4783
+ const hasValue = selectedValues.length > 0;
4784
+ const dismiss = useOverlayDismiss({
4785
+ id: overlayId,
4786
+ active: isOpen,
4787
+ closeOnOutsidePress: dismissOnBackdropPress,
4788
+ requestClose: closeAndFocusTrigger
4789
+ });
4790
+ const { clearValue, selectOption, selectGroup } = useSelectRootActions({
4791
+ multiple: Boolean(props.multiple),
4792
+ maxSelected,
4793
+ closeOnSelect,
4794
+ selectedValues,
4795
+ optionsByValue,
4796
+ selectedFocusValueRef,
4797
+ selectValue,
4798
+ setSelectedValue,
4799
+ announce,
4800
+ closeAndFocusTrigger
4801
+ });
4802
+ const resolvedSearchPlaceholder = searchPlaceholder ?? searchPlaceholderFromChildren ?? "Search...";
4803
+ const resolvedEmpty = empty ?? emptyFromChildren ?? "Nothing found";
4804
+ const resolvedLoadingContent = loadingFromChildren ?? loadingText;
4805
+ return {
4806
+ contextValue: useSelectRootContextValue({
4807
+ color,
4808
+ variant,
4809
+ isOpen,
4810
+ loading,
4811
+ searchable: shouldSearch,
4812
+ multiple: Boolean(props.multiple),
4813
+ maxSelected,
4814
+ virtual,
4815
+ resolvedLabel,
4816
+ resolvedPresentation,
4817
+ zIndex: dismiss.zIndex,
4818
+ position,
4819
+ onFloatingLayout,
4820
+ matchTriggerWidth,
4821
+ triggerWidth,
4822
+ selectedValues,
4823
+ selectedOptions,
4824
+ optionsByValue,
4825
+ filteredRows,
4826
+ selectedRowIndex,
4827
+ itemHeight,
4828
+ query,
4829
+ searchPlaceholder: resolvedSearchPlaceholder,
4830
+ searchInputRef,
4831
+ empty: resolvedEmpty,
4832
+ loadingContent: resolvedLoadingContent,
4833
+ closeContent: dismiss.requestClose,
4834
+ getOutsidePressProps: dismiss.getOutsidePressProps,
4835
+ selectOption,
4836
+ selectGroup,
4837
+ setQuery,
4838
+ renderOption,
4839
+ contentStyle,
4840
+ optionStyle,
4841
+ searchStyle
4842
+ }),
4843
+ displayValue,
4844
+ field,
4845
+ hasOwnField,
4846
+ hasValue,
4847
+ isDisabled,
4848
+ isInvalid,
4849
+ isOpen,
4850
+ isRequired,
4851
+ clearValue,
4852
+ openDropdown,
4853
+ resolvedHint,
4854
+ resolvedLabel,
4855
+ resolvedSize,
4856
+ setTriggerWidth,
4857
+ triggerRef,
4858
+ controlProps: {
4859
+ clearable,
4860
+ color,
4861
+ endIcon,
4862
+ loading,
4863
+ prefix,
4864
+ startIcon,
4865
+ suffix,
4866
+ testID,
4867
+ textStyle,
4868
+ triggerStyle,
4869
+ variant
4870
+ },
4871
+ formFieldProps: {
4872
+ description,
4873
+ error,
4874
+ label,
4875
+ style
4876
+ }
4877
+ };
4878
+ }
4879
+ //#endregion
4880
+ //#region src/components/Select/Root/SelectRoot.tsx
4881
+ function SelectRoot(props) {
4882
+ const { contextValue, controlProps, displayValue, field, formFieldProps, hasOwnField, hasValue, isDisabled, isInvalid, isOpen, isRequired, resolvedHint, resolvedLabel, resolvedSize, triggerRef, clearValue, openDropdown, setTriggerWidth } = useSelectRootState(props);
4630
4883
  const control = /* @__PURE__ */ jsx(SelectContext.Provider, {
4631
4884
  value: contextValue,
4632
4885
  children: /* @__PURE__ */ jsxs(View, {
4633
4886
  ref: triggerRef,
4634
- testID,
4887
+ testID: controlProps.testID,
4635
4888
  onLayout: (event) => setTriggerWidth(event.nativeEvent.layout.width),
4636
4889
  children: [/* @__PURE__ */ jsx(SelectTrigger, {
4637
4890
  displayText: displayValue,
@@ -4639,24 +4892,24 @@ function SelectRoot(props) {
4639
4892
  isOpen,
4640
4893
  hasValue,
4641
4894
  size: resolvedSize,
4642
- color,
4643
- variant,
4895
+ color: controlProps.color,
4896
+ variant: controlProps.variant,
4644
4897
  disabled: isDisabled,
4645
4898
  required: isRequired,
4646
4899
  hasError: isInvalid,
4647
- loading,
4648
- clearable,
4649
- startIcon,
4650
- endIcon,
4651
- prefix,
4652
- suffix,
4900
+ loading: controlProps.loading,
4901
+ clearable: controlProps.clearable,
4902
+ startIcon: controlProps.startIcon,
4903
+ endIcon: controlProps.endIcon,
4904
+ prefix: controlProps.prefix,
4905
+ suffix: controlProps.suffix,
4653
4906
  nativeID: !hasOwnField ? field?.controlId : void 0,
4654
4907
  accessibilityLabel: resolvedLabel,
4655
4908
  accessibilityHint: resolvedHint,
4656
4909
  accessibilityLabelledBy: !hasOwnField ? field?.labelId : void 0,
4657
4910
  ariaDescribedBy: !hasOwnField ? field?.ariaDescribedBy : void 0,
4658
- triggerStyle,
4659
- textStyle,
4911
+ triggerStyle: controlProps.triggerStyle,
4912
+ textStyle: controlProps.textStyle,
4660
4913
  onPress: openDropdown,
4661
4914
  onClear: clearValue
4662
4915
  }), /* @__PURE__ */ jsx(SelectContentSurface, {})]
@@ -4664,14 +4917,14 @@ function SelectRoot(props) {
4664
4917
  });
4665
4918
  if (!hasOwnField && field) return control;
4666
4919
  return /* @__PURE__ */ jsx(FormField, {
4667
- label,
4668
- description,
4669
- error,
4920
+ label: formFieldProps.label,
4921
+ description: formFieldProps.description,
4922
+ error: formFieldProps.error,
4670
4923
  required: isRequired,
4671
4924
  disabled: isDisabled,
4672
4925
  invalid: isInvalid,
4673
4926
  size: resolvedSize,
4674
- style,
4927
+ style: formFieldProps.style,
4675
4928
  children: control
4676
4929
  });
4677
4930
  }
@@ -5570,8 +5823,8 @@ const TooltipContent = ({ children, forceMount = false, withArrow = false, style
5570
5823
  animationType: "fade",
5571
5824
  onRequestClose: tooltip.requestClose,
5572
5825
  children: /* @__PURE__ */ jsx(Pressable, {
5826
+ ...tooltip.getOutsidePressProps({ accessibilityLabel: "Close tooltip" }),
5573
5827
  style: styles.overlay,
5574
- onPress: tooltip.requestOutsideClose,
5575
5828
  children: bubble
5576
5829
  })
5577
5830
  });
@@ -5669,15 +5922,15 @@ const TooltipRoot = ({ children, open: openProp, defaultOpen = false, onOpenChan
5669
5922
  hide,
5670
5923
  zIndex: dismiss.zIndex,
5671
5924
  requestClose: dismiss.requestClose,
5672
- requestOutsideClose: dismiss.requestOutsideClose,
5925
+ getOutsidePressProps: dismiss.getOutsidePressProps,
5673
5926
  onFloatingLayout
5674
5927
  }), [
5675
5928
  arrowPosition,
5676
5929
  contentId,
5677
5930
  disabled,
5678
5931
  dismiss.zIndex,
5932
+ dismiss.getOutsidePressProps,
5679
5933
  dismiss.requestClose,
5680
- dismiss.requestOutsideClose,
5681
5934
  hide,
5682
5935
  open,
5683
5936
  resolvedPlacement,
@@ -5724,11 +5977,6 @@ const Tooltip = Object.assign(TooltipRoot, {
5724
5977
  });
5725
5978
  Tooltip.displayName = "Tooltip";
5726
5979
  //#endregion
5727
- //#region src/utils/devWarning.ts
5728
- const devWarning = (condition, message) => {
5729
- if ((typeof __DEV__ === "undefined" || __DEV__) && !condition) console.warn(message);
5730
- };
5731
- //#endregion
5732
5980
  //#region src/primitives/Button/Button.styles.ts
5733
5981
  const fontWeight = (value) => value;
5734
5982
  const createStyles$2 = (theme) => StyleSheet.create({
@@ -5747,9 +5995,14 @@ const createStyles$2 = (theme) => StyleSheet.create({
5747
5995
  fontFamily: theme.tokens.typography.family.regular,
5748
5996
  fontWeight: fontWeight(theme.tokens.typography.weight.regular),
5749
5997
  lineHeight: theme.tokens.typography.lineHeight.md,
5998
+ textAlign: "center",
5750
5999
  color: theme.components.button.primary.solid.default.fg
5751
6000
  },
5752
- labelSlot: { position: "relative" },
6001
+ labelSlot: {
6002
+ position: "relative",
6003
+ alignItems: "center",
6004
+ justifyContent: "center"
6005
+ },
5753
6006
  labelMeasure: {
5754
6007
  position: "absolute",
5755
6008
  opacity: 0
@@ -5796,22 +6049,22 @@ const createStyles$2 = (theme) => StyleSheet.create({
5796
6049
  //#region src/primitives/Button/Button.tsx
5797
6050
  const sizeMap = {
5798
6051
  sm: {
5799
- px: 12,
5800
- py: 8,
6052
+ px: 16,
6053
+ py: 6,
5801
6054
  height: 36,
5802
6055
  fontSize: 12,
5803
6056
  iconSize: 16
5804
6057
  },
5805
6058
  md: {
5806
- px: 16,
5807
- py: 12,
6059
+ px: 24,
6060
+ py: 8,
5808
6061
  height: 44,
5809
6062
  fontSize: 14,
5810
6063
  iconSize: 20
5811
6064
  },
5812
6065
  lg: {
5813
- px: 20,
5814
- py: 16,
6066
+ px: 32,
6067
+ py: 10,
5815
6068
  height: 52,
5816
6069
  fontSize: 16,
5817
6070
  iconSize: 24
@@ -5901,12 +6154,11 @@ function Button({ children, color = "primary", appearance = "solid", shape = "pi
5901
6154
  }),
5902
6155
  !loading && iconStart && renderIcon(iconStart, contentColor),
5903
6156
  content && !iconOnly && /* @__PURE__ */ jsxs(View, {
5904
- style: styles.labelSlot,
6157
+ style: [styles.labelSlot, labelWidth > 0 && { minWidth: labelWidth }],
5905
6158
  children: [/* @__PURE__ */ jsx(Text, {
5906
6159
  onLayout: handleLabelLayout,
5907
6160
  style: [
5908
6161
  styles.text,
5909
- labelWidth > 0 && { minWidth: labelWidth },
5910
6162
  {
5911
6163
  fontSize: config.fontSize,
5912
6164
  color: contentColor