@nori-ui/core 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/{chunk-V2AWSDDZ.js → chunk-76FZF4GM.js} +3 -3
  2. package/dist/{chunk-V2AWSDDZ.js.map → chunk-76FZF4GM.js.map} +1 -1
  3. package/dist/chunk-BNDUQNG7.js +443 -0
  4. package/dist/chunk-BNDUQNG7.js.map +1 -0
  5. package/dist/{chunk-XXBN6CIK.js → chunk-F7G6R373.js} +3 -3
  6. package/dist/{chunk-XXBN6CIK.js.map → chunk-F7G6R373.js.map} +1 -1
  7. package/dist/{chunk-HZKXPN6B.js → chunk-ND7MRYW7.js} +3 -3
  8. package/dist/{chunk-HZKXPN6B.js.map → chunk-ND7MRYW7.js.map} +1 -1
  9. package/dist/chunk-O4NMS3KB.js +11 -0
  10. package/dist/chunk-O4NMS3KB.js.map +1 -0
  11. package/dist/{chunk-SJZTETUT.js → chunk-VMAGFYHG.js} +3 -3
  12. package/dist/{chunk-SJZTETUT.js.map → chunk-VMAGFYHG.js.map} +1 -1
  13. package/dist/{chunk-OCHEPOOO.js → chunk-Y4ZRSW35.js} +3 -3
  14. package/dist/{chunk-OCHEPOOO.js.map → chunk-Y4ZRSW35.js.map} +1 -1
  15. package/dist/client.cjs +447 -3
  16. package/dist/client.cjs.map +1 -1
  17. package/dist/client.d.cts +2 -0
  18. package/dist/client.d.ts +2 -0
  19. package/dist/client.js +14 -12
  20. package/dist/client.js.map +1 -1
  21. package/dist/components/Accordion/index.js +2 -2
  22. package/dist/components/Calendar/index.js +2 -2
  23. package/dist/components/Combobox/index.cjs +1374 -0
  24. package/dist/components/Combobox/index.cjs.map +1 -0
  25. package/dist/components/Combobox/index.d.cts +17 -0
  26. package/dist/components/Combobox/index.d.ts +17 -0
  27. package/dist/components/Combobox/index.js +9 -0
  28. package/dist/components/Combobox/index.js.map +1 -0
  29. package/dist/components/DatePicker/index.js +3 -3
  30. package/dist/components/Pagination/index.js +2 -2
  31. package/dist/components/Sheet/index.cjs +855 -0
  32. package/dist/components/Sheet/index.cjs.map +1 -0
  33. package/dist/components/Sheet/index.d.cts +104 -0
  34. package/dist/components/Sheet/index.d.ts +104 -0
  35. package/dist/components/Sheet/index.js +8 -0
  36. package/dist/components/Sheet/index.js.map +1 -0
  37. package/dist/components/Switch/index.js +2 -2
  38. package/dist/index.cjs +447 -3
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.d.cts +2 -0
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.js +13 -11
  43. package/package.json +1 -1
