@nori-ui/core 1.1.0 → 1.2.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.
@@ -0,0 +1,3875 @@
1
+ 'use strict';
2
+
3
+ var date = require('@internationalized/date');
4
+ var react = require('react');
5
+ var reactNative = require('react-native');
6
+ var jsxRuntime = require('nativewind/jsx-runtime');
7
+ var reactDom = require('react-dom');
8
+
9
+ var __defProp = Object.defineProperty;
10
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
11
+ var detectLocale = /* @__PURE__ */ __name(() => {
12
+ try {
13
+ return new Intl.DateTimeFormat().resolvedOptions().locale;
14
+ } catch {
15
+ return "en-US";
16
+ }
17
+ }, "detectLocale");
18
+ var LocaleContext = react.createContext(null);
19
+ LocaleContext.displayName = "LocaleContext";
20
+ var useLocale = /* @__PURE__ */ __name(() => {
21
+ const ctx = react.useContext(LocaleContext);
22
+ return ctx ?? detectLocale();
23
+ }, "useLocale");
24
+
25
+ // src/theme/px.ts
26
+ function px(value) {
27
+ if (typeof value === "number") {
28
+ return value;
29
+ }
30
+ const n = Number.parseFloat(value);
31
+ return Number.isFinite(n) ? n : 0;
32
+ }
33
+ __name(px, "px");
34
+
35
+ // ../tokens/build/theme.ts
36
+ var theme = {
37
+ color: {
38
+ danger: "#ef4444",
39
+ info: "#3b82f6",
40
+ neutral: {
41
+ "100": "#f4f4f5",
42
+ "200": "#e4e4e7",
43
+ "300": "#d4d4d8",
44
+ "400": "#a1a1aa",
45
+ "50": "#fafafa",
46
+ "500": "#71717a",
47
+ "600": "#52525b",
48
+ "700": "#3f3f46",
49
+ "800": "#27272a",
50
+ "900": "#18181b"
51
+ },
52
+ primary: {
53
+ "100": "#ccfbf1",
54
+ "200": "#99f6e4",
55
+ "300": "#5eead4",
56
+ "400": "#2dd4bf",
57
+ "50": "#f0fdfa",
58
+ "500": "#14b8a6",
59
+ "600": "#0d9488",
60
+ "700": "#0f766e",
61
+ "800": "#115e59",
62
+ "900": "#134e4a"
63
+ },
64
+ success: "#22c55e",
65
+ warning: "#f59e0b"
66
+ },
67
+ fontFamily: {
68
+ body: "system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
69
+ display: "ui-serif, Georgia, 'Times New Roman', serif",
70
+ mono: "ui-monospace, 'SF Mono', Menlo, Consolas, 'DejaVu Sans Mono', monospace"
71
+ },
72
+ fontSize: {
73
+ "2xl": "24px",
74
+ "3xl": "30px",
75
+ "4xl": "36px",
76
+ lg: "18px",
77
+ md: "16px",
78
+ sm: "14px",
79
+ xl: "20px",
80
+ xs: "12px"
81
+ },
82
+ fontWeight: {
83
+ bold: "700",
84
+ medium: "500",
85
+ regular: "400",
86
+ semibold: "600"
87
+ },
88
+ lineHeight: {
89
+ normal: "1.4",
90
+ relaxed: "1.6",
91
+ tight: "1.2"
92
+ },
93
+ radius: {
94
+ "2xl": "16px",
95
+ full: "9999px",
96
+ lg: "8px",
97
+ md: "6px",
98
+ none: "0px",
99
+ sm: "4px",
100
+ xl: "12px"
101
+ },
102
+ semantic: {
103
+ background: {
104
+ default: "#fafafa",
105
+ elevated: "#ffffff",
106
+ subtle: "#f4f4f5"
107
+ },
108
+ border: {
109
+ default: "#e4e4e7",
110
+ strong: "#d4d4d8"
111
+ },
112
+ interactive: {
113
+ destructive: "#ef4444",
114
+ primary: "#0d9488",
115
+ primaryHover: "#0f766e",
116
+ primaryPressed: "#115e59"
117
+ },
118
+ text: {
119
+ default: "#18181b",
120
+ inverted: "#fafafa",
121
+ muted: "#52525b"
122
+ }
123
+ },
124
+ shadow: {
125
+ lg: "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)",
126
+ md: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)",
127
+ sm: "0 1px 2px 0 rgba(0, 0, 0, 0.05)"
128
+ },
129
+ spacing: {
130
+ "0": "0px",
131
+ "1": "4px",
132
+ "10": "40px",
133
+ "12": "48px",
134
+ "16": "64px",
135
+ "2": "8px",
136
+ "20": "80px",
137
+ "24": "96px",
138
+ "3": "12px",
139
+ "4": "16px",
140
+ "5": "20px",
141
+ "6": "24px",
142
+ "8": "32px"
143
+ }
144
+ };
145
+ var themeDark = {
146
+ color: {
147
+ danger: "#ef4444",
148
+ info: "#3b82f6",
149
+ neutral: {
150
+ "100": "#f4f4f5",
151
+ "200": "#e4e4e7",
152
+ "300": "#d4d4d8",
153
+ "400": "#a1a1aa",
154
+ "50": "#fafafa",
155
+ "500": "#71717a",
156
+ "600": "#52525b",
157
+ "700": "#3f3f46",
158
+ "800": "#27272a",
159
+ "900": "#18181b"
160
+ },
161
+ primary: {
162
+ "100": "#ccfbf1",
163
+ "200": "#99f6e4",
164
+ "300": "#5eead4",
165
+ "400": "#2dd4bf",
166
+ "50": "#f0fdfa",
167
+ "500": "#14b8a6",
168
+ "600": "#0d9488",
169
+ "700": "#0f766e",
170
+ "800": "#115e59",
171
+ "900": "#134e4a"
172
+ },
173
+ success: "#22c55e",
174
+ warning: "#f59e0b"
175
+ },
176
+ fontFamily: {
177
+ body: "system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
178
+ display: "ui-serif, Georgia, 'Times New Roman', serif",
179
+ mono: "ui-monospace, 'SF Mono', Menlo, Consolas, 'DejaVu Sans Mono', monospace"
180
+ },
181
+ fontSize: {
182
+ "2xl": "24px",
183
+ "3xl": "30px",
184
+ "4xl": "36px",
185
+ lg: "18px",
186
+ md: "16px",
187
+ sm: "14px",
188
+ xl: "20px",
189
+ xs: "12px"
190
+ },
191
+ fontWeight: {
192
+ bold: "700",
193
+ medium: "500",
194
+ regular: "400",
195
+ semibold: "600"
196
+ },
197
+ lineHeight: {
198
+ normal: "1.4",
199
+ relaxed: "1.6",
200
+ tight: "1.2"
201
+ },
202
+ radius: {
203
+ "2xl": "16px",
204
+ full: "9999px",
205
+ lg: "8px",
206
+ md: "6px",
207
+ none: "0px",
208
+ sm: "4px",
209
+ xl: "12px"
210
+ },
211
+ semantic: {
212
+ background: {
213
+ default: "#18181b",
214
+ elevated: "#3f3f46",
215
+ subtle: "#27272a"
216
+ },
217
+ border: {
218
+ default: "#3f3f46",
219
+ strong: "#52525b"
220
+ },
221
+ interactive: {
222
+ destructive: "#ef4444",
223
+ primary: "#2dd4bf",
224
+ primaryHover: "#5eead4",
225
+ primaryPressed: "#99f6e4"
226
+ },
227
+ text: {
228
+ default: "#fafafa",
229
+ inverted: "#18181b",
230
+ muted: "#a1a1aa"
231
+ }
232
+ },
233
+ shadow: {
234
+ lg: "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)",
235
+ md: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)",
236
+ sm: "0 1px 2px 0 rgba(0, 0, 0, 0.05)"
237
+ },
238
+ spacing: {
239
+ "0": "0px",
240
+ "1": "4px",
241
+ "10": "40px",
242
+ "12": "48px",
243
+ "16": "64px",
244
+ "2": "8px",
245
+ "20": "80px",
246
+ "24": "96px",
247
+ "3": "12px",
248
+ "4": "16px",
249
+ "5": "20px",
250
+ "6": "24px",
251
+ "8": "32px"
252
+ }
253
+ };
254
+ var defaultTheme = {
255
+ light: theme,
256
+ dark: themeDark
257
+ };
258
+ var ThemeContext = react.createContext(defaultTheme);
259
+ ThemeContext.displayName = "ThemeContext";
260
+ var ColorSchemeOverrideContext = react.createContext(null);
261
+ ColorSchemeOverrideContext.displayName = "ColorSchemeOverrideContext";
262
+ var isWeb = reactNative.Platform.OS === "web";
263
+ function readWebScheme() {
264
+ if (typeof document === "undefined") {
265
+ return "light";
266
+ }
267
+ const root = document.documentElement;
268
+ if (root.classList.contains("dark")) {
269
+ return "dark";
270
+ }
271
+ if (root.getAttribute("data-theme") === "dark") {
272
+ return "dark";
273
+ }
274
+ return "light";
275
+ }
276
+ __name(readWebScheme, "readWebScheme");
277
+ function useColorScheme() {
278
+ const override = react.useContext(ColorSchemeOverrideContext);
279
+ const [scheme, setScheme] = react.useState(() => {
280
+ if (isWeb) {
281
+ return readWebScheme();
282
+ }
283
+ return reactNative.Appearance.getColorScheme() ?? "light";
284
+ });
285
+ react.useEffect(() => {
286
+ if (isWeb) {
287
+ const root = document.documentElement;
288
+ const update = /* @__PURE__ */ __name(() => setScheme(readWebScheme()), "update");
289
+ const observer = new MutationObserver(update);
290
+ observer.observe(root, { attributes: true, attributeFilter: ["class", "data-theme"] });
291
+ update();
292
+ return () => observer.disconnect();
293
+ }
294
+ const sub = reactNative.Appearance.addChangeListener(({ colorScheme }) => {
295
+ setScheme(colorScheme ?? "light");
296
+ });
297
+ return () => sub.remove();
298
+ }, []);
299
+ return override ?? scheme;
300
+ }
301
+ __name(useColorScheme, "useColorScheme");
302
+
303
+ // src/theme/use-theme-colors.ts
304
+ function useThemeColors() {
305
+ const scheme = useColorScheme();
306
+ const themePair = react.useContext(ThemeContext);
307
+ return scheme === "dark" ? themePair.dark : themePair.light;
308
+ }
309
+ __name(useThemeColors, "useThemeColors");
310
+
311
+ // src/utils/cn.ts
312
+ function cn(...inputs) {
313
+ const out = [];
314
+ for (const input of inputs) {
315
+ append(out, input);
316
+ }
317
+ return out.join(" ");
318
+ }
319
+ __name(cn, "cn");
320
+ function append(out, input) {
321
+ if (!input) {
322
+ return;
323
+ }
324
+ if (typeof input === "string") {
325
+ if (input.length > 0) {
326
+ out.push(input);
327
+ }
328
+ return;
329
+ }
330
+ if (typeof input === "number") {
331
+ return;
332
+ }
333
+ if (Array.isArray(input)) {
334
+ for (const inner of input) {
335
+ append(out, inner);
336
+ }
337
+ return;
338
+ }
339
+ if (typeof input === "object") {
340
+ for (const key of Object.keys(input)) {
341
+ if (input[key]) {
342
+ out.push(key);
343
+ }
344
+ }
345
+ }
346
+ }
347
+ __name(append, "append");
348
+
349
+ // src/components/Calendar/scroll/ScrollBody.tsx
350
+ var ScrollBody = /* @__PURE__ */ __name((_props) => {
351
+ throw new Error(
352
+ "[Calendar] ScrollBody: no platform implementation resolved. Ensure your bundler honors *.web.tsx / *.native.tsx extensions."
353
+ );
354
+ }, "ScrollBody");
355
+
356
+ // src/components/Calendar/state/locale-utils.ts
357
+ var getFirstDayOfWeek = /* @__PURE__ */ __name((locale) => {
358
+ try {
359
+ const loc = new Intl.Locale(locale);
360
+ const info = loc.getWeekInfo?.() ?? loc.weekInfo;
361
+ if (typeof info?.firstDay === "number") {
362
+ return info.firstDay === 7 ? 0 : info.firstDay;
363
+ }
364
+ } catch {
365
+ }
366
+ return FIRST_DAY_FALLBACK[locale] ?? FIRST_DAY_FALLBACK[locale.split("-")[0] ?? ""] ?? 1;
367
+ }, "getFirstDayOfWeek");
368
+ var FIRST_DAY_FALLBACK = {
369
+ en: 0,
370
+ "en-US": 0,
371
+ "en-CA": 0,
372
+ "en-GB": 1,
373
+ "en-AU": 1,
374
+ de: 1,
375
+ fr: 1,
376
+ ja: 0,
377
+ ar: 0,
378
+ he: 0,
379
+ fa: 6
380
+ };
381
+ var getWeekendDays = /* @__PURE__ */ __name((locale) => {
382
+ try {
383
+ const loc = new Intl.Locale(locale);
384
+ const info = loc.getWeekInfo?.() ?? loc.weekInfo;
385
+ if (info?.weekend && Array.isArray(info.weekend) && info.weekend.length === 2) {
386
+ const [a, b] = info.weekend.map((d) => d === 7 ? 0 : d);
387
+ return [a, b];
388
+ }
389
+ } catch {
390
+ }
391
+ return WEEKEND_FALLBACK[locale] ?? WEEKEND_FALLBACK[locale.split("-")[0] ?? ""] ?? [6, 0];
392
+ }, "getWeekendDays");
393
+ var WEEKEND_FALLBACK = {
394
+ "en-US": [6, 0],
395
+ "de-DE": [6, 0],
396
+ "fr-FR": [6, 0],
397
+ "ja-JP": [6, 0],
398
+ "ar-SA": [5, 6],
399
+ "ar-AE": [5, 6],
400
+ "he-IL": [5, 6],
401
+ "fa-IR": [4, 5],
402
+ en: [6, 0],
403
+ de: [6, 0],
404
+ fr: [6, 0],
405
+ ar: [5, 6],
406
+ he: [5, 6]
407
+ };
408
+ var formatWeekdayNames = /* @__PURE__ */ __name((locale, format = "short") => {
409
+ const fmt = new Intl.DateTimeFormat(locale, { weekday: format });
410
+ const start = getFirstDayOfWeek(locale);
411
+ return Array.from({ length: 7 }, (_, i) => {
412
+ const d = new Date(Date.UTC(2026, 0, 4 + (start + i) % 7));
413
+ return fmt.format(d);
414
+ });
415
+ }, "formatWeekdayNames");
416
+ var formatMonthYearTitle = /* @__PURE__ */ __name((date, locale) => {
417
+ const fmt = new Intl.DateTimeFormat(locale, { month: "long", year: "numeric" });
418
+ return fmt.format(date.toDate("UTC"));
419
+ }, "formatMonthYearTitle");
420
+ var formatMonthNames = /* @__PURE__ */ __name((locale) => {
421
+ const fmt = new Intl.DateTimeFormat(locale, { month: "long" });
422
+ return Array.from({ length: 12 }, (_, m) => fmt.format(new Date(Date.UTC(2026, m, 15))));
423
+ }, "formatMonthNames");
424
+ var useCalendarKeyboard = /* @__PURE__ */ __name((props) => {
425
+ const { focusedDate, moveFocus, selectDate, firstDayOfWeek = 0 } = props;
426
+ const onKeyDown = react.useCallback(
427
+ (event) => {
428
+ const focusedDow = focusedDate.toDate("UTC").getUTCDay();
429
+ switch (event.key) {
430
+ case "ArrowLeft":
431
+ event.preventDefault();
432
+ moveFocus({ days: -1 });
433
+ return;
434
+ case "ArrowRight":
435
+ event.preventDefault();
436
+ moveFocus({ days: 1 });
437
+ return;
438
+ case "ArrowUp":
439
+ event.preventDefault();
440
+ moveFocus({ weeks: -1 });
441
+ return;
442
+ case "ArrowDown":
443
+ event.preventDefault();
444
+ moveFocus({ weeks: 1 });
445
+ return;
446
+ case "PageUp":
447
+ event.preventDefault();
448
+ moveFocus(event.shiftKey ? { years: -1 } : { months: -1 });
449
+ return;
450
+ case "PageDown":
451
+ event.preventDefault();
452
+ moveFocus(event.shiftKey ? { years: 1 } : { months: 1 });
453
+ return;
454
+ case "Home": {
455
+ event.preventDefault();
456
+ const back = (focusedDow - firstDayOfWeek + 7) % 7;
457
+ moveFocus({ days: -back });
458
+ return;
459
+ }
460
+ case "End": {
461
+ event.preventDefault();
462
+ const back = (focusedDow - firstDayOfWeek + 7) % 7;
463
+ const forward = 6 - back;
464
+ moveFocus({ days: forward });
465
+ return;
466
+ }
467
+ case "Enter":
468
+ case " ":
469
+ event.preventDefault();
470
+ selectDate(focusedDate, "keyboard");
471
+ return;
472
+ default:
473
+ return;
474
+ }
475
+ },
476
+ [focusedDate, firstDayOfWeek, moveFocus, selectDate]
477
+ );
478
+ return { onKeyDown };
479
+ }, "useCalendarKeyboard");
480
+
481
+ // src/components/Calendar/state/constraints.ts
482
+ var cmp = /* @__PURE__ */ __name((a, b) => a.compare(b), "cmp");
483
+ var isOutOfRange = /* @__PURE__ */ __name((date, bounds = {}) => {
484
+ if (bounds.minValue && cmp(date, bounds.minValue) < 0) {
485
+ return true;
486
+ }
487
+ if (bounds.maxValue && cmp(date, bounds.maxValue) > 0) {
488
+ return true;
489
+ }
490
+ return false;
491
+ }, "isOutOfRange");
492
+ var composeUnavailable = /* @__PURE__ */ __name((c) => (date) => {
493
+ if (isOutOfRange(date, c)) {
494
+ return true;
495
+ }
496
+ if (c.isDateUnavailable?.(date)) {
497
+ return true;
498
+ }
499
+ return false;
500
+ }, "composeUnavailable");
501
+
502
+ // src/components/Calendar/state/use-calendar-state.ts
503
+ var initialFocus = /* @__PURE__ */ __name((mode, value, fallback) => {
504
+ if (!value) {
505
+ return fallback;
506
+ }
507
+ if (mode === "single") {
508
+ return value ?? fallback;
509
+ }
510
+ if (mode === "range") {
511
+ const r = value;
512
+ return r?.start ?? fallback;
513
+ }
514
+ const arr = value;
515
+ return arr[0] ?? fallback;
516
+ }, "initialFocus");
517
+ var useCalendarState = /* @__PURE__ */ __name((props) => {
518
+ const mode = props.mode ?? "single";
519
+ const fallback = date.today(date.getLocalTimeZone());
520
+ const [internalValue, setInternalValue] = react.useState(() => {
521
+ if (props.value !== void 0) {
522
+ return props.value;
523
+ }
524
+ if (props.defaultValue !== void 0) {
525
+ return props.defaultValue;
526
+ }
527
+ return mode === "multiple" ? [] : null;
528
+ });
529
+ const isControlled = props.value !== void 0;
530
+ const value = isControlled ? props.value : internalValue;
531
+ const [internalView, setInternalView] = react.useState(props.defaultView ?? "day");
532
+ const isViewControlled = props.view !== void 0;
533
+ const view = isViewControlled ? props.view : internalView;
534
+ const [focusedDate, setFocusedDate] = react.useState(() => initialFocus(mode, value, fallback));
535
+ const isUnavailable = react.useMemo(
536
+ () => composeUnavailable({
537
+ ...props.minValue !== void 0 ? { minValue: props.minValue } : {},
538
+ ...props.maxValue !== void 0 ? { maxValue: props.maxValue } : {},
539
+ ...props.isDateUnavailable !== void 0 ? { isDateUnavailable: props.isDateUnavailable } : {}
540
+ }),
541
+ [props.minValue, props.maxValue, props.isDateUnavailable]
542
+ );
543
+ const setView = react.useCallback(
544
+ (next) => {
545
+ if (!isViewControlled) {
546
+ setInternalView(next);
547
+ }
548
+ props.onViewChange?.(next);
549
+ },
550
+ [isViewControlled, props.onViewChange]
551
+ );
552
+ const moveFocus = react.useCallback(
553
+ (delta) => {
554
+ setFocusedDate((cur) => {
555
+ let next = cur;
556
+ if (delta.days) {
557
+ next = next.add({ days: delta.days });
558
+ }
559
+ if (delta.weeks) {
560
+ next = next.add({ weeks: delta.weeks });
561
+ }
562
+ if (delta.months) {
563
+ next = next.add({ months: delta.months });
564
+ }
565
+ if (delta.years) {
566
+ next = next.add({ years: delta.years });
567
+ }
568
+ if (!isUnavailable(next)) {
569
+ return next;
570
+ }
571
+ const totalDelta = (delta.days ?? 0) + (delta.weeks ?? 0) * 7 + (delta.months ?? 0) * 30 + (delta.years ?? 0) * 365;
572
+ const sign = totalDelta >= 0 ? 1 : -1;
573
+ for (let i = 1; i <= 100; i++) {
574
+ const candidate = next.add({ days: sign * i });
575
+ if (!isUnavailable(candidate)) {
576
+ return candidate;
577
+ }
578
+ }
579
+ return cur;
580
+ });
581
+ },
582
+ [isUnavailable]
583
+ );
584
+ const selectDate = react.useCallback(
585
+ (date, source) => {
586
+ if (isUnavailable(date)) {
587
+ return;
588
+ }
589
+ const meta = { view, source };
590
+ let next;
591
+ if (mode === "single") {
592
+ next = date;
593
+ } else if (mode === "multiple") {
594
+ const arr = value ?? [];
595
+ const exists = arr.some((d) => d.compare(date) === 0);
596
+ next = exists ? arr.filter((d) => d.compare(date) !== 0) : [...arr, date];
597
+ } else {
598
+ return;
599
+ }
600
+ if (!isControlled) {
601
+ setInternalValue(next);
602
+ }
603
+ props.onChange?.(next, meta);
604
+ setFocusedDate(date);
605
+ },
606
+ [isControlled, isUnavailable, mode, props.onChange, value, view]
607
+ );
608
+ return {
609
+ value,
610
+ view,
611
+ focusedDate,
612
+ setView,
613
+ moveFocus,
614
+ setFocusedDate,
615
+ selectDate,
616
+ isUnavailable
617
+ };
618
+ }, "useCalendarState");
619
+ var order = /* @__PURE__ */ __name((a, b) => a.compare(b) <= 0 ? [a, b] : [b, a], "order");
620
+ var nightsBetween = /* @__PURE__ */ __name((a, b) => {
621
+ const [first, last] = order(a, b);
622
+ return Math.round((last.toDate("UTC").getTime() - first.toDate("UTC").getTime()) / 864e5);
623
+ }, "nightsBetween");
624
+ var useRangeState = /* @__PURE__ */ __name((props) => {
625
+ const [internal, setInternal] = react.useState(props.defaultValue ?? null);
626
+ const isControlled = props.value !== void 0;
627
+ const value = isControlled ? props.value ?? null : internal;
628
+ const [hoveredDate, setHoveredDate] = react.useState(null);
629
+ const isUnavailable = react.useMemo(
630
+ () => composeUnavailable({
631
+ ...props.minValue !== void 0 ? { minValue: props.minValue } : {},
632
+ ...props.maxValue !== void 0 ? { maxValue: props.maxValue } : {},
633
+ ...props.isDateUnavailable !== void 0 ? { isDateUnavailable: props.isDateUnavailable } : {}
634
+ }),
635
+ [props.minValue, props.maxValue, props.isDateUnavailable]
636
+ );
637
+ const commit = react.useCallback(
638
+ (next, source) => {
639
+ if (!isControlled) {
640
+ setInternal(next);
641
+ }
642
+ props.onChange?.(next, { view: "day", source });
643
+ },
644
+ [isControlled, props.onChange]
645
+ );
646
+ const selectDate = react.useCallback(
647
+ (date, source = "click") => {
648
+ if (isUnavailable(date)) {
649
+ return;
650
+ }
651
+ if (!value || value.end !== null) {
652
+ commit({ start: date, end: null }, source);
653
+ setHoveredDate(null);
654
+ return;
655
+ }
656
+ const nights = nightsBetween(value.start, date);
657
+ if (props.minNights !== void 0 && nights < props.minNights) {
658
+ return;
659
+ }
660
+ if (props.maxNights !== void 0 && nights > props.maxNights) {
661
+ return;
662
+ }
663
+ const [start, end] = order(value.start, date);
664
+ commit({ start, end }, source);
665
+ setHoveredDate(null);
666
+ },
667
+ [commit, isUnavailable, props.maxNights, props.minNights, value]
668
+ );
669
+ const previewRange = react.useMemo(() => {
670
+ if (!value || value.end !== null || !hoveredDate) {
671
+ return null;
672
+ }
673
+ const [start, end] = order(value.start, hoveredDate);
674
+ return { start, end };
675
+ }, [hoveredDate, value]);
676
+ return {
677
+ value,
678
+ previewRange,
679
+ hoveredDate,
680
+ selectDate,
681
+ setHoveredDate,
682
+ isUnavailable
683
+ };
684
+ }, "useRangeState");
685
+
686
+ // src/i18n/default-dictionary.ts
687
+ var defaultDictionary = {
688
+ // generic / shared
689
+ "common.cancel": "Cancel",
690
+ "common.confirm": "Confirm",
691
+ "common.close": "Close",
692
+ "common.back": "Back",
693
+ "common.loading": "Loading",
694
+ "common.error": "Something went wrong",
695
+ "common.retry": "Try again",
696
+ // breadcrumb
697
+ "breadcrumb.ariaLabel": "Breadcrumb",
698
+ "breadcrumb.expandLabel": "Show full path",
699
+ "breadcrumb.ellipsisLabel": "More",
700
+ "breadcrumb.currentPageLabel": "Current page",
701
+ "breadcrumb.siblingMenuLabel": "Open sibling pages",
702
+ // pagination
703
+ "pagination.ariaLabel": "Pagination",
704
+ "pagination.previous": "Previous page",
705
+ "pagination.next": "Next page",
706
+ "pagination.first": "First page",
707
+ "pagination.last": "Last page",
708
+ "pagination.ellipsis": "More pages",
709
+ "pagination.currentPage": "Current page",
710
+ "pagination.gotoPage": "Go to page {{page}}",
711
+ "pagination.range": "Showing {{from}}\u2013{{to}} of {{total}}",
712
+ "pagination.pageOf": "Page {{page}} of {{total}}",
713
+ "pagination.pageSizeLabel": "Items per page",
714
+ "pagination.jumperLabel": "Go to page",
715
+ "pagination.jumperPlaceholder": "#",
716
+ // floatButton
717
+ "floatButton.backToTop": "Back to top",
718
+ // calendar
719
+ "calendar.header.previous": "Previous month",
720
+ "calendar.header.next": "Next month",
721
+ "calendar.header.openMonthView": "Open month picker",
722
+ "calendar.header.openYearView": "Open year picker",
723
+ "calendar.header.openDayView": "Open day picker",
724
+ "calendar.today": "Today",
725
+ "calendar.clear": "Clear",
726
+ // button
727
+ "button.loadingLabel": "Loading",
728
+ // input
729
+ "input.clear": "Clear",
730
+ "input.passwordShow": "Show password",
731
+ "input.passwordHide": "Hide password",
732
+ // checkbox / switch
733
+ "checkbox.checked": "Checked",
734
+ "checkbox.unchecked": "Unchecked",
735
+ "switch.on": "On",
736
+ "switch.off": "Off",
737
+ // field
738
+ "field.requiredIndicator": "*",
739
+ "field.requiredLabel": "required"
740
+ };
741
+
742
+ // src/i18n/resolve.ts
743
+ function resolveI18n(input, defaults) {
744
+ if (typeof input === "function") {
745
+ return (keyOrKeys, options) => input(keyOrKeys, options);
746
+ }
747
+ const dict = input ?? {};
748
+ return (keyOrKeys, options) => {
749
+ const keys = Array.isArray(keyOrKeys) ? keyOrKeys : [keyOrKeys];
750
+ for (const rawKey of keys) {
751
+ const key = pluralize(rawKey, options?.count);
752
+ const template = dict[key] ?? defaults[key];
753
+ if (template !== void 0) {
754
+ return interpolate(template, options);
755
+ }
756
+ }
757
+ const lastKey = keys[keys.length - 1];
758
+ if (options?.defaultValue !== void 0) {
759
+ return interpolate(options.defaultValue, options);
760
+ }
761
+ return lastKey ?? "";
762
+ };
763
+ }
764
+ __name(resolveI18n, "resolveI18n");
765
+ function pluralize(key, count) {
766
+ if (count === void 0) {
767
+ return key;
768
+ }
769
+ if (count === 1) {
770
+ return `${key}_one`;
771
+ }
772
+ return `${key}_other`;
773
+ }
774
+ __name(pluralize, "pluralize");
775
+ function interpolate(template, options) {
776
+ if (!options) {
777
+ return template;
778
+ }
779
+ return template.replace(/\{\{\s*([A-Za-z0-9_.-]+)\s*\}\}/g, (_match, name) => {
780
+ const value = options[name];
781
+ return value === void 0 || value === null ? "" : String(value);
782
+ });
783
+ }
784
+ __name(interpolate, "interpolate");
785
+ var defaultValue = {
786
+ t: resolveI18n(void 0, defaultDictionary)
787
+ };
788
+ var I18nContext = react.createContext(defaultValue);
789
+ I18nContext.displayName = "I18nContext";
790
+
791
+ // src/i18n/use-translation.ts
792
+ function useTranslation() {
793
+ return react.useContext(I18nContext);
794
+ }
795
+ __name(useTranslation, "useTranslation");
796
+ var isWeb2 = reactNative.Platform.OS === "web";
797
+ var make = /* @__PURE__ */ __name(({ path, glyph }) => /* @__PURE__ */ __name(function PlaceholderIcon({ size = 20, color = "currentColor" }) {
798
+ const colors = useThemeColors();
799
+ if (isWeb2) {
800
+ return /* @__PURE__ */ jsxRuntime.jsx(
801
+ "svg",
802
+ {
803
+ width: size,
804
+ height: size,
805
+ viewBox: "0 0 24 24",
806
+ fill: "none",
807
+ stroke: color,
808
+ strokeWidth: "2",
809
+ strokeLinecap: "round",
810
+ strokeLinejoin: "round",
811
+ "aria-hidden": "true",
812
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: path })
813
+ }
814
+ );
815
+ }
816
+ const resolvedColor = color === "currentColor" ? colors.semantic.text.default : color;
817
+ return /* @__PURE__ */ jsxRuntime.jsx(
818
+ reactNative.Text,
819
+ {
820
+ accessibilityElementsHidden: true,
821
+ importantForAccessibility: "no-hide-descendants",
822
+ style: { fontSize: size, lineHeight: size, color: resolvedColor },
823
+ children: glyph
824
+ }
825
+ );
826
+ }, "PlaceholderIcon"), "make");
827
+ var defaultSemanticIcons = {
828
+ checkmark: make({ path: "M20 6 9 17l-5-5", glyph: "\u2713" }),
829
+ close: make({ path: "M18 6 6 18 M6 6l12 12", glyph: "\u2715" }),
830
+ eye: make({
831
+ 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",
832
+ glyph: "\u{1F441}"
833
+ }),
834
+ eyeOff: make({
835
+ 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",
836
+ glyph: "\u{1F648}"
837
+ }),
838
+ chevronDown: make({ path: "m6 9 6 6 6-6", glyph: "\u2304" }),
839
+ chevronUp: make({ path: "m18 15-6-6-6 6", glyph: "\u2303" }),
840
+ alertTriangle: make({
841
+ 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",
842
+ glyph: "\u26A0"
843
+ }),
844
+ info: make({
845
+ 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",
846
+ glyph: "\u24D8"
847
+ }),
848
+ check: make({ path: "M20 6 9 17l-5-5", glyph: "\u2713" }),
849
+ x: make({ path: "M18 6 6 18 M6 6l12 12", glyph: "\u2715" })
850
+ };
851
+ var DEFAULT_PAGE_SIZE = 50;
852
+ var DEFAULT_ITEM_HEIGHT = 36;
853
+ var DEFAULT_MAX_MENU = 320;
854
+ var SEARCH_DEBOUNCE_MS = 150;
855
+ var VIRTUAL_OVERSCAN = 4;
856
+ var TYPE_AHEAD_RESET_MS = 500;
857
+ var defaultFilter = /* @__PURE__ */ __name((option, search) => {
858
+ if (!search) {
859
+ return true;
860
+ }
861
+ return option.label.toLowerCase().includes(search.toLowerCase());
862
+ }, "defaultFilter");
863
+ var Select = /* @__PURE__ */ __name((props) => {
864
+ const {
865
+ options: staticOptions,
866
+ loadOptions,
867
+ pageSize = DEFAULT_PAGE_SIZE,
868
+ searchable: searchableProp,
869
+ searchPlaceholder = "Search\u2026",
870
+ filterOption,
871
+ renderOption,
872
+ placeholder = "Select\u2026",
873
+ locale,
874
+ sortByLocale = true,
875
+ noOptionsMessage = "No options",
876
+ loadingMessage = "Loading\u2026",
877
+ disabled = false,
878
+ dir = "ltr",
879
+ virtualized: virtualizedProp,
880
+ itemHeight = DEFAULT_ITEM_HEIGHT,
881
+ maxMenuHeight = DEFAULT_MAX_MENU,
882
+ className,
883
+ testID,
884
+ id,
885
+ name
886
+ } = props;
887
+ const ariaLabel = props["aria-label"];
888
+ const ariaLabelledBy = props["aria-labelledby"];
889
+ const ariaDescribedBy = props["aria-describedby"];
890
+ const ariaInvalid = props["aria-invalid"];
891
+ const ariaRequired = props["aria-required"];
892
+ const multiple = props.multiple === true;
893
+ const maxSelected = multiple ? props.maxSelected : void 0;
894
+ const maxChips = multiple ? props.maxChips ?? 3 : void 0;
895
+ const baseId = react.useId();
896
+ const colors = useThemeColors();
897
+ const [open, setOpen] = react.useState(false);
898
+ const controlledValues = multiple ? props.value : props.value !== void 0 ? [props.value] : void 0;
899
+ const defaultValues = multiple ? props.defaultValue ?? [] : props.defaultValue !== void 0 ? [props.defaultValue] : [];
900
+ const [innerValues, setInnerValues] = react.useState(defaultValues);
901
+ const isControlled = controlledValues !== void 0;
902
+ const currentValues = isControlled ? controlledValues : innerValues;
903
+ const current = currentValues[0];
904
+ const [searchInput, setSearchInput] = react.useState("");
905
+ const [debouncedSearch, setDebouncedSearch] = react.useState("");
906
+ const [activeIndex, setActiveIndex] = react.useState(0);
907
+ react.useEffect(() => {
908
+ const t = setTimeout(() => setDebouncedSearch(searchInput), SEARCH_DEBOUNCE_MS);
909
+ return () => clearTimeout(t);
910
+ }, [searchInput]);
911
+ const [asyncItems, setAsyncItems] = react.useState([]);
912
+ const [asyncLoading, setAsyncLoading] = react.useState(false);
913
+ const [asyncTotal, setAsyncTotal] = react.useState(void 0);
914
+ const asyncRequestId = react.useRef(0);
915
+ const isAsync = loadOptions !== void 0;
916
+ react.useEffect(() => {
917
+ if (!isAsync || !loadOptions || !open) {
918
+ return;
919
+ }
920
+ const requestId = ++asyncRequestId.current;
921
+ setAsyncLoading(true);
922
+ setAsyncItems([]);
923
+ setAsyncTotal(void 0);
924
+ loadOptions({ search: debouncedSearch, offset: 0, limit: pageSize }).then((result) => {
925
+ if (requestId !== asyncRequestId.current) {
926
+ return;
927
+ }
928
+ setAsyncItems(result.items.slice());
929
+ setAsyncTotal(result.total);
930
+ }).catch(() => {
931
+ }).finally(() => {
932
+ if (requestId === asyncRequestId.current) {
933
+ setAsyncLoading(false);
934
+ }
935
+ });
936
+ }, [debouncedSearch, isAsync, loadOptions, pageSize, open]);
937
+ const loadMore = react.useCallback(() => {
938
+ if (!isAsync || !loadOptions || asyncLoading) {
939
+ return;
940
+ }
941
+ const haveAll = asyncTotal !== void 0 && asyncItems.length >= asyncTotal;
942
+ if (haveAll) {
943
+ return;
944
+ }
945
+ const requestId = ++asyncRequestId.current;
946
+ setAsyncLoading(true);
947
+ loadOptions({ search: debouncedSearch, offset: asyncItems.length, limit: pageSize }).then((result) => {
948
+ if (requestId !== asyncRequestId.current) {
949
+ return;
950
+ }
951
+ setAsyncItems((prev) => prev.concat(result.items));
952
+ if (result.total !== void 0) {
953
+ setAsyncTotal(result.total);
954
+ }
955
+ }).catch(() => void 0).finally(() => {
956
+ if (requestId === asyncRequestId.current) {
957
+ setAsyncLoading(false);
958
+ }
959
+ });
960
+ }, [asyncItems.length, asyncLoading, asyncTotal, debouncedSearch, isAsync, loadOptions, pageSize]);
961
+ const visibleOptions = react.useMemo(() => {
962
+ const source = isAsync ? asyncItems : staticOptions ?? [];
963
+ const filtered = isAsync ? source.slice() : source.filter((opt) => (filterOption ?? defaultFilter)(opt, debouncedSearch));
964
+ if (locale && sortByLocale) {
965
+ const collator = new Intl.Collator(locale, { sensitivity: "base", numeric: true });
966
+ return filtered.slice().sort((a, b) => {
967
+ const ga = a.group ?? "";
968
+ const gb = b.group ?? "";
969
+ const groupDelta = collator.compare(ga, gb);
970
+ if (groupDelta !== 0) {
971
+ return groupDelta;
972
+ }
973
+ return collator.compare(a.label, b.label);
974
+ });
975
+ }
976
+ return filtered;
977
+ }, [isAsync, asyncItems, staticOptions, filterOption, debouncedSearch, locale, sortByLocale]);
978
+ const selectedOption = react.useMemo(() => {
979
+ const all = isAsync ? asyncItems : staticOptions ?? [];
980
+ return all.find((o) => o.value === current);
981
+ }, [asyncItems, isAsync, staticOptions, current]);
982
+ const selectedOptions = react.useMemo(() => {
983
+ if (!multiple) {
984
+ return [];
985
+ }
986
+ const all = isAsync ? asyncItems : staticOptions ?? [];
987
+ const map = new Map(all.map((o) => [o.value, o]));
988
+ return currentValues.map((v) => map.get(v)).filter((o) => o !== void 0);
989
+ }, [multiple, currentValues, asyncItems, isAsync, staticOptions]);
990
+ const searchable = searchableProp ?? (isAsync || staticOptions !== void 0 && staticOptions.length >= 10);
991
+ const virtualized = virtualizedProp ?? visibleOptions.length > 100;
992
+ react.useEffect(() => {
993
+ setActiveIndex((idx) => Math.min(Math.max(0, idx), Math.max(0, visibleOptions.length - 1)));
994
+ }, [visibleOptions.length]);
995
+ const onSelect = react.useCallback(
996
+ (option) => {
997
+ if (option.disabled) {
998
+ return;
999
+ }
1000
+ if (multiple) {
1001
+ const has = currentValues.includes(option.value);
1002
+ let nextValues;
1003
+ if (has) {
1004
+ nextValues = currentValues.filter((v) => v !== option.value);
1005
+ } else {
1006
+ if (maxSelected !== void 0 && currentValues.length >= maxSelected) {
1007
+ return;
1008
+ }
1009
+ nextValues = [...currentValues, option.value];
1010
+ }
1011
+ if (!isControlled) {
1012
+ setInnerValues(nextValues);
1013
+ }
1014
+ const allOpts = [
1015
+ ...staticOptions ?? [],
1016
+ ...asyncItems
1017
+ ];
1018
+ const optMap = new Map(allOpts.map((o) => [o.value, o]));
1019
+ const selectedOpts = nextValues.map((v) => optMap.get(v)).filter((o) => o !== void 0);
1020
+ props.onChange?.(nextValues, selectedOpts);
1021
+ return;
1022
+ }
1023
+ if (!isControlled) {
1024
+ setInnerValues([option.value]);
1025
+ }
1026
+ props.onChange?.(option.value, option);
1027
+ setOpen(false);
1028
+ setSearchInput("");
1029
+ },
1030
+ // 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
1031
+ [multiple, isControlled, currentValues, maxSelected, staticOptions, asyncItems, props]
1032
+ );
1033
+ const clearAll = react.useCallback(() => {
1034
+ if (!isControlled) {
1035
+ setInnerValues([]);
1036
+ }
1037
+ props.onChange?.([], []);
1038
+ }, [isControlled, props]);
1039
+ const moveActive = react.useCallback(
1040
+ (delta) => {
1041
+ setActiveIndex((idx) => {
1042
+ if (visibleOptions.length === 0) {
1043
+ return 0;
1044
+ }
1045
+ let next = (idx + delta + visibleOptions.length) % visibleOptions.length;
1046
+ for (let attempts = 0; attempts < visibleOptions.length; attempts += 1) {
1047
+ if (!visibleOptions[next]?.disabled) {
1048
+ return next;
1049
+ }
1050
+ next = (next + delta + visibleOptions.length) % visibleOptions.length;
1051
+ }
1052
+ return idx;
1053
+ });
1054
+ },
1055
+ [visibleOptions]
1056
+ );
1057
+ const typeAheadRef = react.useRef({
1058
+ buffer: "",
1059
+ timer: null
1060
+ });
1061
+ react.useEffect(() => {
1062
+ return () => {
1063
+ if (typeAheadRef.current.timer) {
1064
+ clearTimeout(typeAheadRef.current.timer);
1065
+ typeAheadRef.current.timer = null;
1066
+ }
1067
+ };
1068
+ }, []);
1069
+ const handleTypeAhead = react.useCallback(
1070
+ (char) => {
1071
+ if (visibleOptions.length === 0) {
1072
+ return;
1073
+ }
1074
+ if (typeAheadRef.current.timer) {
1075
+ clearTimeout(typeAheadRef.current.timer);
1076
+ }
1077
+ const nextBuffer = typeAheadRef.current.buffer + char.toLowerCase();
1078
+ typeAheadRef.current.buffer = nextBuffer;
1079
+ typeAheadRef.current.timer = setTimeout(() => {
1080
+ typeAheadRef.current.buffer = "";
1081
+ typeAheadRef.current.timer = null;
1082
+ }, TYPE_AHEAD_RESET_MS);
1083
+ const allSame = nextBuffer.length > 1 && nextBuffer.split("").every((c) => c === nextBuffer[0]);
1084
+ const cycleMode = nextBuffer.length === 1 || allSame;
1085
+ const needle = cycleMode ? nextBuffer.charAt(0) : nextBuffer;
1086
+ const len = visibleOptions.length;
1087
+ const startFrom = cycleMode ? (activeIndex + 1) % len : 0;
1088
+ for (let i = 0; i < len; i += 1) {
1089
+ const idx = (startFrom + i) % len;
1090
+ const opt = visibleOptions[idx];
1091
+ if (!opt || opt.disabled) {
1092
+ continue;
1093
+ }
1094
+ if (opt.label.toLowerCase().startsWith(needle)) {
1095
+ setActiveIndex(idx);
1096
+ return;
1097
+ }
1098
+ }
1099
+ },
1100
+ [visibleOptions, activeIndex]
1101
+ );
1102
+ const handleListKeyDown = react.useCallback(
1103
+ (event) => {
1104
+ switch (event.key) {
1105
+ case "ArrowDown":
1106
+ event.preventDefault();
1107
+ moveActive(1);
1108
+ return true;
1109
+ case "ArrowUp":
1110
+ event.preventDefault();
1111
+ moveActive(-1);
1112
+ return true;
1113
+ case "Home": {
1114
+ event.preventDefault();
1115
+ const idx = visibleOptions.findIndex((o) => !o.disabled);
1116
+ if (idx >= 0) {
1117
+ setActiveIndex(idx);
1118
+ }
1119
+ return true;
1120
+ }
1121
+ case "End": {
1122
+ event.preventDefault();
1123
+ for (let i = visibleOptions.length - 1; i >= 0; i -= 1) {
1124
+ if (!visibleOptions[i]?.disabled) {
1125
+ setActiveIndex(i);
1126
+ break;
1127
+ }
1128
+ }
1129
+ return true;
1130
+ }
1131
+ case "Enter": {
1132
+ const opt = visibleOptions[activeIndex];
1133
+ if (opt) {
1134
+ event.preventDefault();
1135
+ onSelect(opt);
1136
+ }
1137
+ return true;
1138
+ }
1139
+ case "Escape":
1140
+ event.preventDefault();
1141
+ setOpen(false);
1142
+ if (reactNative.Platform.OS === "web") {
1143
+ const trigger = triggerRef.current;
1144
+ trigger?.focus?.();
1145
+ }
1146
+ return true;
1147
+ case "Tab":
1148
+ setOpen(false);
1149
+ return true;
1150
+ }
1151
+ return false;
1152
+ },
1153
+ [moveActive, activeIndex, visibleOptions, onSelect]
1154
+ );
1155
+ const handleSearchKeyDown = react.useCallback(
1156
+ (event) => {
1157
+ handleListKeyDown(event);
1158
+ },
1159
+ [handleListKeyDown]
1160
+ );
1161
+ const handlePopupKeyDown = react.useCallback(
1162
+ (event) => {
1163
+ if (handleListKeyDown(event)) {
1164
+ return;
1165
+ }
1166
+ if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey && event.key !== " ") {
1167
+ event.preventDefault();
1168
+ handleTypeAhead(event.key);
1169
+ }
1170
+ },
1171
+ [handleListKeyDown, handleTypeAhead]
1172
+ );
1173
+ const handleTriggerKeyDown = react.useCallback(
1174
+ (event) => {
1175
+ switch (event.key) {
1176
+ case " ":
1177
+ case "Enter":
1178
+ case "ArrowDown":
1179
+ case "ArrowUp":
1180
+ event.preventDefault();
1181
+ setOpen(true);
1182
+ return;
1183
+ }
1184
+ if (!disabled && event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
1185
+ event.preventDefault();
1186
+ setOpen(true);
1187
+ handleTypeAhead(event.key);
1188
+ }
1189
+ },
1190
+ [disabled, handleTypeAhead]
1191
+ );
1192
+ const containerRef = react.useRef(null);
1193
+ const triggerRef = react.useRef(null);
1194
+ const popupRef = react.useRef(null);
1195
+ react.useEffect(() => {
1196
+ if (reactNative.Platform.OS !== "web" || typeof document === "undefined" || typeof document.addEventListener !== "function") {
1197
+ return;
1198
+ }
1199
+ if (!open) {
1200
+ return;
1201
+ }
1202
+ const onDocClick = /* @__PURE__ */ __name((event) => {
1203
+ const node = containerRef.current;
1204
+ const popup = popupRef.current;
1205
+ const target = event.target;
1206
+ if (node?.contains(target)) {
1207
+ return;
1208
+ }
1209
+ if (popup?.contains(target)) {
1210
+ return;
1211
+ }
1212
+ setOpen(false);
1213
+ }, "onDocClick");
1214
+ document.addEventListener("mousedown", onDocClick);
1215
+ return () => document.removeEventListener("mousedown", onDocClick);
1216
+ }, [open]);
1217
+ const [triggerRect, setTriggerRect] = react.useState(
1218
+ null
1219
+ );
1220
+ const measureTrigger = react.useCallback(() => {
1221
+ const node = triggerRef.current;
1222
+ if (!node) {
1223
+ return;
1224
+ }
1225
+ if (reactNative.Platform.OS === "web" && typeof node.getBoundingClientRect === "function") {
1226
+ const rect = node.getBoundingClientRect();
1227
+ setTriggerRect({ top: rect.top, left: rect.left, width: rect.width, height: rect.height });
1228
+ return;
1229
+ }
1230
+ if (typeof node.measure === "function") {
1231
+ node.measure((_x, _y, w, h, pageX, pageY) => {
1232
+ setTriggerRect({ top: pageY, left: pageX, width: w, height: h });
1233
+ });
1234
+ }
1235
+ }, []);
1236
+ react.useEffect(() => {
1237
+ if (!open) {
1238
+ return;
1239
+ }
1240
+ if (reactNative.Platform.OS !== "web" || typeof window === "undefined" || typeof window.addEventListener !== "function") {
1241
+ return;
1242
+ }
1243
+ measureTrigger();
1244
+ window.addEventListener("scroll", measureTrigger, true);
1245
+ window.addEventListener("resize", measureTrigger);
1246
+ return () => {
1247
+ window.removeEventListener("scroll", measureTrigger, true);
1248
+ window.removeEventListener("resize", measureTrigger);
1249
+ };
1250
+ }, [open, measureTrigger]);
1251
+ react.useEffect(() => {
1252
+ if (!open || searchable || reactNative.Platform.OS !== "web") {
1253
+ return;
1254
+ }
1255
+ const id2 = requestAnimationFrame(() => {
1256
+ const node = popupRef.current;
1257
+ node?.focus?.();
1258
+ });
1259
+ return () => cancelAnimationFrame(id2);
1260
+ }, [open, searchable]);
1261
+ const onListScroll = react.useCallback(
1262
+ (event) => {
1263
+ if (!isAsync) {
1264
+ return;
1265
+ }
1266
+ const { contentOffset, contentSize, layoutMeasurement } = event.nativeEvent;
1267
+ const remaining = contentSize.height - contentOffset.y - layoutMeasurement.height;
1268
+ if (remaining < itemHeight * 4) {
1269
+ loadMore();
1270
+ }
1271
+ },
1272
+ [isAsync, itemHeight, loadMore]
1273
+ );
1274
+ const triggerStyle = {
1275
+ flexDirection: "row",
1276
+ alignItems: "center",
1277
+ justifyContent: "space-between",
1278
+ gap: px(colors.spacing["2"]),
1279
+ paddingHorizontal: px(colors.spacing["3"]),
1280
+ paddingVertical: px(colors.spacing["2"]),
1281
+ minHeight: 36,
1282
+ // component-density literal — not from theme
1283
+ borderWidth: 1,
1284
+ borderColor: colors.semantic.border.default,
1285
+ borderRadius: px(colors.radius.md),
1286
+ backgroundColor: colors.semantic.background.elevated,
1287
+ opacity: disabled ? 0.6 : 1
1288
+ };
1289
+ const winDims = reactNative.useWindowDimensions();
1290
+ const popupStyle = triggerRect ? {
1291
+ position: reactNative.Platform.OS === "web" ? "fixed" : "absolute",
1292
+ top: triggerRect.top + triggerRect.height + px(colors.spacing["1"]),
1293
+ left: dir === "rtl" ? void 0 : triggerRect.left,
1294
+ right: dir === "rtl" ? reactNative.Platform.OS === "web" && typeof window !== "undefined" ? window.innerWidth - (triggerRect.left + triggerRect.width) : winDims.width - (triggerRect.left + triggerRect.width) : void 0,
1295
+ minWidth: Math.max(200, triggerRect.width),
1296
+ backgroundColor: colors.semantic.background.elevated,
1297
+ borderRadius: px(colors.radius.lg),
1298
+ borderWidth: 1,
1299
+ borderColor: colors.semantic.border.default,
1300
+ // 2147483646 (max int32 - 1) so we sit above any third-party
1301
+ // chrome (toasts, modals, dev banners) without picking a fight
1302
+ // for the very top slot. Combined with portaling to body below,
1303
+ // this also dodges any ancestor stacking context that would
1304
+ // otherwise trap our z-index inside a sibling preview frame.
1305
+ zIndex: 2147483646,
1306
+ ...{ boxShadow: "0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)" }
1307
+ } : {
1308
+ // Trigger not yet measured — render off-screen until the
1309
+ // first measurement lands. Avoids a one-frame flash at (0,0).
1310
+ position: reactNative.Platform.OS === "web" ? "fixed" : "absolute",
1311
+ top: -9999,
1312
+ left: -9999
1313
+ };
1314
+ const containerProps = {
1315
+ ref: /* @__PURE__ */ __name((node) => {
1316
+ containerRef.current = node;
1317
+ }, "ref"),
1318
+ ...testID !== void 0 ? { testID } : {},
1319
+ dir
1320
+ };
1321
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { ...containerProps, className: cn("relative", className), style: { position: "relative" }, children: [
1322
+ /* @__PURE__ */ jsxRuntime.jsxs(
1323
+ reactNative.Pressable,
1324
+ {
1325
+ ref: (node) => {
1326
+ triggerRef.current = node;
1327
+ },
1328
+ ...{
1329
+ onKeyDown: handleTriggerKeyDown,
1330
+ role: "combobox",
1331
+ accessibilityRole: "combobox",
1332
+ "aria-expanded": open,
1333
+ "aria-controls": `${baseId}-listbox`,
1334
+ "aria-haspopup": "listbox",
1335
+ tabIndex: disabled ? -1 : 0,
1336
+ ...id !== void 0 ? { id, nativeID: id } : {},
1337
+ ...name !== void 0 ? { name } : {},
1338
+ ...ariaLabel !== void 0 ? { "aria-label": ariaLabel, accessibilityLabel: ariaLabel } : {},
1339
+ ...ariaLabelledBy !== void 0 ? { "aria-labelledby": ariaLabelledBy, accessibilityLabelledBy: ariaLabelledBy } : {},
1340
+ ...ariaDescribedBy !== void 0 ? { "aria-describedby": ariaDescribedBy, accessibilityDescribedBy: ariaDescribedBy } : {},
1341
+ ...ariaInvalid === true ? { "aria-invalid": true } : {},
1342
+ ...ariaRequired === true ? { "aria-required": true } : {},
1343
+ ...disabled ? { "aria-disabled": true, disabled: true } : {}
1344
+ },
1345
+ onPress: () => {
1346
+ if (disabled) {
1347
+ return;
1348
+ }
1349
+ measureTrigger();
1350
+ setOpen((v) => !v);
1351
+ },
1352
+ style: triggerStyle,
1353
+ children: [
1354
+ multiple ? /* @__PURE__ */ jsxRuntime.jsx(MultiTriggerLabel, { options: selectedOptions, placeholder, maxChips: maxChips ?? 3 }) : /* @__PURE__ */ jsxRuntime.jsx(
1355
+ reactNative.Text,
1356
+ {
1357
+ style: {
1358
+ color: selectedOption ? colors.semantic.text.default : colors.semantic.text.muted,
1359
+ fontFamily: colors.fontFamily.body,
1360
+ fontSize: px(colors.fontSize.sm),
1361
+ flex: 1
1362
+ },
1363
+ numberOfLines: 1,
1364
+ children: selectedOption?.label ?? placeholder
1365
+ }
1366
+ ),
1367
+ /* @__PURE__ */ jsxRuntime.jsx(defaultSemanticIcons.chevronDown, { size: 16, color: colors.semantic.text.muted })
1368
+ ]
1369
+ }
1370
+ ),
1371
+ open ? renderPopup() : null
1372
+ ] });
1373
+ function renderPopup() {
1374
+ const popup = /* @__PURE__ */ jsxRuntime.jsxs(
1375
+ reactNative.View,
1376
+ {
1377
+ ref: (node) => {
1378
+ popupRef.current = node;
1379
+ },
1380
+ ...{
1381
+ role: "listbox",
1382
+ id: `${baseId}-listbox`,
1383
+ ...multiple ? { "aria-multiselectable": true } : {},
1384
+ // Without a search field there's no input to capture
1385
+ // keystrokes — make the popup itself focusable and own
1386
+ // arrow / Enter / Escape / type-ahead. With a search
1387
+ // field these belong to the input below.
1388
+ ...searchable ? {} : { tabIndex: -1, onKeyDown: handlePopupKeyDown }
1389
+ },
1390
+ style: popupStyle,
1391
+ children: [
1392
+ searchable ? /* @__PURE__ */ jsxRuntime.jsx(
1393
+ SearchInput,
1394
+ {
1395
+ value: searchInput,
1396
+ onChange: setSearchInput,
1397
+ onKeyDown: handleSearchKeyDown,
1398
+ placeholder: searchPlaceholder,
1399
+ dir
1400
+ }
1401
+ ) : null,
1402
+ multiple && currentValues.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(MultiSelectionHeader, { count: currentValues.length, onClearAll: clearAll }) : null,
1403
+ /* @__PURE__ */ jsxRuntime.jsx(
1404
+ SelectList,
1405
+ {
1406
+ options: visibleOptions,
1407
+ activeIndex,
1408
+ currentValue: current,
1409
+ selectedValues: currentValues,
1410
+ multiple,
1411
+ onSelect,
1412
+ onActiveChange: setActiveIndex,
1413
+ ...renderOption !== void 0 ? { renderOption } : {},
1414
+ itemHeight,
1415
+ maxHeight: maxMenuHeight,
1416
+ virtualized,
1417
+ loading: isAsync && asyncLoading,
1418
+ loadingMessage,
1419
+ noOptionsMessage,
1420
+ listboxId: `${baseId}-listbox`,
1421
+ onScroll: onListScroll
1422
+ }
1423
+ )
1424
+ ]
1425
+ }
1426
+ );
1427
+ if (reactNative.Platform.OS === "web" && typeof document !== "undefined") {
1428
+ return reactDom.createPortal(popup, document.body);
1429
+ }
1430
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Modal, { transparent: true, visible: true, animationType: "fade", onRequestClose: () => setOpen(false), statusBarTranslucent: true, children: [
1431
+ /* @__PURE__ */ jsxRuntime.jsx(
1432
+ reactNative.Pressable,
1433
+ {
1434
+ onPress: () => setOpen(false),
1435
+ style: {
1436
+ position: "absolute",
1437
+ top: 0,
1438
+ left: 0,
1439
+ right: 0,
1440
+ bottom: 0
1441
+ }
1442
+ }
1443
+ ),
1444
+ popup
1445
+ ] });
1446
+ }
1447
+ }, "Select");
1448
+ var SearchInput = /* @__PURE__ */ __name(({ value, onChange, onKeyDown, placeholder, dir }) => {
1449
+ const colors = useThemeColors();
1450
+ const inputRef = react.useRef(null);
1451
+ react.useEffect(() => {
1452
+ inputRef.current?.focus?.();
1453
+ }, []);
1454
+ return /* @__PURE__ */ jsxRuntime.jsx(
1455
+ reactNative.View,
1456
+ {
1457
+ style: {
1458
+ paddingHorizontal: px(colors.spacing["2"]),
1459
+ paddingVertical: px(colors.spacing["2"]),
1460
+ borderBottomWidth: 1,
1461
+ borderBottomColor: colors.semantic.border.default
1462
+ },
1463
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1464
+ "input",
1465
+ {
1466
+ ref: inputRef,
1467
+ type: "text",
1468
+ value,
1469
+ onChange: (event) => onChange(event.target.value),
1470
+ onKeyDown,
1471
+ placeholder,
1472
+ dir,
1473
+ "aria-label": "Search options",
1474
+ style: {
1475
+ width: "100%",
1476
+ // Inline `padding: '6px 8px'` shorthand intentionally kept as a
1477
+ // string for the native HTML <input> — it's not an RN style prop.
1478
+ padding: `${px(colors.spacing["2"]) - 2}px ${px(colors.spacing["2"])}px`,
1479
+ fontFamily: colors.fontFamily.body,
1480
+ fontSize: px(colors.fontSize.sm),
1481
+ color: colors.semantic.text.default,
1482
+ backgroundColor: colors.semantic.background.elevated,
1483
+ border: `1px solid ${colors.semantic.border.default}`,
1484
+ borderRadius: px(colors.radius.sm),
1485
+ outline: "none"
1486
+ }
1487
+ }
1488
+ )
1489
+ }
1490
+ );
1491
+ }, "SearchInput");
1492
+ var SelectList = /* @__PURE__ */ __name(({
1493
+ options,
1494
+ activeIndex,
1495
+ currentValue,
1496
+ selectedValues,
1497
+ multiple,
1498
+ onSelect,
1499
+ onActiveChange,
1500
+ renderOption,
1501
+ itemHeight,
1502
+ maxHeight,
1503
+ virtualized,
1504
+ loading,
1505
+ loadingMessage,
1506
+ noOptionsMessage,
1507
+ listboxId,
1508
+ onScroll
1509
+ }) => {
1510
+ const colors = useThemeColors();
1511
+ const [scrollTop, setScrollTop] = react.useState(0);
1512
+ const totalHeight = options.length * itemHeight;
1513
+ const visibleStart = virtualized ? Math.max(0, Math.floor(scrollTop / itemHeight) - VIRTUAL_OVERSCAN) : 0;
1514
+ const visibleEnd = virtualized ? Math.min(options.length, Math.ceil((scrollTop + maxHeight) / itemHeight) + VIRTUAL_OVERSCAN) : options.length;
1515
+ const handleScroll = /* @__PURE__ */ __name((event) => {
1516
+ if (virtualized) {
1517
+ setScrollTop(event.nativeEvent.contentOffset.y);
1518
+ }
1519
+ onScroll(event);
1520
+ }, "handleScroll");
1521
+ if (loading && options.length === 0) {
1522
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { padding: px(colors.spacing["4"]), alignItems: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1523
+ reactNative.Text,
1524
+ {
1525
+ style: {
1526
+ color: colors.semantic.text.muted,
1527
+ fontFamily: colors.fontFamily.body,
1528
+ fontSize: px(colors.fontSize.sm)
1529
+ },
1530
+ children: loadingMessage
1531
+ }
1532
+ ) });
1533
+ }
1534
+ if (options.length === 0) {
1535
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { padding: px(colors.spacing["4"]), alignItems: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1536
+ reactNative.Text,
1537
+ {
1538
+ style: {
1539
+ color: colors.semantic.text.muted,
1540
+ fontFamily: colors.fontFamily.body,
1541
+ fontSize: px(colors.fontSize.sm)
1542
+ },
1543
+ children: noOptionsMessage
1544
+ }
1545
+ ) });
1546
+ }
1547
+ const items = [];
1548
+ let lastGroup;
1549
+ for (let i = visibleStart; i < visibleEnd; i += 1) {
1550
+ const opt = options[i];
1551
+ if (!opt) {
1552
+ continue;
1553
+ }
1554
+ if (opt.group !== lastGroup && opt.group !== void 0) {
1555
+ items.push(
1556
+ /* @__PURE__ */ jsxRuntime.jsx(
1557
+ reactNative.View,
1558
+ {
1559
+ style: {
1560
+ paddingHorizontal: px(colors.spacing["3"]),
1561
+ paddingTop: px(colors.spacing["2"]),
1562
+ paddingBottom: px(colors.spacing["1"]),
1563
+ position: virtualized ? "absolute" : "relative",
1564
+ top: virtualized ? i * itemHeight - px(colors.spacing["4"]) : void 0,
1565
+ left: 0,
1566
+ right: 0
1567
+ },
1568
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1569
+ reactNative.Text,
1570
+ {
1571
+ style: {
1572
+ color: colors.semantic.text.muted,
1573
+ fontFamily: colors.fontFamily.body,
1574
+ fontSize: 11,
1575
+ // group header — component-density literal — not from theme (smaller than xs)
1576
+ fontWeight: colors.fontWeight.semibold,
1577
+ textTransform: "uppercase",
1578
+ letterSpacing: 0.5
1579
+ },
1580
+ children: opt.group
1581
+ }
1582
+ )
1583
+ },
1584
+ `grp-${i}-${opt.group}`
1585
+ )
1586
+ );
1587
+ lastGroup = opt.group;
1588
+ }
1589
+ const selected = multiple ? selectedValues.includes(opt.value) : opt.value === currentValue;
1590
+ const active = i === activeIndex;
1591
+ const itemNode = renderOption ? renderOption(opt, { selected, active }) : /* @__PURE__ */ jsxRuntime.jsx(DefaultOptionRow, { option: opt, selected, active, multiple });
1592
+ items.push(
1593
+ /* @__PURE__ */ jsxRuntime.jsx(
1594
+ reactNative.Pressable,
1595
+ {
1596
+ ...{
1597
+ role: "option",
1598
+ accessibilityRole: "none",
1599
+ "aria-selected": selected,
1600
+ onMouseEnter: /* @__PURE__ */ __name(() => onActiveChange(i), "onMouseEnter"),
1601
+ ...opt.disabled ? { "aria-disabled": true, disabled: true } : {}
1602
+ },
1603
+ onPress: () => onSelect(opt),
1604
+ style: {
1605
+ position: virtualized ? "absolute" : "relative",
1606
+ top: virtualized ? i * itemHeight : void 0,
1607
+ left: 0,
1608
+ right: 0,
1609
+ height: itemHeight,
1610
+ flexDirection: "row",
1611
+ alignItems: "center",
1612
+ paddingHorizontal: px(colors.spacing["3"]),
1613
+ backgroundColor: active ? colors.semantic.background.subtle : "transparent",
1614
+ opacity: opt.disabled ? 0.5 : 1
1615
+ },
1616
+ children: itemNode
1617
+ },
1618
+ `opt-${i}-${opt.value}`
1619
+ )
1620
+ );
1621
+ }
1622
+ return /* @__PURE__ */ jsxRuntime.jsx(
1623
+ reactNative.ScrollView,
1624
+ {
1625
+ nativeID: listboxId,
1626
+ onScroll: handleScroll,
1627
+ scrollEventThrottle: 16,
1628
+ style: { maxHeight },
1629
+ contentContainerStyle: virtualized ? { height: totalHeight, position: "relative" } : void 0,
1630
+ children: items
1631
+ }
1632
+ );
1633
+ }, "SelectList");
1634
+ var DefaultOptionRow = /* @__PURE__ */ __name(({
1635
+ option,
1636
+ selected,
1637
+ active,
1638
+ multiple = false
1639
+ }) => {
1640
+ const colors = useThemeColors();
1641
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flexDirection: "row", alignItems: "center", flex: 1, gap: px(colors.spacing["2"]) }, children: [
1642
+ multiple ? (
1643
+ // Inline checkbox-style indicator. We don't reuse <Checkbox>
1644
+ // here because the row is already a Pressable — nesting two
1645
+ // pressable surfaces breaks tap handling on native; this
1646
+ // is purely visual (the Pressable parent owns the toggle).
1647
+ /* @__PURE__ */ jsxRuntime.jsx(
1648
+ reactNative.View,
1649
+ {
1650
+ "aria-hidden": true,
1651
+ accessibilityElementsHidden: true,
1652
+ importantForAccessibility: "no-hide-descendants",
1653
+ style: {
1654
+ width: 18,
1655
+ height: 18,
1656
+ borderWidth: 1,
1657
+ borderRadius: px(colors.radius.sm),
1658
+ borderColor: selected ? colors.semantic.interactive.primary : colors.semantic.border.strong,
1659
+ backgroundColor: selected ? colors.semantic.interactive.primary : "transparent",
1660
+ alignItems: "center",
1661
+ justifyContent: "center"
1662
+ },
1663
+ children: selected ? /* @__PURE__ */ jsxRuntime.jsx(defaultSemanticIcons.check, { size: 12, color: colors.semantic.text.inverted }) : null
1664
+ }
1665
+ )
1666
+ ) : null,
1667
+ /* @__PURE__ */ jsxRuntime.jsx(
1668
+ reactNative.Text,
1669
+ {
1670
+ style: {
1671
+ color: colors.semantic.text.default,
1672
+ fontFamily: colors.fontFamily.body,
1673
+ fontSize: px(colors.fontSize.sm),
1674
+ fontWeight: selected ? colors.fontWeight.semibold : colors.fontWeight.regular,
1675
+ flex: 1
1676
+ },
1677
+ numberOfLines: 1,
1678
+ children: option.label
1679
+ }
1680
+ ),
1681
+ selected && !multiple ? /* @__PURE__ */ jsxRuntime.jsx(defaultSemanticIcons.check, { size: 16, color: colors.semantic.interactive.primary }) : null,
1682
+ active ? null : null
1683
+ ] });
1684
+ }, "DefaultOptionRow");
1685
+ var MultiTriggerLabel = /* @__PURE__ */ __name(({
1686
+ options,
1687
+ placeholder,
1688
+ maxChips
1689
+ }) => {
1690
+ const colors = useThemeColors();
1691
+ if (options.length === 0) {
1692
+ return /* @__PURE__ */ jsxRuntime.jsx(
1693
+ reactNative.Text,
1694
+ {
1695
+ style: {
1696
+ color: colors.semantic.text.muted,
1697
+ fontFamily: colors.fontFamily.body,
1698
+ fontSize: px(colors.fontSize.sm),
1699
+ flex: 1
1700
+ },
1701
+ numberOfLines: 1,
1702
+ children: placeholder
1703
+ }
1704
+ );
1705
+ }
1706
+ if (options.length > maxChips) {
1707
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1708
+ reactNative.Text,
1709
+ {
1710
+ style: {
1711
+ color: colors.semantic.text.default,
1712
+ fontFamily: colors.fontFamily.body,
1713
+ fontSize: px(colors.fontSize.sm),
1714
+ fontWeight: colors.fontWeight.medium,
1715
+ fontVariant: ["tabular-nums"],
1716
+ flex: 1
1717
+ },
1718
+ numberOfLines: 1,
1719
+ children: [
1720
+ options.length,
1721
+ " selected"
1722
+ ]
1723
+ }
1724
+ );
1725
+ }
1726
+ return /* @__PURE__ */ jsxRuntime.jsx(
1727
+ reactNative.View,
1728
+ {
1729
+ style: {
1730
+ flexDirection: "row",
1731
+ alignItems: "center",
1732
+ flexWrap: "wrap",
1733
+ rowGap: px(colors.spacing["1"]),
1734
+ columnGap: px(colors.spacing["1"]),
1735
+ flex: 1
1736
+ },
1737
+ children: options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(
1738
+ reactNative.View,
1739
+ {
1740
+ style: {
1741
+ flexDirection: "row",
1742
+ alignItems: "center",
1743
+ paddingHorizontal: px(colors.spacing["2"]),
1744
+ paddingVertical: 2,
1745
+ borderRadius: px(colors.radius.sm),
1746
+ backgroundColor: colors.semantic.background.subtle,
1747
+ borderWidth: 1,
1748
+ borderColor: colors.semantic.border.default
1749
+ },
1750
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1751
+ reactNative.Text,
1752
+ {
1753
+ style: {
1754
+ color: colors.semantic.text.default,
1755
+ fontFamily: colors.fontFamily.body,
1756
+ fontSize: px(colors.fontSize.sm)
1757
+ },
1758
+ numberOfLines: 1,
1759
+ children: opt.label
1760
+ }
1761
+ )
1762
+ },
1763
+ `chip-${opt.value}`
1764
+ ))
1765
+ }
1766
+ );
1767
+ }, "MultiTriggerLabel");
1768
+ var MultiSelectionHeader = /* @__PURE__ */ __name(({ count, onClearAll }) => {
1769
+ const colors = useThemeColors();
1770
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1771
+ reactNative.View,
1772
+ {
1773
+ style: {
1774
+ flexDirection: "row",
1775
+ alignItems: "center",
1776
+ justifyContent: "space-between",
1777
+ paddingHorizontal: px(colors.spacing["3"]),
1778
+ paddingVertical: px(colors.spacing["2"]),
1779
+ borderBottomWidth: 1,
1780
+ borderBottomColor: colors.semantic.border.default
1781
+ },
1782
+ children: [
1783
+ /* @__PURE__ */ jsxRuntime.jsxs(
1784
+ reactNative.Text,
1785
+ {
1786
+ style: {
1787
+ color: colors.semantic.text.muted,
1788
+ fontFamily: colors.fontFamily.body,
1789
+ fontSize: px(colors.fontSize.sm),
1790
+ fontVariant: ["tabular-nums"]
1791
+ },
1792
+ children: [
1793
+ count,
1794
+ " selected"
1795
+ ]
1796
+ }
1797
+ ),
1798
+ /* @__PURE__ */ jsxRuntime.jsx(
1799
+ reactNative.Pressable,
1800
+ {
1801
+ role: "button",
1802
+ accessibilityRole: "button",
1803
+ "aria-label": "Clear all",
1804
+ accessibilityLabel: "Clear all",
1805
+ onPress: onClearAll,
1806
+ style: ({ pressed }) => ({
1807
+ paddingHorizontal: px(colors.spacing["2"]),
1808
+ paddingVertical: 2,
1809
+ borderRadius: px(colors.radius.sm),
1810
+ opacity: pressed ? 0.6 : 1
1811
+ }),
1812
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1813
+ reactNative.Text,
1814
+ {
1815
+ style: {
1816
+ color: colors.semantic.interactive.primary,
1817
+ fontFamily: colors.fontFamily.body,
1818
+ fontSize: px(colors.fontSize.sm),
1819
+ fontWeight: colors.fontWeight.medium
1820
+ },
1821
+ children: "Clear all"
1822
+ }
1823
+ )
1824
+ }
1825
+ )
1826
+ ]
1827
+ }
1828
+ );
1829
+ }, "MultiSelectionHeader");
1830
+ var CaptionContext = react.createContext(null);
1831
+ CaptionContext.displayName = "CalendarCaptionContext";
1832
+ var CaptionProvider = /* @__PURE__ */ __name(({ value, children }) => /* @__PURE__ */ jsxRuntime.jsx(CaptionContext.Provider, { value, children }), "CaptionProvider");
1833
+ var ARROW_BUTTON_GAP = 8;
1834
+ var NavButton = /* @__PURE__ */ __name(({ label, onPress, children }) => {
1835
+ const colors = useThemeColors();
1836
+ return /* @__PURE__ */ jsxRuntime.jsx(
1837
+ reactNative.Pressable,
1838
+ {
1839
+ accessibilityRole: "button",
1840
+ accessibilityLabel: label,
1841
+ onPress,
1842
+ style: ({ pressed, hovered, focused }) => {
1843
+ const base = {
1844
+ width: 32,
1845
+ height: 32,
1846
+ alignItems: "center",
1847
+ justifyContent: "center",
1848
+ borderRadius: 8
1849
+ };
1850
+ const transition = {
1851
+ transitionProperty: "background-color, border-color, transform",
1852
+ transitionDuration: "140ms",
1853
+ transitionTimingFunction: "cubic-bezier(0.2, 0, 0, 1)",
1854
+ outlineStyle: "none"
1855
+ };
1856
+ const bg = pressed ? colors.color.primary["200"] : hovered ? colors.color.primary["100"] : "transparent";
1857
+ const border = focused ? { borderWidth: 2, borderColor: colors.semantic.interactive.primary } : { borderWidth: 0 };
1858
+ return [base, transition, { backgroundColor: bg, transform: [{ scale: pressed ? 0.94 : 1 }] }, border];
1859
+ },
1860
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: colors.semantic.text.default, fontSize: 16, lineHeight: 16, fontWeight: "500" }, children })
1861
+ }
1862
+ );
1863
+ }, "NavButton");
1864
+ var TitleButton = /* @__PURE__ */ __name(({
1865
+ text,
1866
+ ariaLabel,
1867
+ onPress,
1868
+ drilldown
1869
+ }) => {
1870
+ const colors = useThemeColors();
1871
+ if (!onPress) {
1872
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { paddingHorizontal: 12, paddingVertical: 6, alignItems: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1873
+ reactNative.Text,
1874
+ {
1875
+ style: {
1876
+ color: colors.semantic.text.default,
1877
+ fontSize: 15,
1878
+ fontWeight: "600",
1879
+ letterSpacing: -0.1
1880
+ },
1881
+ children: text
1882
+ }
1883
+ ) });
1884
+ }
1885
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1886
+ reactNative.Pressable,
1887
+ {
1888
+ accessibilityRole: "button",
1889
+ accessibilityLabel: ariaLabel,
1890
+ onPress,
1891
+ style: ({ pressed, hovered, focused }) => {
1892
+ const base = {
1893
+ flexDirection: "row",
1894
+ alignItems: "center",
1895
+ gap: 6,
1896
+ paddingHorizontal: 12,
1897
+ paddingVertical: 6,
1898
+ borderRadius: 8
1899
+ };
1900
+ const transition = {
1901
+ transitionProperty: "background-color, transform",
1902
+ transitionDuration: "140ms",
1903
+ transitionTimingFunction: "cubic-bezier(0.2, 0, 0, 1)",
1904
+ outlineStyle: "none"
1905
+ };
1906
+ const bg = pressed ? colors.color.primary["200"] : hovered ? colors.color.primary["100"] : "transparent";
1907
+ const border = focused ? { borderWidth: 2, borderColor: colors.semantic.interactive.primary } : { borderWidth: 0 };
1908
+ return [base, transition, { backgroundColor: bg, transform: [{ scale: pressed ? 0.97 : 1 }] }, border];
1909
+ },
1910
+ children: [
1911
+ /* @__PURE__ */ jsxRuntime.jsx(
1912
+ reactNative.Text,
1913
+ {
1914
+ style: {
1915
+ color: colors.semantic.text.default,
1916
+ fontSize: 15,
1917
+ fontWeight: "600",
1918
+ letterSpacing: -0.1
1919
+ },
1920
+ children: text
1921
+ }
1922
+ ),
1923
+ drilldown ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { "aria-hidden": true, style: { color: colors.semantic.text.muted, fontSize: 10, opacity: 0.7 }, children: "\u25BE" }) : null
1924
+ ]
1925
+ }
1926
+ );
1927
+ }, "TitleButton");
1928
+ var Caption = /* @__PURE__ */ __name(({
1929
+ months,
1930
+ locale,
1931
+ view,
1932
+ caption,
1933
+ gridWidth,
1934
+ monthGap,
1935
+ yearRange,
1936
+ isMonthDisabled,
1937
+ isYearDisabled,
1938
+ onPrev,
1939
+ onNext,
1940
+ onTitlePress,
1941
+ onSetMonth,
1942
+ onSetYear,
1943
+ children
1944
+ }) => {
1945
+ const { t } = useTranslation();
1946
+ const titleText = /* @__PURE__ */ __name((m) => {
1947
+ if (view === "day") {
1948
+ return formatMonthYearTitle(m, locale);
1949
+ }
1950
+ if (view === "month") {
1951
+ return String(m.year);
1952
+ }
1953
+ const start = m.year - m.year % 10;
1954
+ return `${start} \u2013 ${start + 11}`;
1955
+ }, "titleText");
1956
+ const titleAriaKey = view === "day" ? "calendar.header.openMonthView" : view === "month" ? "calendar.header.openYearView" : "calendar.header.openDayView";
1957
+ const showMultiTitles = view === "day" && months.length > 1;
1958
+ const titleRowWidth = showMultiTitles ? months.length * gridWidth + (months.length - 1) * monthGap : gridWidth;
1959
+ const monthNames = react.useMemo(() => formatMonthNames(locale), [locale]);
1960
+ const focused = months[0] ?? void 0;
1961
+ if (!focused) {
1962
+ return null;
1963
+ }
1964
+ const ctxValue = {
1965
+ month: focused.month,
1966
+ year: focused.year,
1967
+ visibleMonth: focused,
1968
+ monthOptions: monthNames.map((label, i) => ({
1969
+ value: i + 1,
1970
+ label,
1971
+ disabled: isMonthDisabled?.(focused.year, i + 1) ?? false
1972
+ })),
1973
+ yearOptions: Array.from({ length: yearRange[1] - yearRange[0] + 1 }, (_, i) => ({
1974
+ value: yearRange[0] + i,
1975
+ label: String(yearRange[0] + i),
1976
+ disabled: isYearDisabled?.(yearRange[0] + i) ?? false
1977
+ })),
1978
+ setMonth: /* @__PURE__ */ __name((m) => onSetMonth(0, m), "setMonth"),
1979
+ setYear: /* @__PURE__ */ __name((y) => onSetYear(0, y), "setYear"),
1980
+ goPrev: onPrev,
1981
+ goNext: onNext
1982
+ };
1983
+ if (caption === "custom") {
1984
+ return /* @__PURE__ */ jsxRuntime.jsx(
1985
+ reactNative.View,
1986
+ {
1987
+ style: {
1988
+ flexDirection: "row",
1989
+ alignItems: "center",
1990
+ paddingBottom: 10,
1991
+ gap: ARROW_BUTTON_GAP,
1992
+ alignSelf: "center"
1993
+ },
1994
+ children: /* @__PURE__ */ jsxRuntime.jsx(CaptionProvider, { value: ctxValue, children })
1995
+ }
1996
+ );
1997
+ }
1998
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1999
+ reactNative.View,
2000
+ {
2001
+ style: {
2002
+ flexDirection: "row",
2003
+ alignItems: "center",
2004
+ paddingBottom: 10,
2005
+ gap: ARROW_BUTTON_GAP,
2006
+ alignSelf: "center"
2007
+ },
2008
+ children: [
2009
+ /* @__PURE__ */ jsxRuntime.jsx(NavButton, { label: t("calendar.header.previous", { defaultValue: "Previous month" }), onPress: onPrev, children: "\u2039" }),
2010
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { flexDirection: "row", gap: monthGap, width: titleRowWidth }, children: showMultiTitles ? months.map((m, i) => /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { width: gridWidth, alignItems: "center" }, children: caption === "dropdown" && view === "day" ? /* @__PURE__ */ jsxRuntime.jsx(
2011
+ DropdownPair,
2012
+ {
2013
+ month: m.month,
2014
+ year: m.year,
2015
+ monthOptions: monthNames.map((label, j) => ({
2016
+ value: j + 1,
2017
+ label,
2018
+ disabled: isMonthDisabled?.(m.year, j + 1) ?? false
2019
+ })),
2020
+ yearOptions: makeYearOptions(yearRange, isYearDisabled),
2021
+ onMonthChange: (next) => onSetMonth(i, next),
2022
+ onYearChange: (next) => onSetYear(i, next)
2023
+ }
2024
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
2025
+ TitleButton,
2026
+ {
2027
+ text: titleText(m),
2028
+ ariaLabel: t(titleAriaKey, { defaultValue: "Change view" }),
2029
+ onPress: () => onTitlePress(m),
2030
+ drilldown: true
2031
+ }
2032
+ ) }, `${m.year}-${m.month}`)) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { width: gridWidth, alignItems: "center" }, children: caption === "dropdown" && view === "day" ? /* @__PURE__ */ jsxRuntime.jsx(
2033
+ DropdownPair,
2034
+ {
2035
+ month: focused.month,
2036
+ year: focused.year,
2037
+ monthOptions: monthNames.map((label, j) => ({
2038
+ value: j + 1,
2039
+ label,
2040
+ disabled: isMonthDisabled?.(focused.year, j + 1) ?? false
2041
+ })),
2042
+ yearOptions: makeYearOptions(yearRange, isYearDisabled),
2043
+ onMonthChange: (next) => onSetMonth(0, next),
2044
+ onYearChange: (next) => onSetYear(0, next)
2045
+ }
2046
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
2047
+ TitleButton,
2048
+ {
2049
+ text: titleText(focused),
2050
+ ariaLabel: t(titleAriaKey, { defaultValue: "Change view" }),
2051
+ onPress: () => onTitlePress(focused),
2052
+ drilldown: true
2053
+ }
2054
+ ) }) }),
2055
+ /* @__PURE__ */ jsxRuntime.jsx(NavButton, { label: t("calendar.header.next", { defaultValue: "Next month" }), onPress: onNext, children: "\u203A" })
2056
+ ]
2057
+ }
2058
+ );
2059
+ }, "Caption");
2060
+ var makeYearOptions = /* @__PURE__ */ __name((yearRange, isYearDisabled) => Array.from({ length: yearRange[1] - yearRange[0] + 1 }, (_, i) => ({
2061
+ value: yearRange[0] + i,
2062
+ label: String(yearRange[0] + i),
2063
+ disabled: isYearDisabled?.(yearRange[0] + i) ?? false
2064
+ })), "makeYearOptions");
2065
+ var DropdownPair = /* @__PURE__ */ __name(({ month, year, monthOptions, yearOptions, onMonthChange, onYearChange }) => {
2066
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flexDirection: "row", gap: 6 }, children: [
2067
+ /* @__PURE__ */ jsxRuntime.jsx(
2068
+ Select,
2069
+ {
2070
+ value: String(month),
2071
+ onChange: (v) => onMonthChange(Number(v)),
2072
+ options: monthOptions.map((o) => ({
2073
+ value: String(o.value),
2074
+ label: o.label,
2075
+ disabled: o.disabled
2076
+ })),
2077
+ "aria-label": "Month",
2078
+ searchable: false
2079
+ }
2080
+ ),
2081
+ /* @__PURE__ */ jsxRuntime.jsx(
2082
+ Select,
2083
+ {
2084
+ value: String(year),
2085
+ onChange: (v) => onYearChange(Number(v)),
2086
+ options: yearOptions.map((o) => ({
2087
+ value: String(o.value),
2088
+ label: o.label,
2089
+ disabled: o.disabled
2090
+ })),
2091
+ "aria-label": "Year",
2092
+ searchable: false
2093
+ }
2094
+ )
2095
+ ] });
2096
+ }, "DropdownPair");
2097
+ var CELL_SIZE = 40;
2098
+ var formatterCache = /* @__PURE__ */ new Map();
2099
+ var RUNTIME_DEFAULT_LOCALE_KEY = "__default__";
2100
+ var getDayFormatter = /* @__PURE__ */ __name((locale) => {
2101
+ const key = locale ?? RUNTIME_DEFAULT_LOCALE_KEY;
2102
+ const cached = formatterCache.get(key);
2103
+ if (cached) {
2104
+ return cached;
2105
+ }
2106
+ try {
2107
+ const fmt = new Intl.DateTimeFormat(locale, {
2108
+ weekday: "long",
2109
+ month: "long",
2110
+ day: "numeric",
2111
+ year: "numeric",
2112
+ timeZone: "UTC"
2113
+ });
2114
+ formatterCache.set(key, fmt);
2115
+ return fmt;
2116
+ } catch {
2117
+ return null;
2118
+ }
2119
+ }, "getDayFormatter");
2120
+ var formatDayLabel = /* @__PURE__ */ __name((ctx, locale) => {
2121
+ const jsDate = new Date(Date.UTC(ctx.date.year, ctx.date.month - 1, ctx.date.day));
2122
+ const fmt = getDayFormatter(locale);
2123
+ const base = fmt ? fmt.format(jsDate) : (
2124
+ // Fallback if the runtime rejects the locale tag.
2125
+ `${ctx.date.month}/${ctx.date.day}/${ctx.date.year}`
2126
+ );
2127
+ const suffixes = [];
2128
+ if (ctx.isToday) {
2129
+ suffixes.push("today");
2130
+ }
2131
+ if (ctx.isSelected || ctx.isRangeStart || ctx.isRangeEnd) {
2132
+ suffixes.push("selected");
2133
+ }
2134
+ if (ctx.isInRange && !ctx.isRangeStart && !ctx.isRangeEnd) {
2135
+ suffixes.push("in range");
2136
+ }
2137
+ if (ctx.isUnavailable) {
2138
+ suffixes.push("unavailable");
2139
+ }
2140
+ return suffixes.length > 0 ? `${base}, ${suffixes.join(", ")}` : base;
2141
+ }, "formatDayLabel");
2142
+ var DayCell = /* @__PURE__ */ __name(({ ctx, onPress, onHoverIn, onHoverOut, renderDay, locale }) => {
2143
+ const colors = useThemeColors();
2144
+ const accessibilityLabel = formatDayLabel(ctx, locale);
2145
+ const isSelectedLike = ctx.isSelected || ctx.isRangeStart || ctx.isRangeEnd;
2146
+ const isInsideRange = ctx.isInRange || ctx.isInPreviewRange;
2147
+ const isRangeMiddle = isInsideRange && !isSelectedLike;
2148
+ const dataState = ctx.isSelected ? "selected" : ctx.isRangeStart ? "range-start" : ctx.isRangeEnd ? "range-end" : ctx.isInPreviewRange ? "preview" : ctx.isInRange ? "in-range" : void 0;
2149
+ return /* @__PURE__ */ jsxRuntime.jsx(
2150
+ reactNative.Pressable,
2151
+ {
2152
+ accessibilityRole: "button",
2153
+ accessibilityLabel,
2154
+ accessibilityState: { disabled: ctx.isUnavailable, selected: isSelectedLike },
2155
+ disabled: ctx.isUnavailable,
2156
+ onPress,
2157
+ ...onHoverIn ? { onHoverIn } : {},
2158
+ ...onHoverOut ? { onHoverOut } : {},
2159
+ style: ({ pressed, hovered, focused }) => {
2160
+ const base = {
2161
+ width: CELL_SIZE,
2162
+ height: CELL_SIZE,
2163
+ alignItems: "center",
2164
+ justifyContent: "center",
2165
+ borderRadius: 999,
2166
+ // perfect circle for endpoints/selected
2167
+ position: "relative"
2168
+ };
2169
+ if (ctx.isUnavailable) {
2170
+ return [base, { opacity: 0.28 }];
2171
+ }
2172
+ let backgroundColor;
2173
+ let transform;
2174
+ let borderWidth;
2175
+ let borderColor;
2176
+ if (isSelectedLike) {
2177
+ backgroundColor = colors.semantic.interactive.primary;
2178
+ if (pressed) {
2179
+ backgroundColor = colors.semantic.interactive.primaryPressed;
2180
+ } else if (hovered) {
2181
+ backgroundColor = colors.semantic.interactive.primaryHover;
2182
+ }
2183
+ transform = [{ scale: pressed ? 0.94 : 1 }];
2184
+ } else if (isRangeMiddle) {
2185
+ backgroundColor = hovered ? colors.color.primary["200"] : "transparent";
2186
+ transform = [{ scale: pressed ? 0.94 : 1 }];
2187
+ } else {
2188
+ if (pressed) {
2189
+ backgroundColor = colors.color.primary["200"];
2190
+ } else if (hovered) {
2191
+ backgroundColor = colors.color.primary["100"];
2192
+ } else {
2193
+ backgroundColor = "transparent";
2194
+ }
2195
+ transform = [{ scale: pressed ? 0.94 : 1 }];
2196
+ }
2197
+ if ((ctx.isFocused || focused) && !isSelectedLike) {
2198
+ borderWidth = 2;
2199
+ borderColor = colors.semantic.interactive.primary;
2200
+ }
2201
+ const transition = {
2202
+ transitionProperty: "background-color, transform, border-color, opacity",
2203
+ transitionDuration: "140ms",
2204
+ transitionTimingFunction: "cubic-bezier(0.2, 0, 0, 1)",
2205
+ outlineStyle: "none"
2206
+ };
2207
+ return [
2208
+ base,
2209
+ transition,
2210
+ {
2211
+ backgroundColor,
2212
+ ...transform ? { transform } : {},
2213
+ ...borderWidth ? { borderWidth, borderColor } : {}
2214
+ }
2215
+ ];
2216
+ },
2217
+ ...{
2218
+ dataSet: {
2219
+ dayKey: `${ctx.date.year}-${ctx.date.month}-${ctx.date.day}`,
2220
+ ...dataState ? { state: dataState } : {}
2221
+ }
2222
+ },
2223
+ children: renderDay ? renderDay(ctx) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2224
+ /* @__PURE__ */ jsxRuntime.jsx(
2225
+ reactNative.Text,
2226
+ {
2227
+ style: {
2228
+ color: isSelectedLike ? colors.semantic.text.inverted : ctx.isOutsideMonth ? colors.semantic.text.muted : colors.semantic.text.default,
2229
+ fontSize: 14,
2230
+ fontWeight: ctx.isToday ? "600" : "400",
2231
+ opacity: ctx.isOutsideMonth ? 0.55 : 1,
2232
+ transitionProperty: "color",
2233
+ transitionDuration: "140ms"
2234
+ },
2235
+ children: ctx.date.day
2236
+ }
2237
+ ),
2238
+ ctx.isToday ? /* @__PURE__ */ jsxRuntime.jsx(
2239
+ reactNative.View,
2240
+ {
2241
+ style: {
2242
+ position: "absolute",
2243
+ bottom: 5,
2244
+ width: 4,
2245
+ height: 4,
2246
+ borderRadius: 999,
2247
+ backgroundColor: isSelectedLike ? colors.semantic.text.inverted : colors.semantic.interactive.primary
2248
+ }
2249
+ }
2250
+ ) : null
2251
+ ] })
2252
+ }
2253
+ );
2254
+ }, "DayCell");
2255
+ var isInRange = /* @__PURE__ */ __name((date, range) => {
2256
+ if (!range?.end) {
2257
+ return false;
2258
+ }
2259
+ return date.compare(range.start) >= 0 && date.compare(range.end) <= 0;
2260
+ }, "isInRange");
2261
+ var buildContext = /* @__PURE__ */ __name((date, args) => {
2262
+ const isOutsideMonth = date.month !== args.visibleMonth.month;
2263
+ const isToday = date.compare(args.todayDate) === 0;
2264
+ const dow = date.toDate("UTC").getUTCDay();
2265
+ const isWeekend = args.weekendDays.includes(dow);
2266
+ let isSelected = false;
2267
+ let isRangeStart = false;
2268
+ let isRangeEnd = false;
2269
+ let inRange = false;
2270
+ if (args.mode === "single") {
2271
+ const v = args.value;
2272
+ isSelected = !!v && v.compare(date) === 0;
2273
+ } else if (args.mode === "range") {
2274
+ const r = args.value;
2275
+ if (r) {
2276
+ isRangeStart = r.start.compare(date) === 0;
2277
+ isRangeEnd = r.end !== null && r.end.compare(date) === 0;
2278
+ inRange = isInRange(date, r);
2279
+ }
2280
+ } else {
2281
+ const arr = args.value;
2282
+ isSelected = arr.some((x) => x.compare(date) === 0);
2283
+ }
2284
+ return {
2285
+ date,
2286
+ isOutsideMonth,
2287
+ isToday,
2288
+ isSelected,
2289
+ isRangeStart,
2290
+ isRangeEnd,
2291
+ isInRange: inRange,
2292
+ isInPreviewRange: isInRange(date, args.previewRange ?? null),
2293
+ isUnavailable: args.isUnavailable(date),
2294
+ isFocused: args.focusedDate.compare(date) === 0,
2295
+ isWeekend
2296
+ };
2297
+ }, "buildContext");
2298
+ var ROW_KEYS = ["row-0", "row-1", "row-2", "row-3", "row-4", "row-5"];
2299
+ var DayGrid = /* @__PURE__ */ __name((props) => {
2300
+ const {
2301
+ visibleMonth,
2302
+ locale,
2303
+ mode,
2304
+ value,
2305
+ previewRange,
2306
+ focusedDate,
2307
+ isUnavailable,
2308
+ weekendDays,
2309
+ firstDayOfWeek,
2310
+ onDayPress,
2311
+ onDayHover,
2312
+ renderDay
2313
+ } = props;
2314
+ const colors = useThemeColors();
2315
+ const fdow = firstDayOfWeek ?? getFirstDayOfWeek(locale);
2316
+ const cells = react.useMemo(() => {
2317
+ const start = date.startOfMonth(visibleMonth);
2318
+ const startDow = start.toDate("UTC").getUTCDay();
2319
+ const back = (startDow - fdow + 7) % 7;
2320
+ const first = start.subtract({ days: back });
2321
+ const total = 42;
2322
+ return Array.from({ length: total }, (_, i) => first.add({ days: i }));
2323
+ }, [visibleMonth, fdow]);
2324
+ const weekdayNames = react.useMemo(() => formatWeekdayNames(locale), [locale]);
2325
+ const todayDate = react.useMemo(() => date.today(date.getLocalTimeZone()), []);
2326
+ const gridWidth = 7 * CELL_SIZE;
2327
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { role: "grid", style: { width: gridWidth }, children: [
2328
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { role: "row", style: { flexDirection: "row", marginBottom: 4 }, children: weekdayNames.map((name) => /* @__PURE__ */ jsxRuntime.jsx(
2329
+ reactNative.View,
2330
+ {
2331
+ role: "columnheader",
2332
+ style: { width: CELL_SIZE, alignItems: "center", paddingVertical: 6 },
2333
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2334
+ reactNative.Text,
2335
+ {
2336
+ style: {
2337
+ fontSize: 11,
2338
+ fontWeight: "500",
2339
+ letterSpacing: 0.6,
2340
+ color: colors.semantic.text.muted,
2341
+ textTransform: "uppercase"
2342
+ },
2343
+ children: name
2344
+ }
2345
+ )
2346
+ },
2347
+ name
2348
+ )) }),
2349
+ ROW_KEYS.map((rowKey, row) => /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { role: "row", style: { flexDirection: "row" }, children: cells.slice(row * 7, row * 7 + 7).map((date) => {
2350
+ const ctx = buildContext(date, {
2351
+ visibleMonth,
2352
+ mode,
2353
+ value,
2354
+ ...previewRange !== void 0 ? { previewRange } : {},
2355
+ focusedDate,
2356
+ isUnavailable,
2357
+ weekendDays,
2358
+ todayDate
2359
+ });
2360
+ const isSelectedLike = ctx.isSelected || ctx.isRangeStart || ctx.isRangeEnd;
2361
+ const isInsideRange = ctx.isInRange || ctx.isInPreviewRange;
2362
+ const wrapperStyle = {
2363
+ width: CELL_SIZE,
2364
+ height: CELL_SIZE,
2365
+ position: "relative"
2366
+ };
2367
+ const rangeFillTint = colors.color.primary["100"];
2368
+ let rangeFillStyle = null;
2369
+ if (ctx.isRangeStart && !ctx.isRangeEnd) {
2370
+ rangeFillStyle = {
2371
+ position: "absolute",
2372
+ top: 0,
2373
+ bottom: 0,
2374
+ left: "50%",
2375
+ right: 0,
2376
+ backgroundColor: rangeFillTint
2377
+ };
2378
+ } else if (ctx.isRangeEnd && !ctx.isRangeStart) {
2379
+ rangeFillStyle = {
2380
+ position: "absolute",
2381
+ top: 0,
2382
+ bottom: 0,
2383
+ left: 0,
2384
+ right: "50%",
2385
+ backgroundColor: rangeFillTint
2386
+ };
2387
+ } else if (isInsideRange && !isSelectedLike) {
2388
+ rangeFillStyle = {
2389
+ position: "absolute",
2390
+ top: 0,
2391
+ bottom: 0,
2392
+ left: 0,
2393
+ right: 0,
2394
+ backgroundColor: rangeFillTint
2395
+ };
2396
+ }
2397
+ const gridcellProps = {
2398
+ role: "gridcell",
2399
+ ...isSelectedLike ? { "aria-selected": true } : {}
2400
+ };
2401
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2402
+ reactNative.View,
2403
+ {
2404
+ ...gridcellProps,
2405
+ style: wrapperStyle,
2406
+ children: [
2407
+ rangeFillStyle ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: rangeFillStyle }) : null,
2408
+ /* @__PURE__ */ jsxRuntime.jsx(
2409
+ DayCell,
2410
+ {
2411
+ ctx,
2412
+ locale,
2413
+ onPress: () => onDayPress(date),
2414
+ ...onDayHover ? {
2415
+ onHoverIn: /* @__PURE__ */ __name(() => onDayHover(date), "onHoverIn"),
2416
+ onHoverOut: /* @__PURE__ */ __name(() => onDayHover(null), "onHoverOut")
2417
+ } : {},
2418
+ ...renderDay ? { renderDay } : {}
2419
+ }
2420
+ )
2421
+ ]
2422
+ },
2423
+ `${date.year}-${date.month}-${date.day}`
2424
+ );
2425
+ }) }, rowKey))
2426
+ ] });
2427
+ }, "DayGrid");
2428
+ var Footer = /* @__PURE__ */ __name(({ children }) => /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { paddingTop: 12 }, children }), "Footer");
2429
+ var ROW_KEYS2 = ["r0", "r1", "r2", "r3"];
2430
+ var MonthGrid = /* @__PURE__ */ __name(({ visibleMonth, locale, availableWidth, onSelect }) => {
2431
+ const colors = useThemeColors();
2432
+ const names = formatMonthNames(locale);
2433
+ const cellHeight = 56;
2434
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { width: availableWidth, paddingVertical: 8 }, children: ROW_KEYS2.map((rowKey, row) => /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { flexDirection: "row", marginBottom: 4 }, children: [0, 1, 2].map((col) => {
2435
+ const idx = row * 3 + col;
2436
+ const monthNumber = idx + 1;
2437
+ const isCurrent = monthNumber === visibleMonth.month;
2438
+ const name = names[idx] ?? "";
2439
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { flex: 1, paddingHorizontal: 4 }, children: /* @__PURE__ */ jsxRuntime.jsx(
2440
+ reactNative.Pressable,
2441
+ {
2442
+ accessibilityRole: "button",
2443
+ accessibilityLabel: name,
2444
+ onPress: () => onSelect(monthNumber),
2445
+ style: ({
2446
+ pressed,
2447
+ hovered,
2448
+ focused
2449
+ }) => {
2450
+ const base = {
2451
+ height: cellHeight,
2452
+ alignItems: "center",
2453
+ justifyContent: "center",
2454
+ borderRadius: 10
2455
+ };
2456
+ const transition = {
2457
+ transitionProperty: "background-color, transform, border-color",
2458
+ transitionDuration: "140ms",
2459
+ transitionTimingFunction: "cubic-bezier(0.2, 0, 0, 1)",
2460
+ outlineStyle: "none"
2461
+ };
2462
+ let bg;
2463
+ if (isCurrent) {
2464
+ bg = pressed ? colors.semantic.interactive.primaryPressed : hovered ? colors.semantic.interactive.primaryHover : colors.semantic.interactive.primary;
2465
+ } else if (pressed) {
2466
+ bg = colors.color.primary["200"];
2467
+ } else if (hovered) {
2468
+ bg = colors.color.primary["100"];
2469
+ } else {
2470
+ bg = "transparent";
2471
+ }
2472
+ const border = focused && !isCurrent ? { borderWidth: 2, borderColor: colors.semantic.interactive.primary } : { borderWidth: 0 };
2473
+ return [
2474
+ base,
2475
+ transition,
2476
+ { backgroundColor: bg, transform: [{ scale: pressed ? 0.96 : 1 }] },
2477
+ border
2478
+ ];
2479
+ },
2480
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2481
+ reactNative.Text,
2482
+ {
2483
+ style: {
2484
+ color: isCurrent ? colors.semantic.text.inverted : colors.semantic.text.default,
2485
+ fontSize: 14,
2486
+ fontWeight: isCurrent ? "600" : "500"
2487
+ },
2488
+ children: name
2489
+ }
2490
+ )
2491
+ }
2492
+ ) }, monthNumber);
2493
+ }) }, rowKey)) });
2494
+ }, "MonthGrid");
2495
+ var ROW_KEYS3 = ["r0", "r1", "r2"];
2496
+ var YearGrid = /* @__PURE__ */ __name(({ visibleMonth, availableWidth, onSelect }) => {
2497
+ const colors = useThemeColors();
2498
+ const decadeStart = visibleMonth.year - visibleMonth.year % 10;
2499
+ const years = Array.from({ length: 12 }, (_, i) => decadeStart + i - 1);
2500
+ const cellHeight = 60;
2501
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { width: availableWidth, paddingVertical: 8 }, children: ROW_KEYS3.map((rowKey, row) => /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { flexDirection: "row", marginBottom: 4 }, children: [0, 1, 2, 3].map((col) => {
2502
+ const year = years[row * 4 + col];
2503
+ if (year === void 0) {
2504
+ return null;
2505
+ }
2506
+ const isCurrent = year === visibleMonth.year;
2507
+ const isAdjacentDecade = year < decadeStart || year >= decadeStart + 10;
2508
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { flex: 1, paddingHorizontal: 4 }, children: /* @__PURE__ */ jsxRuntime.jsx(
2509
+ reactNative.Pressable,
2510
+ {
2511
+ accessibilityRole: "button",
2512
+ accessibilityLabel: String(year),
2513
+ onPress: () => onSelect(year),
2514
+ style: ({
2515
+ pressed,
2516
+ hovered,
2517
+ focused
2518
+ }) => {
2519
+ const base = {
2520
+ height: cellHeight,
2521
+ alignItems: "center",
2522
+ justifyContent: "center",
2523
+ borderRadius: 10
2524
+ };
2525
+ const transition = {
2526
+ transitionProperty: "background-color, transform, border-color",
2527
+ transitionDuration: "140ms",
2528
+ transitionTimingFunction: "cubic-bezier(0.2, 0, 0, 1)",
2529
+ outlineStyle: "none"
2530
+ };
2531
+ let bg;
2532
+ if (isCurrent) {
2533
+ bg = pressed ? colors.semantic.interactive.primaryPressed : hovered ? colors.semantic.interactive.primaryHover : colors.semantic.interactive.primary;
2534
+ } else if (pressed) {
2535
+ bg = colors.color.primary["200"];
2536
+ } else if (hovered) {
2537
+ bg = colors.color.primary["100"];
2538
+ } else {
2539
+ bg = "transparent";
2540
+ }
2541
+ const border = focused && !isCurrent ? { borderWidth: 2, borderColor: colors.semantic.interactive.primary } : { borderWidth: 0 };
2542
+ return [
2543
+ base,
2544
+ transition,
2545
+ { backgroundColor: bg, transform: [{ scale: pressed ? 0.96 : 1 }] },
2546
+ border
2547
+ ];
2548
+ },
2549
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2550
+ reactNative.Text,
2551
+ {
2552
+ style: {
2553
+ color: isCurrent ? colors.semantic.text.inverted : colors.semantic.text.default,
2554
+ fontSize: 14,
2555
+ fontWeight: isCurrent ? "600" : "500",
2556
+ opacity: isAdjacentDecade ? 0.45 : 1
2557
+ },
2558
+ children: year
2559
+ }
2560
+ )
2561
+ }
2562
+ ) }, year);
2563
+ }) }, rowKey)) });
2564
+ }, "YearGrid");
2565
+ var GRID_WIDTH = 7 * CELL_SIZE;
2566
+ var MONTH_GAP = 16;
2567
+ var ARROW_AREA = 32 + 8;
2568
+ var SURFACE_PADDING = 16;
2569
+ var SURFACE_BORDER = 1;
2570
+ var requiredOuterWidth = /* @__PURE__ */ __name((n) => 2 * (ARROW_AREA + SURFACE_PADDING + SURFACE_BORDER) + n * GRID_WIDTH + (n - 1) * MONTH_GAP, "requiredOuterWidth");
2571
+ var focusDayCell = /* @__PURE__ */ __name((root, date, force) => {
2572
+ if (!root) {
2573
+ return;
2574
+ }
2575
+ if (typeof document === "undefined" || typeof root.contains !== "function") {
2576
+ return;
2577
+ }
2578
+ if (!force && !root.contains(document.activeElement)) {
2579
+ return;
2580
+ }
2581
+ const sel = `[data-day-key="${date.year}-${date.month}-${date.day}"]`;
2582
+ const cell = root.querySelector(sel);
2583
+ const isDisabled = cell?.disabled === true;
2584
+ if (cell && !isDisabled) {
2585
+ if (cell !== document.activeElement) {
2586
+ cell.focus();
2587
+ }
2588
+ return;
2589
+ }
2590
+ if (force && !root.contains(document.activeElement)) {
2591
+ root.focus();
2592
+ }
2593
+ }, "focusDayCell");
2594
+ var FadeIn = /* @__PURE__ */ __name(({ children }) => {
2595
+ const [mounted, setMounted] = react.useState(false);
2596
+ react.useEffect(() => {
2597
+ const id = requestAnimationFrame(() => setMounted(true));
2598
+ return () => cancelAnimationFrame(id);
2599
+ }, []);
2600
+ return /* @__PURE__ */ jsxRuntime.jsx(
2601
+ reactNative.View,
2602
+ {
2603
+ style: {
2604
+ opacity: mounted ? 1 : 0,
2605
+ transform: [{ translateY: mounted ? 0 : 4 }],
2606
+ transitionProperty: "opacity, transform",
2607
+ transitionDuration: "220ms",
2608
+ transitionTimingFunction: "cubic-bezier(0.2, 0, 0, 1)"
2609
+ },
2610
+ children
2611
+ }
2612
+ );
2613
+ }, "FadeIn");
2614
+ var resolveYearRange = /* @__PURE__ */ __name((input, minValue, maxValue, focusedYear) => {
2615
+ if (input) {
2616
+ return input;
2617
+ }
2618
+ if (minValue && maxValue) {
2619
+ return [minValue.year, maxValue.year];
2620
+ }
2621
+ if (minValue) {
2622
+ return [minValue.year, Math.max(minValue.year, focusedYear + 10)];
2623
+ }
2624
+ if (maxValue) {
2625
+ return [Math.min(maxValue.year, focusedYear - 100), maxValue.year];
2626
+ }
2627
+ return [focusedYear - 100, focusedYear + 10];
2628
+ }, "resolveYearRange");
2629
+ var pickVisibleMonths = /* @__PURE__ */ __name((input, measuredWidth) => {
2630
+ const target = typeof input === "number" ? input : 2;
2631
+ if (measuredWidth == null || measuredWidth === 0) {
2632
+ return typeof input === "number" ? input : 1;
2633
+ }
2634
+ for (let n = target; n >= 1; n--) {
2635
+ if (measuredWidth >= requiredOuterWidth(n)) {
2636
+ return n;
2637
+ }
2638
+ }
2639
+ return 1;
2640
+ }, "pickVisibleMonths");
2641
+ var CalendarRoot = /* @__PURE__ */ __name((props) => {
2642
+ const [containerWidth, setContainerWidth] = react.useState(null);
2643
+ const onLayout = react.useCallback((e) => {
2644
+ const next = e.nativeEvent.layout.width;
2645
+ setContainerWidth((prev) => prev === next ? prev : next);
2646
+ }, []);
2647
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { onLayout, style: { width: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(CalendarSurface, { containerWidth: containerWidth ?? 0, ...props }) });
2648
+ }, "CalendarRoot");
2649
+ var CalendarSurface = /* @__PURE__ */ __name((props) => {
2650
+ const providerLocale = useLocale();
2651
+ const locale = props.locale ?? providerLocale;
2652
+ if ((props.mode ?? "single") === "range") {
2653
+ return /* @__PURE__ */ jsxRuntime.jsx(
2654
+ RangeCalendar,
2655
+ {
2656
+ ...props,
2657
+ locale
2658
+ }
2659
+ );
2660
+ }
2661
+ return /* @__PURE__ */ jsxRuntime.jsx(
2662
+ SingleOrMultiCalendar,
2663
+ {
2664
+ ...props,
2665
+ locale
2666
+ }
2667
+ );
2668
+ }, "CalendarSurface");
2669
+ var surfaceMetrics = /* @__PURE__ */ __name((visibleMonths) => {
2670
+ const innerWidth = 2 * ARROW_AREA + visibleMonths * GRID_WIDTH + (visibleMonths - 1) * MONTH_GAP;
2671
+ return {
2672
+ innerWidth,
2673
+ // Body grids row width (excludes arrow area)
2674
+ gridsRowWidth: visibleMonths * GRID_WIDTH + (visibleMonths - 1) * MONTH_GAP
2675
+ };
2676
+ }, "surfaceMetrics");
2677
+ var SingleOrMultiCalendar = /* @__PURE__ */ __name((props) => {
2678
+ const { locale, renderDay, containerWidth } = props;
2679
+ const colors = useThemeColors();
2680
+ const firstDayOfWeek = props.firstDayOfWeek ?? getFirstDayOfWeek(locale);
2681
+ const weekendDays = props.weekendDays ?? getWeekendDays(locale);
2682
+ const visibleMonths = props.behavior === "scroll" ? 1 : pickVisibleMonths(props.visibleMonths, containerWidth);
2683
+ const { innerWidth, gridsRowWidth } = surfaceMetrics(visibleMonths);
2684
+ react.useEffect(() => {
2685
+ if (process.env.NODE_ENV !== "production" && props.behavior === "scroll" && typeof props.visibleMonths === "number" && props.visibleMonths > 1) {
2686
+ console.warn('[Calendar] visibleMonths is ignored when behavior="scroll"; falling back to single column.');
2687
+ }
2688
+ }, [props.behavior, props.visibleMonths]);
2689
+ const containerRef = react.useRef(null);
2690
+ const state = useCalendarState({
2691
+ ...props.mode !== void 0 ? { mode: props.mode } : {},
2692
+ locale,
2693
+ ...props.value !== void 0 ? { value: props.value } : {},
2694
+ ...props.defaultValue !== void 0 ? { defaultValue: props.defaultValue } : {},
2695
+ ...props.onChange !== void 0 ? { onChange: props.onChange } : {},
2696
+ ...props.view !== void 0 ? { view: props.view } : {},
2697
+ ...props.defaultView !== void 0 ? { defaultView: props.defaultView } : {},
2698
+ ...props.onViewChange !== void 0 ? { onViewChange: props.onViewChange } : {},
2699
+ ...props.minValue !== void 0 ? { minValue: props.minValue } : {},
2700
+ ...props.maxValue !== void 0 ? { maxValue: props.maxValue } : {},
2701
+ ...props.isDateUnavailable !== void 0 ? { isDateUnavailable: props.isDateUnavailable } : {}
2702
+ });
2703
+ const [anchor, setAnchor] = react.useState(state.focusedDate);
2704
+ const anchorRef = react.useRef(anchor);
2705
+ react.useEffect(() => {
2706
+ anchorRef.current = anchor;
2707
+ }, [anchor]);
2708
+ react.useEffect(() => {
2709
+ const start = anchorRef.current;
2710
+ const end = start.add({ months: visibleMonths });
2711
+ if (state.focusedDate.compare(start) < 0 || state.focusedDate.compare(end) >= 0) {
2712
+ setAnchor(state.focusedDate);
2713
+ }
2714
+ }, [state.focusedDate, visibleMonths]);
2715
+ const keyboardNavRef = react.useRef(false);
2716
+ react.useLayoutEffect(() => {
2717
+ focusDayCell(containerRef.current, state.focusedDate, keyboardNavRef.current);
2718
+ keyboardNavRef.current = false;
2719
+ }, [state.focusedDate, state.value]);
2720
+ const months = react.useMemo(
2721
+ () => Array.from({ length: visibleMonths }, (_, i) => anchor.add({ months: i })),
2722
+ [anchor, visibleMonths]
2723
+ );
2724
+ const keyboard = useCalendarKeyboard({
2725
+ focusedDate: state.focusedDate,
2726
+ moveFocus: /* @__PURE__ */ __name((delta) => {
2727
+ state.moveFocus(delta);
2728
+ if (delta.months || delta.years) {
2729
+ setAnchor((a) => {
2730
+ let next = a;
2731
+ if (delta.months) {
2732
+ next = next.add({ months: delta.months });
2733
+ }
2734
+ if (delta.years) {
2735
+ next = next.add({ years: delta.years });
2736
+ }
2737
+ return next;
2738
+ });
2739
+ }
2740
+ }, "moveFocus"),
2741
+ selectDate: state.selectDate,
2742
+ setView: state.setView,
2743
+ view: state.view,
2744
+ firstDayOfWeek
2745
+ });
2746
+ const onPrev = /* @__PURE__ */ __name(() => {
2747
+ if (state.view === "year") {
2748
+ setAnchor((a) => a.add({ years: -10 }));
2749
+ } else if (state.view === "month") {
2750
+ setAnchor((a) => a.add({ years: -1 }));
2751
+ } else if (props.behavior === "scroll") {
2752
+ state.moveFocus({ months: -1 });
2753
+ } else {
2754
+ setAnchor((a) => a.add({ months: -1 }));
2755
+ }
2756
+ }, "onPrev");
2757
+ const onNext = /* @__PURE__ */ __name(() => {
2758
+ if (state.view === "year") {
2759
+ setAnchor((a) => a.add({ years: 10 }));
2760
+ } else if (state.view === "month") {
2761
+ setAnchor((a) => a.add({ years: 1 }));
2762
+ } else if (props.behavior === "scroll") {
2763
+ state.moveFocus({ months: 1 });
2764
+ } else {
2765
+ setAnchor((a) => a.add({ months: 1 }));
2766
+ }
2767
+ }, "onNext");
2768
+ const onTitlePress = /* @__PURE__ */ __name(() => state.setView(state.view === "day" ? "month" : state.view === "month" ? "year" : "day"), "onTitlePress");
2769
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2770
+ reactNative.View,
2771
+ {
2772
+ ref: (node) => {
2773
+ containerRef.current = node;
2774
+ if (typeof props.ref === "function") {
2775
+ props.ref(node);
2776
+ } else if (props.ref) {
2777
+ props.ref.current = node;
2778
+ }
2779
+ },
2780
+ ...props.testID !== void 0 ? { testID: props.testID } : {},
2781
+ onKeyDown: (e) => {
2782
+ keyboardNavRef.current = true;
2783
+ keyboard.onKeyDown(e);
2784
+ },
2785
+ tabIndex: 0,
2786
+ style: {
2787
+ padding: SURFACE_PADDING,
2788
+ backgroundColor: colors.semantic.background.elevated,
2789
+ borderRadius: 16,
2790
+ borderWidth: SURFACE_BORDER,
2791
+ borderColor: colors.semantic.border.default,
2792
+ shadowColor: "#000",
2793
+ shadowOpacity: 0.04,
2794
+ shadowRadius: 12,
2795
+ shadowOffset: { width: 0, height: 4 },
2796
+ width: innerWidth + 2 * SURFACE_PADDING + 2 * SURFACE_BORDER,
2797
+ maxWidth: "100%",
2798
+ alignSelf: "center"
2799
+ },
2800
+ children: [
2801
+ /* @__PURE__ */ jsxRuntime.jsx(
2802
+ Caption,
2803
+ {
2804
+ months,
2805
+ locale,
2806
+ view: state.view,
2807
+ caption: props.caption ?? "title",
2808
+ gridWidth: GRID_WIDTH,
2809
+ monthGap: MONTH_GAP,
2810
+ yearRange: resolveYearRange(props.yearRange, props.minValue, props.maxValue, anchor.year),
2811
+ onPrev,
2812
+ onNext,
2813
+ onTitlePress,
2814
+ onSetMonth: (slot, m) => {
2815
+ const picked = (months[slot] ?? anchor).set({ month: m, day: 1 });
2816
+ setAnchor(picked.subtract({ months: slot }));
2817
+ },
2818
+ onSetYear: (slot, y) => {
2819
+ const picked = (months[slot] ?? anchor).set({ year: y, day: 1 });
2820
+ setAnchor(picked.subtract({ months: slot }));
2821
+ },
2822
+ children: props.children
2823
+ }
2824
+ ),
2825
+ /* @__PURE__ */ jsxRuntime.jsxs(FadeIn, { children: [
2826
+ state.view === "day" && (props.behavior === "scroll" ? /* @__PURE__ */ jsxRuntime.jsx(
2827
+ ScrollBody,
2828
+ {
2829
+ mode: props.mode ?? "single",
2830
+ locale,
2831
+ focusedDate: state.focusedDate,
2832
+ onFocusedMonthChange: (next) => setAnchor(next),
2833
+ value: state.value,
2834
+ onSelectDate: (date) => state.selectDate(date, "click"),
2835
+ firstDayOfWeek,
2836
+ weekendDays,
2837
+ ...props.minValue !== void 0 ? { minValue: props.minValue } : {},
2838
+ ...props.maxValue !== void 0 ? { maxValue: props.maxValue } : {},
2839
+ ...props.isDateUnavailable !== void 0 ? { isDateUnavailable: props.isDateUnavailable } : {},
2840
+ ...renderDay ? { renderDay } : {}
2841
+ }
2842
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
2843
+ reactNative.View,
2844
+ {
2845
+ style: { flexDirection: "row", gap: MONTH_GAP, alignSelf: "center", width: gridsRowWidth },
2846
+ children: months.map((m) => /* @__PURE__ */ jsxRuntime.jsx(
2847
+ DayGrid,
2848
+ {
2849
+ visibleMonth: m,
2850
+ locale,
2851
+ mode: props.mode ?? "single",
2852
+ value: state.value,
2853
+ focusedDate: state.focusedDate,
2854
+ isUnavailable: state.isUnavailable,
2855
+ weekendDays,
2856
+ firstDayOfWeek,
2857
+ onDayPress: (date) => state.selectDate(date, "click"),
2858
+ ...renderDay ? { renderDay } : {}
2859
+ },
2860
+ `${m.year}-${m.month}`
2861
+ ))
2862
+ }
2863
+ )),
2864
+ state.view === "month" && /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { alignItems: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(
2865
+ MonthGrid,
2866
+ {
2867
+ visibleMonth: anchor,
2868
+ locale,
2869
+ availableWidth: gridsRowWidth,
2870
+ onSelect: (month) => {
2871
+ setAnchor(new date.CalendarDate(anchor.year, month, 1));
2872
+ state.setView("day");
2873
+ }
2874
+ }
2875
+ ) }),
2876
+ state.view === "year" && /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { alignItems: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(
2877
+ YearGrid,
2878
+ {
2879
+ visibleMonth: anchor,
2880
+ availableWidth: gridsRowWidth,
2881
+ onSelect: (year) => {
2882
+ setAnchor(new date.CalendarDate(year, anchor.month, 1));
2883
+ state.setView("month");
2884
+ }
2885
+ }
2886
+ ) }),
2887
+ props.children && (props.caption ?? "title") !== "custom" ? /* @__PURE__ */ jsxRuntime.jsx(Footer, { children: props.children }) : null
2888
+ ] }, `smc-${state.view}`)
2889
+ ]
2890
+ }
2891
+ );
2892
+ }, "SingleOrMultiCalendar");
2893
+ var RangeCalendar = /* @__PURE__ */ __name((props) => {
2894
+ const { locale, renderDay, containerWidth } = props;
2895
+ const colors = useThemeColors();
2896
+ const firstDayOfWeek = props.firstDayOfWeek ?? getFirstDayOfWeek(locale);
2897
+ const weekendDays = props.weekendDays ?? getWeekendDays(locale);
2898
+ const visibleMonths = props.behavior === "scroll" ? 1 : pickVisibleMonths(props.visibleMonths, containerWidth);
2899
+ const { innerWidth, gridsRowWidth } = surfaceMetrics(visibleMonths);
2900
+ react.useEffect(() => {
2901
+ if (process.env.NODE_ENV !== "production" && props.behavior === "scroll" && typeof props.visibleMonths === "number" && props.visibleMonths > 1) {
2902
+ console.warn('[Calendar] visibleMonths is ignored when behavior="scroll"; falling back to single column.');
2903
+ }
2904
+ }, [props.behavior, props.visibleMonths]);
2905
+ const containerRef = react.useRef(null);
2906
+ const range = useRangeState({
2907
+ ...props.value !== void 0 ? { value: props.value } : {},
2908
+ ...props.defaultValue !== void 0 ? { defaultValue: props.defaultValue } : {},
2909
+ ...props.onChange !== void 0 ? { onChange: props.onChange } : {},
2910
+ ...props.minValue !== void 0 ? { minValue: props.minValue } : {},
2911
+ ...props.maxValue !== void 0 ? { maxValue: props.maxValue } : {},
2912
+ ...props.isDateUnavailable !== void 0 ? { isDateUnavailable: props.isDateUnavailable } : {},
2913
+ ...props.minNights !== void 0 ? { minNights: props.minNights } : {},
2914
+ ...props.maxNights !== void 0 ? { maxNights: props.maxNights } : {}
2915
+ });
2916
+ const initialFocus2 = range.value?.start ?? date.today(date.getLocalTimeZone());
2917
+ const [focusedDate, setFocusedDate] = react.useState(initialFocus2);
2918
+ const keyboardNavRef = react.useRef(false);
2919
+ react.useLayoutEffect(() => {
2920
+ focusDayCell(containerRef.current, focusedDate, keyboardNavRef.current);
2921
+ keyboardNavRef.current = false;
2922
+ }, [focusedDate, range.value]);
2923
+ const [anchor, setAnchor] = react.useState(initialFocus2);
2924
+ const [internalView, setInternalView] = react.useState(props.defaultView ?? "day");
2925
+ const isViewControlled = props.view !== void 0;
2926
+ const view = isViewControlled ? props.view : internalView;
2927
+ const setView = react.useCallback(
2928
+ (next) => {
2929
+ if (!isViewControlled) {
2930
+ setInternalView(next);
2931
+ }
2932
+ props.onViewChange?.(next);
2933
+ },
2934
+ [isViewControlled, props.onViewChange]
2935
+ );
2936
+ const months = react.useMemo(
2937
+ () => Array.from({ length: visibleMonths }, (_, i) => anchor.add({ months: i })),
2938
+ [anchor, visibleMonths]
2939
+ );
2940
+ const keyboard = useCalendarKeyboard({
2941
+ focusedDate,
2942
+ moveFocus: /* @__PURE__ */ __name((delta) => {
2943
+ setFocusedDate((f) => {
2944
+ let next = f;
2945
+ if (delta.days) {
2946
+ next = next.add({ days: delta.days });
2947
+ }
2948
+ if (delta.weeks) {
2949
+ next = next.add({ weeks: delta.weeks });
2950
+ }
2951
+ if (delta.months) {
2952
+ next = next.add({ months: delta.months });
2953
+ }
2954
+ if (delta.years) {
2955
+ next = next.add({ years: delta.years });
2956
+ }
2957
+ return next;
2958
+ });
2959
+ if (delta.months || delta.years) {
2960
+ setAnchor((a) => {
2961
+ let next = a;
2962
+ if (delta.months) {
2963
+ next = next.add({ months: delta.months });
2964
+ }
2965
+ if (delta.years) {
2966
+ next = next.add({ years: delta.years });
2967
+ }
2968
+ return next;
2969
+ });
2970
+ }
2971
+ }, "moveFocus"),
2972
+ selectDate: /* @__PURE__ */ __name((date) => range.selectDate(date, "keyboard"), "selectDate"),
2973
+ setView,
2974
+ view,
2975
+ firstDayOfWeek
2976
+ });
2977
+ const onPrev = /* @__PURE__ */ __name(() => {
2978
+ if (view === "year") {
2979
+ setAnchor((a) => a.add({ years: -10 }));
2980
+ } else if (view === "month") {
2981
+ setAnchor((a) => a.add({ years: -1 }));
2982
+ } else if (props.behavior === "scroll") {
2983
+ setFocusedDate((f) => f.add({ months: -1 }));
2984
+ } else {
2985
+ setAnchor((a) => a.add({ months: -1 }));
2986
+ }
2987
+ }, "onPrev");
2988
+ const onNext = /* @__PURE__ */ __name(() => {
2989
+ if (view === "year") {
2990
+ setAnchor((a) => a.add({ years: 10 }));
2991
+ } else if (view === "month") {
2992
+ setAnchor((a) => a.add({ years: 1 }));
2993
+ } else if (props.behavior === "scroll") {
2994
+ setFocusedDate((f) => f.add({ months: 1 }));
2995
+ } else {
2996
+ setAnchor((a) => a.add({ months: 1 }));
2997
+ }
2998
+ }, "onNext");
2999
+ const [drilldownSlot, setDrilldownSlot] = react.useState(0);
3000
+ const onTitlePress = /* @__PURE__ */ __name((clicked) => {
3001
+ const slot = months.findIndex((m) => m.year === clicked.year && m.month === clicked.month);
3002
+ setDrilldownSlot(slot >= 0 ? slot : 0);
3003
+ if (clicked.compare(anchor) !== 0) {
3004
+ setAnchor(clicked);
3005
+ }
3006
+ setView(view === "day" ? "month" : view === "month" ? "year" : "day");
3007
+ }, "onTitlePress");
3008
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3009
+ reactNative.View,
3010
+ {
3011
+ ref: (node) => {
3012
+ containerRef.current = node;
3013
+ if (typeof props.ref === "function") {
3014
+ props.ref(node);
3015
+ } else if (props.ref) {
3016
+ props.ref.current = node;
3017
+ }
3018
+ },
3019
+ ...props.testID !== void 0 ? { testID: props.testID } : {},
3020
+ onKeyDown: (e) => {
3021
+ keyboardNavRef.current = true;
3022
+ keyboard.onKeyDown(e);
3023
+ },
3024
+ tabIndex: 0,
3025
+ style: {
3026
+ padding: SURFACE_PADDING,
3027
+ backgroundColor: colors.semantic.background.elevated,
3028
+ borderRadius: 16,
3029
+ borderWidth: SURFACE_BORDER,
3030
+ borderColor: colors.semantic.border.default,
3031
+ shadowColor: "#000",
3032
+ shadowOpacity: 0.04,
3033
+ shadowRadius: 12,
3034
+ shadowOffset: { width: 0, height: 4 },
3035
+ width: innerWidth + 2 * SURFACE_PADDING + 2 * SURFACE_BORDER,
3036
+ maxWidth: "100%",
3037
+ alignSelf: "center"
3038
+ },
3039
+ children: [
3040
+ /* @__PURE__ */ jsxRuntime.jsx(
3041
+ Caption,
3042
+ {
3043
+ months,
3044
+ locale,
3045
+ view,
3046
+ caption: props.caption ?? "title",
3047
+ gridWidth: GRID_WIDTH,
3048
+ monthGap: MONTH_GAP,
3049
+ yearRange: resolveYearRange(props.yearRange, props.minValue, props.maxValue, anchor.year),
3050
+ onPrev,
3051
+ onNext,
3052
+ onTitlePress,
3053
+ onSetMonth: (slot, m) => {
3054
+ const picked = (months[slot] ?? anchor).set({ month: m, day: 1 });
3055
+ setAnchor(picked.subtract({ months: slot }));
3056
+ },
3057
+ onSetYear: (slot, y) => {
3058
+ const picked = (months[slot] ?? anchor).set({ year: y, day: 1 });
3059
+ setAnchor(picked.subtract({ months: slot }));
3060
+ },
3061
+ children: props.children
3062
+ }
3063
+ ),
3064
+ /* @__PURE__ */ jsxRuntime.jsxs(FadeIn, { children: [
3065
+ view === "day" && (props.behavior === "scroll" ? /* @__PURE__ */ jsxRuntime.jsx(
3066
+ ScrollBody,
3067
+ {
3068
+ mode: "range",
3069
+ locale,
3070
+ focusedDate,
3071
+ onFocusedMonthChange: (next) => setAnchor(next),
3072
+ value: range.value,
3073
+ previewRange: range.previewRange,
3074
+ onSelectDate: (date) => range.selectDate(date),
3075
+ firstDayOfWeek,
3076
+ weekendDays,
3077
+ ...props.minValue !== void 0 ? { minValue: props.minValue } : {},
3078
+ ...props.maxValue !== void 0 ? { maxValue: props.maxValue } : {},
3079
+ ...props.isDateUnavailable !== void 0 ? { isDateUnavailable: props.isDateUnavailable } : {},
3080
+ ...renderDay ? { renderDay } : {}
3081
+ }
3082
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
3083
+ reactNative.View,
3084
+ {
3085
+ style: { flexDirection: "row", gap: MONTH_GAP, alignSelf: "center", width: gridsRowWidth },
3086
+ children: months.map((m) => /* @__PURE__ */ jsxRuntime.jsx(
3087
+ DayGrid,
3088
+ {
3089
+ visibleMonth: m,
3090
+ locale,
3091
+ mode: "range",
3092
+ value: range.value,
3093
+ previewRange: range.previewRange,
3094
+ focusedDate,
3095
+ isUnavailable: range.isUnavailable,
3096
+ weekendDays,
3097
+ firstDayOfWeek,
3098
+ onDayPress: (date) => range.selectDate(date),
3099
+ onDayHover: (date) => range.setHoveredDate(date),
3100
+ ...renderDay ? { renderDay } : {}
3101
+ },
3102
+ `${m.year}-${m.month}`
3103
+ ))
3104
+ }
3105
+ )),
3106
+ view === "month" && /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { alignItems: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(
3107
+ MonthGrid,
3108
+ {
3109
+ visibleMonth: anchor,
3110
+ locale,
3111
+ availableWidth: gridsRowWidth,
3112
+ onSelect: (month) => {
3113
+ const picked = new date.CalendarDate(anchor.year, month, 1);
3114
+ setAnchor(picked.subtract({ months: drilldownSlot }));
3115
+ setView("day");
3116
+ }
3117
+ }
3118
+ ) }),
3119
+ view === "year" && /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { alignItems: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(
3120
+ YearGrid,
3121
+ {
3122
+ visibleMonth: anchor,
3123
+ availableWidth: gridsRowWidth,
3124
+ onSelect: (year) => {
3125
+ const picked = new date.CalendarDate(year, anchor.month, 1);
3126
+ setAnchor(picked.subtract({ months: drilldownSlot }));
3127
+ setView("month");
3128
+ }
3129
+ }
3130
+ ) }),
3131
+ props.children && (props.caption ?? "title") !== "custom" ? /* @__PURE__ */ jsxRuntime.jsx(Footer, { children: props.children }) : null
3132
+ ] }, `range-${view}`)
3133
+ ]
3134
+ }
3135
+ );
3136
+ }, "RangeCalendar");
3137
+ var CalendarCaption = /* @__PURE__ */ __name(({ children }) => /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children }), "CalendarCaption");
3138
+ CalendarCaption.displayName = "CalendarCaption";
3139
+ var Calendar = CalendarRoot;
3140
+ Calendar.Caption = CalendarCaption;
3141
+
3142
+ // src/slot/compose-refs.ts
3143
+ function composeRefs(...refs) {
3144
+ return (node) => {
3145
+ for (const ref of refs) {
3146
+ if (ref == null) {
3147
+ continue;
3148
+ }
3149
+ if (typeof ref === "function") {
3150
+ ref(node);
3151
+ } else {
3152
+ ref.current = node;
3153
+ }
3154
+ }
3155
+ };
3156
+ }
3157
+ __name(composeRefs, "composeRefs");
3158
+ var Slot = react.forwardRef(/* @__PURE__ */ __name(function Slot2(props, forwardedRef) {
3159
+ const { children, ...slotProps } = props;
3160
+ if (!react.isValidElement(children)) {
3161
+ return null;
3162
+ }
3163
+ const child = react.Children.only(children);
3164
+ const merged = mergeProps(slotProps, child.props);
3165
+ const childRef = child.ref;
3166
+ if (forwardedRef || childRef) {
3167
+ merged.ref = composeRefs(forwardedRef, childRef);
3168
+ }
3169
+ return react.cloneElement(child, merged);
3170
+ }, "Slot"));
3171
+ Slot.displayName = "Slot";
3172
+ function mergeProps(outer, inner) {
3173
+ const merged = { ...outer };
3174
+ for (const key of Object.keys(inner)) {
3175
+ const outerValue = outer[key];
3176
+ const innerValue = inner[key];
3177
+ if (key === "className" || key === "class") {
3178
+ merged[key] = joinClass(outerValue, innerValue);
3179
+ continue;
3180
+ }
3181
+ if (key === "style") {
3182
+ merged[key] = {
3183
+ ...outerValue,
3184
+ ...innerValue
3185
+ };
3186
+ continue;
3187
+ }
3188
+ if (isEventHandler(key, outerValue, innerValue)) {
3189
+ merged[key] = composeHandlers(outerValue, innerValue);
3190
+ continue;
3191
+ }
3192
+ merged[key] = innerValue;
3193
+ }
3194
+ return merged;
3195
+ }
3196
+ __name(mergeProps, "mergeProps");
3197
+ function joinClass(outer, inner) {
3198
+ const a = typeof outer === "string" ? outer : "";
3199
+ const b = typeof inner === "string" ? inner : "";
3200
+ const joined = [a, b].filter(Boolean).join(" ");
3201
+ return joined.length > 0 ? joined : void 0;
3202
+ }
3203
+ __name(joinClass, "joinClass");
3204
+ function isEventHandler(key, outer, inner) {
3205
+ if (!key.startsWith("on") || key.length < 3) {
3206
+ return false;
3207
+ }
3208
+ if (key[2] !== key[2]?.toUpperCase()) {
3209
+ return false;
3210
+ }
3211
+ return typeof outer === "function" && typeof inner === "function";
3212
+ }
3213
+ __name(isEventHandler, "isEventHandler");
3214
+ function composeHandlers(outer, inner) {
3215
+ return (...args) => {
3216
+ outer(...args);
3217
+ inner(...args);
3218
+ };
3219
+ }
3220
+ __name(composeHandlers, "composeHandlers");
3221
+ var PopoverContext = react.createContext(null);
3222
+ var usePopoverContext = /* @__PURE__ */ __name((label) => {
3223
+ const ctx = react.useContext(PopoverContext);
3224
+ if (!ctx) {
3225
+ throw new Error(`<${label}> must be rendered inside a <Popover>.`);
3226
+ }
3227
+ return ctx;
3228
+ }, "usePopoverContext");
3229
+ var PopoverRoot = /* @__PURE__ */ __name(({ open, defaultOpen = false, onOpenChange, children }) => {
3230
+ const [inner, setInner] = react.useState(defaultOpen);
3231
+ const isControlled = open !== void 0;
3232
+ const current = isControlled ? open : inner;
3233
+ const setOpen = react.useCallback(
3234
+ (next) => {
3235
+ if (!isControlled) {
3236
+ setInner(next);
3237
+ }
3238
+ onOpenChange?.(next);
3239
+ },
3240
+ [isControlled, onOpenChange]
3241
+ );
3242
+ const baseId = react.useId();
3243
+ const triggerRef = react.useRef(null);
3244
+ const contentRef = react.useRef(null);
3245
+ const [triggerRect, setTriggerRect] = react.useState(null);
3246
+ const measureTrigger = react.useCallback(() => {
3247
+ const node = triggerRef.current;
3248
+ if (!node || typeof node.getBoundingClientRect !== "function") {
3249
+ return;
3250
+ }
3251
+ const rect = node.getBoundingClientRect();
3252
+ setTriggerRect({ top: rect.top, left: rect.left, width: rect.width, height: rect.height });
3253
+ }, []);
3254
+ const ctxValue = {
3255
+ open: current,
3256
+ setOpen,
3257
+ contentId: `${baseId}-content`,
3258
+ triggerRef,
3259
+ contentRef,
3260
+ triggerRect,
3261
+ measureTrigger
3262
+ };
3263
+ return /* @__PURE__ */ jsxRuntime.jsx(PopoverContext.Provider, { value: ctxValue, children });
3264
+ }, "PopoverRoot");
3265
+ var PopoverTrigger = /* @__PURE__ */ __name(({ asChild = true, children, className, testID }) => {
3266
+ const ctx = usePopoverContext("PopoverTrigger");
3267
+ const onPress = react.useCallback(() => {
3268
+ ctx.measureTrigger();
3269
+ ctx.setOpen(!ctx.open);
3270
+ }, [ctx]);
3271
+ if (asChild && react.isValidElement(children)) {
3272
+ const child = children;
3273
+ const fire = /* @__PURE__ */ __name((existing) => (event) => {
3274
+ existing?.(event);
3275
+ ctx.measureTrigger();
3276
+ ctx.setOpen(!ctx.open);
3277
+ }, "fire");
3278
+ return /* @__PURE__ */ jsxRuntime.jsx(
3279
+ Slot,
3280
+ {
3281
+ ref: (node) => {
3282
+ ctx.triggerRef.current = node;
3283
+ },
3284
+ onClick: fire(child.props.onClick),
3285
+ onPress: fire(child.props.onPress),
3286
+ "aria-haspopup": "dialog",
3287
+ "aria-expanded": ctx.open,
3288
+ "aria-controls": ctx.contentId,
3289
+ ...testID !== void 0 ? { "data-testid": testID } : {},
3290
+ ...className !== void 0 ? { className } : {},
3291
+ children: child
3292
+ }
3293
+ );
3294
+ }
3295
+ return /* @__PURE__ */ jsxRuntime.jsx(
3296
+ reactNative.Pressable,
3297
+ {
3298
+ ref: (node) => {
3299
+ ctx.triggerRef.current = node;
3300
+ },
3301
+ onPress,
3302
+ ...{
3303
+ "aria-haspopup": "dialog",
3304
+ "aria-expanded": ctx.open,
3305
+ "aria-controls": ctx.contentId
3306
+ },
3307
+ ...testID !== void 0 ? { testID } : {},
3308
+ ...className !== void 0 ? { className } : {},
3309
+ children: wrapStringChildren(children)
3310
+ }
3311
+ );
3312
+ }, "PopoverTrigger");
3313
+ function wrapStringChildren(children) {
3314
+ if (typeof children === "string" || typeof children === "number") {
3315
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { children });
3316
+ }
3317
+ return children;
3318
+ }
3319
+ __name(wrapStringChildren, "wrapStringChildren");
3320
+ var GAP = 4;
3321
+ var MIN_WIDTH = 200;
3322
+ var VIEWPORT_MARGIN = 8;
3323
+ function computePosition(rect, side, align, contentSize) {
3324
+ const cw = contentSize?.width ?? MIN_WIDTH;
3325
+ const ch = contentSize?.height ?? 0;
3326
+ let top = 0;
3327
+ let left = 0;
3328
+ switch (side) {
3329
+ case "top":
3330
+ top = rect.top - GAP - ch;
3331
+ break;
3332
+ case "bottom":
3333
+ top = rect.top + rect.height + GAP;
3334
+ break;
3335
+ case "left":
3336
+ left = rect.left - GAP - cw;
3337
+ break;
3338
+ case "right":
3339
+ left = rect.left + rect.width + GAP;
3340
+ break;
3341
+ }
3342
+ if (side === "top" || side === "bottom") {
3343
+ switch (align) {
3344
+ case "start":
3345
+ left = rect.left;
3346
+ break;
3347
+ case "center":
3348
+ left = rect.left + rect.width / 2 - cw / 2;
3349
+ break;
3350
+ case "end":
3351
+ left = rect.left + rect.width - cw;
3352
+ break;
3353
+ }
3354
+ } else {
3355
+ switch (align) {
3356
+ case "start":
3357
+ top = rect.top;
3358
+ break;
3359
+ case "center":
3360
+ top = rect.top + rect.height / 2 - ch / 2;
3361
+ break;
3362
+ case "end":
3363
+ top = rect.top + rect.height - ch;
3364
+ break;
3365
+ }
3366
+ }
3367
+ return { top, left };
3368
+ }
3369
+ __name(computePosition, "computePosition");
3370
+ var PopoverContent = /* @__PURE__ */ __name(({
3371
+ side = "bottom",
3372
+ align = "center",
3373
+ children,
3374
+ className,
3375
+ testID,
3376
+ ...rest
3377
+ }) => {
3378
+ const ctx = usePopoverContext("PopoverContent");
3379
+ const colors = useThemeColors();
3380
+ const ariaLabel = rest["aria-label"];
3381
+ const [contentSize, setContentSize] = react.useState(null);
3382
+ react.useEffect(() => {
3383
+ if (!ctx.open) {
3384
+ return;
3385
+ }
3386
+ if (reactNative.Platform.OS !== "web") {
3387
+ return;
3388
+ }
3389
+ if (typeof document === "undefined") {
3390
+ return;
3391
+ }
3392
+ ctx.measureTrigger();
3393
+ const onDocMouseDown = /* @__PURE__ */ __name((event) => {
3394
+ const target = event.target;
3395
+ const trigger = ctx.triggerRef.current;
3396
+ const content2 = ctx.contentRef.current;
3397
+ if (trigger?.contains(target)) {
3398
+ return;
3399
+ }
3400
+ if (content2?.contains(target)) {
3401
+ return;
3402
+ }
3403
+ ctx.setOpen(false);
3404
+ }, "onDocMouseDown");
3405
+ const onKeyDown = /* @__PURE__ */ __name((event) => {
3406
+ if (event.key === "Escape") {
3407
+ event.preventDefault();
3408
+ ctx.setOpen(false);
3409
+ }
3410
+ }, "onKeyDown");
3411
+ const onResize = /* @__PURE__ */ __name(() => ctx.measureTrigger(), "onResize");
3412
+ const onScroll = /* @__PURE__ */ __name(() => ctx.measureTrigger(), "onScroll");
3413
+ document.addEventListener("mousedown", onDocMouseDown);
3414
+ document.addEventListener("keydown", onKeyDown);
3415
+ window.addEventListener("resize", onResize);
3416
+ window.addEventListener("scroll", onScroll, true);
3417
+ return () => {
3418
+ document.removeEventListener("mousedown", onDocMouseDown);
3419
+ document.removeEventListener("keydown", onKeyDown);
3420
+ window.removeEventListener("resize", onResize);
3421
+ window.removeEventListener("scroll", onScroll, true);
3422
+ };
3423
+ }, [ctx.open, ctx.measureTrigger, ctx.setOpen, ctx.triggerRef, ctx.contentRef]);
3424
+ react.useEffect(() => {
3425
+ if (!ctx.open) {
3426
+ setContentSize(null);
3427
+ }
3428
+ }, [ctx.open]);
3429
+ if (!ctx.open) {
3430
+ return null;
3431
+ }
3432
+ const position = ctx.triggerRect ? computePosition(ctx.triggerRect, side, align, contentSize) : null;
3433
+ const viewportWidth = reactNative.Dimensions.get("window").width;
3434
+ const maxContentWidth = Math.max(MIN_WIDTH, viewportWidth - VIEWPORT_MARGIN * 2);
3435
+ const contentBaseStyle = {
3436
+ minWidth: MIN_WIDTH,
3437
+ maxWidth: maxContentWidth,
3438
+ borderRadius: px(colors.radius.lg),
3439
+ borderWidth: 1,
3440
+ borderColor: colors.semantic.border.default,
3441
+ backgroundColor: colors.semantic.background.elevated,
3442
+ padding: px(colors.spacing["4"]),
3443
+ ...reactNative.Platform.OS === "web" ? {
3444
+ boxShadow: "0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)",
3445
+ // Subtle scale-in. Skipped on native (do nothing fancy there).
3446
+ transition: "opacity 120ms ease-out, transform 120ms ease-out",
3447
+ transform: "scale(1)",
3448
+ opacity: 1
3449
+ } : { elevation: 8 }
3450
+ };
3451
+ const measuredWidth = contentSize?.width ?? maxContentWidth;
3452
+ const clampedLeft = position ? Math.min(
3453
+ Math.max(VIEWPORT_MARGIN, position.left),
3454
+ Math.max(VIEWPORT_MARGIN, viewportWidth - measuredWidth - VIEWPORT_MARGIN)
3455
+ ) : 0;
3456
+ const positionedStyle = reactNative.Platform.OS === "web" ? position ? {
3457
+ position: "fixed",
3458
+ top: position.top,
3459
+ left: clampedLeft,
3460
+ zIndex: 50
3461
+ } : {
3462
+ // Trigger not yet measured — render off-screen for a
3463
+ // frame to avoid a flash at (0,0).
3464
+ position: "fixed",
3465
+ top: -9999,
3466
+ left: -9999,
3467
+ zIndex: 50
3468
+ } : {};
3469
+ const content = /* @__PURE__ */ jsxRuntime.jsx(
3470
+ reactNative.View,
3471
+ {
3472
+ ref: (node) => {
3473
+ ctx.contentRef.current = node;
3474
+ if (reactNative.Platform.OS !== "web") {
3475
+ return;
3476
+ }
3477
+ if (!node) {
3478
+ return;
3479
+ }
3480
+ if (typeof node.getBoundingClientRect !== "function") {
3481
+ return;
3482
+ }
3483
+ const rect = node.getBoundingClientRect();
3484
+ if (!contentSize || contentSize.width !== rect.width || contentSize.height !== rect.height) {
3485
+ setContentSize({ width: rect.width, height: rect.height });
3486
+ }
3487
+ },
3488
+ ...{
3489
+ role: "dialog",
3490
+ id: ctx.contentId,
3491
+ ...ariaLabel !== void 0 ? { "aria-label": ariaLabel, accessibilityLabel: ariaLabel } : {}
3492
+ },
3493
+ ...testID !== void 0 ? { testID } : {},
3494
+ className: cn(
3495
+ "rounded-lg border border-semantic-border-default bg-semantic-background-elevated",
3496
+ className
3497
+ ),
3498
+ style: [contentBaseStyle, positionedStyle],
3499
+ children
3500
+ }
3501
+ );
3502
+ if (reactNative.Platform.OS === "web") {
3503
+ return content;
3504
+ }
3505
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Modal, { visible: ctx.open, transparent: true, animationType: "fade", onRequestClose: () => ctx.setOpen(false), children: /* @__PURE__ */ jsxRuntime.jsx(
3506
+ reactNative.Pressable,
3507
+ {
3508
+ accessibilityRole: "none",
3509
+ "aria-hidden": true,
3510
+ onPress: () => ctx.setOpen(false),
3511
+ style: {
3512
+ position: "absolute",
3513
+ top: 0,
3514
+ left: 0,
3515
+ right: 0,
3516
+ bottom: 0,
3517
+ backgroundColor: "transparent"
3518
+ },
3519
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3520
+ reactNative.Pressable,
3521
+ {
3522
+ onPress: (event) => event.stopPropagation?.(),
3523
+ style: {
3524
+ position: "absolute",
3525
+ top: ctx.triggerRect ? side === "top" ? Math.max(VIEWPORT_MARGIN, ctx.triggerRect.top - GAP - 80) : ctx.triggerRect.top + ctx.triggerRect.height + GAP : 80,
3526
+ // Clamp horizontally so a wide popover near the
3527
+ // right edge can still grow leftward without
3528
+ // overflowing the screen.
3529
+ left: ctx.triggerRect ? Math.min(
3530
+ Math.max(VIEWPORT_MARGIN, ctx.triggerRect.left),
3531
+ Math.max(VIEWPORT_MARGIN, viewportWidth - measuredWidth - VIEWPORT_MARGIN)
3532
+ ) : VIEWPORT_MARGIN * 2
3533
+ },
3534
+ children: content
3535
+ }
3536
+ )
3537
+ }
3538
+ ) });
3539
+ }, "PopoverContent");
3540
+ var Popover = Object.assign(PopoverRoot, {
3541
+ Trigger: PopoverTrigger,
3542
+ Content: PopoverContent
3543
+ });
3544
+ function formatDate(date$1, locale) {
3545
+ try {
3546
+ return new Intl.DateTimeFormat(locale, { dateStyle: "medium" }).format(date$1.toDate(date.getLocalTimeZone()));
3547
+ } catch {
3548
+ return `${date$1.year}-${String(date$1.month).padStart(2, "0")}-${String(date$1.day).padStart(2, "0")}`;
3549
+ }
3550
+ }
3551
+ __name(formatDate, "formatDate");
3552
+ function CalendarIcon({ size = 16, color = "currentColor" }) {
3553
+ const colors = useThemeColors();
3554
+ if (reactNative.Platform.OS === "web") {
3555
+ return /* @__PURE__ */ jsxRuntime.jsx(
3556
+ "svg",
3557
+ {
3558
+ width: size,
3559
+ height: size,
3560
+ viewBox: "0 0 24 24",
3561
+ fill: "none",
3562
+ stroke: color,
3563
+ strokeWidth: "2",
3564
+ strokeLinecap: "round",
3565
+ strokeLinejoin: "round",
3566
+ "aria-hidden": "true",
3567
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 2v4M16 2v4M3 10h18M5 4h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z" })
3568
+ }
3569
+ );
3570
+ }
3571
+ const resolvedColor = color === "currentColor" ? colors.semantic.text.muted : color;
3572
+ return /* @__PURE__ */ jsxRuntime.jsx(
3573
+ reactNative.Text,
3574
+ {
3575
+ accessibilityElementsHidden: true,
3576
+ importantForAccessibility: "no-hide-descendants",
3577
+ style: { fontSize: size, lineHeight: size, color: resolvedColor },
3578
+ children: "\u{1F4C5}"
3579
+ }
3580
+ );
3581
+ }
3582
+ __name(CalendarIcon, "CalendarIcon");
3583
+ function buildCalendarOptional(minValue, maxValue, isDateUnavailable, firstDayOfWeek) {
3584
+ const out = {};
3585
+ if (minValue !== void 0) {
3586
+ out.minValue = minValue;
3587
+ }
3588
+ if (maxValue !== void 0) {
3589
+ out.maxValue = maxValue;
3590
+ }
3591
+ if (isDateUnavailable !== void 0) {
3592
+ out.isDateUnavailable = isDateUnavailable;
3593
+ }
3594
+ if (firstDayOfWeek !== void 0) {
3595
+ out.firstDayOfWeek = firstDayOfWeek;
3596
+ }
3597
+ return out;
3598
+ }
3599
+ __name(buildCalendarOptional, "buildCalendarOptional");
3600
+ function buildTriggerAriaProps(ariaProps) {
3601
+ const out = {};
3602
+ if (ariaProps["aria-labelledby"] !== void 0) {
3603
+ out["aria-labelledby"] = ariaProps["aria-labelledby"];
3604
+ }
3605
+ if (ariaProps["aria-describedby"] !== void 0) {
3606
+ out["aria-describedby"] = ariaProps["aria-describedby"];
3607
+ }
3608
+ if (ariaProps["aria-invalid"] !== void 0) {
3609
+ out["aria-invalid"] = ariaProps["aria-invalid"];
3610
+ }
3611
+ if (ariaProps["aria-required"] !== void 0) {
3612
+ out["aria-required"] = ariaProps["aria-required"];
3613
+ }
3614
+ return out;
3615
+ }
3616
+ __name(buildTriggerAriaProps, "buildTriggerAriaProps");
3617
+ var DatePickerRoot = /* @__PURE__ */ __name(({
3618
+ value,
3619
+ defaultValue: defaultValue2,
3620
+ onChange,
3621
+ locale: localeProp,
3622
+ minValue,
3623
+ maxValue,
3624
+ isDateUnavailable,
3625
+ firstDayOfWeek,
3626
+ placeholder,
3627
+ disabled = false,
3628
+ id,
3629
+ name: _name,
3630
+ className,
3631
+ testID,
3632
+ ...ariaProps
3633
+ }) => {
3634
+ const providerLocale = useLocale();
3635
+ const locale = localeProp ?? providerLocale;
3636
+ const [open, setOpen] = react.useState(false);
3637
+ const isControlled = value !== void 0;
3638
+ const [inner, setInner] = react.useState(defaultValue2 ?? null);
3639
+ const current = isControlled ? value ?? null : inner;
3640
+ const handleChange = react.useCallback(
3641
+ (date) => {
3642
+ if (!isControlled) {
3643
+ setInner(date);
3644
+ }
3645
+ onChange?.(date);
3646
+ setOpen(false);
3647
+ },
3648
+ [isControlled, onChange]
3649
+ );
3650
+ const handleOpenChange = react.useCallback(
3651
+ (next) => {
3652
+ if (!disabled) {
3653
+ setOpen(next);
3654
+ }
3655
+ },
3656
+ [disabled]
3657
+ );
3658
+ const displayValue = current ? formatDate(current, locale) : null;
3659
+ const calendarOptional = buildCalendarOptional(minValue, maxValue, isDateUnavailable, firstDayOfWeek);
3660
+ const triggerAriaProps = buildTriggerAriaProps(ariaProps);
3661
+ const colors = useThemeColors();
3662
+ const hasError = ariaProps["aria-invalid"] === true || ariaProps["aria-invalid"] === "true";
3663
+ const pressableStyle = {
3664
+ flexDirection: "row",
3665
+ alignItems: "center",
3666
+ borderWidth: 1,
3667
+ borderRadius: px(colors.radius.md),
3668
+ paddingHorizontal: px(colors.spacing["3"]),
3669
+ paddingVertical: px(colors.spacing["2"]),
3670
+ backgroundColor: colors.semantic.background.elevated,
3671
+ borderColor: hasError ? colors.color.danger : colors.semantic.border.default,
3672
+ opacity: disabled ? 0.6 : 1
3673
+ };
3674
+ const textStyle = {
3675
+ flex: 1,
3676
+ fontFamily: colors.fontFamily.body,
3677
+ fontSize: px(colors.fontSize.md),
3678
+ color: displayValue ? colors.semantic.text.default : colors.semantic.text.muted
3679
+ };
3680
+ const triggerExtraProps = {
3681
+ role: "combobox",
3682
+ accessibilityRole: "button",
3683
+ "aria-haspopup": "dialog",
3684
+ "aria-expanded": open,
3685
+ ...triggerAriaProps
3686
+ };
3687
+ if (id !== void 0) {
3688
+ triggerExtraProps.id = id;
3689
+ triggerExtraProps.nativeID = id;
3690
+ }
3691
+ if (testID !== void 0) {
3692
+ triggerExtraProps.testID = testID;
3693
+ }
3694
+ if (hasError) {
3695
+ triggerExtraProps["aria-invalid"] = true;
3696
+ }
3697
+ if (ariaProps["aria-required"]) {
3698
+ triggerExtraProps["aria-required"] = true;
3699
+ }
3700
+ if (disabled) {
3701
+ triggerExtraProps["aria-disabled"] = true;
3702
+ }
3703
+ return /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open, onOpenChange: handleOpenChange, children: [
3704
+ /* @__PURE__ */ jsxRuntime.jsx(Popover.Trigger, { asChild: false, className: cn(className), children: /* @__PURE__ */ jsxRuntime.jsxs(
3705
+ reactNative.Pressable,
3706
+ {
3707
+ onPress: disabled ? void 0 : () => setOpen(!open),
3708
+ disabled,
3709
+ className: cn(
3710
+ "flex-row items-center rounded-md border px-3 py-2",
3711
+ hasError ? "border-semantic-interactive-destructive" : "border-semantic-border-default",
3712
+ disabled ? "opacity-60" : void 0,
3713
+ className
3714
+ ),
3715
+ style: pressableStyle,
3716
+ ...triggerExtraProps,
3717
+ children: [
3718
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: textStyle, numberOfLines: 1, children: displayValue ?? placeholder ?? "" }),
3719
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { marginLeft: px(colors.spacing["2"]) }, children: /* @__PURE__ */ jsxRuntime.jsx(CalendarIcon, { size: 16, color: colors.semantic.text.muted }) })
3720
+ ]
3721
+ }
3722
+ ) }),
3723
+ /* @__PURE__ */ jsxRuntime.jsx(Popover.Content, { "aria-label": "Date picker", side: "bottom", align: "start", children: /* @__PURE__ */ jsxRuntime.jsx(
3724
+ Calendar,
3725
+ {
3726
+ mode: "single",
3727
+ value: current,
3728
+ onChange: (date) => {
3729
+ handleChange(date);
3730
+ },
3731
+ locale,
3732
+ ...calendarOptional
3733
+ }
3734
+ ) })
3735
+ ] });
3736
+ }, "DatePickerRoot");
3737
+ var DatePickerRange = /* @__PURE__ */ __name(({
3738
+ value,
3739
+ defaultValue: defaultValue2,
3740
+ onChange,
3741
+ locale: localeProp,
3742
+ minValue,
3743
+ maxValue,
3744
+ isDateUnavailable,
3745
+ firstDayOfWeek,
3746
+ placeholder,
3747
+ disabled = false,
3748
+ id,
3749
+ name: _name,
3750
+ className,
3751
+ testID,
3752
+ ...ariaProps
3753
+ }) => {
3754
+ const providerLocale = useLocale();
3755
+ const locale = localeProp ?? providerLocale;
3756
+ const [open, setOpen] = react.useState(false);
3757
+ const isControlled = value !== void 0;
3758
+ const [inner, setInner] = react.useState(defaultValue2 ?? { start: null, end: null });
3759
+ const current = isControlled ? value ?? { start: null, end: null } : inner;
3760
+ const calendarValue = current.start !== null ? { start: current.start, end: current.end } : null;
3761
+ const handleChange = react.useCallback(
3762
+ (calRange) => {
3763
+ const next = {
3764
+ start: calRange?.start ?? null,
3765
+ end: calRange?.end ?? null
3766
+ };
3767
+ if (!isControlled) {
3768
+ setInner(next);
3769
+ }
3770
+ onChange?.(next);
3771
+ if (next.start !== null && next.end !== null) {
3772
+ setOpen(false);
3773
+ }
3774
+ },
3775
+ [isControlled, onChange]
3776
+ );
3777
+ const handleOpenChange = react.useCallback(
3778
+ (next) => {
3779
+ if (!disabled) {
3780
+ setOpen(next);
3781
+ }
3782
+ },
3783
+ [disabled]
3784
+ );
3785
+ let displayValue = null;
3786
+ if (current.start !== null) {
3787
+ const startStr = formatDate(current.start, locale);
3788
+ const endStr = current.end !== null ? formatDate(current.end, locale) : "";
3789
+ displayValue = `${startStr} \u2013 ${endStr}`;
3790
+ }
3791
+ const calendarOptional = buildCalendarOptional(minValue, maxValue, isDateUnavailable, firstDayOfWeek);
3792
+ const triggerAriaProps = buildTriggerAriaProps(ariaProps);
3793
+ const colors = useThemeColors();
3794
+ const hasError = ariaProps["aria-invalid"] === true || ariaProps["aria-invalid"] === "true";
3795
+ const pressableStyle = {
3796
+ flexDirection: "row",
3797
+ alignItems: "center",
3798
+ borderWidth: 1,
3799
+ borderRadius: px(colors.radius.md),
3800
+ paddingHorizontal: px(colors.spacing["3"]),
3801
+ paddingVertical: px(colors.spacing["2"]),
3802
+ backgroundColor: colors.semantic.background.elevated,
3803
+ borderColor: hasError ? colors.color.danger : colors.semantic.border.default,
3804
+ opacity: disabled ? 0.6 : 1
3805
+ };
3806
+ const textStyle = {
3807
+ flex: 1,
3808
+ fontFamily: colors.fontFamily.body,
3809
+ fontSize: px(colors.fontSize.md),
3810
+ color: displayValue ? colors.semantic.text.default : colors.semantic.text.muted
3811
+ };
3812
+ const triggerExtraProps = {
3813
+ role: "combobox",
3814
+ accessibilityRole: "button",
3815
+ "aria-haspopup": "dialog",
3816
+ "aria-expanded": open,
3817
+ ...triggerAriaProps
3818
+ };
3819
+ if (id !== void 0) {
3820
+ triggerExtraProps.id = id;
3821
+ triggerExtraProps.nativeID = id;
3822
+ }
3823
+ if (testID !== void 0) {
3824
+ triggerExtraProps.testID = testID;
3825
+ }
3826
+ if (hasError) {
3827
+ triggerExtraProps["aria-invalid"] = true;
3828
+ }
3829
+ if (ariaProps["aria-required"]) {
3830
+ triggerExtraProps["aria-required"] = true;
3831
+ }
3832
+ if (disabled) {
3833
+ triggerExtraProps["aria-disabled"] = true;
3834
+ }
3835
+ return /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open, onOpenChange: handleOpenChange, children: [
3836
+ /* @__PURE__ */ jsxRuntime.jsx(Popover.Trigger, { asChild: false, className: cn(className), children: /* @__PURE__ */ jsxRuntime.jsxs(
3837
+ reactNative.Pressable,
3838
+ {
3839
+ onPress: disabled ? void 0 : () => setOpen(!open),
3840
+ disabled,
3841
+ className: cn(
3842
+ "flex-row items-center rounded-md border px-3 py-2",
3843
+ hasError ? "border-semantic-interactive-destructive" : "border-semantic-border-default",
3844
+ disabled ? "opacity-60" : void 0,
3845
+ className
3846
+ ),
3847
+ style: pressableStyle,
3848
+ ...triggerExtraProps,
3849
+ children: [
3850
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: textStyle, numberOfLines: 1, children: displayValue ?? placeholder ?? "" }),
3851
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { marginLeft: px(colors.spacing["2"]) }, children: /* @__PURE__ */ jsxRuntime.jsx(CalendarIcon, { size: 16, color: colors.semantic.text.muted }) })
3852
+ ]
3853
+ }
3854
+ ) }),
3855
+ /* @__PURE__ */ jsxRuntime.jsx(Popover.Content, { "aria-label": "Date range picker", side: "bottom", align: "start", children: /* @__PURE__ */ jsxRuntime.jsx(
3856
+ Calendar,
3857
+ {
3858
+ mode: "range",
3859
+ value: calendarValue,
3860
+ onChange: (range) => {
3861
+ handleChange(range);
3862
+ },
3863
+ locale,
3864
+ ...calendarOptional
3865
+ }
3866
+ ) })
3867
+ ] });
3868
+ }, "DatePickerRange");
3869
+ var DatePicker = Object.assign(DatePickerRoot, {
3870
+ Range: DatePickerRange
3871
+ });
3872
+
3873
+ exports.DatePicker = DatePicker;
3874
+ //# sourceMappingURL=index.cjs.map
3875
+ //# sourceMappingURL=index.cjs.map