@xaui/native 0.0.19 → 0.0.21

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,1254 @@
1
+ import "../chunk-DXXNBF5P.js";
2
+ import {
3
+ useXUITheme
4
+ } from "../chunk-LTKYHG5V.js";
5
+
6
+ // src/components/input/input.tsx
7
+ import React, { forwardRef, useMemo as useMemo2, useState, useRef, useEffect } from "react";
8
+ import {
9
+ Pressable,
10
+ Text,
11
+ TextInput as RNTextInput,
12
+ View,
13
+ Animated
14
+ } from "react-native";
15
+ import { CloseIcon } from "@xaui/icons";
16
+
17
+ // src/components/input/input.hook.ts
18
+ import { useMemo } from "react";
19
+ import { getSafeThemeColor, withOpacity, withPaletteNumber } from "@xaui/core";
20
+ var sizeMap = {
21
+ sm: {
22
+ minHeight: 40,
23
+ fontSize: 14,
24
+ paddingHorizontal: 12,
25
+ paddingVertical: 8,
26
+ slotGap: 8,
27
+ labelSize: 13,
28
+ helperSize: 12
29
+ },
30
+ md: {
31
+ minHeight: 46,
32
+ fontSize: 16,
33
+ paddingHorizontal: 14,
34
+ paddingVertical: 10,
35
+ slotGap: 10,
36
+ labelSize: 14,
37
+ helperSize: 13
38
+ },
39
+ lg: {
40
+ minHeight: 52,
41
+ fontSize: 17,
42
+ paddingHorizontal: 16,
43
+ paddingVertical: 12,
44
+ slotGap: 12,
45
+ labelSize: 15,
46
+ helperSize: 15
47
+ }
48
+ };
49
+ var useTextInputSizeStyles = (size) => {
50
+ return useMemo(() => sizeMap[size], [size]);
51
+ };
52
+ var useTextInputRadiusStyles = (radius) => {
53
+ const theme = useXUITheme();
54
+ return useMemo(() => {
55
+ if (radius === "full") {
56
+ return { borderRadius: theme.borderRadius.full };
57
+ }
58
+ return { borderRadius: theme.borderRadius[radius] };
59
+ }, [radius, theme.borderRadius]);
60
+ };
61
+ var useTextInputVariantStyles = ({
62
+ themeColor,
63
+ variant,
64
+ isFocused,
65
+ isInvalid,
66
+ isDisabled
67
+ }) => {
68
+ const theme = useXUITheme();
69
+ const safeThemeColor = getSafeThemeColor(themeColor);
70
+ const colorScheme = theme.colors[safeThemeColor];
71
+ return useMemo(() => {
72
+ const focusColor = isInvalid ? theme.colors.danger.main : colorScheme.main;
73
+ const neutralBorder = withOpacity(theme.colors.foreground, 0.1);
74
+ const textColor = isDisabled ? withOpacity(theme.colors.foreground, 0.55) : theme.colors.foreground;
75
+ const placeholderColor = withOpacity(theme.colors.foreground, 0.45);
76
+ const labelColor = isInvalid ? theme.colors.danger.main : isFocused ? colorScheme.main : withOpacity(theme.colors.foreground, 0.8);
77
+ const helperColor = isInvalid ? theme.colors.danger.main : withOpacity(theme.colors.foreground, 0.72);
78
+ if (variant === "underlined") {
79
+ return {
80
+ container: {
81
+ backgroundColor: "transparent",
82
+ borderColor: focusColor,
83
+ borderBottomWidth: isFocused || isInvalid ? 2 : 1
84
+ },
85
+ unfocusedBorderColor: neutralBorder,
86
+ focusedBorderColor: focusColor,
87
+ textColor,
88
+ placeholderColor,
89
+ labelColor,
90
+ helperColor
91
+ };
92
+ }
93
+ if (variant === "bordered") {
94
+ return {
95
+ container: {
96
+ backgroundColor: theme.colors.background,
97
+ borderColor: isFocused || isInvalid ? focusColor : neutralBorder,
98
+ borderWidth: theme.borderWidth.md
99
+ },
100
+ unfocusedBorderColor: neutralBorder,
101
+ focusedBorderColor: focusColor,
102
+ textColor,
103
+ placeholderColor,
104
+ labelColor,
105
+ helperColor
106
+ };
107
+ }
108
+ if (variant === "faded") {
109
+ return {
110
+ container: {
111
+ backgroundColor: withOpacity(colorScheme.background, 0.68),
112
+ borderColor: isFocused || isInvalid ? focusColor : "transparent",
113
+ borderWidth: isFocused || isInvalid ? theme.borderWidth.md : 0
114
+ },
115
+ unfocusedBorderColor: "transparent",
116
+ focusedBorderColor: focusColor,
117
+ textColor,
118
+ placeholderColor,
119
+ labelColor,
120
+ helperColor
121
+ };
122
+ }
123
+ return {
124
+ container: {
125
+ backgroundColor: colorScheme.background,
126
+ borderColor: isFocused || isInvalid ? withPaletteNumber(focusColor, 500) : "transparent",
127
+ borderWidth: isFocused || isInvalid ? theme.borderWidth.md : 0
128
+ },
129
+ unfocusedBorderColor: "transparent",
130
+ focusedBorderColor: withPaletteNumber(focusColor, 500),
131
+ textColor,
132
+ placeholderColor,
133
+ labelColor,
134
+ helperColor
135
+ };
136
+ }, [colorScheme, isDisabled, isFocused, isInvalid, theme, variant]);
137
+ };
138
+
139
+ // src/components/input/input.style.ts
140
+ import { StyleSheet } from "react-native";
141
+ var styles = StyleSheet.create({
142
+ container: {
143
+ width: "100%",
144
+ gap: 6
145
+ },
146
+ noFullWidth: {
147
+ alignSelf: "flex-start"
148
+ },
149
+ inputContainer: {
150
+ gap: 6
151
+ },
152
+ label: {
153
+ fontWeight: "500"
154
+ },
155
+ inputWrapper: {
156
+ flexDirection: "row",
157
+ alignItems: "center"
158
+ },
159
+ underlinedWrapper: {
160
+ borderBottomLeftRadius: 0,
161
+ borderBottomRightRadius: 0,
162
+ paddingHorizontal: 0
163
+ },
164
+ slot: {
165
+ justifyContent: "center",
166
+ alignItems: "center"
167
+ },
168
+ input: {
169
+ flex: 1,
170
+ padding: 0,
171
+ includeFontPadding: false
172
+ },
173
+ clearButton: {
174
+ justifyContent: "center",
175
+ alignItems: "center",
176
+ marginLeft: 6
177
+ },
178
+ helperText: {
179
+ fontWeight: "400"
180
+ },
181
+ disabled: {
182
+ opacity: 0.6
183
+ }
184
+ });
185
+
186
+ // src/components/input/input.tsx
187
+ var TextInput = forwardRef(
188
+ ({
189
+ value,
190
+ defaultValue,
191
+ onValueChange,
192
+ onChangeText,
193
+ onFocus,
194
+ onBlur,
195
+ label,
196
+ labelPlacement = "outside",
197
+ description,
198
+ errorMessage,
199
+ startContent,
200
+ endContent,
201
+ themeColor = "primary",
202
+ variant = "flat",
203
+ size = "md",
204
+ radius = "md",
205
+ isSecured = false,
206
+ isClearable = false,
207
+ isDisabled = false,
208
+ isReadOnly = false,
209
+ isInvalid = false,
210
+ fullWidth = true,
211
+ customAppearance,
212
+ ...nativeProps
213
+ }, forwardedRef) => {
214
+ const isControlled = typeof value === "string";
215
+ const [internalValue, setInternalValue] = useState(defaultValue ?? "");
216
+ const [isFocused, setIsFocused] = useState(false);
217
+ const internalRef = useRef(null);
218
+ const borderAnimation = useRef(new Animated.Value(0)).current;
219
+ const inputValue = isControlled ? value ?? "" : internalValue;
220
+ const secureTextEntry = nativeProps.secureTextEntry ?? isSecured;
221
+ const sizeStyles = useTextInputSizeStyles(size);
222
+ const radiusStyles = useTextInputRadiusStyles(radius);
223
+ const variantStyles = useTextInputVariantStyles({
224
+ themeColor,
225
+ variant,
226
+ isFocused,
227
+ isInvalid,
228
+ isDisabled
229
+ });
230
+ useEffect(() => {
231
+ Animated.timing(borderAnimation, {
232
+ toValue: isFocused || isInvalid ? 1 : 0,
233
+ duration: 200,
234
+ useNativeDriver: false
235
+ }).start();
236
+ }, [borderAnimation, isFocused, isInvalid]);
237
+ const showClearButton = isClearable && !!inputValue && !isDisabled && !isReadOnly && !secureTextEntry && nativeProps.editable !== false;
238
+ const editable = nativeProps.editable ?? (!isDisabled && !isReadOnly);
239
+ const helperText = useMemo2(() => {
240
+ if (isInvalid && errorMessage) {
241
+ return errorMessage;
242
+ }
243
+ return description;
244
+ }, [description, errorMessage, isInvalid]);
245
+ const handleChangeText = (text) => {
246
+ if (!isControlled) {
247
+ setInternalValue(text);
248
+ }
249
+ onValueChange?.(text);
250
+ onChangeText?.(text);
251
+ };
252
+ const handleClear = () => {
253
+ if (!isControlled) {
254
+ setInternalValue("");
255
+ }
256
+ onValueChange?.("");
257
+ onChangeText?.("");
258
+ };
259
+ const handleWrapperPress = () => {
260
+ if (isDisabled || isReadOnly || nativeProps.editable === false) return;
261
+ const inputRef = forwardedRef || internalRef;
262
+ if (typeof forwardedRef === "function") {
263
+ internalRef.current?.focus();
264
+ } else if (inputRef?.current) {
265
+ inputRef.current.focus();
266
+ }
267
+ };
268
+ return /* @__PURE__ */ React.createElement(
269
+ View,
270
+ {
271
+ style: [
272
+ styles.container,
273
+ !fullWidth && styles.noFullWidth,
274
+ isDisabled && styles.disabled,
275
+ customAppearance?.container
276
+ ]
277
+ },
278
+ /* @__PURE__ */ React.createElement(View, { style: [styles.inputContainer, customAppearance?.inputContainer] }, label && labelPlacement === "outside" && /* @__PURE__ */ React.createElement(
279
+ Text,
280
+ {
281
+ style: [
282
+ styles.label,
283
+ {
284
+ color: variantStyles.labelColor,
285
+ fontSize: sizeStyles.labelSize
286
+ },
287
+ customAppearance?.label
288
+ ]
289
+ },
290
+ label
291
+ ), /* @__PURE__ */ React.createElement(Pressable, { onPress: handleWrapperPress, disabled: isDisabled || isReadOnly }, /* @__PURE__ */ React.createElement(
292
+ Animated.View,
293
+ {
294
+ style: [
295
+ styles.inputWrapper,
296
+ {
297
+ minHeight: sizeStyles.minHeight,
298
+ paddingVertical: sizeStyles.paddingVertical,
299
+ paddingHorizontal: sizeStyles.paddingHorizontal,
300
+ gap: sizeStyles.slotGap,
301
+ backgroundColor: variantStyles.container.backgroundColor,
302
+ borderWidth: variant === "underlined" ? 0 : variantStyles.container.borderWidth,
303
+ ...variant === "underlined" && {
304
+ borderBottomWidth: variantStyles.container.borderBottomWidth
305
+ },
306
+ borderRadius: variant === "underlined" ? 0 : radiusStyles.borderRadius,
307
+ ...variant === "underlined" ? {
308
+ borderBottomColor: borderAnimation.interpolate({
309
+ inputRange: [0, 1],
310
+ outputRange: [
311
+ variantStyles.unfocusedBorderColor,
312
+ variantStyles.focusedBorderColor
313
+ ]
314
+ })
315
+ } : {
316
+ borderColor: borderAnimation.interpolate({
317
+ inputRange: [0, 1],
318
+ outputRange: [
319
+ variantStyles.unfocusedBorderColor,
320
+ variantStyles.focusedBorderColor
321
+ ]
322
+ })
323
+ }
324
+ },
325
+ variant === "underlined" && styles.underlinedWrapper,
326
+ customAppearance?.inputWrapper
327
+ ]
328
+ },
329
+ startContent && /* @__PURE__ */ React.createElement(View, { style: styles.slot }, startContent),
330
+ /* @__PURE__ */ React.createElement(View, { style: { flex: 1, gap: labelPlacement === "inside" && label ? 2 : 0 } }, label && labelPlacement === "inside" && /* @__PURE__ */ React.createElement(
331
+ Text,
332
+ {
333
+ style: [
334
+ styles.label,
335
+ {
336
+ color: variantStyles.labelColor,
337
+ fontSize: sizeStyles.helperSize,
338
+ paddingBottom: 2
339
+ },
340
+ customAppearance?.label
341
+ ]
342
+ },
343
+ label
344
+ ), /* @__PURE__ */ React.createElement(
345
+ RNTextInput,
346
+ {
347
+ ref: typeof forwardedRef === "function" ? internalRef : forwardedRef || internalRef,
348
+ value: inputValue,
349
+ onChangeText: handleChangeText,
350
+ onFocus: (event) => {
351
+ setIsFocused(true);
352
+ onFocus?.(event);
353
+ },
354
+ onBlur: (event) => {
355
+ setIsFocused(false);
356
+ onBlur?.(event);
357
+ },
358
+ editable,
359
+ secureTextEntry,
360
+ placeholderTextColor: variantStyles.placeholderColor,
361
+ style: [
362
+ styles.input,
363
+ {
364
+ color: variantStyles.textColor,
365
+ fontSize: sizeStyles.fontSize
366
+ },
367
+ customAppearance?.input
368
+ ],
369
+ ...nativeProps
370
+ }
371
+ )),
372
+ showClearButton ? /* @__PURE__ */ React.createElement(
373
+ Pressable,
374
+ {
375
+ onPress: handleClear,
376
+ accessibilityLabel: "Clear input",
377
+ accessibilityRole: "button",
378
+ style: styles.clearButton
379
+ },
380
+ /* @__PURE__ */ React.createElement(CloseIcon, { size: 16, color: variantStyles.placeholderColor })
381
+ ) : endContent && /* @__PURE__ */ React.createElement(View, { style: styles.slot }, endContent)
382
+ ))),
383
+ helperText && /* @__PURE__ */ React.createElement(
384
+ Text,
385
+ {
386
+ style: [
387
+ styles.helperText,
388
+ {
389
+ color: variantStyles.helperColor,
390
+ fontSize: sizeStyles.helperSize
391
+ },
392
+ customAppearance?.helperText
393
+ ]
394
+ },
395
+ helperText
396
+ )
397
+ );
398
+ }
399
+ );
400
+ TextInput.displayName = "TextInput";
401
+
402
+ // src/components/input/date-time-input.tsx
403
+ import React2, { forwardRef as forwardRef2, useCallback, useMemo as useMemo3 } from "react";
404
+
405
+ // src/components/input/date-time-input.hook.ts
406
+ var YMD_LOCALES = ["ja", "zh", "ko", "hu", "lt", "mn"];
407
+ var MDY_LOCALES = ["en-US", "en-PH", "en-BZ"];
408
+ var getDateOrder = (locale) => {
409
+ try {
410
+ const formatter = new Intl.DateTimeFormat(locale, {
411
+ year: "numeric",
412
+ month: "2-digit",
413
+ day: "2-digit"
414
+ });
415
+ const parts = formatter.formatToParts(new Date(2e3, 0, 2));
416
+ const order = parts.filter((p) => p.type === "year" || p.type === "month" || p.type === "day").map((p) => p.type);
417
+ if (order[0] === "year") return "YMD";
418
+ if (order[0] === "month") return "MDY";
419
+ return "DMY";
420
+ } catch {
421
+ const lang = locale.split("-")[0];
422
+ if (YMD_LOCALES.includes(locale) || YMD_LOCALES.includes(lang)) return "YMD";
423
+ if (MDY_LOCALES.includes(locale)) return "MDY";
424
+ return "DMY";
425
+ }
426
+ };
427
+ var insertAtPositions = (digits, positions, char) => {
428
+ let result = "";
429
+ let digitIndex = 0;
430
+ for (let i = 0; i < positions.length; i++) {
431
+ const segLen = positions[i];
432
+ const segment = digits.slice(digitIndex, digitIndex + segLen);
433
+ if (!segment) break;
434
+ if (i > 0) result += char;
435
+ result += segment;
436
+ digitIndex += segLen;
437
+ }
438
+ return result;
439
+ };
440
+ var formatDateInput = (text, dateOrder, separator) => {
441
+ const digits = text.replace(/\D/g, "").slice(0, 8);
442
+ const segments = dateOrder === "YMD" ? [4, 2, 2] : [2, 2, 4];
443
+ return insertAtPositions(digits, segments, separator);
444
+ };
445
+ var formatTimeInput = (text, granularity, hourCycle) => {
446
+ const cleaned = text.replace(/[^0-9aApPmM ]/g, "");
447
+ const digits = cleaned.replace(/\D/g, "");
448
+ const maxDigits = granularity === "second" ? 6 : 4;
449
+ const trimmed = digits.slice(0, maxDigits);
450
+ const segments = granularity === "second" ? [2, 2, 2] : [2, 2];
451
+ let result = insertAtPositions(trimmed, segments, ":");
452
+ if (hourCycle === 12 && trimmed.length >= 4) {
453
+ const ampmMatch = cleaned.match(/[aApP][mM]?/i);
454
+ if (ampmMatch) {
455
+ const period = ampmMatch[0].toUpperCase().startsWith("P") ? "PM" : "AM";
456
+ result += " " + period;
457
+ }
458
+ }
459
+ return result;
460
+ };
461
+ var formatDateTimeInput = (text, dateOrder, separator, granularity, hourCycle) => {
462
+ const digits = text.replace(/[^0-9aApPmM]/g, "").replace(/[aApPmM]/g, "");
463
+ const dateDigitCount = 8;
464
+ const timeDigitCount = granularity === "second" ? 6 : 4;
465
+ const maxDigits = dateDigitCount + timeDigitCount;
466
+ const trimmed = digits.replace(/\D/g, "").slice(0, maxDigits);
467
+ const datePart = trimmed.slice(0, dateDigitCount);
468
+ const timePart = trimmed.slice(dateDigitCount);
469
+ let result = formatDateInput(datePart, dateOrder, separator);
470
+ if (timePart) {
471
+ const timeSegments = granularity === "second" ? [2, 2, 2] : [2, 2];
472
+ result += " " + insertAtPositions(timePart, timeSegments, ":");
473
+ if (hourCycle === 12 && timePart.length >= 4) {
474
+ const ampmMatch = text.match(/[aApP][mM]?/i);
475
+ if (ampmMatch) {
476
+ const period = ampmMatch[0].toUpperCase().startsWith("P") ? "PM" : "AM";
477
+ result += " " + period;
478
+ }
479
+ }
480
+ }
481
+ return result;
482
+ };
483
+ var getDatePlaceholder = (dateOrder, separator) => {
484
+ const parts = {
485
+ YMD: ["YYYY", "MM", "DD"],
486
+ DMY: ["DD", "MM", "YYYY"],
487
+ MDY: ["MM", "DD", "YYYY"]
488
+ };
489
+ return parts[dateOrder].join(separator);
490
+ };
491
+ var getTimePlaceholder = (granularity, hourCycle) => {
492
+ const hour = hourCycle === 12 ? "hh" : "HH";
493
+ const base = `${hour}:mm`;
494
+ const withSeconds = granularity === "second" ? `${base}:ss` : base;
495
+ return hourCycle === 12 ? `${withSeconds} AM` : withSeconds;
496
+ };
497
+ var getDateTimePlaceholder = (dateOrder, separator, granularity, hourCycle) => {
498
+ return `${getDatePlaceholder(dateOrder, separator)} ${getTimePlaceholder(granularity, hourCycle)}`;
499
+ };
500
+ var getDateMaxLength = (separator) => {
501
+ return 8 + 2 * separator.length;
502
+ };
503
+ var getTimeMaxLength = (granularity, hourCycle) => {
504
+ const base = granularity === "second" ? 8 : 5;
505
+ return hourCycle === 12 ? base + 3 : base;
506
+ };
507
+ var getDateTimeMaxLength = (separator, granularity, hourCycle) => {
508
+ return getDateMaxLength(separator) + 1 + getTimeMaxLength(granularity, hourCycle);
509
+ };
510
+
511
+ // src/components/input/date-time-input.tsx
512
+ var DateInput = forwardRef2(
513
+ ({
514
+ separator = "-",
515
+ dateOrder,
516
+ locale = "en-US",
517
+ placeholder,
518
+ maxLength,
519
+ onChangeText,
520
+ onValueChange,
521
+ ...props
522
+ }, ref) => {
523
+ const resolvedOrder = useMemo3(
524
+ () => dateOrder ?? getDateOrder(locale),
525
+ [dateOrder, locale]
526
+ );
527
+ const handleChangeText = useCallback(
528
+ (text) => {
529
+ const formatted = formatDateInput(text, resolvedOrder, separator);
530
+ onChangeText?.(formatted);
531
+ onValueChange?.(formatted);
532
+ },
533
+ [resolvedOrder, separator, onChangeText, onValueChange]
534
+ );
535
+ return /* @__PURE__ */ React2.createElement(
536
+ TextInput,
537
+ {
538
+ ref,
539
+ placeholder: placeholder ?? getDatePlaceholder(resolvedOrder, separator),
540
+ keyboardType: "number-pad",
541
+ autoCapitalize: "none",
542
+ autoCorrect: false,
543
+ maxLength: maxLength ?? getDateMaxLength(separator),
544
+ onChangeText: handleChangeText,
545
+ ...props
546
+ }
547
+ );
548
+ }
549
+ );
550
+ var TimeInput = forwardRef2(
551
+ ({
552
+ granularity = "minute",
553
+ hourCycle = 24,
554
+ placeholder,
555
+ maxLength,
556
+ onChangeText,
557
+ onValueChange,
558
+ ...props
559
+ }, ref) => {
560
+ const handleChangeText = useCallback(
561
+ (text) => {
562
+ const formatted = formatTimeInput(text, granularity, hourCycle);
563
+ onChangeText?.(formatted);
564
+ onValueChange?.(formatted);
565
+ },
566
+ [granularity, hourCycle, onChangeText, onValueChange]
567
+ );
568
+ return /* @__PURE__ */ React2.createElement(
569
+ TextInput,
570
+ {
571
+ ref,
572
+ placeholder: placeholder ?? getTimePlaceholder(granularity, hourCycle),
573
+ keyboardType: "number-pad",
574
+ autoCapitalize: "none",
575
+ autoCorrect: false,
576
+ maxLength: maxLength ?? getTimeMaxLength(granularity, hourCycle),
577
+ onChangeText: handleChangeText,
578
+ ...props
579
+ }
580
+ );
581
+ }
582
+ );
583
+ var DateTimeInput = forwardRef2(
584
+ ({
585
+ separator = "-",
586
+ dateOrder,
587
+ locale = "en-US",
588
+ granularity = "minute",
589
+ hourCycle = 24,
590
+ placeholder,
591
+ maxLength,
592
+ onChangeText,
593
+ onValueChange,
594
+ ...props
595
+ }, ref) => {
596
+ const resolvedOrder = useMemo3(
597
+ () => dateOrder ?? getDateOrder(locale),
598
+ [dateOrder, locale]
599
+ );
600
+ const handleChangeText = useCallback(
601
+ (text) => {
602
+ const formatted = formatDateTimeInput(
603
+ text,
604
+ resolvedOrder,
605
+ separator,
606
+ granularity,
607
+ hourCycle
608
+ );
609
+ onChangeText?.(formatted);
610
+ onValueChange?.(formatted);
611
+ },
612
+ [resolvedOrder, separator, granularity, hourCycle, onChangeText, onValueChange]
613
+ );
614
+ return /* @__PURE__ */ React2.createElement(
615
+ TextInput,
616
+ {
617
+ ref,
618
+ placeholder: placeholder ?? getDateTimePlaceholder(resolvedOrder, separator, granularity, hourCycle),
619
+ keyboardType: "number-pad",
620
+ autoCapitalize: "none",
621
+ autoCorrect: false,
622
+ maxLength: maxLength ?? getDateTimeMaxLength(separator, granularity, hourCycle),
623
+ onChangeText: handleChangeText,
624
+ ...props
625
+ }
626
+ );
627
+ }
628
+ );
629
+ DateInput.displayName = "DateInput";
630
+ TimeInput.displayName = "TimeInput";
631
+ DateTimeInput.displayName = "DateTimeInput";
632
+
633
+ // src/components/input/otp-input.tsx
634
+ import React3, { useEffect as useEffect2, useRef as useRef3 } from "react";
635
+ import {
636
+ Animated as Animated2,
637
+ Pressable as Pressable2,
638
+ Text as Text2,
639
+ TextInput as RNTextInput2,
640
+ View as View2
641
+ } from "react-native";
642
+
643
+ // src/components/input/otp-input.hook.ts
644
+ import { useCallback as useCallback2, useMemo as useMemo4, useRef as useRef2, useState as useState2 } from "react";
645
+ var segmentSizeMap = {
646
+ sm: { width: 40, height: 40, fontSize: 16 },
647
+ md: { width: 48, height: 48, fontSize: 20 },
648
+ lg: { width: 56, height: 56, fontSize: 24 }
649
+ };
650
+ var useOTPSegmentSizeStyles = (size) => {
651
+ return useMemo4(() => segmentSizeMap[size], [size]);
652
+ };
653
+ var useOTPInputState = ({
654
+ length,
655
+ value,
656
+ defaultValue,
657
+ onValueChange,
658
+ onComplete,
659
+ allowedKeys
660
+ }) => {
661
+ const isControlled = typeof value === "string";
662
+ const [internalValue, setInternalValue] = useState2(
663
+ defaultValue ?? ""
664
+ );
665
+ const [activeIndex, setActiveIndex] = useState2(-1);
666
+ const refs = useRef2([]);
667
+ const currentValue = isControlled ? value : internalValue;
668
+ const segments = useMemo4(() => {
669
+ const chars = currentValue.split("");
670
+ return Array.from({ length }, (_, i) => chars[i] ?? "");
671
+ }, [currentValue, length]);
672
+ const updateValue = useCallback2(
673
+ (newValue) => {
674
+ if (!isControlled) {
675
+ setInternalValue(newValue);
676
+ }
677
+ onValueChange?.(newValue);
678
+ if (newValue.length === length) {
679
+ onComplete?.(newValue);
680
+ }
681
+ },
682
+ [isControlled, length, onValueChange, onComplete]
683
+ );
684
+ const handleSegmentChange = useCallback2(
685
+ (index, text) => {
686
+ if (!text) return;
687
+ const char = text.slice(-1);
688
+ if (!allowedKeys.test(char)) return;
689
+ const chars = currentValue.split("");
690
+ while (chars.length < length) chars.push("");
691
+ chars[index] = char;
692
+ const newValue = chars.join("").replace(/\s+$/, "");
693
+ updateValue(newValue);
694
+ if (index < length - 1) {
695
+ refs.current[index + 1]?.focus();
696
+ }
697
+ },
698
+ [allowedKeys, currentValue, length, updateValue]
699
+ );
700
+ const handleSegmentKeyPress = useCallback2(
701
+ (index, key) => {
702
+ if (key !== "Backspace") return;
703
+ const chars = currentValue.split("");
704
+ while (chars.length < length) chars.push("");
705
+ if (chars[index]) {
706
+ chars[index] = "";
707
+ updateValue(chars.join("").replace(/\s+$/, ""));
708
+ return;
709
+ }
710
+ if (index > 0) {
711
+ chars[index - 1] = "";
712
+ updateValue(chars.join("").replace(/\s+$/, ""));
713
+ refs.current[index - 1]?.focus();
714
+ }
715
+ },
716
+ [currentValue, length, updateValue]
717
+ );
718
+ const handleSegmentFocus = useCallback2((index) => {
719
+ setActiveIndex(index);
720
+ }, []);
721
+ const handleSegmentBlur = useCallback2(() => {
722
+ setActiveIndex(-1);
723
+ }, []);
724
+ return {
725
+ segments,
726
+ activeIndex,
727
+ refs,
728
+ handleSegmentChange,
729
+ handleSegmentKeyPress,
730
+ handleSegmentFocus,
731
+ handleSegmentBlur
732
+ };
733
+ };
734
+
735
+ // src/components/input/otp-input.style.ts
736
+ import { StyleSheet as StyleSheet2 } from "react-native";
737
+ var otpStyles = StyleSheet2.create({
738
+ container: {
739
+ gap: 6
740
+ },
741
+ fullWidth: {
742
+ width: "100%"
743
+ },
744
+ segmentContainer: {
745
+ flexDirection: "row",
746
+ gap: 8
747
+ },
748
+ segment: {
749
+ justifyContent: "center",
750
+ alignItems: "center",
751
+ overflow: "hidden"
752
+ },
753
+ segmentText: {
754
+ fontWeight: "600",
755
+ textAlign: "center"
756
+ },
757
+ securedDot: {
758
+ width: 10,
759
+ height: 10,
760
+ borderRadius: 5
761
+ },
762
+ hiddenInput: {
763
+ position: "absolute",
764
+ width: 1,
765
+ height: 1,
766
+ opacity: 0
767
+ },
768
+ label: {
769
+ fontWeight: "500"
770
+ },
771
+ helperText: {
772
+ fontWeight: "400"
773
+ },
774
+ disabled: {
775
+ opacity: 0.6
776
+ }
777
+ });
778
+
779
+ // src/components/input/otp-input.tsx
780
+ var OTPSegment = ({
781
+ char,
782
+ isActive,
783
+ isSecured,
784
+ isDisabled,
785
+ variantStyles,
786
+ sizeStyles,
787
+ radiusStyles,
788
+ customSegment,
789
+ customSegmentText,
790
+ inputRef,
791
+ onChangeText,
792
+ onKeyPress,
793
+ onFocus,
794
+ onBlur
795
+ }) => {
796
+ const borderAnimation = useRef3(new Animated2.Value(0)).current;
797
+ useEffect2(() => {
798
+ Animated2.timing(borderAnimation, {
799
+ toValue: isActive ? 1 : 0,
800
+ duration: 200,
801
+ useNativeDriver: false
802
+ }).start();
803
+ }, [borderAnimation, isActive]);
804
+ return /* @__PURE__ */ React3.createElement(
805
+ Pressable2,
806
+ {
807
+ onPress: () => {
808
+ if (!isDisabled) {
809
+ const inputEl = inputRef;
810
+ if (typeof inputEl !== "function") return;
811
+ }
812
+ },
813
+ disabled: isDisabled
814
+ },
815
+ /* @__PURE__ */ React3.createElement(
816
+ Animated2.View,
817
+ {
818
+ style: [
819
+ otpStyles.segment,
820
+ {
821
+ width: sizeStyles.width,
822
+ height: sizeStyles.height,
823
+ backgroundColor: variantStyles.container.backgroundColor,
824
+ borderWidth: variantStyles.container.borderWidth ?? 0,
825
+ borderRadius: radiusStyles.borderRadius,
826
+ borderColor: borderAnimation.interpolate({
827
+ inputRange: [0, 1],
828
+ outputRange: [
829
+ variantStyles.unfocusedBorderColor,
830
+ variantStyles.focusedBorderColor
831
+ ]
832
+ })
833
+ },
834
+ customSegment?.segment
835
+ ]
836
+ },
837
+ char && isSecured ? /* @__PURE__ */ React3.createElement(
838
+ View2,
839
+ {
840
+ style: [
841
+ otpStyles.securedDot,
842
+ { backgroundColor: variantStyles.textColor }
843
+ ]
844
+ }
845
+ ) : /* @__PURE__ */ React3.createElement(
846
+ Text2,
847
+ {
848
+ style: [
849
+ otpStyles.segmentText,
850
+ {
851
+ color: variantStyles.textColor,
852
+ fontSize: sizeStyles.fontSize
853
+ },
854
+ customSegmentText?.segmentText
855
+ ]
856
+ },
857
+ char
858
+ ),
859
+ /* @__PURE__ */ React3.createElement(
860
+ RNTextInput2,
861
+ {
862
+ ref: inputRef,
863
+ style: otpStyles.hiddenInput,
864
+ value: char,
865
+ onChangeText,
866
+ onKeyPress: (e) => onKeyPress(e.nativeEvent.key),
867
+ onFocus,
868
+ onBlur,
869
+ maxLength: 2,
870
+ keyboardType: "number-pad",
871
+ editable: !isDisabled,
872
+ caretHidden: true
873
+ }
874
+ )
875
+ )
876
+ );
877
+ };
878
+ var OTPInput = ({
879
+ length = 4,
880
+ value,
881
+ defaultValue,
882
+ onValueChange,
883
+ onComplete,
884
+ variant = "flat",
885
+ size = "md",
886
+ radius = "md",
887
+ themeColor = "primary",
888
+ isDisabled = false,
889
+ isInvalid = false,
890
+ isSecured = false,
891
+ errorMessage,
892
+ description,
893
+ label,
894
+ allowedKeys = /^[0-9]$/,
895
+ customAppearance,
896
+ fullWidth = false
897
+ }) => {
898
+ const sizeStyles = useOTPSegmentSizeStyles(size);
899
+ const radiusStyles = useTextInputRadiusStyles(radius);
900
+ const variantStyles = useTextInputVariantStyles({
901
+ themeColor,
902
+ variant,
903
+ isFocused: false,
904
+ isInvalid,
905
+ isDisabled
906
+ });
907
+ const {
908
+ segments,
909
+ activeIndex,
910
+ refs,
911
+ handleSegmentChange,
912
+ handleSegmentKeyPress,
913
+ handleSegmentFocus,
914
+ handleSegmentBlur
915
+ } = useOTPInputState({
916
+ length,
917
+ value,
918
+ defaultValue,
919
+ onValueChange,
920
+ onComplete,
921
+ allowedKeys
922
+ });
923
+ const helperText = isInvalid && errorMessage ? errorMessage : description;
924
+ const helperColor = isInvalid ? variantStyles.helperColor : variantStyles.helperColor;
925
+ const activeVariantStyles = useTextInputVariantStyles({
926
+ themeColor,
927
+ variant,
928
+ isFocused: true,
929
+ isInvalid,
930
+ isDisabled
931
+ });
932
+ return /* @__PURE__ */ React3.createElement(
933
+ View2,
934
+ {
935
+ style: [
936
+ otpStyles.container,
937
+ fullWidth && otpStyles.fullWidth,
938
+ isDisabled && otpStyles.disabled,
939
+ customAppearance?.container
940
+ ]
941
+ },
942
+ label && /* @__PURE__ */ React3.createElement(
943
+ Text2,
944
+ {
945
+ style: [
946
+ otpStyles.label,
947
+ {
948
+ color: variantStyles.labelColor,
949
+ fontSize: 14
950
+ },
951
+ customAppearance?.label
952
+ ]
953
+ },
954
+ label
955
+ ),
956
+ /* @__PURE__ */ React3.createElement(
957
+ View2,
958
+ {
959
+ style: [otpStyles.segmentContainer, customAppearance?.segmentContainer]
960
+ },
961
+ segments.map((char, index) => /* @__PURE__ */ React3.createElement(
962
+ OTPSegment,
963
+ {
964
+ key: index,
965
+ char,
966
+ isActive: activeIndex === index,
967
+ isSecured,
968
+ isDisabled,
969
+ variantStyles: activeIndex === index ? activeVariantStyles : variantStyles,
970
+ sizeStyles,
971
+ radiusStyles,
972
+ customSegment: customAppearance,
973
+ customSegmentText: customAppearance,
974
+ inputRef: (ref) => {
975
+ refs.current[index] = ref;
976
+ },
977
+ onChangeText: (text) => handleSegmentChange(index, text),
978
+ onKeyPress: (key) => handleSegmentKeyPress(index, key),
979
+ onFocus: () => handleSegmentFocus(index),
980
+ onBlur: handleSegmentBlur
981
+ }
982
+ ))
983
+ ),
984
+ helperText && /* @__PURE__ */ React3.createElement(
985
+ Text2,
986
+ {
987
+ style: [
988
+ otpStyles.helperText,
989
+ { color: helperColor, fontSize: 13 },
990
+ customAppearance?.helperText
991
+ ]
992
+ },
993
+ helperText
994
+ )
995
+ );
996
+ };
997
+ OTPInput.displayName = "OTPInput";
998
+
999
+ // src/components/input/number-input.tsx
1000
+ import React4, { forwardRef as forwardRef3 } from "react";
1001
+ import { Pressable as Pressable3, View as View3 } from "react-native";
1002
+ import { AddIcon, RemoveIcon } from "@xaui/icons";
1003
+ import { withOpacity as withOpacity2 } from "@xaui/core";
1004
+
1005
+ // src/components/input/number-input.hook.ts
1006
+ import { useCallback as useCallback3, useMemo as useMemo5, useState as useState3 } from "react";
1007
+ var useNumberInputState = ({
1008
+ value,
1009
+ defaultValue,
1010
+ onValueChange,
1011
+ minValue,
1012
+ maxValue,
1013
+ step,
1014
+ formatOptions,
1015
+ locale
1016
+ }) => {
1017
+ const isControlled = typeof value === "number";
1018
+ const [internalValue, setInternalValue] = useState3(
1019
+ defaultValue
1020
+ );
1021
+ const [isEditing, setIsEditing] = useState3(false);
1022
+ const [rawText, setRawText] = useState3("");
1023
+ const currentValue = isControlled ? value : internalValue;
1024
+ const formatter = useMemo5(() => {
1025
+ if (!formatOptions) return null;
1026
+ return new Intl.NumberFormat(locale, formatOptions);
1027
+ }, [formatOptions, locale]);
1028
+ const displayValue = useMemo5(() => {
1029
+ if (isEditing) return rawText;
1030
+ if (currentValue === void 0) return "";
1031
+ if (formatter) return formatter.format(currentValue);
1032
+ return String(currentValue);
1033
+ }, [currentValue, formatter, isEditing, rawText]);
1034
+ const updateValue = useCallback3(
1035
+ (newValue) => {
1036
+ if (!isControlled) {
1037
+ setInternalValue(newValue);
1038
+ }
1039
+ onValueChange?.(newValue);
1040
+ },
1041
+ [isControlled, onValueChange]
1042
+ );
1043
+ const clamp = useCallback3(
1044
+ (num) => {
1045
+ let clamped = num;
1046
+ if (minValue !== void 0 && clamped < minValue) clamped = minValue;
1047
+ if (maxValue !== void 0 && clamped > maxValue) clamped = maxValue;
1048
+ return clamped;
1049
+ },
1050
+ [minValue, maxValue]
1051
+ );
1052
+ const handleTextChange = useCallback3(
1053
+ (text) => {
1054
+ setRawText(text);
1055
+ if (text === "" || text === "-") return;
1056
+ const parsed = parseFloat(text);
1057
+ if (isNaN(parsed)) return;
1058
+ updateValue(parsed);
1059
+ },
1060
+ [updateValue]
1061
+ );
1062
+ const handleFocus = useCallback3(() => {
1063
+ setIsEditing(true);
1064
+ setRawText(currentValue !== void 0 ? String(currentValue) : "");
1065
+ }, [currentValue]);
1066
+ const handleBlur = useCallback3(() => {
1067
+ setIsEditing(false);
1068
+ if (rawText === "" || rawText === "-") {
1069
+ updateValue(void 0);
1070
+ return;
1071
+ }
1072
+ const parsed = parseFloat(rawText);
1073
+ if (isNaN(parsed)) {
1074
+ updateValue(void 0);
1075
+ return;
1076
+ }
1077
+ updateValue(clamp(parsed));
1078
+ }, [rawText, clamp, updateValue]);
1079
+ const handleClear = useCallback3(() => {
1080
+ setRawText("");
1081
+ updateValue(void 0);
1082
+ }, [updateValue]);
1083
+ const canIncrement = maxValue === void 0 || (currentValue ?? 0) + step <= maxValue;
1084
+ const canDecrement = minValue === void 0 || (currentValue ?? 0) - step >= minValue;
1085
+ const handleIncrement = useCallback3(() => {
1086
+ const base = currentValue ?? 0;
1087
+ const newValue = clamp(base + step);
1088
+ updateValue(newValue);
1089
+ }, [currentValue, step, clamp, updateValue]);
1090
+ const handleDecrement = useCallback3(() => {
1091
+ const base = currentValue ?? 0;
1092
+ const newValue = clamp(base - step);
1093
+ updateValue(newValue);
1094
+ }, [currentValue, step, clamp, updateValue]);
1095
+ return {
1096
+ displayValue,
1097
+ handleTextChange,
1098
+ handleFocus,
1099
+ handleBlur,
1100
+ handleClear,
1101
+ handleIncrement,
1102
+ handleDecrement,
1103
+ canIncrement,
1104
+ canDecrement
1105
+ };
1106
+ };
1107
+
1108
+ // src/components/input/number-input.style.ts
1109
+ import { StyleSheet as StyleSheet3 } from "react-native";
1110
+ var numberInputStyles = StyleSheet3.create({
1111
+ stepperContainer: {
1112
+ flexDirection: "row",
1113
+ alignItems: "center",
1114
+ gap: 4
1115
+ },
1116
+ stepperButton: {
1117
+ justifyContent: "center",
1118
+ alignItems: "center",
1119
+ width: 28,
1120
+ height: 28,
1121
+ borderRadius: 6
1122
+ },
1123
+ stepperDisabled: {
1124
+ opacity: 0.4
1125
+ }
1126
+ });
1127
+
1128
+ // src/components/input/number-input.tsx
1129
+ var NumberInput = forwardRef3(
1130
+ ({
1131
+ value,
1132
+ defaultValue,
1133
+ onValueChange,
1134
+ minValue,
1135
+ maxValue,
1136
+ step = 1,
1137
+ hideStepper = false,
1138
+ formatOptions,
1139
+ locale = "en-US",
1140
+ label,
1141
+ labelPlacement = "outside",
1142
+ description,
1143
+ errorMessage,
1144
+ placeholder,
1145
+ themeColor = "primary",
1146
+ variant = "flat",
1147
+ size = "md",
1148
+ radius = "md",
1149
+ isDisabled = false,
1150
+ isReadOnly = false,
1151
+ isInvalid = false,
1152
+ isClearable = false,
1153
+ fullWidth = true,
1154
+ customAppearance
1155
+ }, ref) => {
1156
+ const theme = useXUITheme();
1157
+ const {
1158
+ displayValue,
1159
+ handleTextChange,
1160
+ handleFocus,
1161
+ handleBlur,
1162
+ handleIncrement,
1163
+ handleDecrement,
1164
+ canIncrement,
1165
+ canDecrement
1166
+ } = useNumberInputState({
1167
+ value,
1168
+ defaultValue,
1169
+ onValueChange,
1170
+ minValue,
1171
+ maxValue,
1172
+ step,
1173
+ formatOptions,
1174
+ locale
1175
+ });
1176
+ const iconColor = withOpacity2(theme.colors.foreground, 0.7);
1177
+ const stepperContent = hideStepper || isReadOnly ? void 0 : /* @__PURE__ */ React4.createElement(
1178
+ View3,
1179
+ {
1180
+ style: [
1181
+ numberInputStyles.stepperContainer,
1182
+ customAppearance?.stepperContainer
1183
+ ]
1184
+ },
1185
+ /* @__PURE__ */ React4.createElement(
1186
+ Pressable3,
1187
+ {
1188
+ onPress: handleDecrement,
1189
+ disabled: isDisabled || !canDecrement,
1190
+ accessibilityLabel: "Decrease value",
1191
+ accessibilityRole: "button",
1192
+ style: [
1193
+ numberInputStyles.stepperButton,
1194
+ (!canDecrement || isDisabled) && numberInputStyles.stepperDisabled,
1195
+ customAppearance?.stepperButton
1196
+ ]
1197
+ },
1198
+ /* @__PURE__ */ React4.createElement(RemoveIcon, { size: 16, color: iconColor })
1199
+ ),
1200
+ /* @__PURE__ */ React4.createElement(
1201
+ Pressable3,
1202
+ {
1203
+ onPress: handleIncrement,
1204
+ disabled: isDisabled || !canIncrement,
1205
+ accessibilityLabel: "Increase value",
1206
+ accessibilityRole: "button",
1207
+ style: [
1208
+ numberInputStyles.stepperButton,
1209
+ (!canIncrement || isDisabled) && numberInputStyles.stepperDisabled,
1210
+ customAppearance?.stepperButton
1211
+ ]
1212
+ },
1213
+ /* @__PURE__ */ React4.createElement(AddIcon, { size: 16, color: iconColor })
1214
+ )
1215
+ );
1216
+ return /* @__PURE__ */ React4.createElement(
1217
+ TextInput,
1218
+ {
1219
+ ref,
1220
+ value: displayValue,
1221
+ onChangeText: handleTextChange,
1222
+ onFocus: handleFocus,
1223
+ onBlur: handleBlur,
1224
+ label,
1225
+ labelPlacement,
1226
+ description,
1227
+ errorMessage,
1228
+ placeholder,
1229
+ themeColor,
1230
+ variant,
1231
+ size,
1232
+ radius,
1233
+ isDisabled,
1234
+ isReadOnly,
1235
+ isInvalid,
1236
+ isClearable,
1237
+ fullWidth,
1238
+ keyboardType: "numeric",
1239
+ endContent: stepperContent,
1240
+ customAppearance
1241
+ }
1242
+ );
1243
+ }
1244
+ );
1245
+ NumberInput.displayName = "NumberInput";
1246
+ export {
1247
+ DateInput,
1248
+ DateTimeInput,
1249
+ NumberInput,
1250
+ OTPInput,
1251
+ TextInput,
1252
+ TimeInput,
1253
+ getDateOrder
1254
+ };