@vellira-ui/react-native 2.57.0 → 2.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
- import { Children, cloneElement, createContext, forwardRef, isValidElement, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
1
+ import { Children, cloneElement, createContext, forwardRef, isValidElement, useCallback, useContext, useEffect, useId, useMemo, useRef, useState, useSyncExternalStore } from "react";
2
2
  import { Check, ChevronDown, Close, Search } from "@vellira-ui/icons";
3
3
  import { AccessibilityInfo, ActivityIndicator, Animated, BackHandler, Dimensions, Easing, FlatList, Modal as Modal$1, Platform, Pressable, ScrollView, StyleSheet, Text, TextInput, View, findNodeHandle, useWindowDimensions } from "react-native";
4
+ import { createConsoleOverlayDiagnostics, createOverlayManagerStore, createOverlayZIndexPolicy, createRetainedResourceRegistry, deferOverlayFocusRestore, getCompoundSlot, markCompoundSlot, resolveOverlayPresentation } from "@vellira-ui/core";
4
5
  import { darkTheme, highContrastTheme, lightTheme } from "@vellira-ui/tokens";
5
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
7
  //#region src/theme/fontWeight.ts
@@ -23,314 +24,278 @@ function toNativeFontWeight(weight) {
23
24
  return normalizedWeight;
24
25
  }
25
26
  //#endregion
26
- //#region src/managers/FloatingManager/computeFloatingPosition.ts
27
- function clamp(value, min, max) {
28
- return Math.min(Math.max(value, min), Math.max(min, max));
29
- }
30
- function parsePlacement(placement) {
31
- const [side, align = "center"] = placement.split("-");
32
- return {
33
- side,
34
- align
35
- };
36
- }
37
- function createPlacement(side, align) {
38
- return align === "center" ? side : `${side}-${align}`;
39
- }
40
- function getOppositeSide(side) {
41
- switch (side) {
42
- case "top": return "bottom";
43
- case "right": return "left";
44
- case "bottom": return "top";
45
- case "left": return "right";
46
- }
47
- }
48
- function computeBasePosition({ reference, floating, side, align, offset }) {
49
- const referenceCenterX = reference.x + reference.width / 2;
50
- const referenceCenterY = reference.y + reference.height / 2;
51
- const alignedLeft = align === "start" ? reference.x : align === "end" ? reference.x + reference.width - floating.width : referenceCenterX - floating.width / 2;
52
- const alignedTop = align === "start" ? reference.y : align === "end" ? reference.y + reference.height - floating.height : referenceCenterY - floating.height / 2;
53
- switch (side) {
54
- case "top": return {
55
- top: reference.y - floating.height - offset,
56
- left: alignedLeft
57
- };
58
- case "right": return {
59
- top: alignedTop,
60
- left: reference.x + reference.width + offset
61
- };
62
- case "bottom": return {
63
- top: reference.y + reference.height + offset,
64
- left: alignedLeft
65
- };
66
- case "left": return {
67
- top: alignedTop,
68
- left: reference.x - floating.width - offset
69
- };
70
- }
71
- }
72
- function getMainAxisOverflow({ side, position, floating, boundary, padding }) {
73
- switch (side) {
74
- case "top": return padding - position.top;
75
- case "right": return position.left + floating.width + padding - boundary.width;
76
- case "bottom": return position.top + floating.height + padding - boundary.height;
77
- case "left": return padding - position.left;
78
- }
79
- }
80
- function computeFloatingPosition({ reference, floating, boundary, placement, offset = 8, padding = 12, arrowPadding = 16, flip = true, shift = true }) {
81
- const parsed = parsePlacement(placement);
82
- let resolvedSide = parsed.side;
83
- let position = computeBasePosition({
84
- reference,
85
- floating,
86
- side: resolvedSide,
87
- align: parsed.align,
88
- offset
27
+ //#region src/managers/OverlayManager/NativeOverlayManager.ts
28
+ const nativeOverlayZIndexPolicy = createOverlayZIndexPolicy({
29
+ defaultLevel: "modal",
30
+ levels: { modal: lightTheme.tokens.zIndex.modal }
31
+ });
32
+ const nativeOverlayDiagnostics = createConsoleOverlayDiagnostics("NativeOverlayManager");
33
+ const createNativeOverlayManager = () => {
34
+ const store = createOverlayManagerStore({
35
+ diagnostics: nativeOverlayDiagnostics,
36
+ policy: nativeOverlayZIndexPolicy
89
37
  });
90
- if (flip && getMainAxisOverflow({
91
- side: resolvedSide,
92
- position,
93
- floating,
94
- boundary,
95
- padding
96
- }) > 0) {
97
- const oppositeSide = getOppositeSide(resolvedSide);
98
- const oppositePosition = computeBasePosition({
99
- reference,
100
- floating,
101
- side: oppositeSide,
102
- align: parsed.align,
103
- offset
104
- });
105
- if (getMainAxisOverflow({
106
- side: oppositeSide,
107
- position: oppositePosition,
108
- floating,
109
- boundary,
110
- padding
111
- }) <= 0) {
112
- resolvedSide = oppositeSide;
113
- position = oppositePosition;
114
- }
115
- }
116
- if (shift) position = {
117
- top: clamp(position.top, padding, boundary.height - floating.height - padding),
118
- left: clamp(position.left, padding, boundary.width - floating.width - padding)
38
+ const dismissHandlers = /* @__PURE__ */ new Map();
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;
119
57
  };
120
- const referenceCenterX = reference.x + reference.width / 2;
121
- const referenceCenterY = reference.y + reference.height / 2;
122
- const arrowPosition = resolvedSide === "top" || resolvedSide === "bottom" ? { left: clamp(referenceCenterX - position.left, arrowPadding, floating.width - arrowPadding) } : { top: clamp(referenceCenterY - position.top, arrowPadding, floating.height - arrowPadding) };
123
58
  return {
124
- position,
125
- arrowPosition,
126
- placement: createPlacement(resolvedSide, parsed.align)
127
- };
128
- }
129
- //#endregion
130
- //#region src/managers/FloatingManager/useNativeFloatingPosition.ts
131
- function useNativeFloatingPosition(placement = "top", offset = 8) {
132
- const [result, setResult] = useState({
133
- position: {
134
- top: 0,
135
- left: 0
59
+ register(id) {
60
+ const entry = store.register({ id });
61
+ return toNativeEntry(entry.id);
136
62
  },
137
- arrowPosition: {},
138
- placement
139
- });
140
- const floatingSizeRef = useRef({
141
- width: 0,
142
- height: 0
143
- });
144
- const lastTriggerRef = useRef(null);
145
- const lastContainerRef = useRef(null);
146
- const updatePosition = useCallback((triggerRef, containerRef, measuredSize = floatingSizeRef.current) => {
147
- lastTriggerRef.current = triggerRef;
148
- lastContainerRef.current = containerRef ?? null;
149
- const triggerNode = triggerRef.current;
150
- const containerNode = containerRef?.current;
151
- if (!triggerNode || typeof triggerNode.measureInWindow !== "function") {
152
- setResult((current) => ({
153
- ...current,
154
- position: {
155
- top: 0,
156
- left: 0
157
- }
158
- }));
159
- return;
160
- }
161
- triggerNode.measureInWindow((x, y, width, height) => {
162
- const commitPosition = (containerX, containerY, containerWidth, containerHeight) => {
163
- const nextResult = computeFloatingPosition({
164
- reference: {
165
- x: x - containerX,
166
- y: y - containerY,
167
- width,
168
- height
169
- },
170
- floating: measuredSize,
171
- boundary: {
172
- width: containerWidth,
173
- height: containerHeight
174
- },
175
- placement,
176
- offset
177
- });
178
- setResult(nextResult);
63
+ unregister(id) {
64
+ dismissHandlers.delete(id);
65
+ outsidePressHandlers.delete(id);
66
+ store.unregister(id);
67
+ },
68
+ getSnapshot() {
69
+ return getNativeSnapshot();
70
+ },
71
+ isTop(id) {
72
+ return store.isTopmost(id);
73
+ },
74
+ getTop() {
75
+ return getNativeSnapshot().topmost;
76
+ },
77
+ getZIndex(id) {
78
+ return store.getZIndex(id) ?? nativeOverlayZIndexPolicy.levels[nativeOverlayZIndexPolicy.defaultLevel];
79
+ },
80
+ subscribe(listener) {
81
+ return store.subscribe(listener);
82
+ },
83
+ registerDismissHandler(id, handler) {
84
+ dismissHandlers.set(id, handler);
85
+ return () => {
86
+ if (dismissHandlers.get(id) !== handler) return;
87
+ dismissHandlers.delete(id);
179
88
  };
180
- if (!containerNode || typeof containerNode.measureInWindow !== "function") {
181
- const window = Dimensions.get("window");
182
- commitPosition(0, 0, window.width, window.height);
183
- return;
184
- }
185
- containerNode.measureInWindow((containerX, containerY, containerWidth, containerHeight) => {
186
- commitPosition(containerX, containerY, containerWidth, containerHeight);
187
- });
188
- });
189
- }, [placement, offset]);
190
- const onFloatingLayout = useCallback((event) => {
191
- const { width, height } = event.nativeEvent.layout;
192
- const nextSize = {
193
- width,
194
- height
195
- };
196
- floatingSizeRef.current = nextSize;
197
- if (lastTriggerRef.current) updatePosition(lastTriggerRef.current, lastContainerRef.current ?? void 0, nextSize);
198
- }, [updatePosition]);
199
- return {
200
- position: result.position,
201
- arrowPosition: result.arrowPosition,
202
- placement: result.placement,
203
- updatePosition,
204
- onFloatingLayout
89
+ },
90
+ registerOutsidePressHandler(id, handler) {
91
+ outsidePressHandlers.set(id, handler);
92
+ return () => {
93
+ if (outsidePressHandlers.get(id) !== handler) return;
94
+ outsidePressHandlers.delete(id);
95
+ };
96
+ },
97
+ dispatchTopDismiss() {
98
+ const top = this.getTop();
99
+ if (!top) return false;
100
+ const handler = dismissHandlers.get(top.id);
101
+ if (!handler) return false;
102
+ return handler();
103
+ },
104
+ dispatchTopOutsidePress() {
105
+ const top = this.getTop();
106
+ if (!top) return false;
107
+ const handler = outsidePressHandlers.get(top.id);
108
+ if (!handler) return false;
109
+ return handler();
110
+ },
111
+ clear() {
112
+ dismissHandlers.clear();
113
+ outsidePressHandlers.clear();
114
+ store.clear();
115
+ }
205
116
  };
206
- }
207
- //#endregion
208
- //#region src/managers/OverlayManager/NativeOverlayManager.ts
209
- const BASE_LAYER = 1e3;
210
- const LAYER_STEP = 10;
211
- let stack = [];
212
- const nativeOverlayManager = {
213
- register(id) {
214
- stack = stack.filter((item) => item.id !== id);
215
- const entry = {
216
- id,
217
- layer: BASE_LAYER + stack.length * LAYER_STEP
218
- };
219
- stack.push(entry);
220
- return entry;
221
- },
222
- unregister(id) {
223
- stack = stack.filter((item) => item.id !== id);
224
- },
225
- isTop(id) {
226
- return stack.at(-1)?.id === id;
227
- },
228
- getTop() {
229
- return stack.at(-1);
230
- },
231
- getLayer(id) {
232
- return stack.find((item) => item.id === id)?.layer ?? BASE_LAYER;
233
- }
234
117
  };
118
+ const nativeOverlayManager = createNativeOverlayManager();
235
119
  //#endregion
236
- //#region src/managers/OverlayManager/useNativeOverlayRegistration.ts
237
- const useNativeOverlayRegistration = ({ id, visible }) => {
238
- const [layer, setLayer] = useState(() => nativeOverlayManager.getLayer(id));
120
+ //#region src/managers/OverlayManager/OverlayManagerProvider.tsx
121
+ const NativeOverlayManagerContext = createContext(null);
122
+ const useNativeOverlayManager = () => useContext(NativeOverlayManagerContext) ?? nativeOverlayManager;
123
+ //#endregion
124
+ //#region src/hooks/behavior/overlay/useOverlayRegistration.ts
125
+ const useOverlayRegistration = ({ active, id }) => {
126
+ const nativeOverlayManager = useNativeOverlayManager();
127
+ const snapshot = useSyncExternalStore(nativeOverlayManager.subscribe, nativeOverlayManager.getSnapshot, nativeOverlayManager.getSnapshot);
239
128
  useEffect(() => {
240
- if (!visible) return;
241
- const entry = nativeOverlayManager.register(id);
242
- setLayer(entry.layer);
129
+ if (!active) return;
130
+ nativeOverlayManager.register(id);
243
131
  return () => {
244
132
  nativeOverlayManager.unregister(id);
245
133
  };
246
- }, [id, visible]);
134
+ }, [
135
+ active,
136
+ id,
137
+ nativeOverlayManager
138
+ ]);
139
+ const isTopOverlay = useCallback(() => nativeOverlayManager.isTop(id), [id, nativeOverlayManager]);
247
140
  return {
248
- layer,
249
- isTopOverlay: useCallback(() => nativeOverlayManager.isTop(id), [id])
141
+ zIndex: snapshot.registry.get(id)?.zIndex ?? nativeOverlayManager.getZIndex(id),
142
+ isTopmost: snapshot.topmost?.id === id,
143
+ isTopOverlay
250
144
  };
251
145
  };
252
146
  //#endregion
253
- //#region src/hooks/behavior/overlay/useOverlayStack.ts
254
- const useOverlayStack = ({ active, id }) => useNativeOverlayRegistration({
255
- id,
256
- visible: active
257
- });
258
- //#endregion
259
147
  //#region src/hooks/behavior/overlay/useOverlayDismiss.ts
260
- const useOverlayDismiss = ({ active, closeOnEscape = true, closeOnOutsidePress = true, id, requestClose }) => {
261
- const { isTopOverlay } = useOverlayStack({
148
+ const dismissListeners = createRetainedResourceRegistry((manager) => {
149
+ if (Platform.OS === "web") {
150
+ const handleKeyDown = (event) => {
151
+ if (event.key !== "Escape") return;
152
+ manager.dispatchTopDismiss();
153
+ };
154
+ document.addEventListener("keydown", handleKeyDown);
155
+ return () => {
156
+ document.removeEventListener("keydown", handleKeyDown);
157
+ };
158
+ }
159
+ const subscription = BackHandler.addEventListener("hardwareBackPress", () => manager.dispatchTopDismiss());
160
+ return () => {
161
+ subscription.remove();
162
+ };
163
+ });
164
+ function retainDismissListener(manager) {
165
+ return dismissListeners.retain(manager);
166
+ }
167
+ const useOverlayDismiss = ({ active, closeOnEscape = true, closeOnOutsidePress = true, id, requestClose, requestOutsideClose }) => {
168
+ const nativeOverlayManager = useNativeOverlayManager();
169
+ const registration = useOverlayRegistration({
262
170
  active,
263
171
  id
264
172
  });
173
+ const { isTopOverlay } = registration;
265
174
  const requestTopClose = useCallback(() => {
266
175
  if (!isTopOverlay()) return;
267
176
  requestClose();
268
177
  }, [isTopOverlay, requestClose]);
269
- const requestOutsideClose = useCallback(() => {
270
- if (!closeOnOutsidePress) return;
271
- requestTopClose();
272
- }, [closeOnOutsidePress, requestTopClose]);
178
+ const requestOutsideTopClose = useCallback(() => {
179
+ nativeOverlayManager.dispatchTopOutsidePress();
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
+ ]);
273
197
  useEffect(() => {
274
- if (!active || !closeOnEscape) return;
275
- if (Platform.OS === "web") {
276
- const handleKeyDown = (event) => {
277
- if (event.key !== "Escape") return;
278
- requestTopClose();
279
- };
280
- document.addEventListener("keydown", handleKeyDown);
281
- return () => {
282
- document.removeEventListener("keydown", handleKeyDown);
283
- };
284
- }
285
- const subscription = BackHandler.addEventListener("hardwareBackPress", () => {
198
+ if (!active) return;
199
+ return nativeOverlayManager.registerOutsidePressHandler(id, () => {
200
+ if (!closeOnOutsidePress) return false;
286
201
  if (!isTopOverlay()) return false;
202
+ if (requestOutsideClose) {
203
+ requestOutsideClose();
204
+ return true;
205
+ }
287
206
  requestClose();
288
207
  return true;
289
208
  });
209
+ }, [
210
+ active,
211
+ closeOnOutsidePress,
212
+ id,
213
+ isTopOverlay,
214
+ nativeOverlayManager,
215
+ requestClose,
216
+ requestOutsideClose
217
+ ]);
218
+ useEffect(() => {
219
+ if (!active) return;
220
+ const releaseDismissListener = retainDismissListener(nativeOverlayManager);
221
+ const unregisterDismissHandler = nativeOverlayManager.registerDismissHandler(id, () => {
222
+ if (!closeOnEscape) return false;
223
+ requestTopClose();
224
+ return true;
225
+ });
290
226
  return () => {
291
- subscription.remove();
227
+ unregisterDismissHandler();
228
+ releaseDismissListener();
292
229
  };
293
230
  }, [
294
231
  active,
295
232
  closeOnEscape,
296
- isTopOverlay,
297
- requestClose,
233
+ id,
234
+ nativeOverlayManager,
298
235
  requestTopClose
299
236
  ]);
300
237
  return {
238
+ zIndex: registration.zIndex,
301
239
  isTopOverlay,
240
+ getOutsidePressProps,
302
241
  requestClose: requestTopClose,
303
- requestOutsideClose
242
+ requestOutsideClose: requestOutsideTopClose
304
243
  };
305
244
  };
306
245
  //#endregion
307
246
  //#region src/hooks/behavior/overlay/useOverlayFocusRestore.ts
308
- const useOverlayFocusRestore = ({ enabled = true, triggerRef }) => {
247
+ function isFocusableWebNode(node) {
248
+ return typeof node === "object" && node !== null && "focus" in node && typeof node.focus === "function";
249
+ }
250
+ const useOverlayFocusRestore = ({ active = false, enabled = true, finalFocus, triggerRef }) => {
251
+ const previouslyFocusedRef = useRef(null);
252
+ const saveFocusSnapshot = useCallback(() => {
253
+ if (Platform.OS !== "web" || typeof document === "undefined") return;
254
+ previouslyFocusedRef.current = document.activeElement;
255
+ }, []);
309
256
  const restoreFocus = useCallback(() => {
310
257
  if (!enabled) return;
311
258
  if (Platform.OS === "web") {
312
- const triggerNode = triggerRef.current;
313
- if (triggerNode && typeof triggerNode === "object" && "focus" in triggerNode && typeof triggerNode.focus === "function") triggerNode.focus();
259
+ const preferredNode = finalFocus?.current ?? triggerRef.current;
260
+ if (isFocusableWebNode(preferredNode)) {
261
+ preferredNode.focus();
262
+ return;
263
+ }
264
+ const previouslyFocused = previouslyFocusedRef.current;
265
+ if (previouslyFocused instanceof HTMLElement && previouslyFocused.isConnected) previouslyFocused.focus();
314
266
  return;
315
267
  }
316
268
  if (typeof findNodeHandle !== "function") return;
317
- const handle = findNodeHandle(triggerRef.current);
269
+ const handle = findNodeHandle(finalFocus?.current ?? triggerRef.current);
318
270
  if (handle && AccessibilityInfo.setAccessibilityFocus) AccessibilityInfo.setAccessibilityFocus(handle);
319
- }, [enabled, triggerRef]);
271
+ }, [
272
+ enabled,
273
+ finalFocus,
274
+ triggerRef
275
+ ]);
276
+ const restoreFocusAfterClose = useCallback(() => {
277
+ if (!enabled) return;
278
+ deferOverlayFocusRestore(restoreFocus, requestAnimationFrame);
279
+ }, [enabled, restoreFocus]);
280
+ useEffect(() => {
281
+ if (!active) return;
282
+ saveFocusSnapshot();
283
+ }, [active, saveFocusSnapshot]);
320
284
  return {
321
285
  restoreFocus,
322
- restoreFocusAfterClose: useCallback(() => {
323
- if (!enabled) return;
324
- requestAnimationFrame(restoreFocus);
325
- }, [enabled, restoreFocus])
286
+ restoreFocusAfterClose,
287
+ saveFocusSnapshot
326
288
  };
327
289
  };
328
290
  //#endregion
329
291
  //#region src/hooks/behavior/overlay/useOverlayPresentation.ts
330
292
  function useOverlayPresentation(presentation = "auto", breakpoint = 768) {
331
293
  const { width } = useWindowDimensions();
332
- if (presentation === "auto") return width >= breakpoint ? "popover" : "sheet";
333
- return presentation;
294
+ return resolveOverlayPresentation({
295
+ presentation,
296
+ defaultPresentation: "sheet",
297
+ autoPresentation: width >= breakpoint ? "popover" : "sheet"
298
+ });
334
299
  }
335
300
  //#endregion
336
301
  //#region src/hooks/useControllableState.ts
@@ -582,11 +547,23 @@ const useModal = ({ open, defaultOpen = false, onOpenChange, closeOnEscape, clos
582
547
  };
583
548
  //#endregion
584
549
  //#region src/hooks/useSelect.ts
585
- const useSelect = ({ value, defaultValue, onValueChange, onChange, options, multiple = false, maxSelected, closeOnSelect = !multiple, disabled = false, open, defaultOpen = false, onOpenChange }) => {
550
+ const useSelect = ({ value, defaultValue, onValueChange, options, multiple = false, maxSelected, closeOnSelect = !multiple, disabled = false, open, defaultOpen = false, onOpenChange }) => {
551
+ const handleValueChange = useCallback((nextValue) => {
552
+ if (!onValueChange) return;
553
+ if (multiple) {
554
+ if (Array.isArray(nextValue)) {
555
+ onValueChange(nextValue);
556
+ return;
557
+ }
558
+ onValueChange(nextValue ? [nextValue] : []);
559
+ return;
560
+ }
561
+ onValueChange(Array.isArray(nextValue) ? nextValue[0] ?? "" : nextValue);
562
+ }, [multiple, onValueChange]);
586
563
  const [selectedValue, setSelectedValue] = useControllableState({
587
564
  value,
588
565
  defaultValue: defaultValue ?? (multiple ? [] : ""),
589
- onChange: onValueChange ?? onChange
566
+ onChange: handleValueChange
590
567
  });
591
568
  const [isOpen, setIsOpen] = useControllableState({
592
569
  value: open,
@@ -764,6 +741,16 @@ function useThemeStyles(createStyles) {
764
741
  return useMemo(() => createStyles(theme), [createStyles, theme]);
765
742
  }
766
743
  //#endregion
744
+ //#region src/components/Dropdown/internal/DropdownContext.tsx
745
+ const DropdownContext = createContext(null);
746
+ const DropdownProvider = DropdownContext.Provider;
747
+ const useDropdownContext = () => {
748
+ const context = useContext(DropdownContext);
749
+ if (!context) throw new Error("Dropdown components must be used inside Dropdown");
750
+ return context;
751
+ };
752
+ DropdownContext.displayName = "DropdownContext";
753
+ //#endregion
767
754
  //#region src/components/Dropdown/Content/DropdownContent.styles.ts
768
755
  const createStyles$21 = (theme) => StyleSheet.create({
769
756
  modalRoot: { flex: 1 },
@@ -848,13 +835,14 @@ const createStyles$21 = (theme) => StyleSheet.create({
848
835
  //#region src/components/Dropdown/Content/DropdownContent.tsx
849
836
  const nativePointerEventsBoxNone$1 = Platform.OS === "web" ? void 0 : { pointerEvents: "box-none" };
850
837
  const webPointerEventsBoxNone$1 = Platform.OS === "web" ? { pointerEvents: "box-none" } : void 0;
851
- function DropdownContent({ isOpen, children, onClose, color = "primary", contentStyle, accessibilityLabel, presentation, position, onFloatingLayout, searchable = false, searchValue = "", searchPlaceholder = "Search actions...", searchAccessibilityLabel, onSearchChange }) {
838
+ function DropdownContent({ children, contentStyle, accessibilityLabel }) {
839
+ const { open, color, presentation, zIndex, position, searchable, searchValue, searchPlaceholder, searchAccessibilityLabel, requestClose, getOutsidePressProps, onSearchChange, onFloatingLayout } = useDropdownContext();
852
840
  const { theme } = useTheme();
853
841
  const styles = useThemeStyles(createStyles$21);
854
842
  const colorPalette = theme.components.dropdown[color];
855
843
  const isSheet = presentation === "sheet";
856
844
  const isPopover = presentation === "popover";
857
- const animation = useRef(new Animated.Value(isOpen ? 1 : 0)).current;
845
+ const animation = useRef(new Animated.Value(open ? 1 : 0)).current;
858
846
  const [reduceMotion, setReduceMotion] = useState(false);
859
847
  useEffect(() => {
860
848
  AccessibilityInfo.isReduceMotionEnabled?.().then(setReduceMotion);
@@ -864,7 +852,7 @@ function DropdownContent({ isOpen, children, onClose, color = "primary", content
864
852
  };
865
853
  }, []);
866
854
  useEffect(() => {
867
- if (!isOpen) {
855
+ if (!open) {
868
856
  animation.setValue(0);
869
857
  return;
870
858
  }
@@ -880,7 +868,7 @@ function DropdownContent({ isOpen, children, onClose, color = "primary", content
880
868
  }).start();
881
869
  }, [
882
870
  animation,
883
- isOpen,
871
+ open,
884
872
  isSheet,
885
873
  reduceMotion
886
874
  ]);
@@ -904,11 +892,15 @@ function DropdownContent({ isOpen, children, onClose, color = "primary", content
904
892
  }, [animation, isSheet]);
905
893
  return /* @__PURE__ */ jsx(Modal$1, {
906
894
  transparent: true,
907
- visible: isOpen,
895
+ visible: open,
908
896
  animationType: "none",
909
- onRequestClose: onClose,
897
+ onRequestClose: requestClose,
910
898
  children: /* @__PURE__ */ jsxs(View, {
911
- style: [styles.modalRoot, styles[presentation]],
899
+ style: [
900
+ styles.modalRoot,
901
+ styles[presentation],
902
+ Platform.OS === "web" && { zIndex }
903
+ ],
912
904
  children: [/* @__PURE__ */ jsx(Animated.View, {
913
905
  ...nativePointerEventsBoxNone$1,
914
906
  style: [
@@ -917,10 +909,8 @@ function DropdownContent({ isOpen, children, onClose, color = "primary", content
917
909
  webPointerEventsBoxNone$1
918
910
  ],
919
911
  children: /* @__PURE__ */ jsx(Pressable, {
920
- accessibilityRole: "button",
921
- accessibilityLabel: "Close menu",
922
- style: StyleSheet.absoluteFill,
923
- onPress: onClose
912
+ ...getOutsidePressProps({ accessibilityLabel: "Close menu" }),
913
+ style: StyleSheet.absoluteFill
924
914
  })
925
915
  }), /* @__PURE__ */ jsxs(Animated.View, {
926
916
  accessibilityRole: "menu",
@@ -968,7 +958,7 @@ DropdownContent.displayName = "DropdownContent";
968
958
  //#region src/components/Dropdown/internal/DropdownCollection.ts
969
959
  function createDropdownSlot(name, displayName) {
970
960
  const Slot = () => null;
971
- Slot.__velliraDropdownPart = name;
961
+ markCompoundSlot(Slot, name);
972
962
  Slot.displayName = displayName;
973
963
  return Slot;
974
964
  }
@@ -996,11 +986,12 @@ function parseDropdownChildren(children) {
996
986
  Children.forEach(node, (child) => {
997
987
  if (!isValidElement(child)) return;
998
988
  const type = child.type;
989
+ const slot = getCompoundSlot(type);
999
990
  if (type.__velliraPortal) {
1000
991
  visit(child.props.children);
1001
992
  return;
1002
993
  }
1003
- switch (type.__velliraDropdownPart) {
994
+ switch (slot) {
1004
995
  case "trigger":
1005
996
  triggerProps = child.props;
1006
997
  trigger = triggerProps.children;
@@ -1068,6 +1059,279 @@ function getItemLabel(children) {
1068
1059
  return labelParts.join("").trim();
1069
1060
  }
1070
1061
  //#endregion
1062
+ //#region src/hooks/behavior/dropdown/useDropdownAccessibility.ts
1063
+ const useDropdownAccessibility = ({ accessibilityLabel, label, open }) => {
1064
+ const menuAccessibilityLabel = useMemo(() => {
1065
+ if (accessibilityLabel) return accessibilityLabel;
1066
+ return typeof label === "string" ? label : "Menu";
1067
+ }, [accessibilityLabel, label]);
1068
+ useEffect(() => {
1069
+ if (!open) return;
1070
+ AccessibilityInfo.announceForAccessibility(`${menuAccessibilityLabel} opened`);
1071
+ }, [menuAccessibilityLabel, open]);
1072
+ return { menuAccessibilityLabel };
1073
+ };
1074
+ //#endregion
1075
+ //#region src/hooks/behavior/dropdown/useDropdownEntries.ts
1076
+ const useDropdownEntries = ({ parsed, filteredParsed, loading, loadingText, isSearchable, empty }) => {
1077
+ return {
1078
+ navigableItems: useMemo(() => parsed.items.map((item) => ({
1079
+ disabled: item.disabled,
1080
+ label: item.label,
1081
+ value: item.id
1082
+ })), [parsed.items]),
1083
+ data: useMemo(() => {
1084
+ if (loading) return [{
1085
+ type: "loading",
1086
+ id: "loading",
1087
+ props: { children: loadingText }
1088
+ }];
1089
+ if (isSearchable && filteredParsed.items.length === 0) return [{
1090
+ type: "empty",
1091
+ id: "empty",
1092
+ props: { children: empty ?? "No actions found" }
1093
+ }];
1094
+ return filteredParsed.entries;
1095
+ }, [
1096
+ empty,
1097
+ filteredParsed.entries,
1098
+ filteredParsed.items.length,
1099
+ isSearchable,
1100
+ loading,
1101
+ loadingText
1102
+ ])
1103
+ };
1104
+ };
1105
+ //#endregion
1106
+ //#region src/components/Dropdown/internal/DropdownUtils.ts
1107
+ function createDropdownSelectEvent() {
1108
+ let defaultPrevented = false;
1109
+ return {
1110
+ preventDefault: () => {
1111
+ defaultPrevented = true;
1112
+ },
1113
+ get defaultPrevented() {
1114
+ return defaultPrevented;
1115
+ }
1116
+ };
1117
+ }
1118
+ function filterDropdownEntries(parsed, searchValue) {
1119
+ const normalizedSearch = searchValue.trim().toLocaleLowerCase();
1120
+ const matchedItems = new Set(parsed.items.filter((item) => item.label.toLocaleLowerCase().includes(normalizedSearch)).map((item) => item.id));
1121
+ return {
1122
+ ...parsed,
1123
+ items: parsed.items.filter((item) => matchedItems.has(item.id)),
1124
+ entries: parsed.entries.filter((entry) => entry.type !== "item" || matchedItems.has(entry.id))
1125
+ };
1126
+ }
1127
+ //#endregion
1128
+ //#region src/hooks/behavior/dropdown/useDropdownSearch.ts
1129
+ const useDropdownSearch = ({ parsed, searchable, command, searchValue, defaultSearchValue, onSearch }) => {
1130
+ const [uncontrolledSearchValue, setUncontrolledSearchValue] = useState(defaultSearchValue);
1131
+ const resolvedSearchValue = searchValue ?? uncontrolledSearchValue;
1132
+ const contentCommand = parsed.contentProps?.command ?? false;
1133
+ const isSearchable = searchable || command || contentCommand || Boolean(parsed.searchProps);
1134
+ return {
1135
+ contentCommand,
1136
+ filteredParsed: useMemo(() => {
1137
+ if (!isSearchable || !resolvedSearchValue.trim()) return parsed;
1138
+ return filterDropdownEntries(parsed, resolvedSearchValue);
1139
+ }, [
1140
+ isSearchable,
1141
+ parsed,
1142
+ resolvedSearchValue
1143
+ ]),
1144
+ handleSearchChange: useCallback((value) => {
1145
+ if (searchValue === void 0) setUncontrolledSearchValue(value);
1146
+ onSearch?.(value);
1147
+ }, [onSearch, searchValue]),
1148
+ isSearchable,
1149
+ resolvedSearchValue
1150
+ };
1151
+ };
1152
+ //#endregion
1153
+ //#region src/managers/FloatingManager/computeFloatingPosition.ts
1154
+ function clamp(value, min, max) {
1155
+ return Math.min(Math.max(value, min), Math.max(min, max));
1156
+ }
1157
+ function parsePlacement(placement) {
1158
+ const [side, align = "center"] = placement.split("-");
1159
+ return {
1160
+ side,
1161
+ align
1162
+ };
1163
+ }
1164
+ function createPlacement(side, align) {
1165
+ return align === "center" ? side : `${side}-${align}`;
1166
+ }
1167
+ function getOppositeSide(side) {
1168
+ switch (side) {
1169
+ case "top": return "bottom";
1170
+ case "right": return "left";
1171
+ case "bottom": return "top";
1172
+ case "left": return "right";
1173
+ }
1174
+ }
1175
+ function computeBasePosition({ reference, floating, side, align, offset }) {
1176
+ const referenceCenterX = reference.x + reference.width / 2;
1177
+ const referenceCenterY = reference.y + reference.height / 2;
1178
+ const alignedLeft = align === "start" ? reference.x : align === "end" ? reference.x + reference.width - floating.width : referenceCenterX - floating.width / 2;
1179
+ const alignedTop = align === "start" ? reference.y : align === "end" ? reference.y + reference.height - floating.height : referenceCenterY - floating.height / 2;
1180
+ switch (side) {
1181
+ case "top": return {
1182
+ top: reference.y - floating.height - offset,
1183
+ left: alignedLeft
1184
+ };
1185
+ case "right": return {
1186
+ top: alignedTop,
1187
+ left: reference.x + reference.width + offset
1188
+ };
1189
+ case "bottom": return {
1190
+ top: reference.y + reference.height + offset,
1191
+ left: alignedLeft
1192
+ };
1193
+ case "left": return {
1194
+ top: alignedTop,
1195
+ left: reference.x - floating.width - offset
1196
+ };
1197
+ }
1198
+ }
1199
+ function getMainAxisOverflow({ side, position, floating, boundary, padding }) {
1200
+ switch (side) {
1201
+ case "top": return padding - position.top;
1202
+ case "right": return position.left + floating.width + padding - boundary.width;
1203
+ case "bottom": return position.top + floating.height + padding - boundary.height;
1204
+ case "left": return padding - position.left;
1205
+ }
1206
+ }
1207
+ function computeFloatingPosition({ reference, floating, boundary, placement, offset = 8, padding = 12, arrowPadding = 16, flip = true, shift = true }) {
1208
+ const parsed = parsePlacement(placement);
1209
+ let resolvedSide = parsed.side;
1210
+ let position = computeBasePosition({
1211
+ reference,
1212
+ floating,
1213
+ side: resolvedSide,
1214
+ align: parsed.align,
1215
+ offset
1216
+ });
1217
+ if (flip && getMainAxisOverflow({
1218
+ side: resolvedSide,
1219
+ position,
1220
+ floating,
1221
+ boundary,
1222
+ padding
1223
+ }) > 0) {
1224
+ const oppositeSide = getOppositeSide(resolvedSide);
1225
+ const oppositePosition = computeBasePosition({
1226
+ reference,
1227
+ floating,
1228
+ side: oppositeSide,
1229
+ align: parsed.align,
1230
+ offset
1231
+ });
1232
+ if (getMainAxisOverflow({
1233
+ side: oppositeSide,
1234
+ position: oppositePosition,
1235
+ floating,
1236
+ boundary,
1237
+ padding
1238
+ }) <= 0) {
1239
+ resolvedSide = oppositeSide;
1240
+ position = oppositePosition;
1241
+ }
1242
+ }
1243
+ if (shift) position = {
1244
+ top: clamp(position.top, padding, boundary.height - floating.height - padding),
1245
+ left: clamp(position.left, padding, boundary.width - floating.width - padding)
1246
+ };
1247
+ const referenceCenterX = reference.x + reference.width / 2;
1248
+ const referenceCenterY = reference.y + reference.height / 2;
1249
+ const arrowPosition = resolvedSide === "top" || resolvedSide === "bottom" ? { left: clamp(referenceCenterX - position.left, arrowPadding, floating.width - arrowPadding) } : { top: clamp(referenceCenterY - position.top, arrowPadding, floating.height - arrowPadding) };
1250
+ return {
1251
+ position,
1252
+ arrowPosition,
1253
+ placement: createPlacement(resolvedSide, parsed.align)
1254
+ };
1255
+ }
1256
+ //#endregion
1257
+ //#region src/managers/FloatingManager/useNativeFloatingPosition.ts
1258
+ function useNativeFloatingPosition(placement = "top", offset = 8) {
1259
+ const [result, setResult] = useState({
1260
+ position: {
1261
+ top: 0,
1262
+ left: 0
1263
+ },
1264
+ arrowPosition: {},
1265
+ placement
1266
+ });
1267
+ const floatingSizeRef = useRef({
1268
+ width: 0,
1269
+ height: 0
1270
+ });
1271
+ const lastTriggerRef = useRef(null);
1272
+ const lastContainerRef = useRef(null);
1273
+ const updatePosition = useCallback((triggerRef, containerRef, measuredSize = floatingSizeRef.current) => {
1274
+ lastTriggerRef.current = triggerRef;
1275
+ lastContainerRef.current = containerRef ?? null;
1276
+ const triggerNode = triggerRef.current;
1277
+ const containerNode = containerRef?.current;
1278
+ if (!triggerNode || typeof triggerNode.measureInWindow !== "function") {
1279
+ setResult((current) => ({
1280
+ ...current,
1281
+ position: {
1282
+ top: 0,
1283
+ left: 0
1284
+ }
1285
+ }));
1286
+ return;
1287
+ }
1288
+ triggerNode.measureInWindow((x, y, width, height) => {
1289
+ const commitPosition = (containerX, containerY, containerWidth, containerHeight) => {
1290
+ const nextResult = computeFloatingPosition({
1291
+ reference: {
1292
+ x: x - containerX,
1293
+ y: y - containerY,
1294
+ width,
1295
+ height
1296
+ },
1297
+ floating: measuredSize,
1298
+ boundary: {
1299
+ width: containerWidth,
1300
+ height: containerHeight
1301
+ },
1302
+ placement,
1303
+ offset
1304
+ });
1305
+ setResult(nextResult);
1306
+ };
1307
+ if (!containerNode || typeof containerNode.measureInWindow !== "function") {
1308
+ const window = Dimensions.get("window");
1309
+ commitPosition(0, 0, window.width, window.height);
1310
+ return;
1311
+ }
1312
+ containerNode.measureInWindow((containerX, containerY, containerWidth, containerHeight) => {
1313
+ commitPosition(containerX, containerY, containerWidth, containerHeight);
1314
+ });
1315
+ });
1316
+ }, [placement, offset]);
1317
+ const onFloatingLayout = useCallback((event) => {
1318
+ const { width, height } = event.nativeEvent.layout;
1319
+ const nextSize = {
1320
+ width,
1321
+ height
1322
+ };
1323
+ floatingSizeRef.current = nextSize;
1324
+ if (lastTriggerRef.current) updatePosition(lastTriggerRef.current, lastContainerRef.current ?? void 0, nextSize);
1325
+ }, [updatePosition]);
1326
+ return {
1327
+ position: result.position,
1328
+ arrowPosition: result.arrowPosition,
1329
+ placement: result.placement,
1330
+ updatePosition,
1331
+ onFloatingLayout
1332
+ };
1333
+ }
1334
+ //#endregion
1071
1335
  //#region src/components/Dropdown/Dropdown.styles.ts
1072
1336
  const createStyles$20 = (theme) => StyleSheet.create({
1073
1337
  root: { alignSelf: "flex-start" },
@@ -1103,27 +1367,10 @@ function DropdownGroup({ label }) {
1103
1367
  }
1104
1368
  DropdownGroup.displayName = "DropdownGroup";
1105
1369
  //#endregion
1106
- //#region src/components/Dropdown/internal/DropdownUtils.ts
1107
- function createDropdownSelectEvent() {
1108
- let defaultPrevented = false;
1109
- return {
1110
- preventDefault: () => {
1111
- defaultPrevented = true;
1112
- },
1113
- get defaultPrevented() {
1114
- return defaultPrevented;
1115
- }
1116
- };
1117
- }
1118
- function filterDropdownEntries(parsed, searchValue) {
1119
- const normalizedSearch = searchValue.trim().toLocaleLowerCase();
1120
- const matchedItems = new Set(parsed.items.filter((item) => item.label.toLocaleLowerCase().includes(normalizedSearch)).map((item) => item.id));
1121
- return {
1122
- ...parsed,
1123
- items: parsed.items.filter((item) => matchedItems.has(item.id)),
1124
- entries: parsed.entries.filter((entry) => entry.type !== "item" || matchedItems.has(entry.id))
1125
- };
1126
- }
1370
+ //#region src/utils/devWarning.ts
1371
+ const devWarning = (condition, message) => {
1372
+ if ((typeof __DEV__ === "undefined" || __DEV__) && !condition) console.warn(message);
1373
+ };
1127
1374
  //#endregion
1128
1375
  //#region src/components/Dropdown/Item/DropdownItem.styles.ts
1129
1376
  const createStyles$18 = (theme) => StyleSheet.create({
@@ -1149,7 +1396,8 @@ const createStyles$18 = (theme) => StyleSheet.create({
1149
1396
  });
1150
1397
  //#endregion
1151
1398
  //#region src/components/Dropdown/Item/DropdownItem.tsx
1152
- function DropdownItem({ label, value, rootColor = "primary", color = "default", icon, disabled = false, textWrap = "truncate", itemStyle, textStyle, onSelect }) {
1399
+ function DropdownItem({ label, value, asChild = false, children, color = "default", icon, disabled = false, textWrap = "truncate", onSelect }) {
1400
+ const { color: rootColor, itemStyle, textStyle } = useDropdownContext();
1153
1401
  const { theme } = useTheme();
1154
1402
  const styles = useThemeStyles(createStyles$18);
1155
1403
  const rootColorPalette = theme.components.dropdown[rootColor];
@@ -1173,6 +1421,24 @@ function DropdownItem({ label, value, rootColor = "primary", color = "default",
1173
1421
  const accessibilityLabel = typeof label === "string" ? label : value;
1174
1422
  const numberOfLines = textWrap === "wrap" ? void 0 : 1;
1175
1423
  const ellipsizeMode = textWrap === "truncate" ? "tail" : "clip";
1424
+ const child = asChild && isValidElement(children) ? children : void 0;
1425
+ const getItemStyle = (pressed) => [
1426
+ styles.item,
1427
+ { backgroundColor: getBackgroundColor(pressed) },
1428
+ itemStyle
1429
+ ];
1430
+ devWarning(!asChild || Boolean(child), "Dropdown.Item: asChild requires a single valid React element child.");
1431
+ if (child) return cloneElement(child, {
1432
+ accessibilityRole: "menuitem",
1433
+ accessibilityLabel,
1434
+ accessibilityState: { disabled },
1435
+ disabled,
1436
+ onPress: (event) => {
1437
+ child.props.onPress?.(event);
1438
+ if (!event.defaultPrevented && !disabled) onSelect(value);
1439
+ },
1440
+ style: [getItemStyle(false), child.props.style]
1441
+ });
1176
1442
  return /* @__PURE__ */ jsx(Pressable, {
1177
1443
  disabled,
1178
1444
  accessibilityRole: "menuitem",
@@ -1182,11 +1448,7 @@ function DropdownItem({ label, value, rootColor = "primary", color = "default",
1182
1448
  if (disabled) return;
1183
1449
  onSelect(value);
1184
1450
  },
1185
- style: ({ pressed }) => [
1186
- styles.item,
1187
- { backgroundColor: getBackgroundColor(pressed) },
1188
- itemStyle
1189
- ],
1451
+ style: ({ pressed }) => getItemStyle(pressed),
1190
1452
  children: ({ pressed }) => {
1191
1453
  const contentColor = getContentColor(pressed);
1192
1454
  return /* @__PURE__ */ jsxs(Fragment, { children: [icon ? renderColoredNode(icon, contentColor) : null, /* @__PURE__ */ jsx(Text, {
@@ -1303,7 +1565,8 @@ const createStyles$16 = (theme) => StyleSheet.create({
1303
1565
  });
1304
1566
  //#endregion
1305
1567
  //#region src/components/Dropdown/Trigger/DropdownTrigger.tsx
1306
- function DropdownTrigger({ asChild = false, label, trigger, children, icon, arrowIcon, showArrow = true, color = "primary", size = "md", disabled = false, isOpen, triggerStyle, triggerRef, accessibilityLabel, accessibilityHint, onPress }) {
1568
+ function DropdownTrigger({ asChild = false, label, trigger, children, icon, arrowIcon, showArrow = true, triggerStyle, triggerRef, accessibilityLabel, accessibilityHint }) {
1569
+ const { open, color, disabled, size, toggle } = useDropdownContext();
1307
1570
  const { theme } = useTheme();
1308
1571
  const styles = useThemeStyles(createStyles$16);
1309
1572
  const colorPalette = theme.components.dropdown[color];
@@ -1320,14 +1583,14 @@ function DropdownTrigger({ asChild = false, label, trigger, children, icon, arro
1320
1583
  const hasIcon = Boolean(icon);
1321
1584
  const isIconOnly = !trigger && hasIcon && !showArrow;
1322
1585
  const [isPressed, setIsPressed] = useState(false);
1323
- const rotateAnim = useRef(new Animated.Value(isOpen ? 1 : 0)).current;
1586
+ const rotateAnim = useRef(new Animated.Value(open ? 1 : 0)).current;
1324
1587
  useEffect(() => {
1325
1588
  Animated.timing(rotateAnim, {
1326
- toValue: isOpen ? 1 : 0,
1589
+ toValue: open ? 1 : 0,
1327
1590
  duration: 180,
1328
1591
  useNativeDriver: Platform.OS !== "web"
1329
1592
  }).start();
1330
- }, [isOpen, rotateAnim]);
1593
+ }, [open, rotateAnim]);
1331
1594
  const arrowRotate = rotateAnim.interpolate({
1332
1595
  inputRange: [0, 1],
1333
1596
  outputRange: ["0deg", "180deg"]
@@ -1363,12 +1626,12 @@ function DropdownTrigger({ asChild = false, label, trigger, children, icon, arro
1363
1626
  accessibilityLabel: accessibilityLabel ?? (typeof label === "string" ? label : void 0),
1364
1627
  accessibilityHint,
1365
1628
  accessibilityState: {
1366
- expanded: isOpen,
1629
+ expanded: open,
1367
1630
  disabled: isChildDisabled
1368
1631
  },
1369
1632
  onPress: () => {
1370
1633
  child.props.onPress?.();
1371
- if (!isChildDisabled) onPress();
1634
+ if (!isChildDisabled) toggle();
1372
1635
  }
1373
1636
  });
1374
1637
  }
@@ -1379,10 +1642,10 @@ function DropdownTrigger({ asChild = false, label, trigger, children, icon, arro
1379
1642
  accessibilityLabel: accessibilityLabel ?? (typeof label === "string" ? label : void 0),
1380
1643
  accessibilityHint,
1381
1644
  accessibilityState: {
1382
- expanded: isOpen,
1645
+ expanded: open,
1383
1646
  disabled
1384
1647
  },
1385
- onPress,
1648
+ onPress: toggle,
1386
1649
  onPressIn: () => setIsPressed(true),
1387
1650
  onPressOut: () => setIsPressed(false),
1388
1651
  style: [
@@ -1429,8 +1692,6 @@ DropdownTrigger.displayName = "DropdownTrigger";
1429
1692
  function DropdownRoot({ children, label = "Menu", trigger, icon, arrowIcon, showArrow = true, open, defaultOpen = false, onOpenChange, presentation = "auto", placement = "bottom-start", offset = 8, closeOnSelect = true, color = "primary", disabled = false, loading = false, loadingText = "Loading actions...", searchable = false, command = false, searchValue, defaultSearchValue = "", searchPlaceholder, onSearch, empty, size = "md", style, triggerStyle, contentStyle, itemStyle, textStyle, accessibilityLabel, accessibilityHint }) {
1430
1693
  const styles = useThemeStyles(createStyles$20);
1431
1694
  const overlayId = useId();
1432
- const [uncontrolledSearchValue, setUncontrolledSearchValue] = useState(defaultSearchValue);
1433
- const resolvedSearchValue = searchValue ?? uncontrolledSearchValue;
1434
1695
  const triggerRef = useRef(null);
1435
1696
  const setTriggerRef = useCallback((node) => {
1436
1697
  if (node && typeof node === "object" && "measureInWindow" in node && typeof node.measureInWindow === "function") {
@@ -1440,29 +1701,26 @@ function DropdownRoot({ children, label = "Menu", trigger, icon, arrowIcon, show
1440
1701
  triggerRef.current = null;
1441
1702
  }, []);
1442
1703
  const parsed = useMemo(() => parseDropdownChildren(children), [children]);
1443
- const contentCommand = parsed.contentProps?.command ?? false;
1444
- const isSearchable = searchable || command || contentCommand || !!parsed.searchProps;
1704
+ const { contentCommand, filteredParsed, handleSearchChange, isSearchable, resolvedSearchValue } = useDropdownSearch({
1705
+ parsed,
1706
+ searchable,
1707
+ command,
1708
+ searchValue,
1709
+ defaultSearchValue,
1710
+ onSearch
1711
+ });
1712
+ const { navigableItems, data } = useDropdownEntries({
1713
+ parsed,
1714
+ filteredParsed,
1715
+ loading,
1716
+ loadingText,
1717
+ isSearchable,
1718
+ empty
1719
+ });
1445
1720
  const resolvedPresentation = useOverlayPresentation(presentation);
1446
1721
  const contentStyleFromSlot = parsed.contentProps?.style;
1447
1722
  const contentPresentation = (parsed.contentProps?.presentation === "auto" ? void 0 : parsed.contentProps?.presentation) ?? resolvedPresentation;
1448
1723
  const { position, updatePosition, onFloatingLayout } = useNativeFloatingPosition(placement, offset);
1449
- const menuAccessibilityLabel = useMemo(() => {
1450
- if (accessibilityLabel) return accessibilityLabel;
1451
- return typeof label === "string" ? label : "Menu";
1452
- }, [accessibilityLabel, label]);
1453
- const navigableItems = useMemo(() => parsed.items.map((item) => ({
1454
- disabled: item.disabled,
1455
- label: item.label,
1456
- value: item.id
1457
- })), [parsed.items]);
1458
- const filteredParsed = useMemo(() => {
1459
- if (!isSearchable || !resolvedSearchValue.trim()) return parsed;
1460
- return filterDropdownEntries(parsed, resolvedSearchValue);
1461
- }, [
1462
- isSearchable,
1463
- parsed,
1464
- resolvedSearchValue
1465
- ]);
1466
1724
  const { isOpen, closeDropdown, toggleDropdown } = useDropdown({
1467
1725
  items: navigableItems,
1468
1726
  open,
@@ -1472,6 +1730,11 @@ function DropdownRoot({ children, label = "Menu", trigger, icon, arrowIcon, show
1472
1730
  getItemValue: (item) => item.value,
1473
1731
  getItemText: (item) => typeof item.label === "string" ? item.label : item.value
1474
1732
  });
1733
+ const { menuAccessibilityLabel } = useDropdownAccessibility({
1734
+ accessibilityLabel,
1735
+ label,
1736
+ open: isOpen
1737
+ });
1475
1738
  useEffect(() => {
1476
1739
  if (!isOpen || contentPresentation !== "popover") return;
1477
1740
  updatePosition(triggerRef);
@@ -1480,11 +1743,10 @@ function DropdownRoot({ children, label = "Menu", trigger, icon, arrowIcon, show
1480
1743
  contentPresentation,
1481
1744
  updatePosition
1482
1745
  ]);
1483
- useEffect(() => {
1484
- if (!isOpen) return;
1485
- AccessibilityInfo.announceForAccessibility(`${menuAccessibilityLabel} opened`);
1486
- }, [isOpen, menuAccessibilityLabel]);
1487
- const { restoreFocusAfterClose } = useOverlayFocusRestore({ triggerRef });
1746
+ const { restoreFocusAfterClose } = useOverlayFocusRestore({
1747
+ active: isOpen,
1748
+ triggerRef
1749
+ });
1488
1750
  const closeAndFocusTrigger = useCallback(() => {
1489
1751
  closeDropdown();
1490
1752
  restoreFocusAfterClose();
@@ -1508,10 +1770,6 @@ function DropdownRoot({ children, label = "Menu", trigger, icon, arrowIcon, show
1508
1770
  const handleTriggerPress = useCallback(() => {
1509
1771
  toggleDropdown();
1510
1772
  }, [toggleDropdown]);
1511
- const handleSearchChange = useCallback((value) => {
1512
- if (searchValue === void 0) setUncontrolledSearchValue(value);
1513
- onSearch?.(value);
1514
- }, [onSearch, searchValue]);
1515
1773
  const renderEntry = useCallback(({ item }) => {
1516
1774
  if (item.type === "label") return /* @__PURE__ */ jsx(DropdownGroup, { label: item.props.children });
1517
1775
  if (item.type === "separator") return /* @__PURE__ */ jsx(DropdownSeparator, {});
@@ -1524,73 +1782,86 @@ function DropdownRoot({ children, label = "Menu", trigger, icon, arrowIcon, show
1524
1782
  children: item.props.children
1525
1783
  });
1526
1784
  return /* @__PURE__ */ jsx(DropdownItem, {
1785
+ asChild: item.props.asChild,
1527
1786
  label: item.props.children,
1528
1787
  value: item.props.value ?? item.id,
1529
- rootColor: color,
1530
1788
  color: item.props.color,
1531
1789
  icon: item.props.icon,
1532
1790
  disabled: item.props.disabled,
1533
1791
  textWrap: item.props.textWrap,
1534
- itemStyle,
1535
- textStyle,
1536
- onSelect: () => handleSelect(item)
1792
+ onSelect: () => handleSelect(item),
1793
+ children: item.props.children
1537
1794
  });
1538
- }, [
1539
- color,
1540
- handleSelect,
1541
- itemStyle,
1542
- styles.emptyText,
1543
- textStyle
1544
- ]);
1545
- const data = loading ? [{
1546
- type: "loading",
1547
- id: "loading",
1548
- props: { children: loadingText }
1549
- }] : isSearchable && filteredParsed.items.length === 0 ? [{
1550
- type: "empty",
1551
- id: "empty",
1552
- props: { children: empty ?? "No actions found" }
1553
- }] : filteredParsed.entries;
1554
- return /* @__PURE__ */ jsxs(View, {
1555
- style: [styles.root, style],
1556
- children: [/* @__PURE__ */ jsx(DropdownTrigger, {
1557
- asChild: Boolean(parsed.trigger),
1558
- label,
1559
- trigger: trigger ?? parsed.trigger,
1560
- icon,
1561
- arrowIcon,
1562
- showArrow,
1795
+ }, [handleSelect, styles.emptyText]);
1796
+ const resolvedSearchPlaceholder = parsed.searchProps?.placeholder ?? searchPlaceholder ?? (command || contentCommand ? "Type a command..." : "Search actions...");
1797
+ const searchAccessibilityLabel = parsed.searchProps?.accessibilityLabel;
1798
+ return /* @__PURE__ */ jsx(DropdownProvider, {
1799
+ value: useMemo(() => ({
1800
+ open: isOpen,
1801
+ disabled,
1802
+ loading,
1563
1803
  color,
1564
- disabled: disabled || parsed.triggerProps?.disabled,
1565
- isOpen,
1566
1804
  size,
1567
- triggerRef: setTriggerRef,
1568
- triggerStyle,
1569
- accessibilityLabel,
1570
- accessibilityHint,
1571
- onPress: handleTriggerPress
1572
- }), /* @__PURE__ */ jsx(DropdownContent, {
1573
- isOpen,
1574
- onClose: dismiss.requestClose,
1575
- color,
1576
- contentStyle: [contentStyle, contentStyleFromSlot],
1577
- accessibilityLabel: menuAccessibilityLabel,
1578
1805
  presentation: contentPresentation,
1579
1806
  position,
1580
- onFloatingLayout,
1807
+ zIndex: dismiss.zIndex,
1581
1808
  searchable: isSearchable,
1582
1809
  searchValue: resolvedSearchValue,
1583
- searchPlaceholder: parsed.searchProps?.placeholder ?? searchPlaceholder ?? (command || contentCommand ? "Type a command..." : "Search actions..."),
1584
- searchAccessibilityLabel: parsed.searchProps?.accessibilityLabel,
1810
+ searchPlaceholder: resolvedSearchPlaceholder,
1811
+ searchAccessibilityLabel,
1812
+ itemStyle,
1813
+ textStyle,
1814
+ requestClose: dismiss.requestClose,
1815
+ getOutsidePressProps: dismiss.getOutsidePressProps,
1816
+ toggle: handleTriggerPress,
1585
1817
  onSearchChange: handleSearchChange,
1586
- children: /* @__PURE__ */ jsx(FlatList, {
1587
- data,
1588
- keyExtractor: (item) => item.id,
1589
- renderItem: renderEntry,
1590
- keyboardShouldPersistTaps: "handled",
1591
- removeClippedSubviews: data.length > 24
1592
- })
1593
- })]
1818
+ onFloatingLayout
1819
+ }), [
1820
+ color,
1821
+ contentPresentation,
1822
+ disabled,
1823
+ dismiss.zIndex,
1824
+ dismiss.getOutsidePressProps,
1825
+ dismiss.requestClose,
1826
+ handleSearchChange,
1827
+ handleTriggerPress,
1828
+ isOpen,
1829
+ isSearchable,
1830
+ itemStyle,
1831
+ loading,
1832
+ onFloatingLayout,
1833
+ position,
1834
+ resolvedSearchPlaceholder,
1835
+ resolvedSearchValue,
1836
+ searchAccessibilityLabel,
1837
+ size,
1838
+ textStyle
1839
+ ]),
1840
+ children: /* @__PURE__ */ jsxs(View, {
1841
+ style: [styles.root, style],
1842
+ children: [/* @__PURE__ */ jsx(DropdownTrigger, {
1843
+ asChild: Boolean(parsed.trigger),
1844
+ label,
1845
+ trigger: trigger ?? parsed.trigger,
1846
+ icon,
1847
+ arrowIcon,
1848
+ showArrow,
1849
+ triggerRef: setTriggerRef,
1850
+ triggerStyle,
1851
+ accessibilityLabel,
1852
+ accessibilityHint
1853
+ }), /* @__PURE__ */ jsx(DropdownContent, {
1854
+ contentStyle: [contentStyle, contentStyleFromSlot],
1855
+ accessibilityLabel: menuAccessibilityLabel,
1856
+ children: /* @__PURE__ */ jsx(FlatList, {
1857
+ data,
1858
+ keyExtractor: (item) => item.id,
1859
+ renderItem: renderEntry,
1860
+ keyboardShouldPersistTaps: "handled",
1861
+ removeClippedSubviews: data.length > 24
1862
+ })
1863
+ })]
1864
+ })
1594
1865
  });
1595
1866
  }
1596
1867
  DropdownRoot.displayName = "Dropdown";
@@ -1662,13 +1933,23 @@ const createStyles$14 = (theme) => StyleSheet.create({
1662
1933
  justifyContent: "space-between",
1663
1934
  paddingBottom: theme.components.modal.header.paddingBottom
1664
1935
  },
1665
- title: {
1936
+ headerContent: {
1666
1937
  flex: 1,
1938
+ gap: theme.tokens.spacing["1"]
1939
+ },
1940
+ title: {
1667
1941
  color: theme.components.modal.title.fg,
1668
1942
  fontFamily: theme.tokens.typography.family.semibold,
1669
1943
  fontSize: theme.tokens.typography.size.lg,
1670
1944
  lineHeight: theme.tokens.typography.lineHeight.md
1671
1945
  },
1946
+ plainTitle: { flex: 1 },
1947
+ description: {
1948
+ color: theme.components.modal.description.fg,
1949
+ fontFamily: theme.tokens.typography.family.regular,
1950
+ fontSize: theme.tokens.typography.size.sm,
1951
+ lineHeight: theme.tokens.typography.lineHeight.sm
1952
+ },
1672
1953
  closeButton: {
1673
1954
  width: theme.components.modal.closeButton.size,
1674
1955
  height: theme.components.modal.closeButton.size,
@@ -1682,20 +1963,21 @@ const createStyles$14 = (theme) => StyleSheet.create({
1682
1963
  });
1683
1964
  //#endregion
1684
1965
  //#region src/components/Modal/internal/ModalContext.tsx
1685
- const ModalContext = createContext(void 0);
1686
- ModalContext.displayName = "ModalContext";
1966
+ const ModalContext = createContext(null);
1967
+ const ModalProvider = ModalContext.Provider;
1687
1968
  const useModalContext = () => {
1688
1969
  const context = useContext(ModalContext);
1689
1970
  if (!context) throw new Error("Modal compound components must be used inside Modal");
1690
1971
  return context;
1691
1972
  };
1973
+ ModalContext.displayName = "ModalContext";
1692
1974
  //#endregion
1693
1975
  //#region src/components/Modal/Close/ModalClose.tsx
1694
- const ModalClose = ({ children, accessibilityLabel, style }) => {
1976
+ const ModalClose = ({ asChild = false, children, accessibilityLabel, style }) => {
1695
1977
  const { theme } = useTheme();
1696
1978
  const styles = useThemeStyles(createStyles$14);
1697
1979
  const { onClose } = useModalContext();
1698
- if (isValidElement(children)) return cloneElement(children, {
1980
+ if ((asChild || children !== void 0) && isValidElement(children)) return cloneElement(children, {
1699
1981
  accessibilityLabel: children.props.accessibilityLabel ?? accessibilityLabel,
1700
1982
  onPress: (event) => {
1701
1983
  children.props.onPress?.(event);
@@ -1788,19 +2070,46 @@ const ModalFooter = ({ children, style }) => {
1788
2070
  };
1789
2071
  ModalFooter.displayName = "ModalFooter";
1790
2072
  //#endregion
2073
+ //#region src/components/Modal/Header/ModalDescription.tsx
2074
+ const ModalDescription = ({ children, style }) => {
2075
+ return /* @__PURE__ */ jsx(Text, {
2076
+ style: [useThemeStyles(createStyles$14).description, style],
2077
+ children
2078
+ });
2079
+ };
2080
+ ModalDescription.displayName = "Modal.Description";
2081
+ //#endregion
1791
2082
  //#region src/components/Modal/Header/ModalHeader.tsx
1792
2083
  const ModalHeader = ({ children, style, textStyle }) => {
1793
2084
  const styles = useThemeStyles(createStyles$14);
2085
+ const isPlainTitle = typeof children === "string" || typeof children === "number";
1794
2086
  return /* @__PURE__ */ jsxs(View, {
1795
2087
  style: [styles.header, style],
1796
- children: [/* @__PURE__ */ jsx(Text, {
1797
- style: [styles.title, textStyle],
2088
+ children: [isPlainTitle ? /* @__PURE__ */ jsx(Text, {
2089
+ style: [
2090
+ styles.title,
2091
+ styles.plainTitle,
2092
+ textStyle
2093
+ ],
2094
+ children
2095
+ }) : /* @__PURE__ */ jsx(View, {
2096
+ style: styles.headerContent,
1798
2097
  children
1799
2098
  }), /* @__PURE__ */ jsx(ModalClose, {})]
1800
2099
  });
1801
2100
  };
1802
2101
  ModalHeader.displayName = "ModalHeader";
1803
2102
  //#endregion
2103
+ //#region src/components/Modal/Header/ModalTitle.tsx
2104
+ const ModalTitle = ({ children, style }) => {
2105
+ return /* @__PURE__ */ jsx(Text, {
2106
+ accessibilityRole: "header",
2107
+ style: [useThemeStyles(createStyles$14).title, style],
2108
+ children
2109
+ });
2110
+ };
2111
+ ModalTitle.displayName = "Modal.Title";
2112
+ //#endregion
1804
2113
  //#region src/components/Modal/Modal.styles.ts
1805
2114
  const createStyles$11 = (theme) => StyleSheet.create({
1806
2115
  overlay: {
@@ -1818,7 +2127,7 @@ const createStyles$11 = (theme) => StyleSheet.create({
1818
2127
  //#region src/components/Modal/Overlay/ModalOverlay.tsx
1819
2128
  const ModalOverlay = ({ children, overlayStyle }) => {
1820
2129
  const styles = useThemeStyles(createStyles$11);
1821
- const { animation, animationProgress, closeOnOutsidePress, onClose, onOutsideClose, shouldRender } = useModalContext();
2130
+ const { animation, animationProgress, zIndex, onClose, getOutsidePressProps, shouldRender } = useModalContext();
1822
2131
  const backdropStyle = animation === "none" ? void 0 : { opacity: animationProgress };
1823
2132
  return /* @__PURE__ */ jsx(Modal$1, {
1824
2133
  visible: shouldRender,
@@ -1826,22 +2135,24 @@ const ModalOverlay = ({ children, overlayStyle }) => {
1826
2135
  animationType: "none",
1827
2136
  onRequestClose: onClose,
1828
2137
  children: /* @__PURE__ */ jsxs(View, {
1829
- style: [styles.overlay, overlayStyle],
2138
+ style: [
2139
+ styles.overlay,
2140
+ Platform.OS === "web" && { zIndex },
2141
+ overlayStyle
2142
+ ],
1830
2143
  children: [/* @__PURE__ */ jsx(Animated.View, {
1831
2144
  style: [styles.backdrop, backdropStyle],
1832
2145
  children: /* @__PURE__ */ jsx(Pressable, {
1833
2146
  testID: "modal-backdrop",
1834
- accessibilityRole: closeOnOutsidePress ? "button" : void 0,
1835
- accessibilityLabel: closeOnOutsidePress ? "Close modal" : void 0,
1836
- style: StyleSheet.absoluteFill,
1837
- onPress: closeOnOutsidePress ? onOutsideClose : void 0
2147
+ ...getOutsidePressProps({ accessibilityLabel: "Close modal" }),
2148
+ style: StyleSheet.absoluteFill
1838
2149
  })
1839
2150
  }), children]
1840
2151
  })
1841
2152
  });
1842
2153
  };
1843
2154
  //#endregion
1844
- //#region src/components/Modal/Root/ModalRoot.tsx
2155
+ //#region src/components/Modal/Root/useModalRootAnimation.ts
1845
2156
  const linearEasing = (value) => value;
1846
2157
  const easingMap = {
1847
2158
  standard: Easing?.bezier?.(.22, 1, .36, 1) ?? linearEasing,
@@ -1862,23 +2173,10 @@ const resolveDuration = (duration) => {
1862
2173
  open: duration?.open ?? parseDuration(nativeThemes.light.components.modal.motion.openDuration)
1863
2174
  };
1864
2175
  };
1865
- const ModalRoot = ({ open, defaultOpen = false, onOpenChange, animation = "scale", duration, easing = "standard", closeOnOutsidePress = true, children }) => {
1866
- const initialOpen = open ?? defaultOpen;
1867
- const animationProgress = useRef(new Animated.Value(initialOpen ? 1 : 0));
1868
- const [shouldRender, setShouldRender] = useState(initialOpen);
2176
+ function useModalRootAnimation({ animation, defaultOpen, duration, easing, open }) {
2177
+ const animationProgress = useRef(new Animated.Value(defaultOpen ? 1 : 0));
2178
+ const [shouldRender, setShouldRender] = useState(defaultOpen);
1869
2179
  const [reduceMotion, setReduceMotion] = useState(false);
1870
- const modal = useModal({
1871
- open,
1872
- defaultOpen,
1873
- onOpenChange,
1874
- closeOnOutsidePress
1875
- });
1876
- const dismiss = useOverlayDismiss({
1877
- id: modal.contentId,
1878
- active: modal.open,
1879
- closeOnOutsidePress: modal.closeOnOutsidePress,
1880
- requestClose: modal.requestClose
1881
- });
1882
2180
  const animationDuration = resolveDuration(duration);
1883
2181
  const shouldAnimate = animation !== "none" && !reduceMotion;
1884
2182
  useEffect(() => {
@@ -1890,7 +2188,7 @@ const ModalRoot = ({ open, defaultOpen = false, onOpenChange, animation = "scale
1890
2188
  }, []);
1891
2189
  useEffect(() => {
1892
2190
  const progress = animationProgress.current;
1893
- if (modal.open) {
2191
+ if (open) {
1894
2192
  setShouldRender(true);
1895
2193
  if (!shouldAnimate) {
1896
2194
  progress.setValue(1);
@@ -1922,19 +2220,61 @@ const ModalRoot = ({ open, defaultOpen = false, onOpenChange, animation = "scale
1922
2220
  animationDuration.close,
1923
2221
  animationDuration.open,
1924
2222
  easing,
1925
- modal.open,
2223
+ open,
1926
2224
  shouldAnimate
1927
2225
  ]);
1928
- return /* @__PURE__ */ jsx(ModalContext.Provider, {
2226
+ return {
2227
+ animationProgress: animationProgress.current,
2228
+ shouldRender
2229
+ };
2230
+ }
2231
+ //#endregion
2232
+ //#region src/components/Modal/Root/ModalRoot.tsx
2233
+ const ModalRoot = ({ open, defaultOpen = false, onOpenChange, animation = "scale", duration, easing = "standard", closeOnEscape = true, closeOnOutsidePress = true, restoreFocus = true, children }) => {
2234
+ const initialOpen = open ?? defaultOpen;
2235
+ const triggerRef = useRef(null);
2236
+ const modal = useModal({
2237
+ open,
2238
+ defaultOpen,
2239
+ onOpenChange,
2240
+ closeOnEscape,
2241
+ closeOnOutsidePress
2242
+ });
2243
+ const { animationProgress, shouldRender } = useModalRootAnimation({
2244
+ animation,
2245
+ defaultOpen: initialOpen,
2246
+ duration,
2247
+ easing,
2248
+ open: modal.open
2249
+ });
2250
+ const { restoreFocusAfterClose } = useOverlayFocusRestore({
2251
+ active: modal.open,
2252
+ enabled: restoreFocus,
2253
+ triggerRef
2254
+ });
2255
+ const previousOpenRef = useRef(modal.open);
2256
+ useEffect(() => {
2257
+ if (previousOpenRef.current && !modal.open) restoreFocusAfterClose();
2258
+ previousOpenRef.current = modal.open;
2259
+ }, [modal.open, restoreFocusAfterClose]);
2260
+ const dismiss = useOverlayDismiss({
2261
+ id: modal.contentId,
2262
+ active: modal.open,
2263
+ closeOnEscape: modal.closeOnEscape,
2264
+ closeOnOutsidePress: modal.closeOnOutsidePress,
2265
+ requestClose: modal.requestClose
2266
+ });
2267
+ return /* @__PURE__ */ jsx(ModalProvider, {
1929
2268
  value: {
1930
2269
  animation,
1931
- animationProgress: animationProgress.current,
1932
- closeOnOutsidePress: modal.closeOnOutsidePress,
2270
+ animationProgress,
2271
+ zIndex: dismiss.zIndex,
1933
2272
  onClose: dismiss.requestClose,
1934
- onOutsideClose: dismiss.requestOutsideClose,
2273
+ getOutsidePressProps: dismiss.getOutsidePressProps,
1935
2274
  open: modal.open,
1936
2275
  setOpen: modal.setOpen,
1937
- shouldRender
2276
+ shouldRender,
2277
+ triggerRef
1938
2278
  },
1939
2279
  children
1940
2280
  });
@@ -1942,15 +2282,26 @@ const ModalRoot = ({ open, defaultOpen = false, onOpenChange, animation = "scale
1942
2282
  ModalRoot.displayName = "ModalRoot";
1943
2283
  //#endregion
1944
2284
  //#region src/components/Modal/Trigger/ModalTrigger.tsx
2285
+ const composeRefs = (...refs) => (node) => {
2286
+ for (const ref of refs) {
2287
+ if (typeof ref === "function") {
2288
+ ref(node);
2289
+ continue;
2290
+ }
2291
+ if (ref) ref.current = node;
2292
+ }
2293
+ };
1945
2294
  const ModalTrigger = ({ children, asChild = false, disabled = false, accessibilityLabel, style, testID }) => {
1946
2295
  const root = useModalContext();
1947
2296
  const child = asChild && isValidElement(children) ? children : void 0;
2297
+ const composedTriggerRef = child ? composeRefs(root.triggerRef, child.props.ref) : root.triggerRef;
1948
2298
  const handlePress = (event) => {
1949
2299
  if (disabled || child?.props.disabled) return;
1950
2300
  child?.props.onPress?.(event);
1951
2301
  root.setOpen(true);
1952
2302
  };
1953
2303
  if (child) return cloneElement(child, {
2304
+ ref: composedTriggerRef,
1954
2305
  onPress: handlePress,
1955
2306
  accessibilityRole: child.props.accessibilityRole ?? "button",
1956
2307
  accessibilityState: {
@@ -1963,6 +2314,7 @@ const ModalTrigger = ({ children, asChild = false, disabled = false, accessibili
1963
2314
  style: child.props.style
1964
2315
  });
1965
2316
  return /* @__PURE__ */ jsx(Pressable, {
2317
+ ref: root.triggerRef,
1966
2318
  accessibilityRole: "button",
1967
2319
  accessibilityState: {
1968
2320
  expanded: root.open,
@@ -1984,6 +2336,8 @@ const Modal = Object.assign(ModalRoot, {
1984
2336
  Overlay: ModalOverlay,
1985
2337
  Content: ModalContent,
1986
2338
  Header: ModalHeader,
2339
+ Title: ModalTitle,
2340
+ Description: ModalDescription,
1987
2341
  Body: ModalBody,
1988
2342
  Footer: ModalFooter,
1989
2343
  Close: ModalClose
@@ -2132,7 +2486,7 @@ PortalProvider.displayName = "PortalProvider";
2132
2486
  //#endregion
2133
2487
  //#region src/components/Popover/Content/PopoverContent.styles.ts
2134
2488
  const styles = StyleSheet.create({
2135
- layer: { flex: 1 },
2489
+ root: { flex: 1 },
2136
2490
  backdrop: StyleSheet.absoluteFill,
2137
2491
  content: { position: "absolute" }
2138
2492
  });
@@ -2164,7 +2518,7 @@ function PopoverContent({ children, style, ...contentProps }) {
2164
2518
  const { theme } = useTheme();
2165
2519
  const layerRef = useRef(null);
2166
2520
  const themedStyles = useMemo(() => createPopoverContentStyles(theme), [theme]);
2167
- const { open, position, onFloatingLayout, updatePosition, setOpen, closeOnOutsidePress } = usePopoverContext("Popover.Content");
2521
+ const { open, zIndex, position, onFloatingLayout, updatePosition, requestClose, getOutsidePressProps } = usePopoverContext("Popover.Content");
2168
2522
  useEffect(() => {
2169
2523
  if (!open) return;
2170
2524
  requestAnimationFrame(() => {
@@ -2173,20 +2527,14 @@ function PopoverContent({ children, style, ...contentProps }) {
2173
2527
  }, [open, updatePosition]);
2174
2528
  return /* @__PURE__ */ jsx(Portal, {
2175
2529
  visible: open,
2176
- onRequestClose: () => {
2177
- setOpen(false, { reason: "escape-key" });
2178
- },
2530
+ onRequestClose: requestClose,
2179
2531
  children: /* @__PURE__ */ jsxs(View, {
2180
2532
  ref: layerRef,
2181
2533
  pointerEvents: "box-none",
2182
- style: styles.layer,
2534
+ style: [styles.root, { zIndex }],
2183
2535
  children: [/* @__PURE__ */ jsx(Pressable, {
2184
2536
  testID: "popover-backdrop",
2185
- accessibilityLabel: closeOnOutsidePress ? "Close popover" : void 0,
2186
- accessibilityRole: closeOnOutsidePress ? "button" : void 0,
2187
- onPress: closeOnOutsidePress ? () => {
2188
- setOpen(false, { reason: "outside-press" });
2189
- } : void 0,
2537
+ ...getOutsidePressProps({ accessibilityLabel: "Close popover" }),
2190
2538
  style: styles.backdrop
2191
2539
  }), /* @__PURE__ */ jsx(View, {
2192
2540
  ...contentProps,
@@ -2232,6 +2580,7 @@ function getNativePlacement(side, align) {
2232
2580
  function PopoverRoot({ children, open: openProp, defaultOpen = false, onOpenChange, side = "bottom", align = "center", sideOffset = 8, closeOnOutsidePress = true }) {
2233
2581
  const triggerRef = useRef(null);
2234
2582
  const anchorRef = useRef(null);
2583
+ const overlayId = useId();
2235
2584
  const getReferenceRef = useCallback(() => anchorRef.current ? anchorRef : triggerRef, []);
2236
2585
  const openChangeDetailsRef = useRef({ reason: "programmatic" });
2237
2586
  const [open, setOpenState] = useControllableState({
@@ -2241,26 +2590,45 @@ function PopoverRoot({ children, open: openProp, defaultOpen = false, onOpenChan
2241
2590
  onOpenChange?.(nextOpen, openChangeDetailsRef.current);
2242
2591
  }
2243
2592
  });
2593
+ const { restoreFocusAfterClose } = useOverlayFocusRestore({
2594
+ active: open,
2595
+ triggerRef
2596
+ });
2244
2597
  const { position, arrowPosition, placement, updatePosition: updateFloatingPosition, onFloatingLayout } = useNativeFloatingPosition(getNativePlacement(side, align), sideOffset);
2245
2598
  const setOpen = useCallback((nextOpen, details) => {
2246
2599
  openChangeDetailsRef.current = details;
2247
2600
  setOpenState(nextOpen);
2248
- }, [setOpenState]);
2601
+ if (!nextOpen) restoreFocusAfterClose();
2602
+ }, [restoreFocusAfterClose, setOpenState]);
2603
+ const dismiss = useOverlayDismiss({
2604
+ id: overlayId,
2605
+ active: open,
2606
+ closeOnOutsidePress,
2607
+ requestClose: () => {
2608
+ setOpen(false, { reason: "escape-key" });
2609
+ },
2610
+ requestOutsideClose: () => {
2611
+ setOpen(false, { reason: "outside-press" });
2612
+ }
2613
+ });
2614
+ const updatePosition = useCallback((containerRef) => {
2615
+ updateFloatingPosition(getReferenceRef(), containerRef);
2616
+ }, [getReferenceRef, updateFloatingPosition]);
2249
2617
  return /* @__PURE__ */ jsx(PopoverProvider, {
2250
2618
  value: {
2251
2619
  open,
2252
- closeOnOutsidePress,
2253
2620
  triggerRef,
2254
2621
  anchorRef,
2255
2622
  side,
2256
2623
  align,
2257
2624
  placement,
2625
+ zIndex: dismiss.zIndex,
2258
2626
  position,
2259
2627
  arrowPosition,
2628
+ requestClose: dismiss.requestClose,
2629
+ getOutsidePressProps: dismiss.getOutsidePressProps,
2260
2630
  onFloatingLayout,
2261
- updatePosition: useCallback((containerRef) => {
2262
- updateFloatingPosition(getReferenceRef(), containerRef);
2263
- }, [getReferenceRef, updateFloatingPosition]),
2631
+ updatePosition,
2264
2632
  setOpen
2265
2633
  },
2266
2634
  children
@@ -2384,8 +2752,8 @@ const createStyles$10 = (theme) => StyleSheet.create({
2384
2752
  });
2385
2753
  //#endregion
2386
2754
  //#region src/primitives/Radio/Radio.tsx
2387
- const nativePointerEventsNone$4 = Platform.OS === "web" ? void 0 : { pointerEvents: "none" };
2388
- const webPointerEventsNone$4 = Platform.OS === "web" ? { pointerEvents: "none" } : void 0;
2755
+ const nativePointerEventsNone$3 = Platform.OS === "web" ? void 0 : { pointerEvents: "none" };
2756
+ const webPointerEventsNone$3 = Platform.OS === "web" ? { pointerEvents: "none" } : void 0;
2389
2757
  const Radio = forwardRef(({ value, checked, defaultChecked = false, disabled: disabledProp = false, required: requiredProp = false, size: sizeProp, color: colorProp, onCheckedChange, label, description, icon, error, accessibilityLabel, accessibilityHint, containerStyle, labelStyle, descriptionStyle, errorStyle, style, ...rest }, ref) => {
2390
2758
  const { theme } = useTheme();
2391
2759
  const styles = createStyles$10(theme);
@@ -2444,10 +2812,10 @@ const Radio = forwardRef(({ value, checked, defaultChecked = false, disabled: di
2444
2812
  onPress: handlePress,
2445
2813
  style: resolvePressableStyle,
2446
2814
  children: (state) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(View, {
2447
- ...nativePointerEventsNone$4,
2815
+ ...nativePointerEventsNone$3,
2448
2816
  style: [
2449
2817
  styles.control,
2450
- webPointerEventsNone$4,
2818
+ webPointerEventsNone$3,
2451
2819
  {
2452
2820
  width: radioSize.controlSize,
2453
2821
  height: radioSize.controlSize,
@@ -2476,8 +2844,8 @@ const Radio = forwardRef(({ value, checked, defaultChecked = false, disabled: di
2476
2844
  resolvedDisabled && styles.indicatorDisabled
2477
2845
  ] }))
2478
2846
  }), (label || description) && /* @__PURE__ */ jsxs(View, {
2479
- ...nativePointerEventsNone$4,
2480
- style: [styles.content, webPointerEventsNone$4],
2847
+ ...nativePointerEventsNone$3,
2848
+ style: [styles.content, webPointerEventsNone$3],
2481
2849
  children: [label && (typeof label === "string" ? /* @__PURE__ */ jsx(Text, {
2482
2850
  style: [
2483
2851
  styles.label,
@@ -2884,23 +3252,92 @@ const RadioGroupRoot = forwardRef(({ value, defaultValue = "", onValueChange, di
2884
3252
  })
2885
3253
  });
2886
3254
  });
2887
- RadioGroupRoot.displayName = "RadioGroup.Root";
2888
- //#endregion
2889
- //#region src/components/RadioGroup/RadioGroup.tsx
2890
- const RadioGroup = Object.assign(RadioGroupRoot, { Item: RadioGroupItem });
2891
- RadioGroup.displayName = "RadioGroup";
2892
- //#endregion
2893
- //#region src/components/Select/internal/types.ts
2894
- const selectSlotName = Symbol("VelliraNativeSelectSlot");
3255
+ RadioGroupRoot.displayName = "RadioGroup.Root";
3256
+ //#endregion
3257
+ //#region src/components/RadioGroup/RadioGroup.tsx
3258
+ const RadioGroup = Object.assign(RadioGroupRoot, { Item: RadioGroupItem });
3259
+ RadioGroup.displayName = "RadioGroup";
3260
+ //#endregion
3261
+ //#region src/components/Select/Content/SelectContent.styles.ts
3262
+ const createContentStyles = (theme) => StyleSheet.create({
3263
+ toolbar: {
3264
+ minHeight: 52,
3265
+ flexDirection: "row",
3266
+ alignItems: "center",
3267
+ justifyContent: "space-between",
3268
+ paddingHorizontal: theme.tokens.spacing[4],
3269
+ borderBottomColor: theme.components.select.dropdown.separator.bg,
3270
+ borderBottomWidth: 1
3271
+ },
3272
+ title: {
3273
+ flex: 1,
3274
+ marginHorizontal: theme.tokens.spacing[3],
3275
+ color: theme.components.select.dropdown.fg,
3276
+ fontFamily: theme.tokens.typography.family.medium,
3277
+ fontSize: theme.tokens.typography.size.md,
3278
+ lineHeight: theme.tokens.typography.lineHeight.md,
3279
+ textAlign: "center"
3280
+ },
3281
+ toolbarAction: {
3282
+ minWidth: 64,
3283
+ minHeight: 44,
3284
+ alignItems: "center",
3285
+ justifyContent: "center"
3286
+ },
3287
+ cancelText: {
3288
+ color: theme.components.select.trigger.placeholder.fg,
3289
+ fontFamily: theme.tokens.typography.family.medium,
3290
+ fontSize: theme.tokens.typography.size.md,
3291
+ lineHeight: theme.tokens.typography.lineHeight.md
3292
+ },
3293
+ doneText: {
3294
+ color: theme.semantic.text.interactive,
3295
+ fontFamily: theme.tokens.typography.family.medium,
3296
+ fontSize: theme.tokens.typography.size.md,
3297
+ lineHeight: theme.tokens.typography.lineHeight.md
3298
+ },
3299
+ list: { maxHeight: 420 },
3300
+ listContent: {
3301
+ paddingHorizontal: theme.tokens.spacing[2],
3302
+ paddingVertical: theme.tokens.spacing[2]
3303
+ },
3304
+ empty: {
3305
+ minHeight: 72,
3306
+ alignItems: "center",
3307
+ justifyContent: "center",
3308
+ padding: theme.tokens.spacing[4]
3309
+ },
3310
+ emptyText: {
3311
+ color: theme.components.select.dropdown.empty.fg,
3312
+ fontFamily: theme.tokens.typography.family.regular,
3313
+ fontSize: theme.tokens.typography.size.md,
3314
+ lineHeight: theme.tokens.typography.lineHeight.md,
3315
+ textAlign: "center"
3316
+ },
3317
+ loading: {
3318
+ minHeight: 72,
3319
+ flexDirection: "row",
3320
+ alignItems: "center",
3321
+ justifyContent: "center",
3322
+ gap: theme.tokens.spacing[2],
3323
+ padding: theme.tokens.spacing[4]
3324
+ },
3325
+ loadingText: {
3326
+ color: theme.components.select.dropdown.fg,
3327
+ fontFamily: theme.tokens.typography.family.regular,
3328
+ fontSize: theme.tokens.typography.size.md,
3329
+ lineHeight: theme.tokens.typography.lineHeight.md
3330
+ }
3331
+ });
2895
3332
  //#endregion
2896
3333
  //#region src/components/Select/internal/SelectCollection.ts
2897
3334
  const createSelectSlot = (name, displayName) => {
2898
3335
  const Slot = (_props) => null;
2899
- Slot[selectSlotName] = name;
3336
+ markCompoundSlot(Slot, name);
2900
3337
  Slot.displayName = displayName;
2901
3338
  return Slot;
2902
3339
  };
2903
- const getSelectSlot = (type) => type?.[selectSlotName];
3340
+ const getSelectSlot = (type) => getCompoundSlot(type);
2904
3341
  const defaultSelectFilter = (option, query) => option.label.toLowerCase().includes(query.trim().toLowerCase());
2905
3342
  const getTextFromNode = (node) => {
2906
3343
  if (typeof node === "string" || typeof node === "number") return String(node);
@@ -2989,6 +3426,8 @@ const parseSelectChildren = (children) => {
2989
3426
  const option = {
2990
3427
  value: props.value,
2991
3428
  label: props.label,
3429
+ asChild: props.asChild,
3430
+ children: props.children,
2992
3431
  disabled: props.disabled,
2993
3432
  description: props.description,
2994
3433
  icon: props.icon,
@@ -3017,6 +3456,33 @@ const parseSelectChildren = (children) => {
3017
3456
  };
3018
3457
  };
3019
3458
  //#endregion
3459
+ //#region src/components/Select/internal/SelectContext.tsx
3460
+ const SelectContext = createContext(null);
3461
+ const useSelectContext = () => {
3462
+ const context = useContext(SelectContext);
3463
+ if (!context) throw new Error("Select compound components must be used inside Select");
3464
+ return context;
3465
+ };
3466
+ //#endregion
3467
+ //#region src/components/Select/Empty/SelectEmpty.tsx
3468
+ const renderText$1 = (node, style) => {
3469
+ if (typeof node === "string" || typeof node === "number") return /* @__PURE__ */ jsx(Text, {
3470
+ style,
3471
+ children: node
3472
+ });
3473
+ return node;
3474
+ };
3475
+ const SelectEmpty = createSelectSlot("empty", "Select.Empty");
3476
+ const SelectEmptyState = () => {
3477
+ const styles = useThemeStyles(createContentStyles);
3478
+ const { empty } = useSelectContext();
3479
+ return /* @__PURE__ */ jsx(View, {
3480
+ style: styles.empty,
3481
+ children: renderText$1(empty, styles.emptyText)
3482
+ });
3483
+ };
3484
+ SelectEmptyState.displayName = "Select.EmptyState";
3485
+ //#endregion
3020
3486
  //#region src/components/Select/Group/SelectGroup.styles.ts
3021
3487
  const createGroupStyles = (theme) => StyleSheet.create({
3022
3488
  groupLabel: {
@@ -3100,24 +3566,6 @@ const SelectGroupActionRow = ({ label, selectLabel, selectedCount, itemCount, on
3100
3566
  SelectGroupLabelRow.displayName = "Select.GroupLabelRow";
3101
3567
  SelectGroupActionRow.displayName = "Select.GroupActionRow";
3102
3568
  //#endregion
3103
- //#region src/components/Select/Group/SelectLabel.tsx
3104
- const SelectLabel = createSelectSlot("label", "Select.Label");
3105
- //#endregion
3106
- //#region src/components/Select/Group/SelectSeparator.tsx
3107
- const SelectSeparator = createSelectSlot("separator", "Select.Separator");
3108
- const SelectSeparatorRow = () => {
3109
- return /* @__PURE__ */ jsx(View, { style: useThemeStyles(createGroupStyles).separator });
3110
- };
3111
- SelectSeparatorRow.displayName = "Select.SeparatorRow";
3112
- //#endregion
3113
- //#region src/components/Select/internal/SelectContext.tsx
3114
- const SelectContext = createContext(null);
3115
- const useSelectContext = () => {
3116
- const context = useContext(SelectContext);
3117
- if (!context) throw new Error("Select compound components must be used inside Select");
3118
- return context;
3119
- };
3120
- //#endregion
3121
3569
  //#region src/components/Select/Item/SelectItem.styles.ts
3122
3570
  const createItemStyles = (theme) => StyleSheet.create({
3123
3571
  option: {
@@ -3187,17 +3635,54 @@ const renderNodeOrText = (node, textStyle, fallback) => {
3187
3635
  }) : null);
3188
3636
  };
3189
3637
  const SelectItem = createSelectSlot("item", "Select.Item");
3190
- const SelectItemRow = ({ option, isSelected, isDisabled, optionStyle, onSelect }) => {
3638
+ const SelectItemRow = ({ option, isSelected, isDisabled, itemIndex, selectedValues, multiple, optionStyle, onSelect }) => {
3191
3639
  const { theme } = useTheme();
3192
3640
  const styles = useThemeStyles(createItemStyles);
3193
3641
  const { color, variant, renderOption } = useSelectContext();
3194
3642
  const [isHovered, setIsHovered] = useState(false);
3643
+ const child = option.asChild && isValidElement(option.children) ? option.children : void 0;
3195
3644
  const optionPalette = theme.components.select[option.color ?? color][variant].option;
3196
3645
  const getOptionState = (pressed) => {
3197
3646
  if (isDisabled) return theme.components.select.option.disabled;
3198
3647
  if (isSelected) return pressed ? optionPalette.selectedPressed : isHovered ? optionPalette.selectedHover : optionPalette.selected;
3199
3648
  return pressed ? optionPalette.pressed : isHovered ? optionPalette.hover : theme.components.select.option.default;
3200
3649
  };
3650
+ const getOptionStyle = (pressed) => {
3651
+ const optionState = getOptionState(pressed);
3652
+ return [
3653
+ styles.option,
3654
+ {
3655
+ backgroundColor: optionState.bg,
3656
+ borderColor: optionState.border
3657
+ },
3658
+ isDisabled && styles.optionDisabled,
3659
+ optionStyle
3660
+ ];
3661
+ };
3662
+ devWarning(!option.asChild || Boolean(child), "Select.Item: asChild requires a single valid React element child.");
3663
+ if (child) return cloneElement(child, {
3664
+ accessibilityRole: "button",
3665
+ accessibilityLabel: option.accessibilityLabel ?? option.label,
3666
+ accessibilityHint: option.accessibilityHint,
3667
+ accessibilityState: {
3668
+ selected: isSelected,
3669
+ disabled: isDisabled
3670
+ },
3671
+ disabled: isDisabled,
3672
+ onPress: (event) => {
3673
+ child.props.onPress?.(event);
3674
+ if (!event.defaultPrevented && !isDisabled) onSelect(option);
3675
+ },
3676
+ onHoverIn: () => {
3677
+ child.props.onHoverIn?.();
3678
+ setIsHovered(true);
3679
+ },
3680
+ onHoverOut: () => {
3681
+ child.props.onHoverOut?.();
3682
+ setIsHovered(false);
3683
+ },
3684
+ style: [getOptionStyle(false), child.props.style]
3685
+ });
3201
3686
  return /* @__PURE__ */ jsx(Pressable, {
3202
3687
  disabled: isDisabled,
3203
3688
  accessibilityRole: "button",
@@ -3210,24 +3695,19 @@ const SelectItemRow = ({ option, isSelected, isDisabled, optionStyle, onSelect }
3210
3695
  onPress: () => onSelect(option),
3211
3696
  onHoverIn: () => setIsHovered(true),
3212
3697
  onHoverOut: () => setIsHovered(false),
3213
- style: ({ pressed }) => {
3214
- const optionState = getOptionState(pressed);
3215
- return [
3216
- styles.option,
3217
- {
3218
- backgroundColor: optionState.bg,
3219
- borderColor: optionState.border
3220
- },
3221
- isDisabled && styles.optionDisabled,
3222
- optionStyle
3223
- ];
3224
- },
3698
+ style: ({ pressed }) => getOptionStyle(pressed),
3225
3699
  children: ({ pressed }) => {
3226
3700
  const optionFg = getOptionState(pressed).fg;
3227
3701
  const descriptionFg = isSelected || pressed ? optionFg : theme.components.select.option.description.fg;
3228
- return renderOption ? renderNodeOrText(renderOption(option, {
3702
+ return renderOption ? renderNodeOrText(renderOption({
3703
+ option,
3229
3704
  selected: isSelected,
3230
- disabled: isDisabled
3705
+ disabled: isDisabled,
3706
+ active: isHovered,
3707
+ index: itemIndex,
3708
+ values: selectedValues,
3709
+ multiple,
3710
+ pressed
3231
3711
  }), [styles.optionLabel, { color: optionFg }]) : /* @__PURE__ */ jsxs(Fragment, { children: [
3232
3712
  option.icon && /* @__PURE__ */ jsx(View, {
3233
3713
  style: styles.optionIcon,
@@ -3269,14 +3749,29 @@ const SelectItemRow = ({ option, isSelected, isDisabled, optionStyle, onSelect }
3269
3749
  };
3270
3750
  SelectItemRow.displayName = "Select.ItemRow";
3271
3751
  //#endregion
3272
- //#region src/components/Select/Item/SelectItemBadge.tsx
3273
- const SelectItemBadge = createSelectSlot("itemBadge", "Select.ItemBadge");
3274
- //#endregion
3275
- //#region src/components/Select/Item/SelectItemDescription.tsx
3276
- const SelectItemDescription = createSelectSlot("itemDescription", "Select.ItemDescription");
3277
- //#endregion
3278
- //#region src/components/Select/Item/SelectItemIcon.tsx
3279
- const SelectItemIcon = createSelectSlot("itemIcon", "Select.ItemIcon");
3752
+ //#region src/components/Select/Loading/SelectLoading.tsx
3753
+ const renderText = (node, style) => {
3754
+ if (typeof node === "string" || typeof node === "number") return /* @__PURE__ */ jsx(Text, {
3755
+ style,
3756
+ children: node
3757
+ });
3758
+ return node;
3759
+ };
3760
+ const SelectLoading = createSelectSlot("loading", "Select.Loading");
3761
+ const SelectLoadingState = () => {
3762
+ const { theme } = useTheme();
3763
+ const styles = useThemeStyles(createContentStyles);
3764
+ const { loadingContent } = useSelectContext();
3765
+ return /* @__PURE__ */ jsxs(View, {
3766
+ style: styles.loading,
3767
+ children: [/* @__PURE__ */ jsx(ActivityIndicator, {
3768
+ testID: "select-content-loading-indicator",
3769
+ size: "small",
3770
+ color: theme.components.select.dropdown.fg
3771
+ }), renderText(loadingContent, styles.loadingText)]
3772
+ });
3773
+ };
3774
+ SelectLoadingState.displayName = "Select.LoadingState";
3280
3775
  //#endregion
3281
3776
  //#region src/components/Select/Presentation/SelectPresentation.styles.ts
3282
3777
  const createPresentationStyles = (theme) => StyleSheet.create({
@@ -3328,12 +3823,11 @@ const createPresentationStyles = (theme) => StyleSheet.create({
3328
3823
  });
3329
3824
  //#endregion
3330
3825
  //#region src/components/Select/Presentation/SelectBackdrop.tsx
3331
- const SelectBackdrop = ({ onClose, dismissOnBackdropPress }) => {
3826
+ const SelectBackdrop = ({ outsidePressProps }) => {
3827
+ const styles = useThemeStyles(createPresentationStyles);
3332
3828
  return /* @__PURE__ */ jsx(Pressable, {
3333
- style: useThemeStyles(createPresentationStyles).backdrop,
3334
- onPress: dismissOnBackdropPress ? onClose : void 0,
3335
- accessibilityRole: "button",
3336
- accessibilityLabel: "Dismiss select"
3829
+ ...outsidePressProps,
3830
+ style: styles.backdrop
3337
3831
  });
3338
3832
  };
3339
3833
  SelectBackdrop.displayName = "Select.Backdrop";
@@ -3350,7 +3844,7 @@ const SelectHandle = () => {
3350
3844
  SelectHandle.displayName = "Select.Handle";
3351
3845
  //#endregion
3352
3846
  //#region src/components/Select/Presentation/SelectModal.tsx
3353
- const SelectModal = ({ visible, onClose, dismissOnBackdropPress, contentStyle, children }) => {
3847
+ const SelectModal = ({ visible, onClose, outsidePressProps, zIndex, contentStyle, children }) => {
3354
3848
  const styles = useThemeStyles(createPresentationStyles);
3355
3849
  return /* @__PURE__ */ jsx(Modal$1, {
3356
3850
  transparent: true,
@@ -3358,12 +3852,13 @@ const SelectModal = ({ visible, onClose, dismissOnBackdropPress, contentStyle, c
3358
3852
  animationType: "slide",
3359
3853
  onRequestClose: onClose,
3360
3854
  children: /* @__PURE__ */ jsxs(View, {
3361
- style: [styles.modalRoot, styles.modalPresentationRoot],
3855
+ style: [
3856
+ styles.modalRoot,
3857
+ styles.modalPresentationRoot,
3858
+ Platform.OS === "web" && { zIndex }
3859
+ ],
3362
3860
  testID: "select-content-root",
3363
- children: [/* @__PURE__ */ jsx(SelectBackdrop, {
3364
- onClose,
3365
- dismissOnBackdropPress
3366
- }), /* @__PURE__ */ jsx(View, {
3861
+ children: [/* @__PURE__ */ jsx(SelectBackdrop, { outsidePressProps }), /* @__PURE__ */ jsx(View, {
3367
3862
  style: [
3368
3863
  styles.content,
3369
3864
  styles.modalPresentation,
@@ -3378,7 +3873,7 @@ const SelectModal = ({ visible, onClose, dismissOnBackdropPress, contentStyle, c
3378
3873
  SelectModal.displayName = "Select.Modal";
3379
3874
  //#endregion
3380
3875
  //#region src/components/Select/Presentation/SelectPopover.tsx
3381
- const SelectPopover = ({ visible, onClose, dismissOnBackdropPress, position, onFloatingLayout, matchTriggerWidth, triggerWidth, contentStyle, children }) => {
3876
+ const SelectPopover = ({ visible, onClose, outsidePressProps, zIndex, position, onFloatingLayout, matchTriggerWidth, triggerWidth, contentStyle, children }) => {
3382
3877
  const styles = useThemeStyles(createPresentationStyles);
3383
3878
  return /* @__PURE__ */ jsx(Modal$1, {
3384
3879
  transparent: true,
@@ -3386,12 +3881,9 @@ const SelectPopover = ({ visible, onClose, dismissOnBackdropPress, position, onF
3386
3881
  animationType: "fade",
3387
3882
  onRequestClose: onClose,
3388
3883
  children: /* @__PURE__ */ jsxs(View, {
3389
- style: styles.modalRoot,
3884
+ style: [styles.modalRoot, Platform.OS === "web" && { zIndex }],
3390
3885
  testID: "select-content-root",
3391
- children: [/* @__PURE__ */ jsx(SelectBackdrop, {
3392
- onClose,
3393
- dismissOnBackdropPress
3394
- }), /* @__PURE__ */ jsx(View, {
3886
+ children: [/* @__PURE__ */ jsx(SelectBackdrop, { outsidePressProps }), /* @__PURE__ */ jsx(View, {
3395
3887
  onLayout: onFloatingLayout,
3396
3888
  style: [
3397
3889
  styles.content,
@@ -3411,151 +3903,37 @@ const SelectPopover = ({ visible, onClose, dismissOnBackdropPress, position, onF
3411
3903
  });
3412
3904
  };
3413
3905
  SelectPopover.displayName = "Select.Popover";
3414
- //#endregion
3415
- //#region src/components/Select/Presentation/SelectSheet.tsx
3416
- const SelectSheet = ({ visible, onClose, dismissOnBackdropPress, contentStyle, children }) => {
3417
- const styles = useThemeStyles(createPresentationStyles);
3418
- return /* @__PURE__ */ jsx(Modal$1, {
3419
- transparent: true,
3420
- visible,
3421
- animationType: "slide",
3422
- onRequestClose: onClose,
3423
- children: /* @__PURE__ */ jsxs(View, {
3424
- style: [styles.modalRoot, styles.sheetRoot],
3425
- testID: "select-content-root",
3426
- children: [/* @__PURE__ */ jsx(SelectBackdrop, {
3427
- onClose,
3428
- dismissOnBackdropPress
3429
- }), /* @__PURE__ */ jsxs(View, {
3430
- style: [
3431
- styles.content,
3432
- styles.sheet,
3433
- contentStyle
3434
- ],
3435
- testID: "select-sheet",
3436
- children: [/* @__PURE__ */ jsx(SelectHandle, {}), children]
3437
- })]
3438
- })
3439
- });
3440
- };
3441
- SelectSheet.displayName = "Select.Sheet";
3442
- //#endregion
3443
- //#region src/components/Select/Content/SelectContent.styles.ts
3444
- const createContentStyles = (theme) => StyleSheet.create({
3445
- toolbar: {
3446
- minHeight: 52,
3447
- flexDirection: "row",
3448
- alignItems: "center",
3449
- justifyContent: "space-between",
3450
- paddingHorizontal: theme.tokens.spacing[4],
3451
- borderBottomColor: theme.components.select.dropdown.separator.bg,
3452
- borderBottomWidth: 1
3453
- },
3454
- title: {
3455
- flex: 1,
3456
- marginHorizontal: theme.tokens.spacing[3],
3457
- color: theme.components.select.dropdown.fg,
3458
- fontFamily: theme.tokens.typography.family.medium,
3459
- fontSize: theme.tokens.typography.size.md,
3460
- lineHeight: theme.tokens.typography.lineHeight.md,
3461
- textAlign: "center"
3462
- },
3463
- toolbarAction: {
3464
- minWidth: 64,
3465
- minHeight: 44,
3466
- alignItems: "center",
3467
- justifyContent: "center"
3468
- },
3469
- cancelText: {
3470
- color: theme.components.select.trigger.placeholder.fg,
3471
- fontFamily: theme.tokens.typography.family.medium,
3472
- fontSize: theme.tokens.typography.size.md,
3473
- lineHeight: theme.tokens.typography.lineHeight.md
3474
- },
3475
- doneText: {
3476
- color: theme.semantic.text.interactive,
3477
- fontFamily: theme.tokens.typography.family.medium,
3478
- fontSize: theme.tokens.typography.size.md,
3479
- lineHeight: theme.tokens.typography.lineHeight.md
3480
- },
3481
- list: { maxHeight: 420 },
3482
- listContent: {
3483
- paddingHorizontal: theme.tokens.spacing[2],
3484
- paddingVertical: theme.tokens.spacing[2]
3485
- },
3486
- empty: {
3487
- minHeight: 72,
3488
- alignItems: "center",
3489
- justifyContent: "center",
3490
- padding: theme.tokens.spacing[4]
3491
- },
3492
- emptyText: {
3493
- color: theme.components.select.dropdown.empty.fg,
3494
- fontFamily: theme.tokens.typography.family.regular,
3495
- fontSize: theme.tokens.typography.size.md,
3496
- lineHeight: theme.tokens.typography.lineHeight.md,
3497
- textAlign: "center"
3498
- },
3499
- loading: {
3500
- minHeight: 72,
3501
- flexDirection: "row",
3502
- alignItems: "center",
3503
- justifyContent: "center",
3504
- gap: theme.tokens.spacing[2],
3505
- padding: theme.tokens.spacing[4]
3506
- },
3507
- loadingText: {
3508
- color: theme.components.select.dropdown.fg,
3509
- fontFamily: theme.tokens.typography.family.regular,
3510
- fontSize: theme.tokens.typography.size.md,
3511
- lineHeight: theme.tokens.typography.lineHeight.md
3512
- }
3513
- });
3514
- //#endregion
3515
- //#region src/components/Select/Content/SelectEmpty.tsx
3516
- const renderText$1 = (node, style) => {
3517
- if (typeof node === "string" || typeof node === "number") return /* @__PURE__ */ jsx(Text, {
3518
- style,
3519
- children: node
3520
- });
3521
- return node;
3522
- };
3523
- const SelectEmpty = createSelectSlot("empty", "Select.Empty");
3524
- const SelectEmptyState = () => {
3525
- const styles = useThemeStyles(createContentStyles);
3526
- const { empty } = useSelectContext();
3527
- return /* @__PURE__ */ jsx(View, {
3528
- style: styles.empty,
3529
- children: renderText$1(empty, styles.emptyText)
3530
- });
3531
- };
3532
- SelectEmptyState.displayName = "Select.EmptyState";
3533
- //#endregion
3534
- //#region src/components/Select/Content/SelectLoading.tsx
3535
- const renderText = (node, style) => {
3536
- if (typeof node === "string" || typeof node === "number") return /* @__PURE__ */ jsx(Text, {
3537
- style,
3538
- children: node
3539
- });
3540
- return node;
3541
- };
3542
- const SelectLoading = createSelectSlot("loading", "Select.Loading");
3543
- const SelectLoadingState = () => {
3544
- const { theme } = useTheme();
3545
- const styles = useThemeStyles(createContentStyles);
3546
- const { loadingContent } = useSelectContext();
3547
- return /* @__PURE__ */ jsxs(View, {
3548
- style: styles.loading,
3549
- children: [/* @__PURE__ */ jsx(ActivityIndicator, {
3550
- testID: "select-content-loading-indicator",
3551
- size: "small",
3552
- color: theme.components.select.dropdown.fg
3553
- }), renderText(loadingContent, styles.loadingText)]
3906
+ //#endregion
3907
+ //#region src/components/Select/Presentation/SelectSheet.tsx
3908
+ const SelectSheet = ({ visible, onClose, outsidePressProps, zIndex, contentStyle, children }) => {
3909
+ const styles = useThemeStyles(createPresentationStyles);
3910
+ return /* @__PURE__ */ jsx(Modal$1, {
3911
+ transparent: true,
3912
+ visible,
3913
+ animationType: "slide",
3914
+ onRequestClose: onClose,
3915
+ children: /* @__PURE__ */ jsxs(View, {
3916
+ style: [
3917
+ styles.modalRoot,
3918
+ styles.sheetRoot,
3919
+ Platform.OS === "web" && { zIndex }
3920
+ ],
3921
+ testID: "select-content-root",
3922
+ children: [/* @__PURE__ */ jsx(SelectBackdrop, { outsidePressProps }), /* @__PURE__ */ jsxs(View, {
3923
+ style: [
3924
+ styles.content,
3925
+ styles.sheet,
3926
+ contentStyle
3927
+ ],
3928
+ testID: "select-sheet",
3929
+ children: [/* @__PURE__ */ jsx(SelectHandle, {}), children]
3930
+ })]
3931
+ })
3554
3932
  });
3555
3933
  };
3556
- SelectLoadingState.displayName = "Select.LoadingState";
3934
+ SelectSheet.displayName = "Select.Sheet";
3557
3935
  //#endregion
3558
- //#region src/components/Select/Content/SelectSearch.styles.ts
3936
+ //#region src/components/Select/Search/SelectSearch.styles.ts
3559
3937
  const createSearchStyles = (theme) => StyleSheet.create({
3560
3938
  searchWrap: {
3561
3939
  flexDirection: "row",
@@ -3597,7 +3975,7 @@ const createSearchStyles = (theme) => StyleSheet.create({
3597
3975
  }
3598
3976
  });
3599
3977
  //#endregion
3600
- //#region src/components/Select/Content/SelectSearch.tsx
3978
+ //#region src/components/Select/Search/SelectSearch.tsx
3601
3979
  const SelectSearch = createSelectSlot("search", "Select.Search");
3602
3980
  const SelectSearchField = () => {
3603
3981
  const { theme } = useTheme();
@@ -3652,6 +4030,13 @@ const SelectSearchField = () => {
3652
4030
  };
3653
4031
  SelectSearchField.displayName = "Select.SearchField";
3654
4032
  //#endregion
4033
+ //#region src/components/Select/Separator/SelectSeparator.tsx
4034
+ const SelectSeparator = createSelectSlot("separator", "Select.Separator");
4035
+ const SelectSeparatorRow = () => {
4036
+ return /* @__PURE__ */ jsx(View, { style: useThemeStyles(createGroupStyles).separator });
4037
+ };
4038
+ SelectSeparatorRow.displayName = "Select.SeparatorRow";
4039
+ //#endregion
3655
4040
  //#region src/components/Select/Content/SelectContent.tsx
3656
4041
  const SelectContent = createSelectSlot("content", "Select.Content");
3657
4042
  const SelectContentSurface = () => {
@@ -3659,13 +4044,13 @@ const SelectContentSurface = () => {
3659
4044
  const wasOpenRef = useRef(false);
3660
4045
  const [openCycle, setOpenCycle] = useState(0);
3661
4046
  const context = useSelectContext();
3662
- const { isOpen, resolvedPresentation, position, onFloatingLayout, dismissOnBackdropPress, contentStyle, matchTriggerWidth, triggerWidth, resolvedLabel, closeContent, searchable, loading, filteredRows, selectedValues, selectedOptions, maxSelected, optionStyle, selectOption, selectGroup, itemHeight, selectedRowIndex, query } = context;
4047
+ const { isOpen, resolvedPresentation, zIndex, position, onFloatingLayout, contentStyle, matchTriggerWidth, triggerWidth, resolvedLabel, closeContent, getOutsidePressProps, searchable, loading, filteredRows, selectedValues, selectedOptions, maxSelected, optionStyle, selectOption, selectGroup, itemHeight, selectedRowIndex, query } = context;
3663
4048
  const initialScrollIndex = Boolean(context.virtual) && selectedRowIndex > 0 && query === "" ? selectedRowIndex : void 0;
3664
4049
  useEffect(() => {
3665
4050
  if (isOpen && !wasOpenRef.current) setOpenCycle((cycle) => cycle + 1);
3666
4051
  wasOpenRef.current = isOpen;
3667
4052
  }, [isOpen]);
3668
- const renderRow = ({ item }) => {
4053
+ const renderRow = ({ item, index }) => {
3669
4054
  if (item.type === "group") {
3670
4055
  if (item.selectable && context.multiple) {
3671
4056
  const enabledGroupValues = item.itemValues.filter((value) => context.optionsByValue.get(value));
@@ -3687,6 +4072,9 @@ const SelectContentSurface = () => {
3687
4072
  option: item.option,
3688
4073
  isSelected,
3689
4074
  isDisabled: Boolean(item.option.disabled || maxReached),
4075
+ itemIndex: index,
4076
+ selectedValues,
4077
+ multiple: context.multiple,
3690
4078
  optionStyle,
3691
4079
  onSelect: selectOption
3692
4080
  });
@@ -3749,15 +4137,17 @@ const SelectContentSurface = () => {
3749
4137
  if (resolvedPresentation === "sheet") return /* @__PURE__ */ jsx(SelectSheet, {
3750
4138
  visible: isOpen,
3751
4139
  onClose: closeContent,
3752
- dismissOnBackdropPress,
4140
+ outsidePressProps: getOutsidePressProps({ accessibilityLabel: "Dismiss select" }),
4141
+ zIndex,
3753
4142
  contentStyle,
3754
4143
  children: body
3755
4144
  });
3756
4145
  if (resolvedPresentation === "popover") return /* @__PURE__ */ jsx(SelectPopover, {
3757
4146
  visible: isOpen,
3758
4147
  onClose: closeContent,
3759
- dismissOnBackdropPress,
4148
+ outsidePressProps: getOutsidePressProps({ accessibilityLabel: "Dismiss select" }),
3760
4149
  position,
4150
+ zIndex,
3761
4151
  onFloatingLayout,
3762
4152
  matchTriggerWidth,
3763
4153
  triggerWidth,
@@ -3767,104 +4157,28 @@ const SelectContentSurface = () => {
3767
4157
  return /* @__PURE__ */ jsx(SelectModal, {
3768
4158
  visible: isOpen,
3769
4159
  onClose: closeContent,
3770
- dismissOnBackdropPress,
4160
+ outsidePressProps: getOutsidePressProps({ accessibilityLabel: "Dismiss select" }),
4161
+ zIndex,
3771
4162
  contentStyle,
3772
4163
  children: body
3773
4164
  });
3774
4165
  };
3775
4166
  SelectContentSurface.displayName = "Select.ContentSurface";
3776
4167
  //#endregion
3777
- //#region src/components/Select/internal/useSelectAccessibility.ts
3778
- const useSelectAccessibility = ({ accessibilityLabel, accessibilityHint, label, description, error, invalid, placeholder, selectedLabel, hasFieldContext, fieldDescribedBy }) => {
3779
- const descriptionText = typeof description === "string" || typeof description === "number" ? String(description) : void 0;
3780
- const errorText = typeof error === "string" || typeof error === "number" ? String(error) : void 0;
3781
- return {
3782
- resolvedLabel: accessibilityLabel ?? label ?? selectedLabel ?? placeholder ?? "Select",
3783
- resolvedHint: accessibilityHint ?? (invalid && errorText ? errorText : descriptionText ? descriptionText : hasFieldContext && fieldDescribedBy ? "Opens a list of options" : void 0),
3784
- announce: (message) => {
3785
- AccessibilityInfo.announceForAccessibility?.(message);
3786
- }
3787
- };
3788
- };
4168
+ //#region src/components/Select/Icon/SelectIcon.tsx
4169
+ const SelectIcon = createSelectSlot("icon", "Select.Icon");
3789
4170
  //#endregion
3790
- //#region src/components/Select/internal/useSelectCollection.ts
3791
- const useSelectCollection = (children, optionsProp) => {
3792
- const parsedChildren = useMemo(() => parseSelectChildren(children), [children]);
3793
- const options = useMemo(() => [...optionsProp ?? [], ...parsedChildren.options], [optionsProp, parsedChildren.options]);
3794
- return {
3795
- options,
3796
- rows: useMemo(() => {
3797
- if (parsedChildren.rows.length > 0) return parsedChildren.rows;
3798
- return options.map((option) => ({
3799
- type: "item",
3800
- key: `item-${option.value}`,
3801
- option
3802
- }));
3803
- }, [options, parsedChildren.rows]),
3804
- searchableFromChildren: parsedChildren.searchable,
3805
- searchPlaceholderFromChildren: parsedChildren.searchPlaceholder,
3806
- emptyFromChildren: parsedChildren.empty,
3807
- loadingFromChildren: parsedChildren.loading
3808
- };
3809
- };
4171
+ //#region src/components/Select/ItemBadge/SelectItemBadge.tsx
4172
+ const SelectItemBadge = createSelectSlot("itemBadge", "Select.ItemBadge");
3810
4173
  //#endregion
3811
- //#region src/components/Select/internal/useSelectSearch.ts
3812
- const useSelectSearch = ({ rows, isOpen, searchable, searchableFromChildren, onSearch, filterOptions, filter = defaultSelectFilter }) => {
3813
- const [query, setQuery] = useState("");
3814
- const shouldSearch = searchable ?? searchableFromChildren ?? Boolean(onSearch);
3815
- const shouldFilter = filterOptions ?? !onSearch;
3816
- const filteredRows = useMemo(() => {
3817
- if (!query || !shouldFilter) return rows;
3818
- const visibleRows = [];
3819
- let pendingGroup;
3820
- rows.forEach((row) => {
3821
- if (row.type === "group") {
3822
- pendingGroup = row;
3823
- return;
3824
- }
3825
- if (row.type === "separator") {
3826
- if (visibleRows.length > 0 && visibleRows[visibleRows.length - 1]?.type !== "separator") visibleRows.push(row);
3827
- return;
3828
- }
3829
- if (!filter(row.option, query)) return;
3830
- if (pendingGroup) {
3831
- visibleRows.push(pendingGroup);
3832
- pendingGroup = void 0;
3833
- }
3834
- visibleRows.push(row);
3835
- });
3836
- return visibleRows.filter((row, index, collection) => {
3837
- if (row.type !== "separator") return true;
3838
- return index > 0 && index < collection.length - 1 && collection[index - 1]?.type !== "separator";
3839
- });
3840
- }, [
3841
- filter,
3842
- query,
3843
- rows,
3844
- shouldFilter
3845
- ]);
3846
- useEffect(() => {
3847
- if (!isOpen) {
3848
- setQuery("");
3849
- return;
3850
- }
3851
- if (shouldSearch) onSearch?.(query);
3852
- }, [
3853
- isOpen,
3854
- onSearch,
3855
- query,
3856
- shouldSearch
3857
- ]);
3858
- return {
3859
- query,
3860
- setQuery,
3861
- shouldSearch,
3862
- filteredRows
3863
- };
3864
- };
4174
+ //#region src/components/Select/ItemDescription/SelectItemDescription.tsx
4175
+ const SelectItemDescription = createSelectSlot("itemDescription", "Select.ItemDescription");
3865
4176
  //#endregion
3866
- //#region src/components/Select/Trigger/SelectIcon.tsx
3867
- const SelectIcon = createSelectSlot("icon", "Select.Icon");
4177
+ //#region src/components/Select/ItemIcon/SelectItemIcon.tsx
4178
+ const SelectItemIcon = createSelectSlot("itemIcon", "Select.ItemIcon");
4179
+ //#endregion
4180
+ //#region src/components/Select/Label/SelectLabel.tsx
4181
+ const SelectLabel = createSelectSlot("label", "Select.Label");
3868
4182
  //#endregion
3869
4183
  //#region src/components/Select/Trigger/SelectTrigger.styles.ts
3870
4184
  const createTriggerStyles = (theme) => StyleSheet.create({
@@ -3961,9 +4275,9 @@ const createTriggerStyles = (theme) => StyleSheet.create({
3961
4275
  //#endregion
3962
4276
  //#region src/components/Select/Trigger/SelectTrigger.tsx
3963
4277
  const SelectTriggerSlot = createSelectSlot("trigger", "Select.Trigger");
3964
- const nativePointerEventsNone$3 = Platform.OS === "web" ? void 0 : { pointerEvents: "none" };
4278
+ const nativePointerEventsNone$2 = Platform.OS === "web" ? void 0 : { pointerEvents: "none" };
3965
4279
  const nativePointerEventsBoxNone = Platform.OS === "web" ? void 0 : { pointerEvents: "box-none" };
3966
- const webPointerEventsNone$3 = Platform.OS === "web" ? { pointerEvents: "none" } : void 0;
4280
+ const webPointerEventsNone$2 = Platform.OS === "web" ? { pointerEvents: "none" } : void 0;
3967
4281
  const webPointerEventsBoxNone = Platform.OS === "web" ? { pointerEvents: "box-none" } : void 0;
3968
4282
  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 }) {
3969
4283
  const { theme } = useTheme();
@@ -4058,8 +4372,8 @@ function SelectTrigger({ displayText, isPlaceholder, isOpen, size = "md", color
4058
4372
  ],
4059
4373
  children: [
4060
4374
  startIcon && /* @__PURE__ */ jsx(View, {
4061
- ...nativePointerEventsNone$3,
4062
- style: [styles.startIcon, webPointerEventsNone$3],
4375
+ ...nativePointerEventsNone$2,
4376
+ style: [styles.startIcon, webPointerEventsNone$2],
4063
4377
  accessibilityElementsHidden: true,
4064
4378
  importantForAccessibility: "no",
4065
4379
  children: renderIcon(startIcon)
@@ -4081,8 +4395,8 @@ function SelectTrigger({ displayText, isPlaceholder, isOpen, size = "md", color
4081
4395
  size: "small",
4082
4396
  color: iconColor
4083
4397
  }) : showClearButton ? null : endIcon ? /* @__PURE__ */ jsx(View, {
4084
- ...nativePointerEventsNone$3,
4085
- style: [styles.endIcon, webPointerEventsNone$3],
4398
+ ...nativePointerEventsNone$2,
4399
+ style: [styles.endIcon, webPointerEventsNone$2],
4086
4400
  accessibilityElementsHidden: true,
4087
4401
  importantForAccessibility: "no",
4088
4402
  children: renderIcon(endIcon)
@@ -4119,51 +4433,321 @@ function SelectTrigger({ displayText, isPlaceholder, isOpen, size = "md", color
4119
4433
  })]
4120
4434
  });
4121
4435
  }
4122
- SelectTrigger.displayName = "SelectTrigger";
4436
+ SelectTrigger.displayName = "SelectTrigger";
4437
+ //#endregion
4438
+ //#region src/hooks/behavior/select/useSelectCollection.ts
4439
+ const useSelectCollection = (children, optionsProp) => {
4440
+ const parsedChildren = useMemo(() => parseSelectChildren(children), [children]);
4441
+ const options = useMemo(() => [...optionsProp ?? [], ...parsedChildren.options], [optionsProp, parsedChildren.options]);
4442
+ return {
4443
+ options,
4444
+ rows: useMemo(() => {
4445
+ if (parsedChildren.rows.length > 0) return parsedChildren.rows;
4446
+ return options.map((option) => ({
4447
+ type: "item",
4448
+ key: `item-${option.value}`,
4449
+ option
4450
+ }));
4451
+ }, [options, parsedChildren.rows]),
4452
+ searchableFromChildren: parsedChildren.searchable,
4453
+ searchPlaceholderFromChildren: parsedChildren.searchPlaceholder,
4454
+ emptyFromChildren: parsedChildren.empty,
4455
+ loadingFromChildren: parsedChildren.loading
4456
+ };
4457
+ };
4458
+ //#endregion
4459
+ //#region src/hooks/behavior/select/useSelectSearch.ts
4460
+ const useSelectSearch = ({ rows, isOpen, searchable, searchableFromChildren, onSearch, filterOptions, filter = defaultSelectFilter }) => {
4461
+ const [query, setQuery] = useState("");
4462
+ const shouldSearch = searchable ?? searchableFromChildren ?? Boolean(onSearch);
4463
+ const shouldFilter = filterOptions ?? !onSearch;
4464
+ const filteredRows = useMemo(() => {
4465
+ if (!query || !shouldFilter) return rows;
4466
+ const visibleRows = [];
4467
+ let pendingGroup;
4468
+ rows.forEach((row) => {
4469
+ if (row.type === "group") {
4470
+ pendingGroup = row;
4471
+ return;
4472
+ }
4473
+ if (row.type === "separator") {
4474
+ if (visibleRows.length > 0 && visibleRows[visibleRows.length - 1]?.type !== "separator") visibleRows.push(row);
4475
+ return;
4476
+ }
4477
+ if (!filter(row.option, query)) return;
4478
+ if (pendingGroup) {
4479
+ visibleRows.push(pendingGroup);
4480
+ pendingGroup = void 0;
4481
+ }
4482
+ visibleRows.push(row);
4483
+ });
4484
+ return visibleRows.filter((row, index, collection) => {
4485
+ if (row.type !== "separator") return true;
4486
+ return index > 0 && index < collection.length - 1 && collection[index - 1]?.type !== "separator";
4487
+ });
4488
+ }, [
4489
+ filter,
4490
+ query,
4491
+ rows,
4492
+ shouldFilter
4493
+ ]);
4494
+ useEffect(() => {
4495
+ if (!isOpen) {
4496
+ setQuery("");
4497
+ return;
4498
+ }
4499
+ if (shouldSearch) onSearch?.(query);
4500
+ }, [
4501
+ isOpen,
4502
+ onSearch,
4503
+ query,
4504
+ shouldSearch
4505
+ ]);
4506
+ return {
4507
+ query,
4508
+ setQuery,
4509
+ shouldSearch,
4510
+ filteredRows
4511
+ };
4512
+ };
4513
+ //#endregion
4514
+ //#region src/components/Select/internal/resolveSelectAccessibility.ts
4515
+ const resolveSelectAccessibility = ({ accessibilityLabel, accessibilityHint, label, description, error, invalid, placeholder, selectedLabel, hasFieldContext, fieldDescribedBy }) => {
4516
+ const descriptionText = typeof description === "string" || typeof description === "number" ? String(description) : void 0;
4517
+ const errorText = typeof error === "string" || typeof error === "number" ? String(error) : void 0;
4518
+ return {
4519
+ resolvedLabel: accessibilityLabel ?? label ?? selectedLabel ?? placeholder ?? "Select",
4520
+ resolvedHint: accessibilityHint ?? (invalid && errorText ? errorText : descriptionText ? descriptionText : hasFieldContext && fieldDescribedBy ? "Opens a list of options" : void 0),
4521
+ announce: (message) => {
4522
+ AccessibilityInfo.announceForAccessibility?.(message);
4523
+ }
4524
+ };
4525
+ };
4526
+ //#endregion
4527
+ //#region src/components/Select/Root/useSelectRootActions.ts
4528
+ function useSelectRootActions({ multiple, maxSelected, closeOnSelect, selectedValues, optionsByValue, selectedFocusValueRef, selectValue, setSelectedValue, announce, closeAndFocusTrigger }) {
4529
+ return {
4530
+ clearValue: useCallback(() => {
4531
+ selectedFocusValueRef.current = void 0;
4532
+ selectValue("");
4533
+ announce("Selection cleared");
4534
+ }, [
4535
+ announce,
4536
+ selectValue,
4537
+ selectedFocusValueRef
4538
+ ]),
4539
+ selectOption: useCallback((option) => {
4540
+ if (option.disabled) return;
4541
+ const selectedBefore = selectedValues.includes(option.value);
4542
+ if (multiple && !selectedBefore && typeof maxSelected === "number" && selectedValues.length >= maxSelected) return;
4543
+ selectedFocusValueRef.current = option.value;
4544
+ selectValue(option.value);
4545
+ announce(`${option.label} selected`);
4546
+ }, [
4547
+ announce,
4548
+ maxSelected,
4549
+ multiple,
4550
+ selectValue,
4551
+ selectedFocusValueRef,
4552
+ selectedValues
4553
+ ]),
4554
+ selectGroup: useCallback((values) => {
4555
+ if (!multiple || values.length === 0) return;
4556
+ const enabledValues = values.filter((value) => optionsByValue.has(value));
4557
+ const selectedGroupValues = enabledValues.filter((value) => selectedValues.includes(value));
4558
+ const outsideSelectedCount = selectedValues.filter((value) => !enabledValues.includes(value)).length;
4559
+ const maxSelectableGroupCount = typeof maxSelected === "number" ? Math.max(0, Math.min(enabledValues.length, maxSelected - outsideSelectedCount)) : enabledValues.length;
4560
+ if (selectedGroupValues.length > 0 && selectedGroupValues.length >= maxSelectableGroupCount) {
4561
+ selectedFocusValueRef.current = void 0;
4562
+ setSelectedValue(selectedValues.filter((value) => !enabledValues.includes(value)));
4563
+ announce("Group selection cleared");
4564
+ return;
4565
+ }
4566
+ const nextValues = [...selectedValues];
4567
+ for (const value of enabledValues) {
4568
+ if (nextValues.includes(value)) continue;
4569
+ if (typeof maxSelected === "number" && nextValues.length >= maxSelected) break;
4570
+ nextValues.push(value);
4571
+ }
4572
+ setSelectedValue(nextValues);
4573
+ selectedFocusValueRef.current = nextValues.at(-1);
4574
+ announce("Group selected");
4575
+ if (closeOnSelect) closeAndFocusTrigger();
4576
+ }, [
4577
+ announce,
4578
+ closeAndFocusTrigger,
4579
+ closeOnSelect,
4580
+ maxSelected,
4581
+ multiple,
4582
+ optionsByValue,
4583
+ selectedFocusValueRef,
4584
+ selectedValues,
4585
+ setSelectedValue
4586
+ ])
4587
+ };
4588
+ }
4589
+ //#endregion
4590
+ //#region src/components/Select/Root/useSelectRootContextValue.ts
4591
+ function useSelectRootContextValue({ color, variant, isOpen, loading, searchable, multiple, maxSelected, virtual, resolvedLabel, resolvedPresentation, zIndex, position, onFloatingLayout, matchTriggerWidth, triggerWidth, selectedValues, selectedOptions, optionsByValue, filteredRows, selectedRowIndex, itemHeight, query, searchPlaceholder, searchInputRef, empty, loadingContent, closeContent, getOutsidePressProps, selectOption, selectGroup, setQuery, renderOption, contentStyle, optionStyle, searchStyle }) {
4592
+ return useMemo(() => ({
4593
+ color,
4594
+ variant,
4595
+ isOpen,
4596
+ loading,
4597
+ searchable,
4598
+ multiple,
4599
+ maxSelected,
4600
+ virtual,
4601
+ resolvedLabel,
4602
+ resolvedPresentation,
4603
+ zIndex,
4604
+ position,
4605
+ onFloatingLayout,
4606
+ matchTriggerWidth,
4607
+ triggerWidth,
4608
+ selectedValues,
4609
+ selectedOptions,
4610
+ optionsByValue,
4611
+ filteredRows,
4612
+ selectedRowIndex,
4613
+ itemHeight,
4614
+ query,
4615
+ searchPlaceholder,
4616
+ searchInputRef,
4617
+ empty,
4618
+ loadingContent,
4619
+ closeContent,
4620
+ getOutsidePressProps,
4621
+ selectOption,
4622
+ selectGroup,
4623
+ setQuery,
4624
+ renderOption,
4625
+ contentStyle,
4626
+ optionStyle,
4627
+ searchStyle
4628
+ }), [
4629
+ color,
4630
+ variant,
4631
+ isOpen,
4632
+ loading,
4633
+ searchable,
4634
+ multiple,
4635
+ maxSelected,
4636
+ virtual,
4637
+ resolvedLabel,
4638
+ resolvedPresentation,
4639
+ zIndex,
4640
+ position,
4641
+ onFloatingLayout,
4642
+ matchTriggerWidth,
4643
+ triggerWidth,
4644
+ selectedValues,
4645
+ selectedOptions,
4646
+ optionsByValue,
4647
+ filteredRows,
4648
+ selectedRowIndex,
4649
+ itemHeight,
4650
+ query,
4651
+ searchPlaceholder,
4652
+ searchInputRef,
4653
+ empty,
4654
+ loadingContent,
4655
+ closeContent,
4656
+ getOutsidePressProps,
4657
+ selectOption,
4658
+ selectGroup,
4659
+ setQuery,
4660
+ renderOption,
4661
+ contentStyle,
4662
+ optionStyle,
4663
+ searchStyle
4664
+ ]);
4665
+ }
4123
4666
  //#endregion
4124
- //#region src/components/Select/Trigger/SelectValue.tsx
4125
- const SelectValue = createSelectSlot("value", "Select.Value");
4667
+ //#region src/components/Select/Root/useSelectRootDisplayValue.ts
4668
+ function useSelectRootDisplayValue({ multiple, placeholder, renderValue, selectedOption, selectedOptions, selectedValues }) {
4669
+ return useMemo(() => {
4670
+ if (renderValue) return renderValue({
4671
+ option: selectedOption,
4672
+ options: selectedOptions,
4673
+ value: selectedValues[0] ?? "",
4674
+ values: selectedValues,
4675
+ placeholder,
4676
+ multiple
4677
+ });
4678
+ if (multiple && selectedOptions.length > 0) {
4679
+ const visibleLabels = selectedOptions.slice(0, 2).map((option) => option.label);
4680
+ return selectedOptions.length > 2 ? `${visibleLabels.join(", ")} +${selectedOptions.length - 2}` : visibleLabels.join(", ");
4681
+ }
4682
+ return selectedOption?.label ?? placeholder;
4683
+ }, [
4684
+ multiple,
4685
+ placeholder,
4686
+ renderValue,
4687
+ selectedOption,
4688
+ selectedOptions,
4689
+ selectedValues
4690
+ ]);
4691
+ }
4126
4692
  //#endregion
4127
- //#region src/components/Select/Root/SelectRoot.tsx
4128
- function SelectRoot(props) {
4129
- 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;
4693
+ //#region src/components/Select/Root/useSelectRootSelection.ts
4694
+ function useSelectRootSelection({ props, options, isDisabled }) {
4695
+ const selection = useSelect({
4696
+ value: props.multiple ? props.value : props.value === null ? "" : props.value,
4697
+ defaultValue: props.multiple ? props.defaultValue : props.defaultValue === null ? "" : props.defaultValue,
4698
+ onValueChange: (nextValue) => {
4699
+ if (props.multiple) {
4700
+ props.onValueChange?.(Array.isArray(nextValue) ? nextValue : nextValue ? [nextValue] : []);
4701
+ return;
4702
+ }
4703
+ props.onValueChange?.(Array.isArray(nextValue) ? nextValue[0] ?? null : nextValue === "" ? null : nextValue);
4704
+ },
4705
+ options,
4706
+ multiple: props.multiple,
4707
+ maxSelected: props.maxSelected,
4708
+ closeOnSelect: props.closeOnSelect,
4709
+ disabled: isDisabled,
4710
+ open: props.open,
4711
+ defaultOpen: props.defaultOpen,
4712
+ onOpenChange: props.onOpenChange
4713
+ });
4714
+ const optionsByValue = useMemo(() => new Map(options.filter((option) => !option.disabled).map((option) => [option.value, option])), [options]);
4715
+ return {
4716
+ ...selection,
4717
+ optionsByValue
4718
+ };
4719
+ }
4720
+ //#endregion
4721
+ //#region src/components/Select/Root/useSelectRootState.tsx
4722
+ function useSelectRootState(props) {
4723
+ const { label, description, error, invalid = false, required = false, disabled = false, placeholder = "Select...", color = "primary", variant = "outline", size, clearable = false, searchable: searchableProp, searchPlaceholder, loading = false, loadingText = "Loading...", onSearch, filterOptions, filter, empty, startIcon, endIcon, prefix, suffix, renderValue, renderOption, closeOnSelect, maxSelected, presentation = "auto", placement = "bottom-start", offset = 8, matchTriggerWidth = false, dismissOnBackdropPress = true, virtual, options: optionsProp, children, style, triggerStyle, textStyle, contentStyle, optionStyle, searchStyle, accessibilityLabel, accessibilityHint, testID } = props;
4130
4724
  const field = useFormFieldContext();
4131
4725
  const overlayId = useId();
4132
4726
  const hasOwnField = Boolean(label || description || error);
4133
4727
  const [triggerWidth, setTriggerWidth] = useState();
4134
4728
  const triggerRef = useRef(null);
4135
- const { position, placement: resolvedPlacement, updatePosition, onFloatingLayout } = useNativeFloatingPosition(placement, offset);
4136
4729
  const searchInputRef = useRef(null);
4137
4730
  const selectedFocusValueRef = useRef(void 0);
4138
4731
  const resolvedPresentation = useOverlayPresentation(presentation);
4732
+ const { position, onFloatingLayout } = useNativeFloatingPosition(placement, offset);
4139
4733
  const { options, rows, searchableFromChildren, searchPlaceholderFromChildren, emptyFromChildren, loadingFromChildren } = useSelectCollection(children, optionsProp);
4140
4734
  const resolvedSize = size ?? field?.size ?? "md";
4141
4735
  const isInvalid = invalid || Boolean(error) || !hasOwnField && Boolean(field?.invalid);
4142
4736
  const isDisabled = disabled || !hasOwnField && Boolean(field?.disabled);
4143
4737
  const isRequired = required || !hasOwnField && Boolean(field?.required);
4144
- const { selectedValue, setSelectedValue, isOpen, openDropdown, closeDropdown, selectValue } = useSelect({
4145
- value: props.multiple ? props.value : props.value === null ? "" : props.value,
4146
- defaultValue: props.multiple ? props.defaultValue : props.defaultValue === null ? "" : props.defaultValue,
4147
- onValueChange: (nextValue) => {
4148
- if (props.multiple) {
4149
- props.onValueChange?.(nextValue);
4150
- return;
4151
- }
4152
- props.onValueChange?.(nextValue === "" ? null : nextValue);
4153
- },
4738
+ const { setSelectedValue, selectedValues, selectedOption, selectedOptions, optionsByValue, isOpen, openDropdown, closeDropdown, selectValue } = useSelectRootSelection({
4739
+ props,
4154
4740
  options,
4155
- multiple: props.multiple,
4156
- maxSelected,
4157
- closeOnSelect,
4158
- disabled: isDisabled,
4159
- open,
4160
- defaultOpen,
4161
- onOpenChange
4741
+ isDisabled
4162
4742
  });
4163
- const selectedValues = Array.isArray(selectedValue) ? selectedValue : selectedValue ? [selectedValue] : [];
4164
- const selectedOption = options.find((option) => selectedValues.includes(option.value));
4165
- const selectedOptions = options.filter((option) => selectedValues.includes(option.value));
4166
- const optionsByValue = useMemo(() => new Map(options.filter((option) => !option.disabled).map((option) => [option.value, option])), [options]);
4743
+ const { restoreFocusAfterClose } = useOverlayFocusRestore({
4744
+ active: isOpen,
4745
+ triggerRef
4746
+ });
4747
+ const closeAndFocusTrigger = useCallback(() => {
4748
+ closeDropdown();
4749
+ restoreFocusAfterClose();
4750
+ }, [closeDropdown, restoreFocusAfterClose]);
4167
4751
  const { query, setQuery, shouldSearch, filteredRows } = useSelectSearch({
4168
4752
  rows,
4169
4753
  isOpen,
@@ -4176,24 +4760,15 @@ function SelectRoot(props) {
4176
4760
  const selectedFocusValue = selectedValues.includes(selectedFocusValueRef.current ?? "") ? selectedFocusValueRef.current : selectedValues[0];
4177
4761
  const selectedRowIndex = Math.max(0, filteredRows.findIndex((row) => row.type === "item" && row.option.value === selectedFocusValue));
4178
4762
  const itemHeight = typeof virtual === "object" ? virtual.estimatedItemSize ?? 46 : 46;
4179
- const displayValue = useMemo(() => {
4180
- if (renderValue) return renderValue(props.multiple ? selectedOptions : selectedOption ?? null, {
4181
- placeholder,
4182
- multiple: Boolean(props.multiple)
4183
- });
4184
- if (props.multiple && selectedOptions.length > 0) {
4185
- const visibleLabels = selectedOptions.slice(0, 2).map((option) => option.label);
4186
- return selectedOptions.length > 2 ? `${visibleLabels.join(", ")} +${selectedOptions.length - 2}` : visibleLabels.join(", ");
4187
- }
4188
- return selectedOption?.label ?? placeholder;
4189
- }, [
4763
+ const displayValue = useSelectRootDisplayValue({
4764
+ multiple: Boolean(props.multiple),
4190
4765
  placeholder,
4191
- props.multiple,
4192
4766
  renderValue,
4193
4767
  selectedOption,
4194
- selectedOptions
4195
- ]);
4196
- const { resolvedLabel, resolvedHint, announce } = useSelectAccessibility({
4768
+ selectedOptions,
4769
+ selectedValues
4770
+ });
4771
+ const { resolvedLabel, resolvedHint, announce } = resolveSelectAccessibility({
4197
4772
  accessibilityLabel,
4198
4773
  accessibilityHint,
4199
4774
  label: !hasOwnField ? void 0 : label,
@@ -4210,111 +4785,106 @@ function SelectRoot(props) {
4210
4785
  id: overlayId,
4211
4786
  active: isOpen,
4212
4787
  closeOnOutsidePress: dismissOnBackdropPress,
4213
- requestClose: closeDropdown
4788
+ requestClose: closeAndFocusTrigger
4214
4789
  });
4215
- const clearValue = () => {
4216
- selectedFocusValueRef.current = void 0;
4217
- selectValue("");
4218
- announce("Selection cleared");
4219
- };
4220
- const selectOption = (option) => {
4221
- if (option.disabled) return;
4222
- const selectedBefore = selectedValues.includes(option.value);
4223
- if (Boolean(props.multiple) && !selectedBefore && typeof maxSelected === "number" && selectedValues.length >= maxSelected) return;
4224
- selectedFocusValueRef.current = option.value;
4225
- selectValue(option.value);
4226
- announce(`${option.label} selected`);
4227
- };
4228
- const selectGroup = (values) => {
4229
- if (!props.multiple || values.length === 0) return;
4230
- const enabledValues = values.filter((value) => optionsByValue.has(value));
4231
- const selectedGroupValues = enabledValues.filter((value) => selectedValues.includes(value));
4232
- const outsideSelectedCount = selectedValues.filter((value) => !enabledValues.includes(value)).length;
4233
- const maxSelectableGroupCount = typeof maxSelected === "number" ? Math.max(0, Math.min(enabledValues.length, maxSelected - outsideSelectedCount)) : enabledValues.length;
4234
- if (selectedGroupValues.length > 0 && selectedGroupValues.length >= maxSelectableGroupCount) {
4235
- selectedFocusValueRef.current = void 0;
4236
- setSelectedValue(selectedValues.filter((value) => !enabledValues.includes(value)));
4237
- announce("Group selection cleared");
4238
- return;
4239
- }
4240
- const nextValues = [...selectedValues];
4241
- for (const value of enabledValues) {
4242
- if (nextValues.includes(value)) continue;
4243
- if (typeof maxSelected === "number" && nextValues.length >= maxSelected) break;
4244
- nextValues.push(value);
4245
- }
4246
- setSelectedValue(nextValues);
4247
- selectedFocusValueRef.current = nextValues.at(-1);
4248
- announce("Group selected");
4249
- if (closeOnSelect) closeDropdown();
4250
- };
4251
- const openContent = () => {
4252
- updatePosition(triggerRef);
4253
- openDropdown();
4254
- };
4255
- const contextValue = {
4256
- label,
4257
- description,
4258
- error,
4259
- placeholder,
4260
- color,
4261
- variant,
4262
- size: resolvedSize,
4263
- isOpen,
4264
- hasValue,
4265
- loading,
4266
- clearable,
4267
- searchable: shouldSearch,
4790
+ const { clearValue, selectOption, selectGroup } = useSelectRootActions({
4268
4791
  multiple: Boolean(props.multiple),
4269
4792
  maxSelected,
4270
- virtual,
4271
- resolvedLabel,
4272
- resolvedHint,
4273
- resolvedPresentation,
4274
- placement: resolvedPlacement,
4275
- position,
4276
- onFloatingLayout,
4277
- dismissOnBackdropPress,
4278
- matchTriggerWidth,
4279
- triggerWidth,
4793
+ closeOnSelect,
4280
4794
  selectedValues,
4281
- selectedOptions,
4282
4795
  optionsByValue,
4283
- rows,
4284
- filteredRows,
4285
- selectedRowIndex,
4286
- itemHeight,
4287
- query,
4288
- searchPlaceholder: searchPlaceholder ?? searchPlaceholderFromChildren ?? "Search...",
4289
- searchInputRef,
4290
- empty: empty ?? emptyFromChildren ?? "Nothing found",
4291
- loadingContent: loadingFromChildren ?? loadingText,
4292
- closeContent: dismiss.requestClose,
4293
- openContent,
4796
+ selectedFocusValueRef,
4797
+ selectValue,
4798
+ setSelectedValue,
4799
+ announce,
4800
+ closeAndFocusTrigger
4801
+ });
4802
+ const resolvedSearchPlaceholder = searchPlaceholder ?? searchPlaceholderFromChildren ?? "Search...";
4803
+ const resolvedEmpty = empty ?? emptyFromChildren ?? "Nothing found";
4804
+ const resolvedLoadingContent = loadingFromChildren ?? loadingText;
4805
+ return {
4806
+ contextValue: useSelectRootContextValue({
4807
+ color,
4808
+ variant,
4809
+ isOpen,
4810
+ loading,
4811
+ searchable: shouldSearch,
4812
+ multiple: Boolean(props.multiple),
4813
+ maxSelected,
4814
+ virtual,
4815
+ resolvedLabel,
4816
+ resolvedPresentation,
4817
+ zIndex: dismiss.zIndex,
4818
+ position,
4819
+ onFloatingLayout,
4820
+ matchTriggerWidth,
4821
+ triggerWidth,
4822
+ selectedValues,
4823
+ selectedOptions,
4824
+ optionsByValue,
4825
+ filteredRows,
4826
+ selectedRowIndex,
4827
+ itemHeight,
4828
+ query,
4829
+ searchPlaceholder: resolvedSearchPlaceholder,
4830
+ searchInputRef,
4831
+ empty: resolvedEmpty,
4832
+ loadingContent: resolvedLoadingContent,
4833
+ closeContent: dismiss.requestClose,
4834
+ getOutsidePressProps: dismiss.getOutsidePressProps,
4835
+ selectOption,
4836
+ selectGroup,
4837
+ setQuery,
4838
+ renderOption,
4839
+ contentStyle,
4840
+ optionStyle,
4841
+ searchStyle
4842
+ }),
4843
+ displayValue,
4844
+ field,
4845
+ hasOwnField,
4846
+ hasValue,
4847
+ isDisabled,
4848
+ isInvalid,
4849
+ isOpen,
4850
+ isRequired,
4294
4851
  clearValue,
4295
- selectOption,
4296
- selectGroup,
4297
- setQuery,
4298
- renderValue,
4299
- renderOption,
4300
- startIcon,
4301
- endIcon,
4302
- prefix,
4303
- suffix,
4304
- triggerStyle,
4305
- textStyle,
4306
- contentStyle,
4307
- optionStyle,
4308
- searchStyle,
4309
- fieldControlId: !hasOwnField ? field?.controlId : void 0,
4310
- fieldLabelId: !hasOwnField ? field?.labelId : void 0,
4311
- fieldDescribedBy: !hasOwnField ? field?.ariaDescribedBy : void 0
4852
+ openDropdown,
4853
+ resolvedHint,
4854
+ resolvedLabel,
4855
+ resolvedSize,
4856
+ setTriggerWidth,
4857
+ triggerRef,
4858
+ controlProps: {
4859
+ clearable,
4860
+ color,
4861
+ endIcon,
4862
+ loading,
4863
+ prefix,
4864
+ startIcon,
4865
+ suffix,
4866
+ testID,
4867
+ textStyle,
4868
+ triggerStyle,
4869
+ variant
4870
+ },
4871
+ formFieldProps: {
4872
+ description,
4873
+ error,
4874
+ label,
4875
+ style
4876
+ }
4312
4877
  };
4878
+ }
4879
+ //#endregion
4880
+ //#region src/components/Select/Root/SelectRoot.tsx
4881
+ function SelectRoot(props) {
4882
+ const { contextValue, controlProps, displayValue, field, formFieldProps, hasOwnField, hasValue, isDisabled, isInvalid, isOpen, isRequired, resolvedHint, resolvedLabel, resolvedSize, triggerRef, clearValue, openDropdown, setTriggerWidth } = useSelectRootState(props);
4313
4883
  const control = /* @__PURE__ */ jsx(SelectContext.Provider, {
4314
4884
  value: contextValue,
4315
4885
  children: /* @__PURE__ */ jsxs(View, {
4316
4886
  ref: triggerRef,
4317
- testID,
4887
+ testID: controlProps.testID,
4318
4888
  onLayout: (event) => setTriggerWidth(event.nativeEvent.layout.width),
4319
4889
  children: [/* @__PURE__ */ jsx(SelectTrigger, {
4320
4890
  displayText: displayValue,
@@ -4322,24 +4892,24 @@ function SelectRoot(props) {
4322
4892
  isOpen,
4323
4893
  hasValue,
4324
4894
  size: resolvedSize,
4325
- color,
4326
- variant,
4895
+ color: controlProps.color,
4896
+ variant: controlProps.variant,
4327
4897
  disabled: isDisabled,
4328
4898
  required: isRequired,
4329
4899
  hasError: isInvalid,
4330
- loading,
4331
- clearable,
4332
- startIcon,
4333
- endIcon,
4334
- prefix,
4335
- suffix,
4900
+ loading: controlProps.loading,
4901
+ clearable: controlProps.clearable,
4902
+ startIcon: controlProps.startIcon,
4903
+ endIcon: controlProps.endIcon,
4904
+ prefix: controlProps.prefix,
4905
+ suffix: controlProps.suffix,
4336
4906
  nativeID: !hasOwnField ? field?.controlId : void 0,
4337
4907
  accessibilityLabel: resolvedLabel,
4338
4908
  accessibilityHint: resolvedHint,
4339
4909
  accessibilityLabelledBy: !hasOwnField ? field?.labelId : void 0,
4340
4910
  ariaDescribedBy: !hasOwnField ? field?.ariaDescribedBy : void 0,
4341
- triggerStyle,
4342
- textStyle,
4911
+ triggerStyle: controlProps.triggerStyle,
4912
+ textStyle: controlProps.textStyle,
4343
4913
  onPress: openDropdown,
4344
4914
  onClear: clearValue
4345
4915
  }), /* @__PURE__ */ jsx(SelectContentSurface, {})]
@@ -4347,19 +4917,22 @@ function SelectRoot(props) {
4347
4917
  });
4348
4918
  if (!hasOwnField && field) return control;
4349
4919
  return /* @__PURE__ */ jsx(FormField, {
4350
- label,
4351
- description,
4352
- error,
4920
+ label: formFieldProps.label,
4921
+ description: formFieldProps.description,
4922
+ error: formFieldProps.error,
4353
4923
  required: isRequired,
4354
4924
  disabled: isDisabled,
4355
4925
  invalid: isInvalid,
4356
4926
  size: resolvedSize,
4357
- style,
4927
+ style: formFieldProps.style,
4358
4928
  children: control
4359
4929
  });
4360
4930
  }
4361
4931
  SelectRoot.displayName = "Select";
4362
4932
  //#endregion
4933
+ //#region src/components/Select/Value/SelectValue.tsx
4934
+ const SelectValue = createSelectSlot("value", "Select.Value");
4935
+ //#endregion
4363
4936
  //#region src/components/Select/Select.tsx
4364
4937
  const Select = Object.assign(SelectRoot, {
4365
4938
  Trigger: SelectTriggerSlot,
@@ -4378,7 +4951,7 @@ const Select = Object.assign(SelectRoot, {
4378
4951
  Loading: SelectLoading
4379
4952
  });
4380
4953
  //#endregion
4381
- //#region src/components/Tabs/TabsContext.tsx
4954
+ //#region src/components/Tabs/internal/TabsContext.tsx
4382
4955
  const TabsContext = createContext(null);
4383
4956
  const TabsProvider = TabsContext.Provider;
4384
4957
  const useTabs = () => {
@@ -4441,8 +5014,8 @@ const COLLAPSED_SIZE = 8;
4441
5014
  const LINE_ANIMATION_DURATION = 360;
4442
5015
  const SURFACE_ANIMATION_DURATION = 220;
4443
5016
  const easing = Easing?.bezier?.(.22, 1, .36, 1) ?? ((value) => value);
4444
- const nativePointerEventsNone$2 = Platform.OS === "web" ? void 0 : { pointerEvents: "none" };
4445
- const webPointerEventsNone$2 = Platform.OS === "web" ? { pointerEvents: "none" } : void 0;
5017
+ const nativePointerEventsNone$1 = Platform.OS === "web" ? void 0 : { pointerEvents: "none" };
5018
+ const webPointerEventsNone$1 = Platform.OS === "web" ? { pointerEvents: "none" } : void 0;
4446
5019
  const animateValue = (value, toValue, duration) => Animated.timing(value, {
4447
5020
  toValue,
4448
5021
  duration,
@@ -4645,8 +5218,8 @@ const TabsIndicator = ({ children, style }) => {
4645
5218
  return /* @__PURE__ */ jsx(Animated.View, {
4646
5219
  accessibilityElementsHidden: true,
4647
5220
  importantForAccessibility: "no-hide-descendants",
4648
- ...nativePointerEventsNone$2,
4649
- style: [indicatorStyle, webPointerEventsNone$2],
5221
+ ...nativePointerEventsNone$1,
5222
+ style: [indicatorStyle, webPointerEventsNone$1],
4650
5223
  children
4651
5224
  });
4652
5225
  };
@@ -5148,13 +5721,45 @@ const useTooltipContext = () => {
5148
5721
  };
5149
5722
  TooltipContext.displayName = "TooltipContext";
5150
5723
  //#endregion
5724
+ //#region src/components/Tooltip/Arrow/TooltipArrow.tsx
5725
+ function TooltipArrow() {
5726
+ const { theme } = useTheme();
5727
+ const tooltip = useTooltipContext();
5728
+ const size = theme.components.tooltip.arrow.size;
5729
+ const side = tooltip.placement.split("-")[0];
5730
+ const staticSide = {
5731
+ top: "bottom",
5732
+ right: "left",
5733
+ bottom: "top",
5734
+ left: "right"
5735
+ }[side];
5736
+ const crossAxisStyle = side === "left" || side === "right" ? {
5737
+ top: tooltip.arrowPosition.top ?? 0,
5738
+ marginTop: -size / 2
5739
+ } : {
5740
+ left: tooltip.arrowPosition.left ?? 0,
5741
+ marginLeft: -size / 2
5742
+ };
5743
+ return /* @__PURE__ */ jsx(View, {
5744
+ pointerEvents: "none",
5745
+ style: {
5746
+ position: "absolute",
5747
+ width: size,
5748
+ height: size,
5749
+ backgroundColor: theme.components.tooltip.arrow.bg,
5750
+ transform: [{ rotate: "45deg" }],
5751
+ [staticSide]: -size / 2,
5752
+ ...crossAxisStyle
5753
+ }
5754
+ });
5755
+ }
5756
+ //#endregion
5151
5757
  //#region src/components/Tooltip/Tooltip.styles.ts
5152
5758
  const createStyles$3 = (theme) => StyleSheet.create({
5153
5759
  root: { alignSelf: "flex-start" },
5154
5760
  overlay: { ...StyleSheet.absoluteFill },
5155
5761
  bubble: {
5156
5762
  position: "absolute",
5157
- zIndex: 1e3,
5158
5763
  maxWidth: theme.components.tooltip.content.maxWidth,
5159
5764
  paddingHorizontal: theme.components.tooltip.content.paddingX,
5160
5765
  paddingVertical: theme.components.tooltip.content.paddingY,
@@ -5187,8 +5792,6 @@ const createStyles$3 = (theme) => StyleSheet.create({
5187
5792
  });
5188
5793
  //#endregion
5189
5794
  //#region src/components/Tooltip/Content/TooltipContent.tsx
5190
- const nativePointerEventsNone$1 = Platform.OS === "web" ? void 0 : { pointerEvents: "none" };
5191
- const webPointerEventsNone$1 = Platform.OS === "web" ? { pointerEvents: "none" } : void 0;
5192
5795
  const TooltipContent = ({ children, forceMount = false, withArrow = false, style, textStyle }) => {
5193
5796
  const styles = useThemeStyles(createStyles$3);
5194
5797
  const tooltip = useTooltipContext();
@@ -5196,13 +5799,13 @@ const TooltipContent = ({ children, forceMount = false, withArrow = false, style
5196
5799
  if (!forceMount && !visible) return null;
5197
5800
  const bubble = /* @__PURE__ */ jsxs(View, {
5198
5801
  nativeID: tooltip.contentId,
5199
- ...nativePointerEventsNone$1,
5802
+ pointerEvents: "none",
5200
5803
  style: [
5201
5804
  styles.bubble,
5202
- webPointerEventsNone$1,
5203
5805
  {
5204
5806
  top: tooltip.position.top,
5205
- left: tooltip.position.left
5807
+ left: tooltip.position.left,
5808
+ zIndex: tooltip.zIndex
5206
5809
  },
5207
5810
  !visible && { display: "none" },
5208
5811
  style
@@ -5211,7 +5814,7 @@ const TooltipContent = ({ children, forceMount = false, withArrow = false, style
5211
5814
  children: [Children.map(children, (child) => typeof child === "string" || typeof child === "number" ? /* @__PURE__ */ jsx(Text, {
5212
5815
  style: [styles.text, textStyle],
5213
5816
  children: child
5214
- }) : child), withArrow && /* @__PURE__ */ jsx(InternalArrow, {})]
5817
+ }) : child), withArrow && /* @__PURE__ */ jsx(TooltipArrow, {})]
5215
5818
  });
5216
5819
  if (!visible) return bubble;
5217
5820
  return /* @__PURE__ */ jsx(Modal$1, {
@@ -5220,46 +5823,15 @@ const TooltipContent = ({ children, forceMount = false, withArrow = false, style
5220
5823
  animationType: "fade",
5221
5824
  onRequestClose: tooltip.requestClose,
5222
5825
  children: /* @__PURE__ */ jsx(Pressable, {
5826
+ ...tooltip.getOutsidePressProps({ accessibilityLabel: "Close tooltip" }),
5223
5827
  style: styles.overlay,
5224
- onPress: tooltip.requestOutsideClose,
5225
5828
  children: bubble
5226
5829
  })
5227
5830
  });
5228
5831
  };
5229
5832
  TooltipContent.displayName = "Tooltip.Content";
5230
- function InternalArrow() {
5231
- const { theme } = useTheme();
5232
- const tooltip = useTooltipContext();
5233
- const size = theme.components.tooltip.arrow.size;
5234
- const side = tooltip.placement.split("-")[0];
5235
- const staticSide = {
5236
- top: "bottom",
5237
- right: "left",
5238
- bottom: "top",
5239
- left: "right"
5240
- }[side];
5241
- const crossAxisStyle = side === "left" || side === "right" ? {
5242
- top: tooltip.arrowPosition.top ?? 0,
5243
- marginTop: -size / 2
5244
- } : {
5245
- left: tooltip.arrowPosition.left ?? 0,
5246
- marginLeft: -size / 2
5247
- };
5248
- return /* @__PURE__ */ jsx(View, {
5249
- ...nativePointerEventsNone$1,
5250
- style: [{
5251
- position: "absolute",
5252
- width: size,
5253
- height: size,
5254
- backgroundColor: theme.components.tooltip.arrow.bg,
5255
- transform: [{ rotate: "45deg" }],
5256
- [staticSide]: -size / 2,
5257
- ...crossAxisStyle
5258
- }, webPointerEventsNone$1]
5259
- });
5260
- }
5261
5833
  //#endregion
5262
- //#region src/components/Tooltip/internal/useTooltipDelay.ts
5834
+ //#region src/components/Tooltip/internal/resolveTooltipDelay.ts
5263
5835
  const resolveTooltipDelay = (delay) => {
5264
5836
  if (typeof delay === "number") return {
5265
5837
  open: delay,
@@ -5348,15 +5920,17 @@ const TooltipRoot = ({ children, open: openProp, defaultOpen = false, onOpenChan
5348
5920
  setOpen,
5349
5921
  show,
5350
5922
  hide,
5923
+ zIndex: dismiss.zIndex,
5351
5924
  requestClose: dismiss.requestClose,
5352
- requestOutsideClose: dismiss.requestOutsideClose,
5925
+ getOutsidePressProps: dismiss.getOutsidePressProps,
5353
5926
  onFloatingLayout
5354
5927
  }), [
5355
5928
  arrowPosition,
5356
5929
  contentId,
5357
5930
  disabled,
5931
+ dismiss.zIndex,
5932
+ dismiss.getOutsidePressProps,
5358
5933
  dismiss.requestClose,
5359
- dismiss.requestOutsideClose,
5360
5934
  hide,
5361
5935
  open,
5362
5936
  resolvedPlacement,
@@ -5403,11 +5977,6 @@ const Tooltip = Object.assign(TooltipRoot, {
5403
5977
  });
5404
5978
  Tooltip.displayName = "Tooltip";
5405
5979
  //#endregion
5406
- //#region src/utils/devWarning.ts
5407
- const devWarning = (condition, message) => {
5408
- if ((typeof __DEV__ === "undefined" || __DEV__) && !condition) console.warn(message);
5409
- };
5410
- //#endregion
5411
5980
  //#region src/primitives/Button/Button.styles.ts
5412
5981
  const fontWeight = (value) => value;
5413
5982
  const createStyles$2 = (theme) => StyleSheet.create({
@@ -5426,9 +5995,14 @@ const createStyles$2 = (theme) => StyleSheet.create({
5426
5995
  fontFamily: theme.tokens.typography.family.regular,
5427
5996
  fontWeight: fontWeight(theme.tokens.typography.weight.regular),
5428
5997
  lineHeight: theme.tokens.typography.lineHeight.md,
5998
+ textAlign: "center",
5429
5999
  color: theme.components.button.primary.solid.default.fg
5430
6000
  },
5431
- labelSlot: { position: "relative" },
6001
+ labelSlot: {
6002
+ position: "relative",
6003
+ alignItems: "center",
6004
+ justifyContent: "center"
6005
+ },
5432
6006
  labelMeasure: {
5433
6007
  position: "absolute",
5434
6008
  opacity: 0
@@ -5475,22 +6049,22 @@ const createStyles$2 = (theme) => StyleSheet.create({
5475
6049
  //#region src/primitives/Button/Button.tsx
5476
6050
  const sizeMap = {
5477
6051
  sm: {
5478
- px: 12,
5479
- py: 8,
6052
+ px: 16,
6053
+ py: 6,
5480
6054
  height: 36,
5481
6055
  fontSize: 12,
5482
6056
  iconSize: 16
5483
6057
  },
5484
6058
  md: {
5485
- px: 16,
5486
- py: 12,
6059
+ px: 24,
6060
+ py: 8,
5487
6061
  height: 44,
5488
6062
  fontSize: 14,
5489
6063
  iconSize: 20
5490
6064
  },
5491
6065
  lg: {
5492
- px: 20,
5493
- py: 16,
6066
+ px: 32,
6067
+ py: 10,
5494
6068
  height: 52,
5495
6069
  fontSize: 16,
5496
6070
  iconSize: 24
@@ -5580,12 +6154,11 @@ function Button({ children, color = "primary", appearance = "solid", shape = "pi
5580
6154
  }),
5581
6155
  !loading && iconStart && renderIcon(iconStart, contentColor),
5582
6156
  content && !iconOnly && /* @__PURE__ */ jsxs(View, {
5583
- style: styles.labelSlot,
6157
+ style: [styles.labelSlot, labelWidth > 0 && { minWidth: labelWidth }],
5584
6158
  children: [/* @__PURE__ */ jsx(Text, {
5585
6159
  onLayout: handleLabelLayout,
5586
6160
  style: [
5587
6161
  styles.text,
5588
- labelWidth > 0 && { minWidth: labelWidth },
5589
6162
  {
5590
6163
  fontSize: config.fontSize,
5591
6164
  color: contentColor