@vellira-ui/react-native 2.58.0 → 2.59.1

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,8 +1,8 @@
1
- import { Children, cloneElement, createContext, forwardRef, isValidElement, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
1
+ import { Children, cloneElement, createContext, createElement, 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";
5
- import { darkTheme, highContrastTheme, lightTheme } from "@vellira-ui/tokens";
4
+ import { createConsoleOverlayDiagnostics, createOverlayManagerStore, createOverlayZIndexPolicy, createRetainedResourceRegistry, deferOverlayFocusRestore, getCompoundSlot, markCompoundSlot, resolveOverlayPresentation } from "@vellira-ui/core";
5
+ import { controlSizes, darkTheme, highContrastTheme, lightTheme } from "@vellira-ui/tokens";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
7
  //#region src/theme/fontWeight.ts
8
8
  const nativeFontWeights = /* @__PURE__ */ new Set([
@@ -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
@@ -369,19 +385,17 @@ const useKeyboardNavigation = ({ activeIndex, setActiveIndex, items, isOpen, onO
369
385
  case "Tab":
370
386
  onClose?.();
371
387
  break;
372
- default:
373
- if (event.key.length === 1 && !event.altKey && !event.ctrlKey && !event.metaKey) {
374
- event.preventDefault();
375
- if (searchRef.current.timeoutId) clearTimeout(searchRef.current.timeoutId);
376
- searchRef.current.value += event.key;
377
- searchRef.current.timeoutId = setTimeout(() => {
378
- searchRef.current.value = "";
379
- searchRef.current.timeoutId = void 0;
380
- }, 700);
381
- const nextIndex = getTypeaheadIndex(searchRef.current.value);
382
- if (nextIndex >= 0) setActiveIndex(nextIndex);
383
- }
384
- break;
388
+ default: if (event.key.length === 1 && !event.altKey && !event.ctrlKey && !event.metaKey) {
389
+ event.preventDefault();
390
+ if (searchRef.current.timeoutId) clearTimeout(searchRef.current.timeoutId);
391
+ searchRef.current.value += event.key;
392
+ searchRef.current.timeoutId = setTimeout(() => {
393
+ searchRef.current.value = "";
394
+ searchRef.current.timeoutId = void 0;
395
+ }, 700);
396
+ const nextIndex = getTypeaheadIndex(searchRef.current.value);
397
+ if (nextIndex >= 0) setActiveIndex(nextIndex);
398
+ }
385
399
  }
386
400
  }, [
387
401
  activeIndex,
@@ -531,11 +545,23 @@ const useModal = ({ open, defaultOpen = false, onOpenChange, closeOnEscape, clos
531
545
  };
532
546
  //#endregion
533
547
  //#region src/hooks/useSelect.ts
534
- const useSelect = ({ value, defaultValue, onValueChange, onChange, options, multiple = false, maxSelected, closeOnSelect = !multiple, disabled = false, open, defaultOpen = false, onOpenChange }) => {
548
+ const useSelect = ({ value, defaultValue, onValueChange, options, multiple = false, maxSelected, closeOnSelect = !multiple, disabled = false, open, defaultOpen = false, onOpenChange }) => {
549
+ const handleValueChange = useCallback((nextValue) => {
550
+ if (!onValueChange) return;
551
+ if (multiple) {
552
+ if (Array.isArray(nextValue)) {
553
+ onValueChange(nextValue);
554
+ return;
555
+ }
556
+ onValueChange(nextValue ? [nextValue] : []);
557
+ return;
558
+ }
559
+ onValueChange(Array.isArray(nextValue) ? nextValue[0] ?? "" : nextValue);
560
+ }, [multiple, onValueChange]);
535
561
  const [selectedValue, setSelectedValue] = useControllableState({
536
562
  value,
537
563
  defaultValue: defaultValue ?? (multiple ? [] : ""),
538
- onChange: onValueChange ?? onChange
564
+ onChange: handleValueChange
539
565
  });
540
566
  const [isOpen, setIsOpen] = useControllableState({
541
567
  value: open,
@@ -808,7 +834,7 @@ const createStyles$21 = (theme) => StyleSheet.create({
808
834
  const nativePointerEventsBoxNone$1 = Platform.OS === "web" ? void 0 : { pointerEvents: "box-none" };
809
835
  const webPointerEventsBoxNone$1 = Platform.OS === "web" ? { pointerEvents: "box-none" } : void 0;
810
836
  function DropdownContent({ children, contentStyle, accessibilityLabel }) {
811
- const { open, color, presentation, zIndex, position, searchable, searchValue, searchPlaceholder, searchAccessibilityLabel, requestClose, requestOutsideClose, onSearchChange, onFloatingLayout } = useDropdownContext();
837
+ const { open, color, presentation, zIndex, position, searchable, searchValue, searchPlaceholder, searchAccessibilityLabel, requestClose, getOutsidePressProps, onSearchChange, onFloatingLayout } = useDropdownContext();
812
838
  const { theme } = useTheme();
813
839
  const styles = useThemeStyles(createStyles$21);
814
840
  const colorPalette = theme.components.dropdown[color];
@@ -881,10 +907,8 @@ function DropdownContent({ children, contentStyle, accessibilityLabel }) {
881
907
  webPointerEventsBoxNone$1
882
908
  ],
883
909
  children: /* @__PURE__ */ jsx(Pressable, {
884
- accessibilityRole: "button",
885
- accessibilityLabel: "Close menu",
886
- style: StyleSheet.absoluteFill,
887
- onPress: requestOutsideClose
910
+ ...getOutsidePressProps({ accessibilityLabel: "Close menu" }),
911
+ style: StyleSheet.absoluteFill
888
912
  })
889
913
  }), /* @__PURE__ */ jsxs(Animated.View, {
890
914
  accessibilityRole: "menu",
@@ -932,7 +956,7 @@ DropdownContent.displayName = "DropdownContent";
932
956
  //#region src/components/Dropdown/internal/DropdownCollection.ts
933
957
  function createDropdownSlot(name, displayName) {
934
958
  const Slot = () => null;
935
- Slot.__velliraDropdownPart = name;
959
+ markCompoundSlot(Slot, name);
936
960
  Slot.displayName = displayName;
937
961
  return Slot;
938
962
  }
@@ -960,11 +984,12 @@ function parseDropdownChildren(children) {
960
984
  Children.forEach(node, (child) => {
961
985
  if (!isValidElement(child)) return;
962
986
  const type = child.type;
987
+ const slot = getCompoundSlot(type);
963
988
  if (type.__velliraPortal) {
964
989
  visit(child.props.children);
965
990
  return;
966
991
  }
967
- switch (type.__velliraDropdownPart) {
992
+ switch (slot) {
968
993
  case "trigger":
969
994
  triggerProps = child.props;
970
995
  trigger = triggerProps.children;
@@ -1332,14 +1357,20 @@ const createStyles$19 = (theme) => StyleSheet.create({ groupLabel: {
1332
1357
  //#endregion
1333
1358
  //#region src/components/Dropdown/Group/DropdownGroup.tsx
1334
1359
  function DropdownGroup({ label }) {
1360
+ const styles = useThemeStyles(createStyles$19);
1335
1361
  return /* @__PURE__ */ jsx(Text, {
1336
1362
  accessibilityRole: "header",
1337
- style: useThemeStyles(createStyles$19).groupLabel,
1363
+ style: styles.groupLabel,
1338
1364
  children: label
1339
1365
  });
1340
1366
  }
1341
1367
  DropdownGroup.displayName = "DropdownGroup";
1342
1368
  //#endregion
1369
+ //#region src/utils/devWarning.ts
1370
+ const devWarning = (condition, message) => {
1371
+ if ((typeof __DEV__ === "undefined" || __DEV__) && !condition) console.warn(message);
1372
+ };
1373
+ //#endregion
1343
1374
  //#region src/components/Dropdown/Item/DropdownItem.styles.ts
1344
1375
  const createStyles$18 = (theme) => StyleSheet.create({
1345
1376
  item: {
@@ -1364,7 +1395,7 @@ const createStyles$18 = (theme) => StyleSheet.create({
1364
1395
  });
1365
1396
  //#endregion
1366
1397
  //#region src/components/Dropdown/Item/DropdownItem.tsx
1367
- function DropdownItem({ label, value, color = "default", icon, disabled = false, textWrap = "truncate", onSelect }) {
1398
+ function DropdownItem({ label, value, asChild = false, children, color = "default", icon, disabled = false, textWrap = "truncate", onSelect }) {
1368
1399
  const { color: rootColor, itemStyle, textStyle } = useDropdownContext();
1369
1400
  const { theme } = useTheme();
1370
1401
  const styles = useThemeStyles(createStyles$18);
@@ -1389,6 +1420,24 @@ function DropdownItem({ label, value, color = "default", icon, disabled = false,
1389
1420
  const accessibilityLabel = typeof label === "string" ? label : value;
1390
1421
  const numberOfLines = textWrap === "wrap" ? void 0 : 1;
1391
1422
  const ellipsizeMode = textWrap === "truncate" ? "tail" : "clip";
1423
+ const child = asChild && isValidElement(children) ? children : void 0;
1424
+ const getItemStyle = (pressed) => [
1425
+ styles.item,
1426
+ { backgroundColor: getBackgroundColor(pressed) },
1427
+ itemStyle
1428
+ ];
1429
+ devWarning(!asChild || Boolean(child), "Dropdown.Item: asChild requires a single valid React element child.");
1430
+ if (child) return cloneElement(child, {
1431
+ accessibilityRole: "menuitem",
1432
+ accessibilityLabel,
1433
+ accessibilityState: { disabled },
1434
+ disabled,
1435
+ onPress: (event) => {
1436
+ child.props.onPress?.(event);
1437
+ if (!event.defaultPrevented && !disabled) onSelect(value);
1438
+ },
1439
+ style: [getItemStyle(false), child.props.style]
1440
+ });
1392
1441
  return /* @__PURE__ */ jsx(Pressable, {
1393
1442
  disabled,
1394
1443
  accessibilityRole: "menuitem",
@@ -1398,11 +1447,7 @@ function DropdownItem({ label, value, color = "default", icon, disabled = false,
1398
1447
  if (disabled) return;
1399
1448
  onSelect(value);
1400
1449
  },
1401
- style: ({ pressed }) => [
1402
- styles.item,
1403
- { backgroundColor: getBackgroundColor(pressed) },
1404
- itemStyle
1405
- ],
1450
+ style: ({ pressed }) => getItemStyle(pressed),
1406
1451
  children: ({ pressed }) => {
1407
1452
  const contentColor = getContentColor(pressed);
1408
1453
  return /* @__PURE__ */ jsxs(Fragment, { children: [icon ? renderColoredNode(icon, contentColor) : null, /* @__PURE__ */ jsx(Text, {
@@ -1428,7 +1473,8 @@ const createStyles$17 = (theme) => StyleSheet.create({ separator: {
1428
1473
  //#endregion
1429
1474
  //#region src/components/Dropdown/Separator/DropdownSeparator.tsx
1430
1475
  function DropdownSeparator() {
1431
- return /* @__PURE__ */ jsx(View, { style: useThemeStyles(createStyles$17).separator });
1476
+ const styles = useThemeStyles(createStyles$17);
1477
+ return /* @__PURE__ */ jsx(View, { style: styles.separator });
1432
1478
  }
1433
1479
  DropdownSeparator.displayName = "DropdownSeparator";
1434
1480
  //#endregion
@@ -1736,59 +1782,62 @@ 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...");
1749
1797
  const searchAccessibilityLabel = parsed.searchProps?.accessibilityLabel;
1798
+ const contextValue = useMemo(() => ({
1799
+ open: isOpen,
1800
+ disabled,
1801
+ loading,
1802
+ color,
1803
+ size,
1804
+ presentation: contentPresentation,
1805
+ position,
1806
+ zIndex: dismiss.zIndex,
1807
+ searchable: isSearchable,
1808
+ searchValue: resolvedSearchValue,
1809
+ searchPlaceholder: resolvedSearchPlaceholder,
1810
+ searchAccessibilityLabel,
1811
+ itemStyle,
1812
+ textStyle,
1813
+ requestClose: dismiss.requestClose,
1814
+ getOutsidePressProps: dismiss.getOutsidePressProps,
1815
+ toggle: handleTriggerPress,
1816
+ onSearchChange: handleSearchChange,
1817
+ onFloatingLayout
1818
+ }), [
1819
+ color,
1820
+ contentPresentation,
1821
+ disabled,
1822
+ dismiss.zIndex,
1823
+ dismiss.getOutsidePressProps,
1824
+ dismiss.requestClose,
1825
+ handleSearchChange,
1826
+ handleTriggerPress,
1827
+ isOpen,
1828
+ isSearchable,
1829
+ itemStyle,
1830
+ loading,
1831
+ onFloatingLayout,
1832
+ position,
1833
+ resolvedSearchPlaceholder,
1834
+ resolvedSearchValue,
1835
+ searchAccessibilityLabel,
1836
+ size,
1837
+ textStyle
1838
+ ]);
1750
1839
  return /* @__PURE__ */ jsx(DropdownProvider, {
1751
- value: useMemo(() => ({
1752
- open: isOpen,
1753
- disabled,
1754
- loading,
1755
- color,
1756
- size,
1757
- presentation: contentPresentation,
1758
- position,
1759
- zIndex: dismiss.zIndex,
1760
- searchable: isSearchable,
1761
- searchValue: resolvedSearchValue,
1762
- searchPlaceholder: resolvedSearchPlaceholder,
1763
- searchAccessibilityLabel,
1764
- itemStyle,
1765
- textStyle,
1766
- requestClose: dismiss.requestClose,
1767
- requestOutsideClose: dismiss.requestOutsideClose,
1768
- toggle: handleTriggerPress,
1769
- onSearchChange: handleSearchChange,
1770
- onFloatingLayout
1771
- }), [
1772
- color,
1773
- contentPresentation,
1774
- disabled,
1775
- dismiss.zIndex,
1776
- dismiss.requestClose,
1777
- dismiss.requestOutsideClose,
1778
- handleSearchChange,
1779
- handleTriggerPress,
1780
- isOpen,
1781
- isSearchable,
1782
- itemStyle,
1783
- loading,
1784
- onFloatingLayout,
1785
- position,
1786
- resolvedSearchPlaceholder,
1787
- resolvedSearchValue,
1788
- searchAccessibilityLabel,
1789
- size,
1790
- textStyle
1791
- ]),
1840
+ value: contextValue,
1792
1841
  children: /* @__PURE__ */ jsxs(View, {
1793
1842
  style: [styles.root, style],
1794
1843
  children: [/* @__PURE__ */ jsx(DropdownTrigger, {
@@ -1885,13 +1934,23 @@ const createStyles$14 = (theme) => StyleSheet.create({
1885
1934
  justifyContent: "space-between",
1886
1935
  paddingBottom: theme.components.modal.header.paddingBottom
1887
1936
  },
1888
- title: {
1937
+ headerContent: {
1889
1938
  flex: 1,
1939
+ gap: theme.tokens.spacing["1"]
1940
+ },
1941
+ title: {
1890
1942
  color: theme.components.modal.title.fg,
1891
1943
  fontFamily: theme.tokens.typography.family.semibold,
1892
1944
  fontSize: theme.tokens.typography.size.lg,
1893
1945
  lineHeight: theme.tokens.typography.lineHeight.md
1894
1946
  },
1947
+ plainTitle: { flex: 1 },
1948
+ description: {
1949
+ color: theme.components.modal.description.fg,
1950
+ fontFamily: theme.tokens.typography.family.regular,
1951
+ fontSize: theme.tokens.typography.size.sm,
1952
+ lineHeight: theme.tokens.typography.lineHeight.sm
1953
+ },
1895
1954
  closeButton: {
1896
1955
  width: theme.components.modal.closeButton.size,
1897
1956
  height: theme.components.modal.closeButton.size,
@@ -1915,11 +1974,11 @@ const useModalContext = () => {
1915
1974
  ModalContext.displayName = "ModalContext";
1916
1975
  //#endregion
1917
1976
  //#region src/components/Modal/Close/ModalClose.tsx
1918
- const ModalClose = ({ children, accessibilityLabel, style }) => {
1977
+ const ModalClose = ({ asChild = false, children, accessibilityLabel, style }) => {
1919
1978
  const { theme } = useTheme();
1920
1979
  const styles = useThemeStyles(createStyles$14);
1921
1980
  const { onClose } = useModalContext();
1922
- if (isValidElement(children)) return cloneElement(children, {
1981
+ if ((asChild || children !== void 0) && isValidElement(children)) return cloneElement(children, {
1923
1982
  accessibilityLabel: children.props.accessibilityLabel ?? accessibilityLabel,
1924
1983
  onPress: (event) => {
1925
1984
  children.props.onPress?.(event);
@@ -2005,26 +2064,56 @@ const createStyles$12 = (theme) => StyleSheet.create({ footer: {
2005
2064
  //#endregion
2006
2065
  //#region src/components/Modal/Footer/ModalFooter.tsx
2007
2066
  const ModalFooter = ({ children, style }) => {
2067
+ const styles = useThemeStyles(createStyles$12);
2008
2068
  return /* @__PURE__ */ jsx(View, {
2009
- style: [useThemeStyles(createStyles$12).footer, style],
2069
+ style: [styles.footer, style],
2010
2070
  children
2011
2071
  });
2012
2072
  };
2013
2073
  ModalFooter.displayName = "ModalFooter";
2014
2074
  //#endregion
2075
+ //#region src/components/Modal/Header/ModalDescription.tsx
2076
+ const ModalDescription = ({ children, style }) => {
2077
+ const styles = useThemeStyles(createStyles$14);
2078
+ return /* @__PURE__ */ jsx(Text, {
2079
+ style: [styles.description, style],
2080
+ children
2081
+ });
2082
+ };
2083
+ ModalDescription.displayName = "Modal.Description";
2084
+ //#endregion
2015
2085
  //#region src/components/Modal/Header/ModalHeader.tsx
2016
2086
  const ModalHeader = ({ children, style, textStyle }) => {
2017
2087
  const styles = useThemeStyles(createStyles$14);
2088
+ const isPlainTitle = typeof children === "string" || typeof children === "number";
2018
2089
  return /* @__PURE__ */ jsxs(View, {
2019
2090
  style: [styles.header, style],
2020
- children: [/* @__PURE__ */ jsx(Text, {
2021
- style: [styles.title, textStyle],
2091
+ children: [isPlainTitle ? /* @__PURE__ */ jsx(Text, {
2092
+ style: [
2093
+ styles.title,
2094
+ styles.plainTitle,
2095
+ textStyle
2096
+ ],
2097
+ children
2098
+ }) : /* @__PURE__ */ jsx(View, {
2099
+ style: styles.headerContent,
2022
2100
  children
2023
2101
  }), /* @__PURE__ */ jsx(ModalClose, {})]
2024
2102
  });
2025
2103
  };
2026
2104
  ModalHeader.displayName = "ModalHeader";
2027
2105
  //#endregion
2106
+ //#region src/components/Modal/Header/ModalTitle.tsx
2107
+ const ModalTitle = ({ children, style }) => {
2108
+ const styles = useThemeStyles(createStyles$14);
2109
+ return /* @__PURE__ */ jsx(Text, {
2110
+ accessibilityRole: "header",
2111
+ style: [styles.title, style],
2112
+ children
2113
+ });
2114
+ };
2115
+ ModalTitle.displayName = "Modal.Title";
2116
+ //#endregion
2028
2117
  //#region src/components/Modal/Modal.styles.ts
2029
2118
  const createStyles$11 = (theme) => StyleSheet.create({
2030
2119
  overlay: {
@@ -2042,7 +2131,7 @@ const createStyles$11 = (theme) => StyleSheet.create({
2042
2131
  //#region src/components/Modal/Overlay/ModalOverlay.tsx
2043
2132
  const ModalOverlay = ({ children, overlayStyle }) => {
2044
2133
  const styles = useThemeStyles(createStyles$11);
2045
- const { animation, animationProgress, closeOnOutsidePress, zIndex, onClose, onOutsideClose, shouldRender } = useModalContext();
2134
+ const { animation, animationProgress, zIndex, onClose, getOutsidePressProps, shouldRender } = useModalContext();
2046
2135
  const backdropStyle = animation === "none" ? void 0 : { opacity: animationProgress };
2047
2136
  return /* @__PURE__ */ jsx(Modal$1, {
2048
2137
  visible: shouldRender,
@@ -2059,17 +2148,15 @@ const ModalOverlay = ({ children, overlayStyle }) => {
2059
2148
  style: [styles.backdrop, backdropStyle],
2060
2149
  children: /* @__PURE__ */ jsx(Pressable, {
2061
2150
  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
2151
+ ...getOutsidePressProps({ accessibilityLabel: "Close modal" }),
2152
+ style: StyleSheet.absoluteFill
2066
2153
  })
2067
2154
  }), children]
2068
2155
  })
2069
2156
  });
2070
2157
  };
2071
2158
  //#endregion
2072
- //#region src/components/Modal/Root/ModalRoot.tsx
2159
+ //#region src/components/Modal/Root/useModalRootAnimation.ts
2073
2160
  const linearEasing = (value) => value;
2074
2161
  const easingMap = {
2075
2162
  standard: Easing?.bezier?.(.22, 1, .36, 1) ?? linearEasing,
@@ -2090,33 +2177,10 @@ const resolveDuration = (duration) => {
2090
2177
  open: duration?.open ?? parseDuration(nativeThemes.light.components.modal.motion.openDuration)
2091
2178
  };
2092
2179
  };
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);
2180
+ function useModalRootAnimation({ animation, defaultOpen, duration, easing, open }) {
2181
+ const animationProgress = useRef(new Animated.Value(defaultOpen ? 1 : 0));
2182
+ const [shouldRender, setShouldRender] = useState(defaultOpen);
2098
2183
  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
2184
  const animationDuration = resolveDuration(duration);
2121
2185
  const shouldAnimate = animation !== "none" && !reduceMotion;
2122
2186
  useEffect(() => {
@@ -2128,7 +2192,7 @@ const ModalRoot = ({ open, defaultOpen = false, onOpenChange, animation = "scale
2128
2192
  }, []);
2129
2193
  useEffect(() => {
2130
2194
  const progress = animationProgress.current;
2131
- if (modal.open) {
2195
+ if (open) {
2132
2196
  setShouldRender(true);
2133
2197
  if (!shouldAnimate) {
2134
2198
  progress.setValue(1);
@@ -2160,17 +2224,57 @@ const ModalRoot = ({ open, defaultOpen = false, onOpenChange, animation = "scale
2160
2224
  animationDuration.close,
2161
2225
  animationDuration.open,
2162
2226
  easing,
2163
- modal.open,
2227
+ open,
2164
2228
  shouldAnimate
2165
2229
  ]);
2230
+ return {
2231
+ animationProgress: animationProgress.current,
2232
+ shouldRender
2233
+ };
2234
+ }
2235
+ //#endregion
2236
+ //#region src/components/Modal/Root/ModalRoot.tsx
2237
+ const ModalRoot = ({ open, defaultOpen = false, onOpenChange, animation = "scale", duration, easing = "standard", closeOnEscape = true, closeOnOutsidePress = true, restoreFocus = true, children }) => {
2238
+ const initialOpen = open ?? defaultOpen;
2239
+ const triggerRef = useRef(null);
2240
+ const modal = useModal({
2241
+ open,
2242
+ defaultOpen,
2243
+ onOpenChange,
2244
+ closeOnEscape,
2245
+ closeOnOutsidePress
2246
+ });
2247
+ const { animationProgress, shouldRender } = useModalRootAnimation({
2248
+ animation,
2249
+ defaultOpen: initialOpen,
2250
+ duration,
2251
+ easing,
2252
+ open: modal.open
2253
+ });
2254
+ const { restoreFocusAfterClose } = useOverlayFocusRestore({
2255
+ active: modal.open,
2256
+ enabled: restoreFocus,
2257
+ triggerRef
2258
+ });
2259
+ const previousOpenRef = useRef(modal.open);
2260
+ useEffect(() => {
2261
+ if (previousOpenRef.current && !modal.open) restoreFocusAfterClose();
2262
+ previousOpenRef.current = modal.open;
2263
+ }, [modal.open, restoreFocusAfterClose]);
2264
+ const dismiss = useOverlayDismiss({
2265
+ id: modal.contentId,
2266
+ active: modal.open,
2267
+ closeOnEscape: modal.closeOnEscape,
2268
+ closeOnOutsidePress: modal.closeOnOutsidePress,
2269
+ requestClose: modal.requestClose
2270
+ });
2166
2271
  return /* @__PURE__ */ jsx(ModalProvider, {
2167
2272
  value: {
2168
2273
  animation,
2169
- animationProgress: animationProgress.current,
2170
- closeOnOutsidePress: modal.closeOnOutsidePress,
2274
+ animationProgress,
2171
2275
  zIndex: dismiss.zIndex,
2172
2276
  onClose: dismiss.requestClose,
2173
- onOutsideClose: dismiss.requestOutsideClose,
2277
+ getOutsidePressProps: dismiss.getOutsidePressProps,
2174
2278
  open: modal.open,
2175
2279
  setOpen: modal.setOpen,
2176
2280
  shouldRender,
@@ -2236,6 +2340,8 @@ const Modal = Object.assign(ModalRoot, {
2236
2340
  Overlay: ModalOverlay,
2237
2341
  Content: ModalContent,
2238
2342
  Header: ModalHeader,
2343
+ Title: ModalTitle,
2344
+ Description: ModalDescription,
2239
2345
  Body: ModalBody,
2240
2346
  Footer: ModalFooter,
2241
2347
  Close: ModalClose
@@ -2300,12 +2406,12 @@ function createPopoverArrowStyles({ theme, side, arrowPosition }) {
2300
2406
  position.left = -halfSize;
2301
2407
  border.borderLeftWidth = 1;
2302
2408
  border.borderBottomWidth = 1;
2303
- break;
2304
2409
  }
2305
2410
  return StyleSheet.create({ arrow: {
2306
2411
  position: "absolute",
2307
2412
  width: tokens.size,
2308
2413
  height: tokens.size,
2414
+ ...Platform.OS === "web" ? { pointerEvents: "none" } : {},
2309
2415
  backgroundColor: tokens.bg,
2310
2416
  ...position,
2311
2417
  ...border
@@ -2317,18 +2423,19 @@ function PopoverArrow({ style }) {
2317
2423
  const { theme } = useTheme();
2318
2424
  const { placement, arrowPosition } = usePopoverContext("Popover.Arrow");
2319
2425
  const side = placement.split("-")[0];
2426
+ const styles = useMemo(() => createPopoverArrowStyles({
2427
+ theme,
2428
+ side,
2429
+ arrowPosition
2430
+ }), [
2431
+ theme,
2432
+ side,
2433
+ arrowPosition
2434
+ ]);
2320
2435
  return /* @__PURE__ */ jsx(View, {
2321
2436
  accessible: false,
2322
- pointerEvents: "none",
2323
- style: [useMemo(() => createPopoverArrowStyles({
2324
- theme,
2325
- side,
2326
- arrowPosition
2327
- }), [
2328
- theme,
2329
- side,
2330
- arrowPosition
2331
- ]).arrow, style]
2437
+ pointerEvents: Platform.OS === "web" ? void 0 : "none",
2438
+ style: [styles.arrow, style]
2332
2439
  });
2333
2440
  }
2334
2441
  PopoverArrow.displayName = "Popover.Arrow";
@@ -2353,7 +2460,10 @@ PopoverClose.displayName = "PopoverClose";
2353
2460
  //#endregion
2354
2461
  //#region src/primitives/Portal/Portal.tsx
2355
2462
  const PortalContext = createContext(null);
2356
- const styles$1 = StyleSheet.create({ host: { flex: 1 } });
2463
+ const styles$1 = StyleSheet.create({ host: {
2464
+ flex: 1,
2465
+ ...Platform.OS === "web" ? { pointerEvents: "box-none" } : {}
2466
+ } });
2357
2467
  const PortalProvider = ({ children, container = null }) => /* @__PURE__ */ jsx(PortalContext.Provider, {
2358
2468
  value: container,
2359
2469
  children
@@ -2372,7 +2482,7 @@ const Portal = ({ children, visible = true, animationType = "none", hardwareAcce
2372
2482
  transparent: true,
2373
2483
  visible: true,
2374
2484
  children: /* @__PURE__ */ jsx(View, {
2375
- pointerEvents: "box-none",
2485
+ pointerEvents: Platform.OS === "web" ? void 0 : "box-none",
2376
2486
  style: styles$1.host,
2377
2487
  children
2378
2488
  })
@@ -2384,7 +2494,10 @@ PortalProvider.displayName = "PortalProvider";
2384
2494
  //#endregion
2385
2495
  //#region src/components/Popover/Content/PopoverContent.styles.ts
2386
2496
  const styles = StyleSheet.create({
2387
- root: { flex: 1 },
2497
+ root: {
2498
+ flex: 1,
2499
+ ...Platform.OS === "web" ? { pointerEvents: "box-none" } : {}
2500
+ },
2388
2501
  backdrop: StyleSheet.absoluteFill,
2389
2502
  content: { position: "absolute" }
2390
2503
  });
@@ -2400,14 +2513,16 @@ function createPopoverContentStyles(theme) {
2400
2513
  borderColor: tokens.border,
2401
2514
  borderWidth: tokens.borderWidth,
2402
2515
  borderRadius: tokens.radius,
2403
- shadowColor: shadow.color,
2404
- shadowOpacity: shadow.opacity,
2405
- shadowRadius: shadow.blur,
2406
- shadowOffset: {
2407
- width: shadow.x,
2408
- height: shadow.y
2409
- },
2410
- elevation: shadow.elevation
2516
+ ...Platform.OS === "web" ? { boxShadow: tokens.shadow.web } : {
2517
+ shadowColor: shadow.color,
2518
+ shadowOpacity: shadow.opacity,
2519
+ shadowRadius: shadow.blur,
2520
+ shadowOffset: {
2521
+ width: shadow.x,
2522
+ height: shadow.y
2523
+ },
2524
+ elevation: shadow.elevation
2525
+ }
2411
2526
  } });
2412
2527
  }
2413
2528
  //#endregion
@@ -2416,7 +2531,7 @@ function PopoverContent({ children, style, ...contentProps }) {
2416
2531
  const { theme } = useTheme();
2417
2532
  const layerRef = useRef(null);
2418
2533
  const themedStyles = useMemo(() => createPopoverContentStyles(theme), [theme]);
2419
- const { open, zIndex, position, onFloatingLayout, updatePosition, requestClose, requestOutsideClose, closeOnOutsidePress } = usePopoverContext("Popover.Content");
2534
+ const { open, zIndex, position, onFloatingLayout, updatePosition, requestClose, getOutsidePressProps } = usePopoverContext("Popover.Content");
2420
2535
  useEffect(() => {
2421
2536
  if (!open) return;
2422
2537
  requestAnimationFrame(() => {
@@ -2428,13 +2543,11 @@ function PopoverContent({ children, style, ...contentProps }) {
2428
2543
  onRequestClose: requestClose,
2429
2544
  children: /* @__PURE__ */ jsxs(View, {
2430
2545
  ref: layerRef,
2431
- pointerEvents: "box-none",
2546
+ pointerEvents: Platform.OS === "web" ? void 0 : "box-none",
2432
2547
  style: [styles.root, { zIndex }],
2433
2548
  children: [/* @__PURE__ */ jsx(Pressable, {
2434
2549
  testID: "popover-backdrop",
2435
- accessibilityLabel: closeOnOutsidePress ? "Close popover" : void 0,
2436
- accessibilityRole: closeOnOutsidePress ? "button" : void 0,
2437
- onPress: closeOnOutsidePress ? requestOutsideClose : void 0,
2550
+ ...getOutsidePressProps({ accessibilityLabel: "Close popover" }),
2438
2551
  style: styles.backdrop
2439
2552
  }), /* @__PURE__ */ jsx(View, {
2440
2553
  ...contentProps,
@@ -2517,7 +2630,6 @@ function PopoverRoot({ children, open: openProp, defaultOpen = false, onOpenChan
2517
2630
  return /* @__PURE__ */ jsx(PopoverProvider, {
2518
2631
  value: {
2519
2632
  open,
2520
- closeOnOutsidePress,
2521
2633
  triggerRef,
2522
2634
  anchorRef,
2523
2635
  side,
@@ -2527,7 +2639,7 @@ function PopoverRoot({ children, open: openProp, defaultOpen = false, onOpenChan
2527
2639
  position,
2528
2640
  arrowPosition,
2529
2641
  requestClose: dismiss.requestClose,
2530
- requestOutsideClose: dismiss.requestOutsideClose,
2642
+ getOutsidePressProps: dismiss.getOutsidePressProps,
2531
2643
  onFloatingLayout,
2532
2644
  updatePosition,
2533
2645
  setOpen
@@ -2602,8 +2714,6 @@ function useRadioGroupContext() {
2602
2714
  const createStyles$10 = (theme) => StyleSheet.create({
2603
2715
  root: { alignSelf: "flex-start" },
2604
2716
  pressable: {
2605
- minWidth: 32,
2606
- minHeight: 32,
2607
2717
  flexDirection: "row",
2608
2718
  alignItems: "flex-start",
2609
2719
  gap: theme.tokens.spacing[2]
@@ -2674,6 +2784,8 @@ const Radio = forwardRef(({ value, checked, defaultChecked = false, disabled: di
2674
2784
  const radioColor = theme.components.radio[resolvedColor];
2675
2785
  const radioSize = theme.components.radio.size[resolvedSize];
2676
2786
  const controlMarginTop = (radioSize.labelLineHeight - radioSize.controlSize) / 2;
2787
+ const visualHeight = Math.max(radioSize.labelLineHeight, radioSize.controlSize);
2788
+ const hitSlop = Math.max(0, (32 - visualHeight) / 2);
2677
2789
  useEffect(() => {
2678
2790
  if (typeof __DEV__ !== "undefined" && __DEV__ && !label && !accessibilityLabel) console.warn("Radio requires either a visible label or accessibilityLabel.");
2679
2791
  }, [accessibilityLabel, label]);
@@ -2710,6 +2822,7 @@ const Radio = forwardRef(({ value, checked, defaultChecked = false, disabled: di
2710
2822
  disabled: resolvedDisabled
2711
2823
  },
2712
2824
  disabled: resolvedDisabled,
2825
+ hitSlop: Platform.OS === "web" ? void 0 : hitSlop,
2713
2826
  onPress: handlePress,
2714
2827
  style: resolvePressableStyle,
2715
2828
  children: (state) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(View, {
@@ -2774,7 +2887,7 @@ const Radio = forwardRef(({ value, checked, defaultChecked = false, disabled: di
2774
2887
  children: description
2775
2888
  }) : description)]
2776
2889
  })] })
2777
- }), error && /* @__PURE__ */ jsx(Text, {
2890
+ }), Boolean(error) && /* @__PURE__ */ jsx(Text, {
2778
2891
  accessibilityLiveRegion: "polite",
2779
2892
  style: [
2780
2893
  styles.error,
@@ -2804,7 +2917,7 @@ const createStyles$9 = (theme) => StyleSheet.create({
2804
2917
  root: {
2805
2918
  width: "100%",
2806
2919
  minWidth: 0,
2807
- alignSelf: "stretch",
2920
+ alignSelf: "auto",
2808
2921
  gap: theme.components.formField.size.md.gap
2809
2922
  },
2810
2923
  label: {
@@ -2988,7 +3101,7 @@ function FormFieldRoot({ id, label, description, error, message, messageTone, me
2988
3101
  style: styles.required,
2989
3102
  accessible: false,
2990
3103
  importantForAccessibility: "no",
2991
- children: " *"
3104
+ children: "*"
2992
3105
  }),
2993
3106
  !required && resolvedOptionalText && /* @__PURE__ */ jsx(Text, {
2994
3107
  style: [styles.optional, {
@@ -3119,10 +3232,13 @@ const RadioGroupRoot = forwardRef(({ value, defaultValue = "", onValueChange, di
3119
3232
  required,
3120
3233
  disabled,
3121
3234
  size,
3235
+ accessibilityRole: "radiogroup",
3236
+ accessibilityLabel: resolvedAccessibilityLabel,
3237
+ accessibilityHint: resolvedAccessibilityHint,
3122
3238
  labelStyle,
3123
3239
  descriptionStyle,
3124
3240
  errorStyle,
3125
- style,
3241
+ style: [{ alignSelf: "auto" }, style],
3126
3242
  children: /* @__PURE__ */ jsx(RadioGroupProvider, {
3127
3243
  value: {
3128
3244
  value: selectedValue,
@@ -3136,10 +3252,6 @@ const RadioGroupRoot = forwardRef(({ value, defaultValue = "", onValueChange, di
3136
3252
  children: /* @__PURE__ */ jsx(View, {
3137
3253
  ...rest,
3138
3254
  ref,
3139
- accessibilityRole: "radiogroup",
3140
- accessibilityLabel: resolvedAccessibilityLabel,
3141
- accessibilityHint: resolvedAccessibilityHint,
3142
- accessibilityState: { disabled },
3143
3255
  style: [
3144
3256
  styles.items,
3145
3257
  orientation === "horizontal" ? [styles.horizontal, {
@@ -3231,17 +3343,14 @@ const createContentStyles = (theme) => StyleSheet.create({
3231
3343
  }
3232
3344
  });
3233
3345
  //#endregion
3234
- //#region src/components/Select/internal/types.ts
3235
- const selectSlotName = Symbol("VelliraNativeSelectSlot");
3236
- //#endregion
3237
3346
  //#region src/components/Select/internal/SelectCollection.ts
3238
3347
  const createSelectSlot = (name, displayName) => {
3239
3348
  const Slot = (_props) => null;
3240
- Slot[selectSlotName] = name;
3349
+ markCompoundSlot(Slot, name);
3241
3350
  Slot.displayName = displayName;
3242
3351
  return Slot;
3243
3352
  };
3244
- const getSelectSlot = (type) => type?.[selectSlotName];
3353
+ const getSelectSlot = (type) => getCompoundSlot(type);
3245
3354
  const defaultSelectFilter = (option, query) => option.label.toLowerCase().includes(query.trim().toLowerCase());
3246
3355
  const getTextFromNode = (node) => {
3247
3356
  if (typeof node === "string" || typeof node === "number") return String(node);
@@ -3330,6 +3439,8 @@ const parseSelectChildren = (children) => {
3330
3439
  const option = {
3331
3440
  value: props.value,
3332
3441
  label: props.label,
3442
+ asChild: props.asChild,
3443
+ children: props.children,
3333
3444
  disabled: props.disabled,
3334
3445
  description: props.description,
3335
3446
  icon: props.icon,
@@ -3430,8 +3541,9 @@ const createGroupStyles = (theme) => StyleSheet.create({
3430
3541
  //#region src/components/Select/Group/SelectGroup.tsx
3431
3542
  const SelectGroup = createSelectSlot("group", "Select.Group");
3432
3543
  const SelectGroupLabelRow = ({ label }) => {
3544
+ const styles = useThemeStyles(createGroupStyles);
3433
3545
  return /* @__PURE__ */ jsx(Text, {
3434
- style: useThemeStyles(createGroupStyles).groupLabel,
3546
+ style: styles.groupLabel,
3435
3547
  children: label
3436
3548
  });
3437
3549
  };
@@ -3537,17 +3649,54 @@ const renderNodeOrText = (node, textStyle, fallback) => {
3537
3649
  }) : null);
3538
3650
  };
3539
3651
  const SelectItem = createSelectSlot("item", "Select.Item");
3540
- const SelectItemRow = ({ option, isSelected, isDisabled, optionStyle, onSelect }) => {
3652
+ const SelectItemRow = ({ option, isSelected, isDisabled, itemIndex, selectedValues, multiple, optionStyle, onSelect }) => {
3541
3653
  const { theme } = useTheme();
3542
3654
  const styles = useThemeStyles(createItemStyles);
3543
3655
  const { color, variant, renderOption } = useSelectContext();
3544
3656
  const [isHovered, setIsHovered] = useState(false);
3657
+ const child = option.asChild && isValidElement(option.children) ? option.children : void 0;
3545
3658
  const optionPalette = theme.components.select[option.color ?? color][variant].option;
3546
3659
  const getOptionState = (pressed) => {
3547
3660
  if (isDisabled) return theme.components.select.option.disabled;
3548
3661
  if (isSelected) return pressed ? optionPalette.selectedPressed : isHovered ? optionPalette.selectedHover : optionPalette.selected;
3549
3662
  return pressed ? optionPalette.pressed : isHovered ? optionPalette.hover : theme.components.select.option.default;
3550
3663
  };
3664
+ const getOptionStyle = (pressed) => {
3665
+ const optionState = getOptionState(pressed);
3666
+ return [
3667
+ styles.option,
3668
+ {
3669
+ backgroundColor: optionState.bg,
3670
+ borderColor: optionState.border
3671
+ },
3672
+ isDisabled && styles.optionDisabled,
3673
+ optionStyle
3674
+ ];
3675
+ };
3676
+ devWarning(!option.asChild || Boolean(child), "Select.Item: asChild requires a single valid React element child.");
3677
+ if (child) return cloneElement(child, {
3678
+ accessibilityRole: "button",
3679
+ accessibilityLabel: option.accessibilityLabel ?? option.label,
3680
+ accessibilityHint: option.accessibilityHint,
3681
+ accessibilityState: {
3682
+ selected: isSelected,
3683
+ disabled: isDisabled
3684
+ },
3685
+ disabled: isDisabled,
3686
+ onPress: (event) => {
3687
+ child.props.onPress?.(event);
3688
+ if (!event.defaultPrevented && !isDisabled) onSelect(option);
3689
+ },
3690
+ onHoverIn: () => {
3691
+ child.props.onHoverIn?.();
3692
+ setIsHovered(true);
3693
+ },
3694
+ onHoverOut: () => {
3695
+ child.props.onHoverOut?.();
3696
+ setIsHovered(false);
3697
+ },
3698
+ style: [getOptionStyle(false), child.props.style]
3699
+ });
3551
3700
  return /* @__PURE__ */ jsx(Pressable, {
3552
3701
  disabled: isDisabled,
3553
3702
  accessibilityRole: "button",
@@ -3560,26 +3709,21 @@ const SelectItemRow = ({ option, isSelected, isDisabled, optionStyle, onSelect }
3560
3709
  onPress: () => onSelect(option),
3561
3710
  onHoverIn: () => setIsHovered(true),
3562
3711
  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
- },
3712
+ style: ({ pressed }) => getOptionStyle(pressed),
3575
3713
  children: ({ pressed }) => {
3576
3714
  const optionFg = getOptionState(pressed).fg;
3577
3715
  const descriptionFg = isSelected || pressed ? optionFg : theme.components.select.option.description.fg;
3578
- return renderOption ? renderNodeOrText(renderOption(option, {
3716
+ return renderOption ? renderNodeOrText(renderOption({
3717
+ option,
3579
3718
  selected: isSelected,
3580
- disabled: isDisabled
3719
+ disabled: isDisabled,
3720
+ active: isHovered,
3721
+ index: itemIndex,
3722
+ values: selectedValues,
3723
+ multiple,
3724
+ pressed
3581
3725
  }), [styles.optionLabel, { color: optionFg }]) : /* @__PURE__ */ jsxs(Fragment, { children: [
3582
- option.icon && /* @__PURE__ */ jsx(View, {
3726
+ Boolean(option.icon) && /* @__PURE__ */ jsx(View, {
3583
3727
  style: styles.optionIcon,
3584
3728
  children: isValidElement(option.icon) ? cloneElement(option.icon, {
3585
3729
  color: optionFg,
@@ -3592,13 +3736,13 @@ const SelectItemRow = ({ option, isSelected, isDisabled, optionStyle, onSelect }
3592
3736
  numberOfLines: 1,
3593
3737
  style: [styles.optionLabel, { color: optionFg }],
3594
3738
  children: option.label
3595
- }), option.description && /* @__PURE__ */ jsx(Text, {
3739
+ }), Boolean(option.description) && /* @__PURE__ */ jsx(Text, {
3596
3740
  numberOfLines: 2,
3597
3741
  style: [styles.optionDescription, { color: descriptionFg }],
3598
3742
  children: option.description
3599
3743
  })]
3600
3744
  }),
3601
- option.badge && /* @__PURE__ */ jsx(View, {
3745
+ Boolean(option.badge) && /* @__PURE__ */ jsx(View, {
3602
3746
  style: [styles.badge, {
3603
3747
  backgroundColor: optionPalette.badge.bg,
3604
3748
  borderColor: optionPalette.badge.border
@@ -3693,12 +3837,11 @@ const createPresentationStyles = (theme) => StyleSheet.create({
3693
3837
  });
3694
3838
  //#endregion
3695
3839
  //#region src/components/Select/Presentation/SelectBackdrop.tsx
3696
- const SelectBackdrop = ({ onClose, dismissOnBackdropPress }) => {
3840
+ const SelectBackdrop = ({ outsidePressProps }) => {
3841
+ const styles = useThemeStyles(createPresentationStyles);
3697
3842
  return /* @__PURE__ */ jsx(Pressable, {
3698
- style: useThemeStyles(createPresentationStyles).backdrop,
3699
- onPress: dismissOnBackdropPress ? onClose : void 0,
3700
- accessibilityRole: "button",
3701
- accessibilityLabel: "Dismiss select"
3843
+ ...outsidePressProps,
3844
+ style: styles.backdrop
3702
3845
  });
3703
3846
  };
3704
3847
  SelectBackdrop.displayName = "Select.Backdrop";
@@ -3715,7 +3858,7 @@ const SelectHandle = () => {
3715
3858
  SelectHandle.displayName = "Select.Handle";
3716
3859
  //#endregion
3717
3860
  //#region src/components/Select/Presentation/SelectModal.tsx
3718
- const SelectModal = ({ visible, onClose, dismissOnBackdropPress, zIndex, contentStyle, children }) => {
3861
+ const SelectModal = ({ visible, onClose, outsidePressProps, zIndex, contentStyle, children }) => {
3719
3862
  const styles = useThemeStyles(createPresentationStyles);
3720
3863
  return /* @__PURE__ */ jsx(Modal$1, {
3721
3864
  transparent: true,
@@ -3729,10 +3872,7 @@ const SelectModal = ({ visible, onClose, dismissOnBackdropPress, zIndex, content
3729
3872
  Platform.OS === "web" && { zIndex }
3730
3873
  ],
3731
3874
  testID: "select-content-root",
3732
- children: [/* @__PURE__ */ jsx(SelectBackdrop, {
3733
- onClose,
3734
- dismissOnBackdropPress
3735
- }), /* @__PURE__ */ jsx(View, {
3875
+ children: [/* @__PURE__ */ jsx(SelectBackdrop, { outsidePressProps }), /* @__PURE__ */ jsx(View, {
3736
3876
  style: [
3737
3877
  styles.content,
3738
3878
  styles.modalPresentation,
@@ -3747,7 +3887,7 @@ const SelectModal = ({ visible, onClose, dismissOnBackdropPress, zIndex, content
3747
3887
  SelectModal.displayName = "Select.Modal";
3748
3888
  //#endregion
3749
3889
  //#region src/components/Select/Presentation/SelectPopover.tsx
3750
- const SelectPopover = ({ visible, onClose, dismissOnBackdropPress, zIndex, position, onFloatingLayout, matchTriggerWidth, triggerWidth, contentStyle, children }) => {
3890
+ const SelectPopover = ({ visible, onClose, outsidePressProps, zIndex, position, onFloatingLayout, matchTriggerWidth, triggerWidth, contentStyle, children }) => {
3751
3891
  const styles = useThemeStyles(createPresentationStyles);
3752
3892
  return /* @__PURE__ */ jsx(Modal$1, {
3753
3893
  transparent: true,
@@ -3757,10 +3897,7 @@ const SelectPopover = ({ visible, onClose, dismissOnBackdropPress, zIndex, posit
3757
3897
  children: /* @__PURE__ */ jsxs(View, {
3758
3898
  style: [styles.modalRoot, Platform.OS === "web" && { zIndex }],
3759
3899
  testID: "select-content-root",
3760
- children: [/* @__PURE__ */ jsx(SelectBackdrop, {
3761
- onClose,
3762
- dismissOnBackdropPress
3763
- }), /* @__PURE__ */ jsx(View, {
3900
+ children: [/* @__PURE__ */ jsx(SelectBackdrop, { outsidePressProps }), /* @__PURE__ */ jsx(View, {
3764
3901
  onLayout: onFloatingLayout,
3765
3902
  style: [
3766
3903
  styles.content,
@@ -3782,7 +3919,7 @@ const SelectPopover = ({ visible, onClose, dismissOnBackdropPress, zIndex, posit
3782
3919
  SelectPopover.displayName = "Select.Popover";
3783
3920
  //#endregion
3784
3921
  //#region src/components/Select/Presentation/SelectSheet.tsx
3785
- const SelectSheet = ({ visible, onClose, dismissOnBackdropPress, zIndex, contentStyle, children }) => {
3922
+ const SelectSheet = ({ visible, onClose, outsidePressProps, zIndex, contentStyle, children }) => {
3786
3923
  const styles = useThemeStyles(createPresentationStyles);
3787
3924
  return /* @__PURE__ */ jsx(Modal$1, {
3788
3925
  transparent: true,
@@ -3796,10 +3933,7 @@ const SelectSheet = ({ visible, onClose, dismissOnBackdropPress, zIndex, content
3796
3933
  Platform.OS === "web" && { zIndex }
3797
3934
  ],
3798
3935
  testID: "select-content-root",
3799
- children: [/* @__PURE__ */ jsx(SelectBackdrop, {
3800
- onClose,
3801
- dismissOnBackdropPress
3802
- }), /* @__PURE__ */ jsxs(View, {
3936
+ children: [/* @__PURE__ */ jsx(SelectBackdrop, { outsidePressProps }), /* @__PURE__ */ jsxs(View, {
3803
3937
  style: [
3804
3938
  styles.content,
3805
3939
  styles.sheet,
@@ -3893,7 +4027,7 @@ const SelectSearchField = () => {
3893
4027
  accessibilityLabel: searchPlaceholder,
3894
4028
  style: [styles.searchInput, searchStyle]
3895
4029
  }),
3896
- query && /* @__PURE__ */ jsx(Pressable, {
4030
+ Boolean(query) && /* @__PURE__ */ jsx(Pressable, {
3897
4031
  accessibilityRole: "button",
3898
4032
  accessibilityLabel: "Clear search",
3899
4033
  hitSlop: 8,
@@ -3913,7 +4047,8 @@ SelectSearchField.displayName = "Select.SearchField";
3913
4047
  //#region src/components/Select/Separator/SelectSeparator.tsx
3914
4048
  const SelectSeparator = createSelectSlot("separator", "Select.Separator");
3915
4049
  const SelectSeparatorRow = () => {
3916
- return /* @__PURE__ */ jsx(View, { style: useThemeStyles(createGroupStyles).separator });
4050
+ const styles = useThemeStyles(createGroupStyles);
4051
+ return /* @__PURE__ */ jsx(View, { style: styles.separator });
3917
4052
  };
3918
4053
  SelectSeparatorRow.displayName = "Select.SeparatorRow";
3919
4054
  //#endregion
@@ -3924,13 +4059,13 @@ const SelectContentSurface = () => {
3924
4059
  const wasOpenRef = useRef(false);
3925
4060
  const [openCycle, setOpenCycle] = useState(0);
3926
4061
  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;
4062
+ 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
4063
  const initialScrollIndex = Boolean(context.virtual) && selectedRowIndex > 0 && query === "" ? selectedRowIndex : void 0;
3929
4064
  useEffect(() => {
3930
4065
  if (isOpen && !wasOpenRef.current) setOpenCycle((cycle) => cycle + 1);
3931
4066
  wasOpenRef.current = isOpen;
3932
4067
  }, [isOpen]);
3933
- const renderRow = ({ item }) => {
4068
+ const renderRow = ({ item, index }) => {
3934
4069
  if (item.type === "group") {
3935
4070
  if (item.selectable && context.multiple) {
3936
4071
  const enabledGroupValues = item.itemValues.filter((value) => context.optionsByValue.get(value));
@@ -3952,6 +4087,9 @@ const SelectContentSurface = () => {
3952
4087
  option: item.option,
3953
4088
  isSelected,
3954
4089
  isDisabled: Boolean(item.option.disabled || maxReached),
4090
+ itemIndex: index,
4091
+ selectedValues,
4092
+ multiple: context.multiple,
3955
4093
  optionStyle,
3956
4094
  onSelect: selectOption
3957
4095
  });
@@ -4014,7 +4152,7 @@ const SelectContentSurface = () => {
4014
4152
  if (resolvedPresentation === "sheet") return /* @__PURE__ */ jsx(SelectSheet, {
4015
4153
  visible: isOpen,
4016
4154
  onClose: closeContent,
4017
- dismissOnBackdropPress,
4155
+ outsidePressProps: getOutsidePressProps({ accessibilityLabel: "Dismiss select" }),
4018
4156
  zIndex,
4019
4157
  contentStyle,
4020
4158
  children: body
@@ -4022,7 +4160,7 @@ const SelectContentSurface = () => {
4022
4160
  if (resolvedPresentation === "popover") return /* @__PURE__ */ jsx(SelectPopover, {
4023
4161
  visible: isOpen,
4024
4162
  onClose: closeContent,
4025
- dismissOnBackdropPress,
4163
+ outsidePressProps: getOutsidePressProps({ accessibilityLabel: "Dismiss select" }),
4026
4164
  position,
4027
4165
  zIndex,
4028
4166
  onFloatingLayout,
@@ -4034,7 +4172,7 @@ const SelectContentSurface = () => {
4034
4172
  return /* @__PURE__ */ jsx(SelectModal, {
4035
4173
  visible: isOpen,
4036
4174
  onClose: closeContent,
4037
- dismissOnBackdropPress,
4175
+ outsidePressProps: getOutsidePressProps({ accessibilityLabel: "Dismiss select" }),
4038
4176
  zIndex,
4039
4177
  contentStyle,
4040
4178
  children: body
@@ -4057,95 +4195,6 @@ const SelectItemIcon = createSelectSlot("itemIcon", "Select.ItemIcon");
4057
4195
  //#region src/components/Select/Label/SelectLabel.tsx
4058
4196
  const SelectLabel = createSelectSlot("label", "Select.Label");
4059
4197
  //#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
4198
  //#region src/components/Select/Trigger/SelectTrigger.styles.ts
4150
4199
  const createTriggerStyles = (theme) => StyleSheet.create({
4151
4200
  container: {
@@ -4156,14 +4205,14 @@ const createTriggerStyles = (theme) => StyleSheet.create({
4156
4205
  trigger: {
4157
4206
  width: "100%",
4158
4207
  minWidth: 0,
4159
- minHeight: 44,
4208
+ minHeight: 38,
4160
4209
  alignItems: "center",
4161
4210
  flexDirection: "row",
4162
4211
  borderRadius: theme.tokens.radius.md,
4163
4212
  borderWidth: 1
4164
4213
  },
4165
4214
  sm: {
4166
- minHeight: 44,
4215
+ minHeight: 38,
4167
4216
  paddingHorizontal: theme.tokens.spacing[3],
4168
4217
  paddingVertical: theme.tokens.spacing[2]
4169
4218
  },
@@ -4177,9 +4226,9 @@ const createTriggerStyles = (theme) => StyleSheet.create({
4177
4226
  paddingHorizontal: theme.tokens.spacing[5],
4178
4227
  paddingVertical: theme.tokens.spacing[4]
4179
4228
  },
4180
- triggerWithClearSm: { paddingRight: theme.tokens.spacing[3] + 28 + theme.tokens.spacing[2] },
4181
- triggerWithClearMd: { paddingRight: theme.tokens.spacing[4] + 28 + theme.tokens.spacing[2] },
4182
- triggerWithClearLg: { paddingRight: theme.tokens.spacing[5] + 28 + theme.tokens.spacing[2] },
4229
+ triggerWithClearSm: { paddingRight: theme.tokens.spacing[3] + 24 + theme.tokens.spacing[2] },
4230
+ triggerWithClearMd: { paddingRight: theme.tokens.spacing[4] + 24 + theme.tokens.spacing[2] },
4231
+ triggerWithClearLg: { paddingRight: theme.tokens.spacing[5] + 24 + theme.tokens.spacing[2] },
4183
4232
  clearButtonContainer: {
4184
4233
  position: "absolute",
4185
4234
  top: 0,
@@ -4215,23 +4264,23 @@ const createTriggerStyles = (theme) => StyleSheet.create({
4215
4264
  lineHeight: theme.tokens.typography.lineHeight.md
4216
4265
  },
4217
4266
  startIcon: {
4218
- width: 18,
4219
- height: 18,
4267
+ width: 16,
4268
+ height: 16,
4220
4269
  marginRight: theme.tokens.spacing[2],
4221
4270
  alignItems: "center",
4222
4271
  justifyContent: "center"
4223
4272
  },
4224
4273
  endIcon: {
4225
- width: 18,
4226
- height: 18,
4274
+ width: 16,
4275
+ height: 16,
4227
4276
  marginLeft: theme.tokens.spacing[2],
4228
4277
  alignItems: "center",
4229
4278
  justifyContent: "center"
4230
4279
  },
4231
4280
  iconOpen: { transform: [{ rotate: "180deg" }] },
4232
4281
  clearButton: {
4233
- width: 28,
4234
- height: 28,
4282
+ width: 24,
4283
+ height: 24,
4235
4284
  alignItems: "center",
4236
4285
  justifyContent: "center",
4237
4286
  borderRadius: 999,
@@ -4245,12 +4294,18 @@ const nativePointerEventsNone$2 = Platform.OS === "web" ? void 0 : { pointerEven
4245
4294
  const nativePointerEventsBoxNone = Platform.OS === "web" ? void 0 : { pointerEvents: "box-none" };
4246
4295
  const webPointerEventsNone$2 = Platform.OS === "web" ? { pointerEvents: "none" } : void 0;
4247
4296
  const webPointerEventsBoxNone = Platform.OS === "web" ? { pointerEvents: "box-none" } : void 0;
4297
+ const compactTouchHitSlop = {
4298
+ top: 3,
4299
+ bottom: 3,
4300
+ left: 0,
4301
+ right: 0
4302
+ };
4248
4303
  function SelectTrigger({ displayText, isPlaceholder, isOpen, size = "md", color = "primary", variant = "outline", disabled = false, required = false, hasError = false, hasValue = false, loading = false, clearable = false, startIcon, endIcon, prefix, suffix, accessibilityLabel, accessibilityHint, nativeID, accessibilityLabelledBy, ariaDescribedBy, triggerStyle, textStyle, onPress, onClear }) {
4249
4304
  const { theme } = useTheme();
4250
4305
  const styles = useThemeStyles(createTriggerStyles);
4251
4306
  const palette = theme.components.select[color][variant];
4252
4307
  const triggerState = isOpen ? palette.focus : palette.default;
4253
- const resolvedIconSize = size === "lg" ? 18 : 16;
4308
+ const resolvedIconSize = 16;
4254
4309
  const showClearButton = clearable && hasValue && !disabled && !loading;
4255
4310
  const openRingStyle = Platform.OS === "web" ? { boxShadow: `0 0 0 3px ${theme.components.select[color].ring}` } : {
4256
4311
  shadowColor: theme.components.select[color].ring,
@@ -4319,6 +4374,7 @@ function SelectTrigger({ displayText, isPlaceholder, isOpen, size = "md", color
4319
4374
  selected: hasValue,
4320
4375
  busy: loading
4321
4376
  },
4377
+ hitSlop: size === "sm" ? compactTouchHitSlop : void 0,
4322
4378
  onPress,
4323
4379
  style: [
4324
4380
  styles.trigger,
@@ -4391,8 +4447,8 @@ function SelectTrigger({ displayText, isPlaceholder, isOpen, size = "md", color
4391
4447
  onPress: onClear,
4392
4448
  style: styles.clearButton,
4393
4449
  children: /* @__PURE__ */ jsx(Close, {
4394
- width: 14,
4395
- height: 14,
4450
+ width: 16,
4451
+ height: 16,
4396
4452
  color: theme.components.select.clearButton.hoverFg
4397
4453
  })
4398
4454
  })
@@ -4401,41 +4457,312 @@ function SelectTrigger({ displayText, isPlaceholder, isOpen, size = "md", color
4401
4457
  }
4402
4458
  SelectTrigger.displayName = "SelectTrigger";
4403
4459
  //#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;
4460
+ //#region src/hooks/behavior/select/useSelectCollection.ts
4461
+ const useSelectCollection = (children, optionsProp) => {
4462
+ const parsedChildren = useMemo(() => parseSelectChildren(children), [children]);
4463
+ const options = useMemo(() => [...optionsProp ?? [], ...parsedChildren.options], [optionsProp, parsedChildren.options]);
4464
+ return {
4465
+ options,
4466
+ rows: useMemo(() => {
4467
+ if (parsedChildren.rows.length > 0) return parsedChildren.rows;
4468
+ return options.map((option) => ({
4469
+ type: "item",
4470
+ key: `item-${option.value}`,
4471
+ option
4472
+ }));
4473
+ }, [options, parsedChildren.rows]),
4474
+ searchableFromChildren: parsedChildren.searchable,
4475
+ searchPlaceholderFromChildren: parsedChildren.searchPlaceholder,
4476
+ emptyFromChildren: parsedChildren.empty,
4477
+ loadingFromChildren: parsedChildren.loading
4478
+ };
4479
+ };
4480
+ //#endregion
4481
+ //#region src/hooks/behavior/select/useSelectSearch.ts
4482
+ const useSelectSearch = ({ rows, isOpen, searchable, searchableFromChildren, onSearch, filterOptions, filter = defaultSelectFilter }) => {
4483
+ const [query, setQuery] = useState("");
4484
+ const shouldSearch = searchable ?? searchableFromChildren ?? Boolean(onSearch);
4485
+ const shouldFilter = filterOptions ?? !onSearch;
4486
+ const filteredRows = useMemo(() => {
4487
+ if (!query || !shouldFilter) return rows;
4488
+ const visibleRows = [];
4489
+ let pendingGroup;
4490
+ rows.forEach((row) => {
4491
+ if (row.type === "group") {
4492
+ pendingGroup = row;
4493
+ return;
4494
+ }
4495
+ if (row.type === "separator") {
4496
+ if (visibleRows.length > 0 && visibleRows[visibleRows.length - 1]?.type !== "separator") visibleRows.push(row);
4497
+ return;
4498
+ }
4499
+ if (!filter(row.option, query)) return;
4500
+ if (pendingGroup) {
4501
+ visibleRows.push(pendingGroup);
4502
+ pendingGroup = void 0;
4503
+ }
4504
+ visibleRows.push(row);
4505
+ });
4506
+ return visibleRows.filter((row, index, collection) => {
4507
+ if (row.type !== "separator") return true;
4508
+ return index > 0 && index < collection.length - 1 && collection[index - 1]?.type !== "separator";
4509
+ });
4510
+ }, [
4511
+ filter,
4512
+ query,
4513
+ rows,
4514
+ shouldFilter
4515
+ ]);
4516
+ useEffect(() => {
4517
+ if (!isOpen) {
4518
+ setQuery("");
4519
+ return;
4520
+ }
4521
+ if (shouldSearch) onSearch?.(query);
4522
+ }, [
4523
+ isOpen,
4524
+ onSearch,
4525
+ query,
4526
+ shouldSearch
4527
+ ]);
4528
+ return {
4529
+ query,
4530
+ setQuery,
4531
+ shouldSearch,
4532
+ filteredRows
4533
+ };
4534
+ };
4535
+ //#endregion
4536
+ //#region src/components/Select/internal/resolveSelectAccessibility.ts
4537
+ const resolveSelectAccessibility = ({ accessibilityLabel, accessibilityHint, label, description, error, invalid, placeholder, selectedLabel, hasFieldContext, fieldDescribedBy }) => {
4538
+ const descriptionText = typeof description === "string" || typeof description === "number" ? String(description) : void 0;
4539
+ const errorText = typeof error === "string" || typeof error === "number" ? String(error) : void 0;
4540
+ return {
4541
+ resolvedLabel: accessibilityLabel ?? label ?? selectedLabel ?? placeholder ?? "Select",
4542
+ resolvedHint: accessibilityHint ?? (invalid && errorText ? errorText : descriptionText ? descriptionText : hasFieldContext && fieldDescribedBy ? "Opens a list of options" : void 0),
4543
+ announce: (message) => {
4544
+ AccessibilityInfo.announceForAccessibility?.(message);
4545
+ }
4546
+ };
4547
+ };
4548
+ //#endregion
4549
+ //#region src/components/Select/Root/useSelectRootActions.ts
4550
+ function useSelectRootActions({ multiple, maxSelected, closeOnSelect, selectedValues, optionsByValue, selectedFocusValueRef, selectValue, setSelectedValue, announce, closeAndFocusTrigger }) {
4551
+ return {
4552
+ clearValue: useCallback(() => {
4553
+ selectedFocusValueRef.current = void 0;
4554
+ selectValue("");
4555
+ announce("Selection cleared");
4556
+ }, [
4557
+ announce,
4558
+ selectValue,
4559
+ selectedFocusValueRef
4560
+ ]),
4561
+ selectOption: useCallback((option) => {
4562
+ if (option.disabled) return;
4563
+ const selectedBefore = selectedValues.includes(option.value);
4564
+ if (multiple && !selectedBefore && typeof maxSelected === "number" && selectedValues.length >= maxSelected) return;
4565
+ selectedFocusValueRef.current = option.value;
4566
+ selectValue(option.value);
4567
+ announce(`${option.label} selected`);
4568
+ }, [
4569
+ announce,
4570
+ maxSelected,
4571
+ multiple,
4572
+ selectValue,
4573
+ selectedFocusValueRef,
4574
+ selectedValues
4575
+ ]),
4576
+ selectGroup: useCallback((values) => {
4577
+ if (!multiple || values.length === 0) return;
4578
+ const enabledValues = values.filter((value) => optionsByValue.has(value));
4579
+ const selectedGroupValues = enabledValues.filter((value) => selectedValues.includes(value));
4580
+ const outsideSelectedCount = selectedValues.filter((value) => !enabledValues.includes(value)).length;
4581
+ const maxSelectableGroupCount = typeof maxSelected === "number" ? Math.max(0, Math.min(enabledValues.length, maxSelected - outsideSelectedCount)) : enabledValues.length;
4582
+ if (selectedGroupValues.length > 0 && selectedGroupValues.length >= maxSelectableGroupCount) {
4583
+ selectedFocusValueRef.current = void 0;
4584
+ setSelectedValue(selectedValues.filter((value) => !enabledValues.includes(value)));
4585
+ announce("Group selection cleared");
4586
+ return;
4587
+ }
4588
+ const nextValues = [...selectedValues];
4589
+ for (const value of enabledValues) {
4590
+ if (nextValues.includes(value)) continue;
4591
+ if (typeof maxSelected === "number" && nextValues.length >= maxSelected) break;
4592
+ nextValues.push(value);
4593
+ }
4594
+ setSelectedValue(nextValues);
4595
+ selectedFocusValueRef.current = nextValues.at(-1);
4596
+ announce("Group selected");
4597
+ if (closeOnSelect) closeAndFocusTrigger();
4598
+ }, [
4599
+ announce,
4600
+ closeAndFocusTrigger,
4601
+ closeOnSelect,
4602
+ maxSelected,
4603
+ multiple,
4604
+ optionsByValue,
4605
+ selectedFocusValueRef,
4606
+ selectedValues,
4607
+ setSelectedValue
4608
+ ])
4609
+ };
4610
+ }
4611
+ //#endregion
4612
+ //#region src/components/Select/Root/useSelectRootContextValue.ts
4613
+ 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 }) {
4614
+ return useMemo(() => ({
4615
+ color,
4616
+ variant,
4617
+ isOpen,
4618
+ loading,
4619
+ searchable,
4620
+ multiple,
4621
+ maxSelected,
4622
+ virtual,
4623
+ resolvedLabel,
4624
+ resolvedPresentation,
4625
+ zIndex,
4626
+ position,
4627
+ onFloatingLayout,
4628
+ matchTriggerWidth,
4629
+ triggerWidth,
4630
+ selectedValues,
4631
+ selectedOptions,
4632
+ optionsByValue,
4633
+ filteredRows,
4634
+ selectedRowIndex,
4635
+ itemHeight,
4636
+ query,
4637
+ searchPlaceholder,
4638
+ searchInputRef,
4639
+ empty,
4640
+ loadingContent,
4641
+ closeContent,
4642
+ getOutsidePressProps,
4643
+ selectOption,
4644
+ selectGroup,
4645
+ setQuery,
4646
+ renderOption,
4647
+ contentStyle,
4648
+ optionStyle,
4649
+ searchStyle
4650
+ }), [
4651
+ color,
4652
+ variant,
4653
+ isOpen,
4654
+ loading,
4655
+ searchable,
4656
+ multiple,
4657
+ maxSelected,
4658
+ virtual,
4659
+ resolvedLabel,
4660
+ resolvedPresentation,
4661
+ zIndex,
4662
+ position,
4663
+ onFloatingLayout,
4664
+ matchTriggerWidth,
4665
+ triggerWidth,
4666
+ selectedValues,
4667
+ selectedOptions,
4668
+ optionsByValue,
4669
+ filteredRows,
4670
+ selectedRowIndex,
4671
+ itemHeight,
4672
+ query,
4673
+ searchPlaceholder,
4674
+ searchInputRef,
4675
+ empty,
4676
+ loadingContent,
4677
+ closeContent,
4678
+ getOutsidePressProps,
4679
+ selectOption,
4680
+ selectGroup,
4681
+ setQuery,
4682
+ renderOption,
4683
+ contentStyle,
4684
+ optionStyle,
4685
+ searchStyle
4686
+ ]);
4687
+ }
4688
+ //#endregion
4689
+ //#region src/components/Select/Root/useSelectRootDisplayValue.ts
4690
+ function useSelectRootDisplayValue({ multiple, placeholder, renderValue, selectedOption, selectedOptions, selectedValues }) {
4691
+ return useMemo(() => {
4692
+ if (renderValue) return renderValue({
4693
+ option: selectedOption,
4694
+ options: selectedOptions,
4695
+ value: selectedValues[0] ?? "",
4696
+ values: selectedValues,
4697
+ placeholder,
4698
+ multiple
4699
+ });
4700
+ if (multiple && selectedOptions.length > 0) {
4701
+ const visibleLabels = selectedOptions.slice(0, 2).map((option) => option.label);
4702
+ return selectedOptions.length > 2 ? `${visibleLabels.join(", ")} +${selectedOptions.length - 2}` : visibleLabels.join(", ");
4703
+ }
4704
+ return selectedOption?.label ?? placeholder;
4705
+ }, [
4706
+ multiple,
4707
+ placeholder,
4708
+ renderValue,
4709
+ selectedOption,
4710
+ selectedOptions,
4711
+ selectedValues
4712
+ ]);
4713
+ }
4714
+ //#endregion
4715
+ //#region src/components/Select/Root/useSelectRootSelection.ts
4716
+ function useSelectRootSelection({ props, options, isDisabled }) {
4717
+ const controlledValue = props.multiple ? props.value : props.value === null ? "" : props.value;
4718
+ const controlledDefaultValue = props.multiple ? props.defaultValue : props.defaultValue === null ? "" : props.defaultValue;
4719
+ const selection = useSelect({
4720
+ value: controlledValue,
4721
+ defaultValue: controlledDefaultValue,
4722
+ onValueChange: (nextValue) => {
4723
+ if (props.multiple) {
4724
+ props.onValueChange?.(Array.isArray(nextValue) ? nextValue : nextValue ? [nextValue] : []);
4725
+ return;
4726
+ }
4727
+ props.onValueChange?.(Array.isArray(nextValue) ? nextValue[0] ?? null : nextValue === "" ? null : nextValue);
4728
+ },
4729
+ options,
4730
+ multiple: props.multiple,
4731
+ maxSelected: props.maxSelected,
4732
+ closeOnSelect: props.closeOnSelect,
4733
+ disabled: isDisabled,
4734
+ open: props.open,
4735
+ defaultOpen: props.defaultOpen,
4736
+ onOpenChange: props.onOpenChange
4737
+ });
4738
+ const optionsByValue = useMemo(() => new Map(options.filter((option) => !option.disabled).map((option) => [option.value, option])), [options]);
4739
+ return {
4740
+ ...selection,
4741
+ optionsByValue
4742
+ };
4743
+ }
4744
+ //#endregion
4745
+ //#region src/components/Select/Root/useSelectRootState.tsx
4746
+ function useSelectRootState(props) {
4747
+ 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;
4407
4748
  const field = useFormFieldContext();
4408
4749
  const overlayId = useId();
4409
4750
  const hasOwnField = Boolean(label || description || error);
4410
4751
  const [triggerWidth, setTriggerWidth] = useState();
4411
4752
  const triggerRef = useRef(null);
4412
- const { position, onFloatingLayout } = useNativeFloatingPosition(placement, offset);
4413
4753
  const searchInputRef = useRef(null);
4414
4754
  const selectedFocusValueRef = useRef(void 0);
4415
4755
  const resolvedPresentation = useOverlayPresentation(presentation);
4756
+ const { position, updatePosition, onFloatingLayout } = useNativeFloatingPosition(placement, offset);
4416
4757
  const { options, rows, searchableFromChildren, searchPlaceholderFromChildren, emptyFromChildren, loadingFromChildren } = useSelectCollection(children, optionsProp);
4417
4758
  const resolvedSize = size ?? field?.size ?? "md";
4418
4759
  const isInvalid = invalid || Boolean(error) || !hasOwnField && Boolean(field?.invalid);
4419
4760
  const isDisabled = disabled || !hasOwnField && Boolean(field?.disabled);
4420
4761
  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);
4427
- return;
4428
- }
4429
- props.onValueChange?.(nextValue === "" ? null : nextValue);
4430
- },
4762
+ const { setSelectedValue, selectedValues, selectedOption, selectedOptions, optionsByValue, isOpen, openDropdown, closeDropdown, selectValue } = useSelectRootSelection({
4763
+ props,
4431
4764
  options,
4432
- multiple: props.multiple,
4433
- maxSelected,
4434
- closeOnSelect,
4435
- disabled: isDisabled,
4436
- open,
4437
- defaultOpen,
4438
- onOpenChange
4765
+ isDisabled
4439
4766
  });
4440
4767
  const { restoreFocusAfterClose } = useOverlayFocusRestore({
4441
4768
  active: isOpen,
@@ -4445,13 +4772,6 @@ function SelectRoot(props) {
4445
4772
  closeDropdown();
4446
4773
  restoreFocusAfterClose();
4447
4774
  }, [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
4775
  const { query, setQuery, shouldSearch, filteredRows } = useSelectSearch({
4456
4776
  rows,
4457
4777
  isOpen,
@@ -4461,26 +4781,21 @@ function SelectRoot(props) {
4461
4781
  filterOptions,
4462
4782
  filter
4463
4783
  });
4784
+ const openAndPositionDropdown = useCallback(() => {
4785
+ updatePosition(triggerRef);
4786
+ openDropdown();
4787
+ }, [openDropdown, updatePosition]);
4464
4788
  const selectedFocusValue = selectedValues.includes(selectedFocusValueRef.current ?? "") ? selectedFocusValueRef.current : selectedValues[0];
4465
4789
  const selectedRowIndex = Math.max(0, filteredRows.findIndex((row) => row.type === "item" && row.option.value === selectedFocusValue));
4466
4790
  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)
4471
- });
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
- }, [
4791
+ const displayValue = useSelectRootDisplayValue({
4792
+ multiple: Boolean(props.multiple),
4478
4793
  placeholder,
4479
- props.multiple,
4480
4794
  renderValue,
4481
4795
  selectedOption,
4482
- selectedOptions
4483
- ]);
4796
+ selectedOptions,
4797
+ selectedValues
4798
+ });
4484
4799
  const { resolvedLabel, resolvedHint, announce } = resolveSelectAccessibility({
4485
4800
  accessibilityLabel,
4486
4801
  accessibilityHint,
@@ -4499,139 +4814,105 @@ function SelectRoot(props) {
4499
4814
  active: isOpen,
4500
4815
  closeOnOutsidePress: dismissOnBackdropPress,
4501
4816
  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
- }, [
4516
- announce,
4517
- maxSelected,
4518
- props.multiple,
4519
- selectValue,
4520
- selectedValues
4521
- ]);
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");
4532
- return;
4533
- }
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();
4544
- }, [
4545
- announce,
4546
- closeAndFocusTrigger,
4547
- closeOnSelect,
4817
+ });
4818
+ const { clearValue, selectOption, selectGroup } = useSelectRootActions({
4819
+ multiple: Boolean(props.multiple),
4548
4820
  maxSelected,
4549
- optionsByValue,
4550
- props.multiple,
4821
+ closeOnSelect,
4551
4822
  selectedValues,
4552
- setSelectedValue
4553
- ]);
4823
+ optionsByValue,
4824
+ selectedFocusValueRef,
4825
+ selectValue,
4826
+ setSelectedValue,
4827
+ announce,
4828
+ closeAndFocusTrigger
4829
+ });
4554
4830
  const resolvedSearchPlaceholder = searchPlaceholder ?? searchPlaceholderFromChildren ?? "Search...";
4555
4831
  const resolvedEmpty = empty ?? emptyFromChildren ?? "Nothing found";
4556
4832
  const resolvedLoadingContent = loadingFromChildren ?? loadingText;
4557
- const contextValue = useMemo(() => ({
4558
- color,
4559
- variant,
4560
- isOpen,
4561
- loading,
4562
- searchable: shouldSearch,
4563
- multiple: Boolean(props.multiple),
4564
- maxSelected,
4565
- virtual,
4566
- resolvedLabel,
4567
- resolvedPresentation,
4568
- zIndex: dismiss.zIndex,
4569
- position,
4570
- onFloatingLayout,
4571
- dismissOnBackdropPress,
4572
- matchTriggerWidth,
4573
- triggerWidth,
4574
- selectedValues,
4575
- selectedOptions,
4576
- optionsByValue,
4577
- filteredRows,
4578
- selectedRowIndex,
4579
- itemHeight,
4580
- query,
4581
- searchPlaceholder: resolvedSearchPlaceholder,
4582
- searchInputRef,
4583
- empty: resolvedEmpty,
4584
- loadingContent: resolvedLoadingContent,
4585
- closeContent: dismiss.requestClose,
4586
- selectOption,
4587
- selectGroup,
4588
- setQuery,
4589
- renderOption,
4590
- contentStyle,
4591
- optionStyle,
4592
- searchStyle
4593
- }), [
4594
- color,
4595
- variant,
4833
+ return {
4834
+ contextValue: useSelectRootContextValue({
4835
+ color,
4836
+ variant,
4837
+ isOpen,
4838
+ loading,
4839
+ searchable: shouldSearch,
4840
+ multiple: Boolean(props.multiple),
4841
+ maxSelected,
4842
+ virtual,
4843
+ resolvedLabel,
4844
+ resolvedPresentation,
4845
+ zIndex: dismiss.zIndex,
4846
+ position,
4847
+ onFloatingLayout,
4848
+ matchTriggerWidth,
4849
+ triggerWidth,
4850
+ selectedValues,
4851
+ selectedOptions,
4852
+ optionsByValue,
4853
+ filteredRows,
4854
+ selectedRowIndex,
4855
+ itemHeight,
4856
+ query,
4857
+ searchPlaceholder: resolvedSearchPlaceholder,
4858
+ searchInputRef,
4859
+ empty: resolvedEmpty,
4860
+ loadingContent: resolvedLoadingContent,
4861
+ closeContent: dismiss.requestClose,
4862
+ getOutsidePressProps: dismiss.getOutsidePressProps,
4863
+ selectOption,
4864
+ selectGroup,
4865
+ setQuery,
4866
+ renderOption,
4867
+ contentStyle,
4868
+ optionStyle,
4869
+ searchStyle
4870
+ }),
4871
+ displayValue,
4872
+ field,
4873
+ hasOwnField,
4874
+ hasValue,
4875
+ isDisabled,
4876
+ isInvalid,
4596
4877
  isOpen,
4597
- loading,
4598
- shouldSearch,
4599
- props.multiple,
4600
- maxSelected,
4601
- virtual,
4878
+ isRequired,
4879
+ clearValue,
4880
+ openDropdown: openAndPositionDropdown,
4881
+ resolvedHint,
4602
4882
  resolvedLabel,
4603
- resolvedPresentation,
4604
- dismiss.zIndex,
4605
- position,
4606
- onFloatingLayout,
4607
- dismissOnBackdropPress,
4608
- matchTriggerWidth,
4609
- triggerWidth,
4610
- selectedValues,
4611
- selectedOptions,
4612
- optionsByValue,
4613
- filteredRows,
4614
- selectedRowIndex,
4615
- itemHeight,
4616
- query,
4617
- resolvedSearchPlaceholder,
4618
- searchInputRef,
4619
- resolvedEmpty,
4620
- resolvedLoadingContent,
4621
- dismiss.requestClose,
4622
- selectOption,
4623
- selectGroup,
4624
- setQuery,
4625
- renderOption,
4626
- contentStyle,
4627
- optionStyle,
4628
- searchStyle
4629
- ]);
4883
+ resolvedSize,
4884
+ setTriggerWidth,
4885
+ triggerRef,
4886
+ controlProps: {
4887
+ clearable,
4888
+ color,
4889
+ endIcon,
4890
+ loading,
4891
+ prefix,
4892
+ startIcon,
4893
+ suffix,
4894
+ testID,
4895
+ textStyle,
4896
+ triggerStyle,
4897
+ variant
4898
+ },
4899
+ formFieldProps: {
4900
+ description,
4901
+ error,
4902
+ label,
4903
+ style
4904
+ }
4905
+ };
4906
+ }
4907
+ //#endregion
4908
+ //#region src/components/Select/Root/SelectRoot.tsx
4909
+ function SelectRoot(props) {
4910
+ const { contextValue, controlProps, displayValue, field, formFieldProps, hasOwnField, hasValue, isDisabled, isInvalid, isOpen, isRequired, resolvedHint, resolvedLabel, resolvedSize, triggerRef, clearValue, openDropdown, setTriggerWidth } = useSelectRootState(props);
4630
4911
  const control = /* @__PURE__ */ jsx(SelectContext.Provider, {
4631
4912
  value: contextValue,
4632
4913
  children: /* @__PURE__ */ jsxs(View, {
4633
4914
  ref: triggerRef,
4634
- testID,
4915
+ testID: controlProps.testID,
4635
4916
  onLayout: (event) => setTriggerWidth(event.nativeEvent.layout.width),
4636
4917
  children: [/* @__PURE__ */ jsx(SelectTrigger, {
4637
4918
  displayText: displayValue,
@@ -4639,24 +4920,24 @@ function SelectRoot(props) {
4639
4920
  isOpen,
4640
4921
  hasValue,
4641
4922
  size: resolvedSize,
4642
- color,
4643
- variant,
4923
+ color: controlProps.color,
4924
+ variant: controlProps.variant,
4644
4925
  disabled: isDisabled,
4645
4926
  required: isRequired,
4646
4927
  hasError: isInvalid,
4647
- loading,
4648
- clearable,
4649
- startIcon,
4650
- endIcon,
4651
- prefix,
4652
- suffix,
4928
+ loading: controlProps.loading,
4929
+ clearable: controlProps.clearable,
4930
+ startIcon: controlProps.startIcon,
4931
+ endIcon: controlProps.endIcon,
4932
+ prefix: controlProps.prefix,
4933
+ suffix: controlProps.suffix,
4653
4934
  nativeID: !hasOwnField ? field?.controlId : void 0,
4654
4935
  accessibilityLabel: resolvedLabel,
4655
4936
  accessibilityHint: resolvedHint,
4656
4937
  accessibilityLabelledBy: !hasOwnField ? field?.labelId : void 0,
4657
4938
  ariaDescribedBy: !hasOwnField ? field?.ariaDescribedBy : void 0,
4658
- triggerStyle,
4659
- textStyle,
4939
+ triggerStyle: controlProps.triggerStyle,
4940
+ textStyle: controlProps.textStyle,
4660
4941
  onPress: openDropdown,
4661
4942
  onClear: clearValue
4662
4943
  }), /* @__PURE__ */ jsx(SelectContentSurface, {})]
@@ -4664,14 +4945,14 @@ function SelectRoot(props) {
4664
4945
  });
4665
4946
  if (!hasOwnField && field) return control;
4666
4947
  return /* @__PURE__ */ jsx(FormField, {
4667
- label,
4668
- description,
4669
- error,
4948
+ label: formFieldProps.label,
4949
+ description: formFieldProps.description,
4950
+ error: formFieldProps.error,
4670
4951
  required: isRequired,
4671
4952
  disabled: isDisabled,
4672
4953
  invalid: isInvalid,
4673
4954
  size: resolvedSize,
4674
- style,
4955
+ style: formFieldProps.style,
4675
4956
  children: control
4676
4957
  });
4677
4958
  }
@@ -4982,7 +5263,17 @@ const createStyles$6 = (theme) => StyleSheet.create({
4982
5263
  gap: theme.tokens.spacing[5],
4983
5264
  marginBottom: theme.tokens.spacing[6]
4984
5265
  },
5266
+ listLine: {
5267
+ borderBottomWidth: 1,
5268
+ borderColor: theme.components.tabs.list.border
5269
+ },
5270
+ listLineVertical: {
5271
+ borderRightWidth: 1,
5272
+ borderBottomWidth: 0
5273
+ },
4985
5274
  listSegmented: {
5275
+ alignSelf: "flex-start",
5276
+ width: "auto",
4986
5277
  gap: theme.tokens.spacing[1],
4987
5278
  padding: 2,
4988
5279
  backgroundColor: theme.components.tabs.list.segmentedBg,
@@ -5010,8 +5301,10 @@ const TabsList = ({ children, scrollable: scrollableProp, style }) => {
5010
5301
  const scrollable = scrollableProp ?? false;
5011
5302
  const listStyle = [
5012
5303
  styles.list,
5304
+ variant === "line" && styles.listLine,
5013
5305
  variant === "segmented" && styles.listSegmented,
5014
5306
  orientation === "vertical" && styles.listVertical,
5307
+ variant === "line" && orientation === "vertical" && styles.listLineVertical,
5015
5308
  style
5016
5309
  ];
5017
5310
  if (scrollable && orientation === "horizontal") return /* @__PURE__ */ jsx(ScrollView, {
@@ -5033,7 +5326,7 @@ TabsList.displayName = "TabsList";
5033
5326
  const fontWeight$1 = (value) => value;
5034
5327
  const createStyles$5 = (theme) => StyleSheet.create({
5035
5328
  tab: {
5036
- minHeight: 44,
5329
+ minHeight: 38,
5037
5330
  minWidth: 44,
5038
5331
  alignItems: "center",
5039
5332
  flexDirection: "row",
@@ -5050,7 +5343,7 @@ const createStyles$5 = (theme) => StyleSheet.create({
5050
5343
  paddingHorizontal: 0
5051
5344
  },
5052
5345
  tabSm: {
5053
- minHeight: 36,
5346
+ minHeight: 32,
5054
5347
  paddingHorizontal: theme.tokens.spacing[3],
5055
5348
  paddingVertical: 6
5056
5349
  },
@@ -5060,7 +5353,7 @@ const createStyles$5 = (theme) => StyleSheet.create({
5060
5353
  paddingHorizontal: 0
5061
5354
  },
5062
5355
  tabLg: {
5063
- minHeight: 52,
5356
+ minHeight: 51,
5064
5357
  paddingHorizontal: theme.tokens.spacing[5],
5065
5358
  paddingVertical: theme.tokens.spacing[3]
5066
5359
  },
@@ -5070,7 +5363,8 @@ const createStyles$5 = (theme) => StyleSheet.create({
5070
5363
  paddingHorizontal: 0
5071
5364
  },
5072
5365
  tabSegmented: {
5073
- flex: 1,
5366
+ flexGrow: 0,
5367
+ flexShrink: 0,
5074
5368
  minWidth: 0,
5075
5369
  minHeight: 32,
5076
5370
  paddingVertical: 5,
@@ -5115,7 +5409,7 @@ const createStyles$5 = (theme) => StyleSheet.create({
5115
5409
  tabText: {
5116
5410
  flexShrink: 0,
5117
5411
  textAlign: "center",
5118
- lineHeight: theme.tokens.typography.lineHeight.md,
5412
+ lineHeight: theme.tokens.typography.size.md * 1.25,
5119
5413
  color: theme.components.tabs.primary.trigger.default.fg,
5120
5414
  fontFamily: theme.tokens.typography.family.regular,
5121
5415
  fontSize: theme.tokens.typography.size.md,
@@ -5123,11 +5417,11 @@ const createStyles$5 = (theme) => StyleSheet.create({
5123
5417
  },
5124
5418
  tabTextSm: {
5125
5419
  fontSize: theme.tokens.typography.size.sm,
5126
- lineHeight: theme.tokens.typography.lineHeight.sm
5420
+ lineHeight: theme.tokens.typography.size.sm * 1.25
5127
5421
  },
5128
5422
  tabTextLg: {
5129
5423
  fontSize: theme.tokens.typography.size.lg,
5130
- lineHeight: theme.tokens.typography.lineHeight.lg
5424
+ lineHeight: theme.tokens.typography.size.lg * 1.25
5131
5425
  },
5132
5426
  tabTextHover: { color: theme.components.tabs.primary.trigger.hover.fg },
5133
5427
  tabTextPressed: { color: theme.components.tabs.primary.trigger.active.fg },
@@ -5205,8 +5499,9 @@ TabsBadge.displayName = "Tabs.Badge";
5205
5499
  //#endregion
5206
5500
  //#region src/components/Tabs/Trigger/TabsIcon.tsx
5207
5501
  const TabsIcon = ({ children, style }) => {
5502
+ const styles = useThemeStyles(createStyles$5);
5208
5503
  return /* @__PURE__ */ jsx(View, {
5209
- style: [useThemeStyles(createStyles$5).tabIcon, style],
5504
+ style: [styles.tabIcon, style],
5210
5505
  children
5211
5506
  });
5212
5507
  };
@@ -5403,38 +5698,39 @@ const TabsRoot = ({ children, value: controlledValue, defaultValue, onValueChang
5403
5698
  value,
5404
5699
  version
5405
5700
  ]);
5701
+ const contextValue = useMemo(() => ({
5702
+ value,
5703
+ setValue,
5704
+ orientation,
5705
+ activationMode,
5706
+ variant,
5707
+ color,
5708
+ size,
5709
+ keepMounted,
5710
+ lazyMount,
5711
+ disabled,
5712
+ registerTrigger,
5713
+ indicatorVersion,
5714
+ getTriggerLayout,
5715
+ registerTriggerLayout
5716
+ }), [
5717
+ activationMode,
5718
+ color,
5719
+ disabled,
5720
+ keepMounted,
5721
+ lazyMount,
5722
+ orientation,
5723
+ getTriggerLayout,
5724
+ indicatorVersion,
5725
+ registerTriggerLayout,
5726
+ registerTrigger,
5727
+ setValue,
5728
+ size,
5729
+ value,
5730
+ variant
5731
+ ]);
5406
5732
  return /* @__PURE__ */ jsx(TabsProvider, {
5407
- value: useMemo(() => ({
5408
- value,
5409
- setValue,
5410
- orientation,
5411
- activationMode,
5412
- variant,
5413
- color,
5414
- size,
5415
- keepMounted,
5416
- lazyMount,
5417
- disabled,
5418
- registerTrigger,
5419
- indicatorVersion,
5420
- getTriggerLayout,
5421
- registerTriggerLayout
5422
- }), [
5423
- activationMode,
5424
- color,
5425
- disabled,
5426
- keepMounted,
5427
- lazyMount,
5428
- orientation,
5429
- getTriggerLayout,
5430
- indicatorVersion,
5431
- registerTriggerLayout,
5432
- registerTrigger,
5433
- setValue,
5434
- size,
5435
- value,
5436
- variant
5437
- ]),
5733
+ value: contextValue,
5438
5734
  children: /* @__PURE__ */ jsx(View, {
5439
5735
  style: [
5440
5736
  styles.root,
@@ -5488,9 +5784,10 @@ function TooltipArrow() {
5488
5784
  marginLeft: -size / 2
5489
5785
  };
5490
5786
  return /* @__PURE__ */ jsx(View, {
5491
- pointerEvents: "none",
5787
+ pointerEvents: Platform.OS === "web" ? void 0 : "none",
5492
5788
  style: {
5493
5789
  position: "absolute",
5790
+ ...Platform.OS === "web" ? { pointerEvents: "none" } : {},
5494
5791
  width: size,
5495
5792
  height: size,
5496
5793
  backgroundColor: theme.components.tooltip.arrow.bg,
@@ -5546,9 +5843,10 @@ const TooltipContent = ({ children, forceMount = false, withArrow = false, style
5546
5843
  if (!forceMount && !visible) return null;
5547
5844
  const bubble = /* @__PURE__ */ jsxs(View, {
5548
5845
  nativeID: tooltip.contentId,
5549
- pointerEvents: "none",
5846
+ pointerEvents: Platform.OS === "web" ? void 0 : "none",
5550
5847
  style: [
5551
5848
  styles.bubble,
5849
+ Platform.OS === "web" && { pointerEvents: "none" },
5552
5850
  {
5553
5851
  top: tooltip.position.top,
5554
5852
  left: tooltip.position.left,
@@ -5570,8 +5868,8 @@ const TooltipContent = ({ children, forceMount = false, withArrow = false, style
5570
5868
  animationType: "fade",
5571
5869
  onRequestClose: tooltip.requestClose,
5572
5870
  children: /* @__PURE__ */ jsx(Pressable, {
5871
+ ...tooltip.getOutsidePressProps({ accessibilityLabel: "Close tooltip" }),
5573
5872
  style: styles.overlay,
5574
- onPress: tooltip.requestOutsideClose,
5575
5873
  children: bubble
5576
5874
  })
5577
5875
  });
@@ -5655,37 +5953,38 @@ const TooltipRoot = ({ children, open: openProp, defaultOpen = false, onOpenChan
5655
5953
  useEffect(() => {
5656
5954
  return clearCloseTimer;
5657
5955
  }, [clearCloseTimer]);
5956
+ const contextValue = useMemo(() => ({
5957
+ contentId,
5958
+ open,
5959
+ disabled,
5960
+ placement: resolvedPlacement,
5961
+ position,
5962
+ arrowPosition,
5963
+ triggerRef,
5964
+ setOpen,
5965
+ show,
5966
+ hide,
5967
+ zIndex: dismiss.zIndex,
5968
+ requestClose: dismiss.requestClose,
5969
+ getOutsidePressProps: dismiss.getOutsidePressProps,
5970
+ onFloatingLayout
5971
+ }), [
5972
+ arrowPosition,
5973
+ contentId,
5974
+ disabled,
5975
+ dismiss.zIndex,
5976
+ dismiss.getOutsidePressProps,
5977
+ dismiss.requestClose,
5978
+ hide,
5979
+ open,
5980
+ resolvedPlacement,
5981
+ position,
5982
+ setOpen,
5983
+ show,
5984
+ onFloatingLayout
5985
+ ]);
5658
5986
  return /* @__PURE__ */ jsx(TooltipProvider, {
5659
- value: useMemo(() => ({
5660
- contentId,
5661
- open,
5662
- disabled,
5663
- placement: resolvedPlacement,
5664
- position,
5665
- arrowPosition,
5666
- triggerRef,
5667
- setOpen,
5668
- show,
5669
- hide,
5670
- zIndex: dismiss.zIndex,
5671
- requestClose: dismiss.requestClose,
5672
- requestOutsideClose: dismiss.requestOutsideClose,
5673
- onFloatingLayout
5674
- }), [
5675
- arrowPosition,
5676
- contentId,
5677
- disabled,
5678
- dismiss.zIndex,
5679
- dismiss.requestClose,
5680
- dismiss.requestOutsideClose,
5681
- hide,
5682
- open,
5683
- resolvedPlacement,
5684
- position,
5685
- setOpen,
5686
- show,
5687
- onFloatingLayout
5688
- ]),
5987
+ value: contextValue,
5689
5988
  children: /* @__PURE__ */ jsx(View, {
5690
5989
  style,
5691
5990
  children
@@ -5695,9 +5994,65 @@ const TooltipRoot = ({ children, open: openProp, defaultOpen = false, onOpenChan
5695
5994
  TooltipRoot.displayName = "Tooltip.Root";
5696
5995
  //#endregion
5697
5996
  //#region src/components/Tooltip/Trigger/TooltipTrigger.tsx
5698
- const TooltipTrigger = ({ children, disabled, onLongPress, style, ...props }) => {
5997
+ function flattenWebStyle(style) {
5998
+ if (!style) return;
5999
+ if (Array.isArray(style)) return Object.assign({}, ...style.map(flattenWebStyle).filter(Boolean));
6000
+ return typeof style === "object" ? style : void 0;
6001
+ }
6002
+ const TooltipTrigger = ({ children, disabled, onBlur, onFocus, onHoverIn, onHoverOut, onLongPress, onPress, onPressIn, style, ...props }) => {
5699
6003
  const tooltip = useTooltipContext();
5700
6004
  const isDisabled = tooltip.disabled || disabled;
6005
+ if (Platform.OS === "web") {
6006
+ const webProps = props;
6007
+ const showFromWebEvent = (event) => {
6008
+ if (event.defaultPrevented || isDisabled) return;
6009
+ tooltip.show();
6010
+ };
6011
+ return createElement("button", {
6012
+ "aria-label": webProps.accessibilityLabel,
6013
+ "data-testid": webProps.testID,
6014
+ disabled: isDisabled || void 0,
6015
+ type: "button",
6016
+ ref: (node) => {
6017
+ if (node) Object.assign(node, { measureInWindow(callback) {
6018
+ const rect = node.getBoundingClientRect();
6019
+ callback(rect.left, rect.top, rect.width, rect.height);
6020
+ } });
6021
+ tooltip.triggerRef.current = node;
6022
+ },
6023
+ onBlur: (event) => {
6024
+ onBlur?.(event);
6025
+ tooltip.hide();
6026
+ },
6027
+ onClick: (event) => {
6028
+ onPress?.(event);
6029
+ showFromWebEvent(event);
6030
+ },
6031
+ onClickCapture: showFromWebEvent,
6032
+ onPointerDown: showFromWebEvent,
6033
+ onPointerDownCapture: showFromWebEvent,
6034
+ onMouseDown: showFromWebEvent,
6035
+ onMouseDownCapture: showFromWebEvent,
6036
+ onFocusCapture: showFromWebEvent,
6037
+ onFocus: (event) => {
6038
+ onFocus?.(event);
6039
+ tooltip.show();
6040
+ },
6041
+ onMouseEnter: (event) => {
6042
+ onHoverIn?.(event);
6043
+ tooltip.show();
6044
+ },
6045
+ onMouseLeave: (event) => {
6046
+ onHoverOut?.(event);
6047
+ },
6048
+ style: {
6049
+ all: "unset",
6050
+ cursor: isDisabled ? "default" : "pointer",
6051
+ display: "inline-flex",
6052
+ ...flattenWebStyle(style)
6053
+ }
6054
+ }, children);
6055
+ }
5701
6056
  return /* @__PURE__ */ jsx(Pressable, {
5702
6057
  ...props,
5703
6058
  ref: tooltip.triggerRef,
@@ -5706,11 +6061,36 @@ const TooltipTrigger = ({ children, disabled, onLongPress, style, ...props }) =>
5706
6061
  disabled: isDisabled || props.accessibilityState?.disabled
5707
6062
  },
5708
6063
  disabled: isDisabled,
6064
+ onBlur: (event) => {
6065
+ onBlur?.(event);
6066
+ if (Platform.OS === "web") tooltip.hide();
6067
+ },
6068
+ onFocus: (event) => {
6069
+ onFocus?.(event);
6070
+ if (Platform.OS === "web") tooltip.show();
6071
+ },
6072
+ onHoverIn: (event) => {
6073
+ onHoverIn?.(event);
6074
+ if (Platform.OS === "web") tooltip.show();
6075
+ },
6076
+ onHoverOut: (event) => {
6077
+ onHoverOut?.(event);
6078
+ if (Platform.OS === "web") tooltip.hide();
6079
+ },
5709
6080
  onLongPress: (event) => {
5710
6081
  onLongPress?.(event);
5711
6082
  if (event.defaultPrevented) return;
5712
6083
  tooltip.show();
5713
6084
  },
6085
+ onPress: (event) => {
6086
+ onPress?.(event);
6087
+ if (event.defaultPrevented || Platform.OS !== "web") return;
6088
+ tooltip.show();
6089
+ },
6090
+ onPressIn: (event) => {
6091
+ onPressIn?.(event);
6092
+ if (Platform.OS === "web") tooltip.show();
6093
+ },
5714
6094
  style,
5715
6095
  children
5716
6096
  });
@@ -5724,11 +6104,6 @@ const Tooltip = Object.assign(TooltipRoot, {
5724
6104
  });
5725
6105
  Tooltip.displayName = "Tooltip";
5726
6106
  //#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
6107
  //#region src/primitives/Button/Button.styles.ts
5733
6108
  const fontWeight = (value) => value;
5734
6109
  const createStyles$2 = (theme) => StyleSheet.create({
@@ -5739,25 +6114,23 @@ const createStyles$2 = (theme) => StyleSheet.create({
5739
6114
  gap: 8,
5740
6115
  borderWidth: 1
5741
6116
  },
5742
- fullWidth: {
5743
- alignSelf: "stretch",
5744
- width: "100%"
5745
- },
6117
+ fullWidth: { width: "100%" },
5746
6118
  text: {
5747
6119
  fontFamily: theme.tokens.typography.family.regular,
5748
6120
  fontWeight: fontWeight(theme.tokens.typography.weight.regular),
5749
- lineHeight: theme.tokens.typography.lineHeight.md,
6121
+ textAlign: "center",
5750
6122
  color: theme.components.button.primary.solid.default.fg
5751
6123
  },
5752
- labelSlot: { position: "relative" },
6124
+ labelSlot: {
6125
+ position: "relative",
6126
+ alignItems: "center",
6127
+ justifyContent: "center"
6128
+ },
5753
6129
  labelMeasure: {
5754
6130
  position: "absolute",
5755
6131
  opacity: 0
5756
6132
  },
5757
- spinner: {
5758
- fontSize: 12,
5759
- lineHeight: theme.tokens.typography.lineHeight.md
5760
- },
6133
+ spinner: { fontSize: 12 },
5761
6134
  badge: {
5762
6135
  minWidth: 18,
5763
6136
  height: 18,
@@ -5794,33 +6167,16 @@ const createStyles$2 = (theme) => StyleSheet.create({
5794
6167
  });
5795
6168
  //#endregion
5796
6169
  //#region src/primitives/Button/Button.tsx
5797
- const sizeMap = {
5798
- sm: {
5799
- px: 12,
5800
- py: 8,
5801
- height: 36,
5802
- fontSize: 12,
5803
- iconSize: 16
5804
- },
5805
- md: {
5806
- px: 16,
5807
- py: 12,
5808
- height: 44,
5809
- fontSize: 14,
5810
- iconSize: 20
5811
- },
5812
- lg: {
5813
- px: 20,
5814
- py: 16,
5815
- height: 52,
5816
- fontSize: 16,
5817
- iconSize: 24
5818
- }
6170
+ const buttonSizeMap = {
6171
+ sm: { px: 16 },
6172
+ md: { px: 24 },
6173
+ lg: { px: 32 }
5819
6174
  };
5820
6175
  function Button({ children, color = "primary", appearance = "solid", shape = "pill", disabled = false, loading = false, loadingText, onPress, onFocus, onBlur, onHoverIn, onHoverOut, size = "md", iconStart, iconEnd, badge, shortcut, fullWidth = false, iconOnly: iconOnlyProp = false, style, textStyle, accessibilityLabel, iconSize, testID, ...props }) {
5821
6176
  const { theme } = useTheme();
5822
6177
  const styles = useThemeStyles(createStyles$2);
5823
- const config = sizeMap[size];
6178
+ const controlSize = controlSizes[size];
6179
+ const buttonSize = buttonSizeMap[size];
5824
6180
  const radius = shape === "square" ? theme.tokens.radius.sm : shape === "rounded" ? theme.tokens.radius.md : theme.tokens.radius.full;
5825
6181
  const appearanceTheme = theme.components.button[color][appearance];
5826
6182
  const [isHovered, setIsHovered] = useState(false);
@@ -5831,7 +6187,7 @@ function Button({ children, color = "primary", appearance = "solid", shape = "pi
5831
6187
  const content = loading && loadingText ? loadingText : children;
5832
6188
  const measureLabel = loadingText && children && !iconOnly ? loading ? children : loadingText : void 0;
5833
6189
  devWarning(!iconOnly || Boolean(accessibilityLabel), "Button: icon-only buttons must provide an accessibilityLabel.");
5834
- const resolvedIconSize = iconSize ?? config.iconSize;
6190
+ const resolvedIconSize = iconSize ?? controlSize.iconSize;
5835
6191
  const handleFocus = (event) => {
5836
6192
  setIsFocused(true);
5837
6193
  onFocus?.(event);
@@ -5880,10 +6236,11 @@ function Button({ children, color = "primary", appearance = "solid", shape = "pi
5880
6236
  backgroundColor: interactionTheme.bg,
5881
6237
  borderColor: interactionTheme.border,
5882
6238
  borderRadius: radius,
5883
- paddingHorizontal: iconOnly ? 0 : config.px,
5884
- paddingVertical: iconOnly ? 0 : config.py,
5885
- width: iconOnly ? config.height : void 0,
5886
- height: iconOnly ? config.height : void 0
6239
+ minHeight: controlSize.height,
6240
+ paddingHorizontal: iconOnly ? 0 : buttonSize.px,
6241
+ paddingVertical: 0,
6242
+ width: iconOnly ? controlSize.height : void 0,
6243
+ height: iconOnly ? controlSize.height : void 0
5887
6244
  },
5888
6245
  fullWidth && !iconOnly && styles.fullWidth,
5889
6246
  isDisabled && styles.disabled,
@@ -5896,19 +6253,19 @@ function Button({ children, color = "primary", appearance = "solid", shape = "pi
5896
6253
  const contentColor = getInteractionTheme(pressed).fg;
5897
6254
  return /* @__PURE__ */ jsxs(Fragment, { children: [
5898
6255
  loading && /* @__PURE__ */ jsx(ActivityIndicator, {
5899
- size: "small",
6256
+ size: controlSize.iconSize,
5900
6257
  color: contentColor
5901
6258
  }),
5902
6259
  !loading && iconStart && renderIcon(iconStart, contentColor),
5903
6260
  content && !iconOnly && /* @__PURE__ */ jsxs(View, {
5904
- style: styles.labelSlot,
6261
+ style: [styles.labelSlot, labelWidth > 0 && { minWidth: labelWidth }],
5905
6262
  children: [/* @__PURE__ */ jsx(Text, {
5906
6263
  onLayout: handleLabelLayout,
5907
6264
  style: [
5908
6265
  styles.text,
5909
- labelWidth > 0 && { minWidth: labelWidth },
5910
6266
  {
5911
- fontSize: config.fontSize,
6267
+ fontSize: controlSize.fontSize,
6268
+ lineHeight: controlSize.lineHeight,
5912
6269
  color: contentColor
5913
6270
  },
5914
6271
  textStyle
@@ -5921,7 +6278,10 @@ function Button({ children, color = "primary", appearance = "solid", shape = "pi
5921
6278
  style: [
5922
6279
  styles.text,
5923
6280
  styles.labelMeasure,
5924
- { fontSize: config.fontSize },
6281
+ {
6282
+ fontSize: controlSize.fontSize,
6283
+ lineHeight: controlSize.lineHeight
6284
+ },
5925
6285
  textStyle
5926
6286
  ],
5927
6287
  children: measureLabel
@@ -6021,7 +6381,7 @@ const createStyles$1 = (theme) => StyleSheet.create({
6021
6381
  labelCheckedPressed: { color: theme.components.checkbox.primary.pressed.labelFg },
6022
6382
  labelSm: {
6023
6383
  fontSize: theme.tokens.typography.size.sm,
6024
- lineHeight: theme.tokens.typography.lineHeight.sm
6384
+ lineHeight: theme.tokens.typography.lineHeight.md
6025
6385
  },
6026
6386
  labelMd: {
6027
6387
  fontSize: theme.tokens.typography.size.md,
@@ -6029,7 +6389,7 @@ const createStyles$1 = (theme) => StyleSheet.create({
6029
6389
  },
6030
6390
  labelLg: {
6031
6391
  fontSize: theme.tokens.typography.size.lg,
6032
- lineHeight: theme.tokens.typography.lineHeight.lg
6392
+ lineHeight: theme.tokens.typography.lineHeight.md
6033
6393
  },
6034
6394
  labelDisabled: { color: theme.components.checkbox.disabled.fg },
6035
6395
  requiredMark: { color: theme.components.checkbox.error.fg },
@@ -6141,7 +6501,7 @@ const Checkbox = forwardRef(({ label, description, icon, indeterminateIcon, chec
6141
6501
  size: iconSizeBySize[size],
6142
6502
  color: checkColor
6143
6503
  }))
6144
- }), label && /* @__PURE__ */ jsxs(Text, {
6504
+ }), Boolean(label) && /* @__PURE__ */ jsxs(Text, {
6145
6505
  style: [
6146
6506
  styles.label,
6147
6507
  labelSizeStyle[size],
@@ -6156,7 +6516,7 @@ const Checkbox = forwardRef(({ label, description, icon, indeterminateIcon, chec
6156
6516
  })] });
6157
6517
  }
6158
6518
  }),
6159
- description && /* @__PURE__ */ jsx(Text, {
6519
+ Boolean(description) && /* @__PURE__ */ jsx(Text, {
6160
6520
  style: [
6161
6521
  styles.descriptionText,
6162
6522
  helperTextSizeStyle[size],
@@ -6256,22 +6616,16 @@ const createStyles = (theme) => StyleSheet.create({
6256
6616
  },
6257
6617
  clearButtonPressed: { backgroundColor: theme.components.input.clearButton.pressedBg },
6258
6618
  sm: {
6259
- minHeight: 36,
6260
6619
  paddingHorizontal: theme.tokens.spacing[3],
6261
- paddingVertical: theme.tokens.spacing[2],
6262
- fontSize: theme.tokens.typography.size.sm
6620
+ paddingVertical: 0
6263
6621
  },
6264
6622
  md: {
6265
- minHeight: 44,
6266
6623
  paddingHorizontal: theme.tokens.spacing[4],
6267
- paddingVertical: theme.tokens.spacing[3],
6268
- fontSize: theme.tokens.typography.size.md
6624
+ paddingVertical: 0
6269
6625
  },
6270
6626
  lg: {
6271
- minHeight: 52,
6272
6627
  paddingHorizontal: theme.tokens.spacing[5],
6273
- paddingVertical: theme.tokens.spacing[4],
6274
- fontSize: theme.tokens.typography.size.lg
6628
+ paddingVertical: 0
6275
6629
  },
6276
6630
  focused: {
6277
6631
  color: theme.components.input.focus.fg,
@@ -6383,6 +6737,8 @@ const Input = forwardRef(({ label, description, value, defaultValue, onValueChan
6383
6737
  const displayValue = format ? format(currentValue) : currentValue;
6384
6738
  const hasValue = currentValue !== "";
6385
6739
  const resolvedSize = size ?? field?.size ?? "md";
6740
+ const controlSize = controlSizes[resolvedSize];
6741
+ const resolvedIconSize = iconSize ?? controlSize.iconSize;
6386
6742
  const inputColorPalette = theme.components.input[color];
6387
6743
  const inputPalette = inputColorPalette[variant];
6388
6744
  const inputState = isFocused ? inputPalette.focus : inputPalette.default;
@@ -6390,6 +6746,7 @@ const Input = forwardRef(({ label, description, value, defaultValue, onValueChan
6390
6746
  const isDisabled = disabled || !hasOwnField && Boolean(field?.disabled);
6391
6747
  const isRequired = required || !hasOwnField && Boolean(field?.required);
6392
6748
  const isReadOnly = readOnly || loading;
6749
+ const showLoading = loading && !isDisabled;
6393
6750
  const placeholderTextColor = isDisabled ? getDisabledPlaceholderTextColor(theme) : readOnly ? theme.components.input.readOnly.placeholder : inputState.placeholder;
6394
6751
  const focusedRingStyle = Platform.OS === "web" ? { boxShadow: `0 0 0 3px ${inputColorPalette.ring}` } : {
6395
6752
  shadowColor: inputColorPalette.ring,
@@ -6403,7 +6760,6 @@ const Input = forwardRef(({ label, description, value, defaultValue, onValueChan
6403
6760
  };
6404
6761
  const isPassword = type === "password";
6405
6762
  const [isPasswordRevealed, setIsPasswordRevealed] = useState(false);
6406
- const resolvedIconSize = iconSize ?? 16;
6407
6763
  const handleFocus = (event) => {
6408
6764
  setIsFocused(true);
6409
6765
  onFocus?.(event);
@@ -6423,9 +6779,9 @@ const Input = forwardRef(({ label, description, value, defaultValue, onValueChan
6423
6779
  onValueChange?.("");
6424
6780
  onClear?.();
6425
6781
  };
6426
- const showClearButton = clearable && hasValue && !isDisabled && !isReadOnly;
6427
- const showRevealButton = revealPassword && isPassword && !isDisabled;
6428
- const showRightIcon = !showClearButton && !showRevealButton && Boolean(endIcon);
6782
+ const showClearButton = clearable && hasValue && !isDisabled && !isReadOnly && !showLoading;
6783
+ const showRevealButton = revealPassword && isPassword && !isDisabled && !showLoading && !showClearButton;
6784
+ const showRightIcon = !showLoading && !showClearButton && !showRevealButton && Boolean(endIcon);
6429
6785
  const startIconColor = isDisabled ? theme.components.input.disabled.icon : theme.components.input.icon[startIconTone];
6430
6786
  const endIconColor = isDisabled ? theme.components.input.disabled.icon : theme.components.input.icon[endIconTone];
6431
6787
  const clearIconColor = isDisabled ? theme.components.input.disabled.icon : theme.components.input.icon[clearIconTone];
@@ -6462,7 +6818,10 @@ const Input = forwardRef(({ label, description, value, defaultValue, onValueChan
6462
6818
  placeholderTextColor,
6463
6819
  accessibilityLabel: accessibilityLabel ?? label,
6464
6820
  accessibilityHint,
6465
- accessibilityState: { disabled: isDisabled },
6821
+ accessibilityState: {
6822
+ disabled: isDisabled,
6823
+ busy: loading
6824
+ },
6466
6825
  accessibilityLabelledBy: !hasOwnField ? field?.labelId : void 0,
6467
6826
  "aria-describedby": !hasOwnField ? field?.ariaDescribedBy : void 0,
6468
6827
  style: [
@@ -6470,12 +6829,15 @@ const Input = forwardRef(({ label, description, value, defaultValue, onValueChan
6470
6829
  {
6471
6830
  color: inputState.fg,
6472
6831
  backgroundColor: inputState.bg,
6473
- borderColor: inputState.border
6832
+ borderColor: inputState.border,
6833
+ height: controlSize.height,
6834
+ fontSize: controlSize.fontSize,
6835
+ lineHeight: controlSize.lineHeight
6474
6836
  },
6475
6837
  styles[resolvedSize],
6476
6838
  inputStyle,
6477
6839
  startIcon && styles.inputWithLeftAdornment,
6478
- (showRightIcon || showClearButton || showRevealButton) && styles.inputWithRightAdornment,
6840
+ (showLoading || showRightIcon || showClearButton || showRevealButton) && styles.inputWithRightAdornment,
6479
6841
  isFocused && !isDisabled && !isReadOnly && {
6480
6842
  color: inputPalette.focus.fg,
6481
6843
  backgroundColor: inputPalette.focus.bg,
@@ -6488,7 +6850,16 @@ const Input = forwardRef(({ label, description, value, defaultValue, onValueChan
6488
6850
  isDisabled && styles.disabled
6489
6851
  ]
6490
6852
  }),
6491
- showClearButton ? /* @__PURE__ */ jsx(Pressable, {
6853
+ showLoading ? /* @__PURE__ */ jsx(View, {
6854
+ ...nativePointerEventsNone,
6855
+ style: [styles.rightIcon, webPointerEventsNone],
6856
+ accessibilityElementsHidden: true,
6857
+ importantForAccessibility: "no",
6858
+ children: /* @__PURE__ */ jsx(ActivityIndicator, {
6859
+ size: "small",
6860
+ color: endIconColor
6861
+ })
6862
+ }) : showClearButton ? /* @__PURE__ */ jsx(Pressable, {
6492
6863
  accessibilityRole: "button",
6493
6864
  accessibilityLabel: "Clear input",
6494
6865
  hitSlop: 8,