@@ -0,0 +1,1374 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var reactDom = require('react-dom');
5
+ var reactNative = require('react-native');
6
+ var jsxRuntime = require('nativewind/jsx-runtime');
7
+
8
+ var __defProp = Object.defineProperty;
9
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
10
+
11
+ // ../tokens/build/theme.ts
12
+ var theme = {
13
+ color: {
14
+ danger: "#ef4444",
15
+ info: "#3b82f6",
16
+ neutral: {
17
+ "100": "#f4f4f5",
18
+ "200": "#e4e4e7",
19
+ "300": "#d4d4d8",
20
+ "400": "#a1a1aa",
21
+ "50": "#fafafa",
22
+ "500": "#71717a",
23
+ "600": "#52525b",
24
+ "700": "#3f3f46",
25
+ "800": "#27272a",
26
+ "900": "#18181b"
27
+ },
28
+ primary: {
29
+ "100": "#ccfbf1",
30
+ "200": "#99f6e4",
31
+ "300": "#5eead4",
32
+ "400": "#2dd4bf",
33
+ "50": "#f0fdfa",
34
+ "500": "#14b8a6",
35
+ "600": "#0d9488",
36
+ "700": "#0f766e",
37
+ "800": "#115e59",
38
+ "900": "#134e4a"
39
+ },
40
+ success: "#22c55e",
41
+ warning: "#f59e0b"
42
+ },
43
+ fontFamily: {
44
+ body: "system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
45
+ display: "ui-serif, Georgia, 'Times New Roman', serif",
46
+ mono: "ui-monospace, 'SF Mono', Menlo, Consolas, 'DejaVu Sans Mono', monospace"
47
+ },
48
+ fontSize: {
49
+ "2xl": "24px",
50
+ "3xl": "30px",
51
+ "4xl": "36px",
52
+ lg: "18px",
53
+ md: "16px",
54
+ sm: "14px",
55
+ xl: "20px",
56
+ xs: "12px"
57
+ },
58
+ fontWeight: {
59
+ bold: "700",
60
+ medium: "500",
61
+ regular: "400",
62
+ semibold: "600"
63
+ },
64
+ lineHeight: {
65
+ normal: "1.4",
66
+ relaxed: "1.6",
67
+ tight: "1.2"
68
+ },
69
+ radius: {
70
+ "2xl": "16px",
71
+ full: "9999px",
72
+ lg: "8px",
73
+ md: "6px",
74
+ none: "0px",
75
+ sm: "4px",
76
+ xl: "12px"
77
+ },
78
+ semantic: {
79
+ background: {
80
+ default: "#fafafa",
81
+ elevated: "#ffffff",
82
+ subtle: "#f4f4f5"
83
+ },
84
+ border: {
85
+ default: "#e4e4e7",
86
+ strong: "#d4d4d8"
87
+ },
88
+ interactive: {
89
+ destructive: "#ef4444",
90
+ primary: "#0d9488",
91
+ primaryHover: "#0f766e",
92
+ primaryPressed: "#115e59"
93
+ },
94
+ text: {
95
+ default: "#18181b",
96
+ inverted: "#fafafa",
97
+ muted: "#52525b"
98
+ }
99
+ },
100
+ shadow: {
101
+ lg: "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)",
102
+ md: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)",
103
+ sm: "0 1px 2px 0 rgba(0, 0, 0, 0.05)"
104
+ },
105
+ spacing: {
106
+ "0": "0px",
107
+ "1": "4px",
108
+ "10": "40px",
109
+ "12": "48px",
110
+ "16": "64px",
111
+ "2": "8px",
112
+ "20": "80px",
113
+ "24": "96px",
114
+ "3": "12px",
115
+ "4": "16px",
116
+ "5": "20px",
117
+ "6": "24px",
118
+ "8": "32px"
119
+ }
120
+ };
121
+ var themeDark = {
122
+ color: {
123
+ danger: "#ef4444",
124
+ info: "#3b82f6",
125
+ neutral: {
126
+ "100": "#f4f4f5",
127
+ "200": "#e4e4e7",
128
+ "300": "#d4d4d8",
129
+ "400": "#a1a1aa",
130
+ "50": "#fafafa",
131
+ "500": "#71717a",
132
+ "600": "#52525b",
133
+ "700": "#3f3f46",
134
+ "800": "#27272a",
135
+ "900": "#18181b"
136
+ },
137
+ primary: {
138
+ "100": "#ccfbf1",
139
+ "200": "#99f6e4",
140
+ "300": "#5eead4",
141
+ "400": "#2dd4bf",
142
+ "50": "#f0fdfa",
143
+ "500": "#14b8a6",
144
+ "600": "#0d9488",
145
+ "700": "#0f766e",
146
+ "800": "#115e59",
147
+ "900": "#134e4a"
148
+ },
149
+ success: "#22c55e",
150
+ warning: "#f59e0b"
151
+ },
152
+ fontFamily: {
153
+ body: "system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
154
+ display: "ui-serif, Georgia, 'Times New Roman', serif",
155
+ mono: "ui-monospace, 'SF Mono', Menlo, Consolas, 'DejaVu Sans Mono', monospace"
156
+ },
157
+ fontSize: {
158
+ "2xl": "24px",
159
+ "3xl": "30px",
160
+ "4xl": "36px",
161
+ lg: "18px",
162
+ md: "16px",
163
+ sm: "14px",
164
+ xl: "20px",
165
+ xs: "12px"
166
+ },
167
+ fontWeight: {
168
+ bold: "700",
169
+ medium: "500",
170
+ regular: "400",
171
+ semibold: "600"
172
+ },
173
+ lineHeight: {
174
+ normal: "1.4",
175
+ relaxed: "1.6",
176
+ tight: "1.2"
177
+ },
178
+ radius: {
179
+ "2xl": "16px",
180
+ full: "9999px",
181
+ lg: "8px",
182
+ md: "6px",
183
+ none: "0px",
184
+ sm: "4px",
185
+ xl: "12px"
186
+ },
187
+ semantic: {
188
+ background: {
189
+ default: "#18181b",
190
+ elevated: "#3f3f46",
191
+ subtle: "#27272a"
192
+ },
193
+ border: {
194
+ default: "#3f3f46",
195
+ strong: "#52525b"
196
+ },
197
+ interactive: {
198
+ destructive: "#ef4444",
199
+ primary: "#2dd4bf",
200
+ primaryHover: "#5eead4",
201
+ primaryPressed: "#99f6e4"
202
+ },
203
+ text: {
204
+ default: "#fafafa",
205
+ inverted: "#18181b",
206
+ muted: "#a1a1aa"
207
+ }
208
+ },
209
+ shadow: {
210
+ lg: "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)",
211
+ md: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)",
212
+ sm: "0 1px 2px 0 rgba(0, 0, 0, 0.05)"
213
+ },
214
+ spacing: {
215
+ "0": "0px",
216
+ "1": "4px",
217
+ "10": "40px",
218
+ "12": "48px",
219
+ "16": "64px",
220
+ "2": "8px",
221
+ "20": "80px",
222
+ "24": "96px",
223
+ "3": "12px",
224
+ "4": "16px",
225
+ "5": "20px",
226
+ "6": "24px",
227
+ "8": "32px"
228
+ }
229
+ };
230
+ var defaultTheme = {
231
+ light: theme,
232
+ dark: themeDark
233
+ };
234
+ var ThemeContext = react.createContext(defaultTheme);
235
+ ThemeContext.displayName = "ThemeContext";
236
+ var ColorSchemeOverrideContext = react.createContext(null);
237
+ ColorSchemeOverrideContext.displayName = "ColorSchemeOverrideContext";
238
+ var isWeb = reactNative.Platform.OS === "web";
239
+ function readWebScheme() {
240
+ if (typeof document === "undefined") {
241
+ return "light";
242
+ }
243
+ const root = document.documentElement;
244
+ if (root.classList.contains("dark")) {
245
+ return "dark";
246
+ }
247
+ if (root.getAttribute("data-theme") === "dark") {
248
+ return "dark";
249
+ }
250
+ return "light";
251
+ }
252
+ __name(readWebScheme, "readWebScheme");
253
+ function useColorScheme() {
254
+ const override = react.useContext(ColorSchemeOverrideContext);
255
+ const [scheme, setScheme] = react.useState(() => {
256
+ if (isWeb) {
257
+ return readWebScheme();
258
+ }
259
+ return reactNative.Appearance.getColorScheme() ?? "light";
260
+ });
261
+ react.useEffect(() => {
262
+ if (isWeb) {
263
+ const root = document.documentElement;
264
+ const update = /* @__PURE__ */ __name(() => setScheme(readWebScheme()), "update");
265
+ const observer = new MutationObserver(update);
266
+ observer.observe(root, { attributes: true, attributeFilter: ["class", "data-theme"] });
267
+ update();
268
+ return () => observer.disconnect();
269
+ }
270
+ const sub = reactNative.Appearance.addChangeListener(({ colorScheme }) => {
271
+ setScheme(colorScheme ?? "light");
272
+ });
273
+ return () => sub.remove();
274
+ }, []);
275
+ return override ?? scheme;
276
+ }
277
+ __name(useColorScheme, "useColorScheme");
278
+
279
+ // src/theme/use-theme-colors.ts
280
+ function useThemeColors() {
281
+ const scheme = useColorScheme();
282
+ const themePair = react.useContext(ThemeContext);
283
+ return scheme === "dark" ? themePair.dark : themePair.light;
284
+ }
285
+ __name(useThemeColors, "useThemeColors");
286
+ var isWeb2 = reactNative.Platform.OS === "web";
287
+ var make = /* @__PURE__ */ __name(({ path, glyph }) => /* @__PURE__ */ __name(function PlaceholderIcon({ size = 20, color = "currentColor" }) {
288
+ const colors = useThemeColors();
289
+ if (isWeb2) {
290
+ return /* @__PURE__ */ jsxRuntime.jsx(
291
+ "svg",
292
+ {
293
+ width: size,
294
+ height: size,
295
+ viewBox: "0 0 24 24",
296
+ fill: "none",
297
+ stroke: color,
298
+ strokeWidth: "2",
299
+ strokeLinecap: "round",
300
+ strokeLinejoin: "round",
301
+ "aria-hidden": "true",
302
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: path })
303
+ }
304
+ );
305
+ }
306
+ const resolvedColor = color === "currentColor" ? colors.semantic.text.default : color;
307
+ return /* @__PURE__ */ jsxRuntime.jsx(
308
+ reactNative.Text,
309
+ {
310
+ accessibilityElementsHidden: true,
311
+ importantForAccessibility: "no-hide-descendants",
312
+ style: { fontSize: size, lineHeight: size, color: resolvedColor },
313
+ children: glyph
314
+ }
315
+ );
316
+ }, "PlaceholderIcon"), "make");
317
+ var defaultSemanticIcons = {
318
+ checkmark: make({ path: "M20 6 9 17l-5-5", glyph: "\u2713" }),
319
+ close: make({ path: "M18 6 6 18 M6 6l12 12", glyph: "\u2715" }),
320
+ eye: make({
321
+ path: "M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z M12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6z",
322
+ glyph: "\u{1F441}"
323
+ }),
324
+ eyeOff: make({
325
+ path: "M17.94 17.94A10 10 0 0 1 2 12s3.5-7 10-7c2 0 3.8.6 5.4 1.5 M1 1l22 22",
326
+ glyph: "\u{1F648}"
327
+ }),
328
+ chevronDown: make({ path: "m6 9 6 6 6-6", glyph: "\u2304" }),
329
+ chevronUp: make({ path: "m18 15-6-6-6 6", glyph: "\u2303" }),
330
+ alertTriangle: make({
331
+ path: "M12 9v4 M12 17h.01 M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z",
332
+ glyph: "\u26A0"
333
+ }),
334
+ info: make({
335
+ path: "M12 8h.01 M11 12h1v4h1 M12 22C6.48 22 2 17.52 2 12 2 6.48 6.48 2 12 2c5.52 0 10 4.48 10 10 0 5.52-4.48 10-10 10z",
336
+ glyph: "\u24D8"
337
+ }),
338
+ check: make({ path: "M20 6 9 17l-5-5", glyph: "\u2713" }),
339
+ x: make({ path: "M18 6 6 18 M6 6l12 12", glyph: "\u2715" })
340
+ };
341
+
342
+ // src/theme/px.ts
343
+ function px(value) {
344
+ if (typeof value === "number") {
345
+ return value;
346
+ }
347
+ const n = Number.parseFloat(value);
348
+ return Number.isFinite(n) ? n : 0;
349
+ }
350
+ __name(px, "px");
351
+
352
+ // src/utils/cn.ts
353
+ function cn(...inputs) {
354
+ const out = [];
355
+ for (const input of inputs) {
356
+ append(out, input);
357
+ }
358
+ return out.join(" ");
359
+ }
360
+ __name(cn, "cn");
361
+ function append(out, input) {
362
+ if (!input) {
363
+ return;
364
+ }
365
+ if (typeof input === "string") {
366
+ if (input.length > 0) {
367
+ out.push(input);
368
+ }
369
+ return;
370
+ }
371
+ if (typeof input === "number") {
372
+ return;
373
+ }
374
+ if (Array.isArray(input)) {
375
+ for (const inner of input) {
376
+ append(out, inner);
377
+ }
378
+ return;
379
+ }
380
+ if (typeof input === "object") {
381
+ for (const key of Object.keys(input)) {
382
+ if (input[key]) {
383
+ out.push(key);
384
+ }
385
+ }
386
+ }
387
+ }
388
+ __name(append, "append");
389
+ var DEFAULT_PAGE_SIZE = 50;
390
+ var DEFAULT_ITEM_HEIGHT = 36;
391
+ var DEFAULT_MAX_MENU = 320;
392
+ var SEARCH_DEBOUNCE_MS = 150;
393
+ var VIRTUAL_OVERSCAN = 4;
394
+ var TYPE_AHEAD_RESET_MS = 500;
395
+ var defaultFilter = /* @__PURE__ */ __name((option, search) => {
396
+ if (!search) {
397
+ return true;
398
+ }
399
+ return option.label.toLowerCase().includes(search.toLowerCase());
400
+ }, "defaultFilter");
401
+ var Select = /* @__PURE__ */ __name((props) => {
402
+ const {
403
+ options: staticOptions,
404
+ loadOptions,
405
+ pageSize = DEFAULT_PAGE_SIZE,
406
+ searchable: searchableProp,
407
+ searchPlaceholder = "Search\u2026",
408
+ filterOption,
409
+ renderOption,
410
+ placeholder = "Select\u2026",
411
+ locale,
412
+ sortByLocale = true,
413
+ noOptionsMessage = "No options",
414
+ loadingMessage = "Loading\u2026",
415
+ disabled = false,
416
+ dir = "ltr",
417
+ virtualized: virtualizedProp,
418
+ itemHeight = DEFAULT_ITEM_HEIGHT,
419
+ maxMenuHeight = DEFAULT_MAX_MENU,
420
+ className,
421
+ testID,
422
+ id,
423
+ name
424
+ } = props;
425
+ const ariaLabel = props["aria-label"];
426
+ const ariaLabelledBy = props["aria-labelledby"];
427
+ const ariaDescribedBy = props["aria-describedby"];
428
+ const ariaInvalid = props["aria-invalid"];
429
+ const ariaRequired = props["aria-required"];
430
+ const multiple = props.multiple === true;
431
+ const maxSelected = multiple ? props.maxSelected : void 0;
432
+ const maxChips = multiple ? props.maxChips ?? 3 : void 0;
433
+ const baseId = react.useId();
434
+ const colors = useThemeColors();
435
+ const [open, setOpen] = react.useState(false);
436
+ const controlledValues = multiple ? props.value : props.value !== void 0 ? [props.value] : void 0;
437
+ const defaultValues = multiple ? props.defaultValue ?? [] : props.defaultValue !== void 0 ? [props.defaultValue] : [];
438
+ const [innerValues, setInnerValues] = react.useState(defaultValues);
439
+ const isControlled = controlledValues !== void 0;
440
+ const currentValues = isControlled ? controlledValues : innerValues;
441
+ const current = currentValues[0];
442
+ const [searchInput, setSearchInput] = react.useState("");
443
+ const [debouncedSearch, setDebouncedSearch] = react.useState("");
444
+ const [activeIndex, setActiveIndex] = react.useState(0);
445
+ react.useEffect(() => {
446
+ const t = setTimeout(() => setDebouncedSearch(searchInput), SEARCH_DEBOUNCE_MS);
447
+ return () => clearTimeout(t);
448
+ }, [searchInput]);
449
+ const [asyncItems, setAsyncItems] = react.useState([]);
450
+ const [asyncLoading, setAsyncLoading] = react.useState(false);
451
+ const [asyncTotal, setAsyncTotal] = react.useState(void 0);
452
+ const asyncRequestId = react.useRef(0);
453
+ const isAsync = loadOptions !== void 0;
454
+ react.useEffect(() => {
455
+ if (!isAsync || !loadOptions || !open) {
456
+ return;
457
+ }
458
+ const requestId = ++asyncRequestId.current;
459
+ setAsyncLoading(true);
460
+ setAsyncItems([]);
461
+ setAsyncTotal(void 0);
462
+ loadOptions({ search: debouncedSearch, offset: 0, limit: pageSize }).then((result) => {
463
+ if (requestId !== asyncRequestId.current) {
464
+ return;
465
+ }
466
+ setAsyncItems(result.items.slice());
467
+ setAsyncTotal(result.total);
468
+ }).catch(() => {
469
+ }).finally(() => {
470
+ if (requestId === asyncRequestId.current) {
471
+ setAsyncLoading(false);
472
+ }
473
+ });
474
+ }, [debouncedSearch, isAsync, loadOptions, pageSize, open]);
475
+ const loadMore = react.useCallback(() => {
476
+ if (!isAsync || !loadOptions || asyncLoading) {
477
+ return;
478
+ }
479
+ const haveAll = asyncTotal !== void 0 && asyncItems.length >= asyncTotal;
480
+ if (haveAll) {
481
+ return;
482
+ }
483
+ const requestId = ++asyncRequestId.current;
484
+ setAsyncLoading(true);
485
+ loadOptions({ search: debouncedSearch, offset: asyncItems.length, limit: pageSize }).then((result) => {
486
+ if (requestId !== asyncRequestId.current) {
487
+ return;
488
+ }
489
+ setAsyncItems((prev) => prev.concat(result.items));
490
+ if (result.total !== void 0) {
491
+ setAsyncTotal(result.total);
492
+ }
493
+ }).catch(() => void 0).finally(() => {
494
+ if (requestId === asyncRequestId.current) {
495
+ setAsyncLoading(false);
496
+ }
497
+ });
498
+ }, [asyncItems.length, asyncLoading, asyncTotal, debouncedSearch, isAsync, loadOptions, pageSize]);
499
+ const visibleOptions = react.useMemo(() => {
500
+ const source = isAsync ? asyncItems : staticOptions ?? [];
501
+ const filtered = isAsync ? source.slice() : source.filter((opt) => (filterOption ?? defaultFilter)(opt, debouncedSearch));
502
+ if (locale && sortByLocale) {
503
+ const collator = new Intl.Collator(locale, { sensitivity: "base", numeric: true });
504
+ return filtered.slice().sort((a, b) => {
505
+ const ga = a.group ?? "";
506
+ const gb = b.group ?? "";
507
+ const groupDelta = collator.compare(ga, gb);
508
+ if (groupDelta !== 0) {
509
+ return groupDelta;
510
+ }
511
+ return collator.compare(a.label, b.label);
512
+ });
513
+ }
514
+ return filtered;
515
+ }, [isAsync, asyncItems, staticOptions, filterOption, debouncedSearch, locale, sortByLocale]);
516
+ const selectedOption = react.useMemo(() => {
517
+ const all = isAsync ? asyncItems : staticOptions ?? [];
518
+ return all.find((o) => o.value === current);
519
+ }, [asyncItems, isAsync, staticOptions, current]);
520
+ const selectedOptions = react.useMemo(() => {
521
+ if (!multiple) {
522
+ return [];
523
+ }
524
+ const all = isAsync ? asyncItems : staticOptions ?? [];
525
+ const map = new Map(all.map((o) => [o.value, o]));
526
+ return currentValues.map((v) => map.get(v)).filter((o) => o !== void 0);
527
+ }, [multiple, currentValues, asyncItems, isAsync, staticOptions]);
528
+ const searchable = searchableProp ?? (isAsync || staticOptions !== void 0 && staticOptions.length >= 10);
529
+ const virtualized = virtualizedProp ?? visibleOptions.length > 100;
530
+ react.useEffect(() => {
531
+ setActiveIndex((idx) => Math.min(Math.max(0, idx), Math.max(0, visibleOptions.length - 1)));
532
+ }, [visibleOptions.length]);
533
+ const onSelect = react.useCallback(
534
+ (option) => {
535
+ if (option.disabled) {
536
+ return;
537
+ }
538
+ if (multiple) {
539
+ const has = currentValues.includes(option.value);
540
+ let nextValues;
541
+ if (has) {
542
+ nextValues = currentValues.filter((v) => v !== option.value);
543
+ } else {
544
+ if (maxSelected !== void 0 && currentValues.length >= maxSelected) {
545
+ return;
546
+ }
547
+ nextValues = [...currentValues, option.value];
548
+ }
549
+ if (!isControlled) {
550
+ setInnerValues(nextValues);
551
+ }
552
+ const allOpts = [
553
+ ...staticOptions ?? [],
554
+ ...asyncItems
555
+ ];
556
+ const optMap = new Map(allOpts.map((o) => [o.value, o]));
557
+ const selectedOpts = nextValues.map((v) => optMap.get(v)).filter((o) => o !== void 0);
558
+ props.onChange?.(nextValues, selectedOpts);
559
+ return;
560
+ }
561
+ if (!isControlled) {
562
+ setInnerValues([option.value]);
563
+ }
564
+ props.onChange?.(option.value, option);
565
+ setOpen(false);
566
+ setSearchInput("");
567
+ },
568
+ // biome-ignore lint/correctness/useExhaustiveDependencies: `props` is the discriminated union — destructuring it would defeat the narrowing; the asyncItems / staticOptions captures intentionally re-trigger the callback when the option pool changes
569
+ [multiple, isControlled, currentValues, maxSelected, staticOptions, asyncItems, props]
570
+ );
571
+ const clearAll = react.useCallback(() => {
572
+ if (!isControlled) {
573
+ setInnerValues([]);
574
+ }
575
+ props.onChange?.([], []);
576
+ }, [isControlled, props]);
577
+ const moveActive = react.useCallback(
578
+ (delta) => {
579
+ setActiveIndex((idx) => {
580
+ if (visibleOptions.length === 0) {
581
+ return 0;
582
+ }
583
+ let next = (idx + delta + visibleOptions.length) % visibleOptions.length;
584
+ for (let attempts = 0; attempts < visibleOptions.length; attempts += 1) {
585
+ if (!visibleOptions[next]?.disabled) {
586
+ return next;
587
+ }
588
+ next = (next + delta + visibleOptions.length) % visibleOptions.length;
589
+ }
590
+ return idx;
591
+ });
592
+ },
593
+ [visibleOptions]
594
+ );
595
+ const typeAheadRef = react.useRef({
596
+ buffer: "",
597
+ timer: null
598
+ });
599
+ react.useEffect(() => {
600
+ return () => {
601
+ if (typeAheadRef.current.timer) {
602
+ clearTimeout(typeAheadRef.current.timer);
603
+ typeAheadRef.current.timer = null;
604
+ }
605
+ };
606
+ }, []);
607
+ const handleTypeAhead = react.useCallback(
608
+ (char) => {
609
+ if (visibleOptions.length === 0) {
610
+ return;
611
+ }
612
+ if (typeAheadRef.current.timer) {
613
+ clearTimeout(typeAheadRef.current.timer);
614
+ }
615
+ const nextBuffer = typeAheadRef.current.buffer + char.toLowerCase();
616
+ typeAheadRef.current.buffer = nextBuffer;
617
+ typeAheadRef.current.timer = setTimeout(() => {
618
+ typeAheadRef.current.buffer = "";
619
+ typeAheadRef.current.timer = null;
620
+ }, TYPE_AHEAD_RESET_MS);
621
+ const allSame = nextBuffer.length > 1 && nextBuffer.split("").every((c) => c === nextBuffer[0]);
622
+ const cycleMode = nextBuffer.length === 1 || allSame;
623
+ const needle = cycleMode ? nextBuffer.charAt(0) : nextBuffer;
624
+ const len = visibleOptions.length;
625
+ const startFrom = cycleMode ? (activeIndex + 1) % len : 0;
626
+ for (let i = 0; i < len; i += 1) {
627
+ const idx = (startFrom + i) % len;
628
+ const opt = visibleOptions[idx];
629
+ if (!opt || opt.disabled) {
630
+ continue;
631
+ }
632
+ if (opt.label.toLowerCase().startsWith(needle)) {
633
+ setActiveIndex(idx);
634
+ return;
635
+ }
636
+ }
637
+ },
638
+ [visibleOptions, activeIndex]
639
+ );
640
+ const handleListKeyDown = react.useCallback(
641
+ (event) => {
642
+ switch (event.key) {
643
+ case "ArrowDown":
644
+ event.preventDefault();
645
+ moveActive(1);
646
+ return true;
647
+ case "ArrowUp":
648
+ event.preventDefault();
649
+ moveActive(-1);
650
+ return true;
651
+ case "Home": {
652
+ event.preventDefault();
653
+ const idx = visibleOptions.findIndex((o) => !o.disabled);
654
+ if (idx >= 0) {
655
+ setActiveIndex(idx);
656
+ }
657
+ return true;
658
+ }
659
+ case "End": {
660
+ event.preventDefault();
661
+ for (let i = visibleOptions.length - 1; i >= 0; i -= 1) {
662
+ if (!visibleOptions[i]?.disabled) {
663
+ setActiveIndex(i);
664
+ break;
665
+ }
666
+ }
667
+ return true;
668
+ }
669
+ case "Enter": {
670
+ const opt = visibleOptions[activeIndex];
671
+ if (opt) {
672
+ event.preventDefault();
673
+ onSelect(opt);
674
+ }
675
+ return true;
676
+ }
677
+ case "Escape":
678
+ event.preventDefault();
679
+ setOpen(false);
680
+ if (reactNative.Platform.OS === "web") {
681
+ const trigger = triggerRef.current;
682
+ trigger?.focus?.();
683
+ }
684
+ return true;
685
+ case "Tab":
686
+ setOpen(false);
687
+ return true;
688
+ }
689
+ return false;
690
+ },
691
+ [moveActive, activeIndex, visibleOptions, onSelect]
692
+ );
693
+ const handleSearchKeyDown = react.useCallback(
694
+ (event) => {
695
+ handleListKeyDown(event);
696
+ },
697
+ [handleListKeyDown]
698
+ );
699
+ const handlePopupKeyDown = react.useCallback(
700
+ (event) => {
701
+ if (handleListKeyDown(event)) {
702
+ return;
703
+ }
704
+ if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey && event.key !== " ") {
705
+ event.preventDefault();
706
+ handleTypeAhead(event.key);
707
+ }
708
+ },
709
+ [handleListKeyDown, handleTypeAhead]
710
+ );
711
+ const handleTriggerKeyDown = react.useCallback(
712
+ (event) => {
713
+ switch (event.key) {
714
+ case " ":
715
+ case "Enter":
716
+ case "ArrowDown":
717
+ case "ArrowUp":
718
+ event.preventDefault();
719
+ setOpen(true);
720
+ return;
721
+ }
722
+ if (!disabled && event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
723
+ event.preventDefault();
724
+ setOpen(true);
725
+ handleTypeAhead(event.key);
726
+ }
727
+ },
728
+ [disabled, handleTypeAhead]
729
+ );
730
+ const containerRef = react.useRef(null);
731
+ const triggerRef = react.useRef(null);
732
+ const popupRef = react.useRef(null);
733
+ react.useEffect(() => {
734
+ if (reactNative.Platform.OS !== "web" || typeof document === "undefined" || typeof document.addEventListener !== "function") {
735
+ return;
736
+ }
737
+ if (!open) {
738
+ return;
739
+ }
740
+ const onDocClick = /* @__PURE__ */ __name((event) => {
741
+ const node = containerRef.current;
742
+ const popup = popupRef.current;
743
+ const target = event.target;
744
+ if (node?.contains(target)) {
745
+ return;
746
+ }
747
+ if (popup?.contains(target)) {
748
+ return;
749
+ }
750
+ setOpen(false);
751
+ }, "onDocClick");
752
+ document.addEventListener("mousedown", onDocClick);
753
+ return () => document.removeEventListener("mousedown", onDocClick);
754
+ }, [open]);
755
+ const [triggerRect, setTriggerRect] = react.useState(
756
+ null
757
+ );
758
+ const measureTrigger = react.useCallback(() => {
759
+ const node = triggerRef.current;
760
+ if (!node) {
761
+ return;
762
+ }
763
+ if (reactNative.Platform.OS === "web" && typeof node.getBoundingClientRect === "function") {
764
+ const rect = node.getBoundingClientRect();
765
+ setTriggerRect({ top: rect.top, left: rect.left, width: rect.width, height: rect.height });
766
+ return;
767
+ }
768
+ if (typeof node.measure === "function") {
769
+ node.measure((_x, _y, w, h, pageX, pageY) => {
770
+ setTriggerRect({ top: pageY, left: pageX, width: w, height: h });
771
+ });
772
+ }
773
+ }, []);
774
+ react.useEffect(() => {
775
+ if (!open) {
776
+ return;
777
+ }
778
+ if (reactNative.Platform.OS !== "web" || typeof window === "undefined" || typeof window.addEventListener !== "function") {
779
+ return;
780
+ }
781
+ measureTrigger();
782
+ window.addEventListener("scroll", measureTrigger, true);
783
+ window.addEventListener("resize", measureTrigger);
784
+ return () => {
785
+ window.removeEventListener("scroll", measureTrigger, true);
786
+ window.removeEventListener("resize", measureTrigger);
787
+ };
788
+ }, [open, measureTrigger]);
789
+ react.useEffect(() => {
790
+ if (!open || searchable || reactNative.Platform.OS !== "web") {
791
+ return;
792
+ }
793
+ const id2 = requestAnimationFrame(() => {
794
+ const node = popupRef.current;
795
+ node?.focus?.();
796
+ });
797
+ return () => cancelAnimationFrame(id2);
798
+ }, [open, searchable]);
799
+ const onListScroll = react.useCallback(
800
+ (event) => {
801
+ if (!isAsync) {
802
+ return;
803
+ }
804
+ const { contentOffset, contentSize, layoutMeasurement } = event.nativeEvent;
805
+ const remaining = contentSize.height - contentOffset.y - layoutMeasurement.height;
806
+ if (remaining < itemHeight * 4) {
807
+ loadMore();
808
+ }
809
+ },
810
+ [isAsync, itemHeight, loadMore]
811
+ );
812
+ const triggerStyle = {
813
+ flexDirection: "row",
814
+ alignItems: "center",
815
+ justifyContent: "space-between",
816
+ gap: px(colors.spacing["2"]),
817
+ paddingHorizontal: px(colors.spacing["3"]),
818
+ paddingVertical: px(colors.spacing["2"]),
819
+ minHeight: 36,
820
+ // component-density literal — not from theme
821
+ borderWidth: 1,
822
+ borderColor: colors.semantic.border.default,
823
+ borderRadius: px(colors.radius.md),
824
+ backgroundColor: colors.semantic.background.elevated,
825
+ opacity: disabled ? 0.6 : 1
826
+ };
827
+ const winDims = reactNative.useWindowDimensions();
828
+ const popupStyle = triggerRect ? {
829
+ position: reactNative.Platform.OS === "web" ? "fixed" : "absolute",
830
+ top: triggerRect.top + triggerRect.height + px(colors.spacing["1"]),
831
+ left: dir === "rtl" ? void 0 : triggerRect.left,
832
+ right: dir === "rtl" ? reactNative.Platform.OS === "web" && typeof window !== "undefined" ? window.innerWidth - (triggerRect.left + triggerRect.width) : winDims.width - (triggerRect.left + triggerRect.width) : void 0,
833
+ minWidth: Math.max(200, triggerRect.width),
834
+ backgroundColor: colors.semantic.background.elevated,
835
+ borderRadius: px(colors.radius.lg),
836
+ borderWidth: 1,
837
+ borderColor: colors.semantic.border.default,
838
+ // 2147483646 (max int32 - 1) so we sit above any third-party
839
+ // chrome (toasts, modals, dev banners) without picking a fight
840
+ // for the very top slot. Combined with portaling to body below,
841
+ // this also dodges any ancestor stacking context that would
842
+ // otherwise trap our z-index inside a sibling preview frame.
843
+ zIndex: 2147483646,
844
+ ...{ boxShadow: "0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)" }
845
+ } : {
846
+ // Trigger not yet measured — render off-screen until the
847
+ // first measurement lands. Avoids a one-frame flash at (0,0).
848
+ position: reactNative.Platform.OS === "web" ? "fixed" : "absolute",
849
+ top: -9999,
850
+ left: -9999
851
+ };
852
+ const containerProps = {
853
+ ref: /* @__PURE__ */ __name((node) => {
854
+ containerRef.current = node;
855
+ }, "ref"),
856
+ ...testID !== void 0 ? { testID } : {},
857
+ dir
858
+ };
859
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { ...containerProps, className: cn("relative", className), style: { position: "relative" }, children: [
860
+ /* @__PURE__ */ jsxRuntime.jsxs(
861
+ reactNative.Pressable,
862
+ {
863
+ ref: (node) => {
864
+ triggerRef.current = node;
865
+ },
866
+ ...{
867
+ onKeyDown: handleTriggerKeyDown,
868
+ role: "combobox",
869
+ accessibilityRole: "combobox",
870
+ "aria-expanded": open,
871
+ "aria-controls": `${baseId}-listbox`,
872
+ "aria-haspopup": "listbox",
873
+ tabIndex: disabled ? -1 : 0,
874
+ ...id !== void 0 ? { id, nativeID: id } : {},
875
+ ...name !== void 0 ? { name } : {},
876
+ ...ariaLabel !== void 0 ? { "aria-label": ariaLabel, accessibilityLabel: ariaLabel } : {},
877
+ ...ariaLabelledBy !== void 0 ? { "aria-labelledby": ariaLabelledBy, accessibilityLabelledBy: ariaLabelledBy } : {},
878
+ ...ariaDescribedBy !== void 0 ? { "aria-describedby": ariaDescribedBy, accessibilityDescribedBy: ariaDescribedBy } : {},
879
+ ...ariaInvalid === true ? { "aria-invalid": true } : {},
880
+ ...ariaRequired === true ? { "aria-required": true } : {},
881
+ ...disabled ? { "aria-disabled": true, disabled: true } : {}
882
+ },
883
+ onPress: () => {
884
+ if (disabled) {
885
+ return;
886
+ }
887
+ measureTrigger();
888
+ setOpen((v) => !v);
889
+ },
890
+ style: triggerStyle,
891
+ children: [
892
+ multiple ? /* @__PURE__ */ jsxRuntime.jsx(MultiTriggerLabel, { options: selectedOptions, placeholder, maxChips: maxChips ?? 3 }) : /* @__PURE__ */ jsxRuntime.jsx(
893
+ reactNative.Text,
894
+ {
895
+ style: {
896
+ color: selectedOption ? colors.semantic.text.default : colors.semantic.text.muted,
897
+ fontFamily: colors.fontFamily.body,
898
+ fontSize: px(colors.fontSize.sm),
899
+ flex: 1
900
+ },
901
+ numberOfLines: 1,
902
+ children: selectedOption?.label ?? placeholder
903
+ }
904
+ ),
905
+ /* @__PURE__ */ jsxRuntime.jsx(defaultSemanticIcons.chevronDown, { size: 16, color: colors.semantic.text.muted })
906
+ ]
907
+ }
908
+ ),
909
+ open ? renderPopup() : null
910
+ ] });
911
+ function renderPopup() {
912
+ const popup = /* @__PURE__ */ jsxRuntime.jsxs(
913
+ reactNative.View,
914
+ {
915
+ ref: (node) => {
916
+ popupRef.current = node;
917
+ },
918
+ ...{
919
+ role: "listbox",
920
+ id: `${baseId}-listbox`,
921
+ ...multiple ? { "aria-multiselectable": true } : {},
922
+ // Without a search field there's no input to capture
923
+ // keystrokes — make the popup itself focusable and own
924
+ // arrow / Enter / Escape / type-ahead. With a search
925
+ // field these belong to the input below.
926
+ ...searchable ? {} : { tabIndex: -1, onKeyDown: handlePopupKeyDown }
927
+ },
928
+ style: popupStyle,
929
+ children: [
930
+ searchable ? /* @__PURE__ */ jsxRuntime.jsx(
931
+ SearchInput,
932
+ {
933
+ value: searchInput,
934
+ onChange: setSearchInput,
935
+ onKeyDown: handleSearchKeyDown,
936
+ placeholder: searchPlaceholder,
937
+ dir
938
+ }
939
+ ) : null,
940
+ multiple && currentValues.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(MultiSelectionHeader, { count: currentValues.length, onClearAll: clearAll }) : null,
941
+ /* @__PURE__ */ jsxRuntime.jsx(
942
+ SelectList,
943
+ {
944
+ options: visibleOptions,
945
+ activeIndex,
946
+ currentValue: current,
947
+ selectedValues: currentValues,
948
+ multiple,
949
+ onSelect,
950
+ onActiveChange: setActiveIndex,
951
+ ...renderOption !== void 0 ? { renderOption } : {},
952
+ itemHeight,
953
+ maxHeight: maxMenuHeight,
954
+ virtualized,
955
+ loading: isAsync && asyncLoading,
956
+ loadingMessage,
957
+ noOptionsMessage,
958
+ listboxId: `${baseId}-listbox`,
959
+ onScroll: onListScroll
960
+ }
961
+ )
962
+ ]
963
+ }
964
+ );
965
+ if (reactNative.Platform.OS === "web" && typeof document !== "undefined") {
966
+ return reactDom.createPortal(popup, document.body);
967
+ }
968
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Modal, { transparent: true, visible: true, animationType: "fade", onRequestClose: () => setOpen(false), statusBarTranslucent: true, children: [
969
+ /* @__PURE__ */ jsxRuntime.jsx(
970
+ reactNative.Pressable,
971
+ {
972
+ onPress: () => setOpen(false),
973
+ style: {
974
+ position: "absolute",
975
+ top: 0,
976
+ left: 0,
977
+ right: 0,
978
+ bottom: 0
979
+ }
980
+ }
981
+ ),
982
+ popup
983
+ ] });
984
+ }
985
+ }, "Select");
986
+ var SearchInput = /* @__PURE__ */ __name(({ value, onChange, onKeyDown, placeholder, dir }) => {
987
+ const colors = useThemeColors();
988
+ const inputRef = react.useRef(null);
989
+ react.useEffect(() => {
990
+ inputRef.current?.focus?.();
991
+ }, []);
992
+ return /* @__PURE__ */ jsxRuntime.jsx(
993
+ reactNative.View,
994
+ {
995
+ style: {
996
+ paddingHorizontal: px(colors.spacing["2"]),
997
+ paddingVertical: px(colors.spacing["2"]),
998
+ borderBottomWidth: 1,
999
+ borderBottomColor: colors.semantic.border.default
1000
+ },
1001
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1002
+ "input",
1003
+ {
1004
+ ref: inputRef,
1005
+ type: "text",
1006
+ value,
1007
+ onChange: (event) => onChange(event.target.value),
1008
+ onKeyDown,
1009
+ placeholder,
1010
+ dir,
1011
+ "aria-label": "Search options",
1012
+ style: {
1013
+ width: "100%",
1014
+ // Inline `padding: '6px 8px'` shorthand intentionally kept as a
1015
+ // string for the native HTML <input> — it's not an RN style prop.
1016
+ padding: `${px(colors.spacing["2"]) - 2}px ${px(colors.spacing["2"])}px`,
1017
+ fontFamily: colors.fontFamily.body,
1018
+ fontSize: px(colors.fontSize.sm),
1019
+ color: colors.semantic.text.default,
1020
+ backgroundColor: colors.semantic.background.elevated,
1021
+ border: `1px solid ${colors.semantic.border.default}`,
1022
+ borderRadius: px(colors.radius.sm),
1023
+ outline: "none"
1024
+ }
1025
+ }
1026
+ )
1027
+ }
1028
+ );
1029
+ }, "SearchInput");
1030
+ var SelectList = /* @__PURE__ */ __name(({
1031
+ options,
1032
+ activeIndex,
1033
+ currentValue,
1034
+ selectedValues,
1035
+ multiple,
1036
+ onSelect,
1037
+ onActiveChange,
1038
+ renderOption,
1039
+ itemHeight,
1040
+ maxHeight,
1041
+ virtualized,
1042
+ loading,
1043
+ loadingMessage,
1044
+ noOptionsMessage,
1045
+ listboxId,
1046
+ onScroll
1047
+ }) => {
1048
+ const colors = useThemeColors();
1049
+ const [scrollTop, setScrollTop] = react.useState(0);
1050
+ const totalHeight = options.length * itemHeight;
1051
+ const visibleStart = virtualized ? Math.max(0, Math.floor(scrollTop / itemHeight) - VIRTUAL_OVERSCAN) : 0;
1052
+ const visibleEnd = virtualized ? Math.min(options.length, Math.ceil((scrollTop + maxHeight) / itemHeight) + VIRTUAL_OVERSCAN) : options.length;
1053
+ const handleScroll = /* @__PURE__ */ __name((event) => {
1054
+ if (virtualized) {
1055
+ setScrollTop(event.nativeEvent.contentOffset.y);
1056
+ }
1057
+ onScroll(event);
1058
+ }, "handleScroll");
1059
+ if (loading && options.length === 0) {
1060
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { padding: px(colors.spacing["4"]), alignItems: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1061
+ reactNative.Text,
1062
+ {
1063
+ style: {
1064
+ color: colors.semantic.text.muted,
1065
+ fontFamily: colors.fontFamily.body,
1066
+ fontSize: px(colors.fontSize.sm)
1067
+ },
1068
+ children: loadingMessage
1069
+ }
1070
+ ) });
1071
+ }
1072
+ if (options.length === 0) {
1073
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { padding: px(colors.spacing["4"]), alignItems: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1074
+ reactNative.Text,
1075
+ {
1076
+ style: {
1077
+ color: colors.semantic.text.muted,
1078
+ fontFamily: colors.fontFamily.body,
1079
+ fontSize: px(colors.fontSize.sm)
1080
+ },
1081
+ children: noOptionsMessage
1082
+ }
1083
+ ) });
1084
+ }
1085
+ const items = [];
1086
+ let lastGroup;
1087
+ for (let i = visibleStart; i < visibleEnd; i += 1) {
1088
+ const opt = options[i];
1089
+ if (!opt) {
1090
+ continue;
1091
+ }
1092
+ if (opt.group !== lastGroup && opt.group !== void 0) {
1093
+ items.push(
1094
+ /* @__PURE__ */ jsxRuntime.jsx(
1095
+ reactNative.View,
1096
+ {
1097
+ style: {
1098
+ paddingHorizontal: px(colors.spacing["3"]),
1099
+ paddingTop: px(colors.spacing["2"]),
1100
+ paddingBottom: px(colors.spacing["1"]),
1101
+ position: virtualized ? "absolute" : "relative",
1102
+ top: virtualized ? i * itemHeight - px(colors.spacing["4"]) : void 0,
1103
+ left: 0,
1104
+ right: 0
1105
+ },
1106
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1107
+ reactNative.Text,
1108
+ {
1109
+ style: {
1110
+ color: colors.semantic.text.muted,
1111
+ fontFamily: colors.fontFamily.body,
1112
+ fontSize: 11,
1113
+ // group header — component-density literal — not from theme (smaller than xs)
1114
+ fontWeight: colors.fontWeight.semibold,
1115
+ textTransform: "uppercase",
1116
+ letterSpacing: 0.5
1117
+ },
1118
+ children: opt.group
1119
+ }
1120
+ )
1121
+ },
1122
+ `grp-${i}-${opt.group}`
1123
+ )
1124
+ );
1125
+ lastGroup = opt.group;
1126
+ }
1127
+ const selected = multiple ? selectedValues.includes(opt.value) : opt.value === currentValue;
1128
+ const active = i === activeIndex;
1129
+ const itemNode = renderOption ? renderOption(opt, { selected, active }) : /* @__PURE__ */ jsxRuntime.jsx(DefaultOptionRow, { option: opt, selected, active, multiple });
1130
+ items.push(
1131
+ /* @__PURE__ */ jsxRuntime.jsx(
1132
+ reactNative.Pressable,
1133
+ {
1134
+ ...{
1135
+ role: "option",
1136
+ accessibilityRole: "none",
1137
+ "aria-selected": selected,
1138
+ onMouseEnter: /* @__PURE__ */ __name(() => onActiveChange(i), "onMouseEnter"),
1139
+ ...opt.disabled ? { "aria-disabled": true, disabled: true } : {}
1140
+ },
1141
+ onPress: () => onSelect(opt),
1142
+ style: {
1143
+ position: virtualized ? "absolute" : "relative",
1144
+ top: virtualized ? i * itemHeight : void 0,
1145
+ left: 0,
1146
+ right: 0,
1147
+ height: itemHeight,
1148
+ flexDirection: "row",
1149
+ alignItems: "center",
1150
+ paddingHorizontal: px(colors.spacing["3"]),
1151
+ backgroundColor: active ? colors.semantic.background.subtle : "transparent",
1152
+ opacity: opt.disabled ? 0.5 : 1
1153
+ },
1154
+ children: itemNode
1155
+ },
1156
+ `opt-${i}-${opt.value}`
1157
+ )
1158
+ );
1159
+ }
1160
+ return /* @__PURE__ */ jsxRuntime.jsx(
1161
+ reactNative.ScrollView,
1162
+ {
1163
+ nativeID: listboxId,
1164
+ onScroll: handleScroll,
1165
+ scrollEventThrottle: 16,
1166
+ style: { maxHeight },
1167
+ contentContainerStyle: virtualized ? { height: totalHeight, position: "relative" } : void 0,
1168
+ children: items
1169
+ }
1170
+ );
1171
+ }, "SelectList");
1172
+ var DefaultOptionRow = /* @__PURE__ */ __name(({
1173
+ option,
1174
+ selected,
1175
+ active,
1176
+ multiple = false
1177
+ }) => {
1178
+ const colors = useThemeColors();
1179
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flexDirection: "row", alignItems: "center", flex: 1, gap: px(colors.spacing["2"]) }, children: [
1180
+ multiple ? (
1181
+ // Inline checkbox-style indicator. We don't reuse <Checkbox>
1182
+ // here because the row is already a Pressable — nesting two
1183
+ // pressable surfaces breaks tap handling on native; this
1184
+ // is purely visual (the Pressable parent owns the toggle).
1185
+ /* @__PURE__ */ jsxRuntime.jsx(
1186
+ reactNative.View,
1187
+ {
1188
+ "aria-hidden": true,
1189
+ accessibilityElementsHidden: true,
1190
+ importantForAccessibility: "no-hide-descendants",
1191
+ style: {
1192
+ width: 18,
1193
+ height: 18,
1194
+ borderWidth: 1,
1195
+ borderRadius: px(colors.radius.sm),
1196
+ borderColor: selected ? colors.semantic.interactive.primary : colors.semantic.border.strong,
1197
+ backgroundColor: selected ? colors.semantic.interactive.primary : "transparent",
1198
+ alignItems: "center",
1199
+ justifyContent: "center"
1200
+ },
1201
+ children: selected ? /* @__PURE__ */ jsxRuntime.jsx(defaultSemanticIcons.check, { size: 12, color: colors.semantic.text.inverted }) : null
1202
+ }
1203
+ )
1204
+ ) : null,
1205
+ /* @__PURE__ */ jsxRuntime.jsx(
1206
+ reactNative.Text,
1207
+ {
1208
+ style: {
1209
+ color: colors.semantic.text.default,
1210
+ fontFamily: colors.fontFamily.body,
1211
+ fontSize: px(colors.fontSize.sm),
1212
+ fontWeight: selected ? colors.fontWeight.semibold : colors.fontWeight.regular,
1213
+ flex: 1
1214
+ },
1215
+ numberOfLines: 1,
1216
+ children: option.label
1217
+ }
1218
+ ),
1219
+ selected && !multiple ? /* @__PURE__ */ jsxRuntime.jsx(defaultSemanticIcons.check, { size: 16, color: colors.semantic.interactive.primary }) : null,
1220
+ active ? null : null
1221
+ ] });
1222
+ }, "DefaultOptionRow");
1223
+ var MultiTriggerLabel = /* @__PURE__ */ __name(({
1224
+ options,
1225
+ placeholder,
1226
+ maxChips
1227
+ }) => {
1228
+ const colors = useThemeColors();
1229
+ if (options.length === 0) {
1230
+ return /* @__PURE__ */ jsxRuntime.jsx(
1231
+ reactNative.Text,
1232
+ {
1233
+ style: {
1234
+ color: colors.semantic.text.muted,
1235
+ fontFamily: colors.fontFamily.body,
1236
+ fontSize: px(colors.fontSize.sm),
1237
+ flex: 1
1238
+ },
1239
+ numberOfLines: 1,
1240
+ children: placeholder
1241
+ }
1242
+ );
1243
+ }
1244
+ if (options.length > maxChips) {
1245
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1246
+ reactNative.Text,
1247
+ {
1248
+ style: {
1249
+ color: colors.semantic.text.default,
1250
+ fontFamily: colors.fontFamily.body,
1251
+ fontSize: px(colors.fontSize.sm),
1252
+ fontWeight: colors.fontWeight.medium,
1253
+ fontVariant: ["tabular-nums"],
1254
+ flex: 1
1255
+ },
1256
+ numberOfLines: 1,
1257
+ children: [
1258
+ options.length,
1259
+ " selected"
1260
+ ]
1261
+ }
1262
+ );
1263
+ }
1264
+ return /* @__PURE__ */ jsxRuntime.jsx(
1265
+ reactNative.View,
1266
+ {
1267
+ style: {
1268
+ flexDirection: "row",
1269
+ alignItems: "center",
1270
+ flexWrap: "wrap",
1271
+ rowGap: px(colors.spacing["1"]),
1272
+ columnGap: px(colors.spacing["1"]),
1273
+ flex: 1
1274
+ },
1275
+ children: options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(
1276
+ reactNative.View,
1277
+ {
1278
+ style: {
1279
+ flexDirection: "row",
1280
+ alignItems: "center",
1281
+ paddingHorizontal: px(colors.spacing["2"]),
1282
+ paddingVertical: 2,
1283
+ borderRadius: px(colors.radius.sm),
1284
+ backgroundColor: colors.semantic.background.subtle,
1285
+ borderWidth: 1,
1286
+ borderColor: colors.semantic.border.default
1287
+ },
1288
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1289
+ reactNative.Text,
1290
+ {
1291
+ style: {
1292
+ color: colors.semantic.text.default,
1293
+ fontFamily: colors.fontFamily.body,
1294
+ fontSize: px(colors.fontSize.sm)
1295
+ },
1296
+ numberOfLines: 1,
1297
+ children: opt.label
1298
+ }
1299
+ )
1300
+ },
1301
+ `chip-${opt.value}`
1302
+ ))
1303
+ }
1304
+ );
1305
+ }, "MultiTriggerLabel");
1306
+ var MultiSelectionHeader = /* @__PURE__ */ __name(({ count, onClearAll }) => {
1307
+ const colors = useThemeColors();
1308
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1309
+ reactNative.View,
1310
+ {
1311
+ style: {
1312
+ flexDirection: "row",
1313
+ alignItems: "center",
1314
+ justifyContent: "space-between",
1315
+ paddingHorizontal: px(colors.spacing["3"]),
1316
+ paddingVertical: px(colors.spacing["2"]),
1317
+ borderBottomWidth: 1,
1318
+ borderBottomColor: colors.semantic.border.default
1319
+ },
1320
+ children: [
1321
+ /* @__PURE__ */ jsxRuntime.jsxs(
1322
+ reactNative.Text,
1323
+ {
1324
+ style: {
1325
+ color: colors.semantic.text.muted,
1326
+ fontFamily: colors.fontFamily.body,
1327
+ fontSize: px(colors.fontSize.sm),
1328
+ fontVariant: ["tabular-nums"]
1329
+ },
1330
+ children: [
1331
+ count,
1332
+ " selected"
1333
+ ]
1334
+ }
1335
+ ),
1336
+ /* @__PURE__ */ jsxRuntime.jsx(
1337
+ reactNative.Pressable,
1338
+ {
1339
+ role: "button",
1340
+ accessibilityRole: "button",
1341
+ "aria-label": "Clear all",
1342
+ accessibilityLabel: "Clear all",
1343
+ onPress: onClearAll,
1344
+ style: ({ pressed }) => ({
1345
+ paddingHorizontal: px(colors.spacing["2"]),
1346
+ paddingVertical: 2,
1347
+ borderRadius: px(colors.radius.sm),
1348
+ opacity: pressed ? 0.6 : 1
1349
+ }),
1350
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1351
+ reactNative.Text,
1352
+ {
1353
+ style: {
1354
+ color: colors.semantic.interactive.primary,
1355
+ fontFamily: colors.fontFamily.body,
1356
+ fontSize: px(colors.fontSize.sm),
1357
+ fontWeight: colors.fontWeight.medium
1358
+ },
1359
+ children: "Clear all"
1360
+ }
1361
+ )
1362
+ }
1363
+ )
1364
+ ]
1365
+ }
1366
+ );
1367
+ }, "MultiSelectionHeader");
1368
+ var Combobox = /* @__PURE__ */ __name((props) => {
1369
+ return /* @__PURE__ */ jsxRuntime.jsx(Select, { searchable: true, ...props });
1370
+ }, "Combobox");
1371
+
1372
+ exports.Combobox = Combobox;
1373
+ //# sourceMappingURL=index.cjs.map
1374
+ //# sourceMappingURL=index.cjs.map