@xsolla/xui-input-pin 0.64.0-pr56.1768440195

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,704 @@
1
+ // src/InputPin.tsx
2
+ import { useState, useRef, useEffect, useCallback } from "react";
3
+
4
+ // ../primitives-native/src/Box.tsx
5
+ import {
6
+ View,
7
+ Pressable,
8
+ Image
9
+ } from "react-native";
10
+ import { jsx } from "react/jsx-runtime";
11
+ var Box = ({
12
+ children,
13
+ onPress,
14
+ onLayout,
15
+ onMoveShouldSetResponder,
16
+ onResponderGrant,
17
+ onResponderMove,
18
+ onResponderRelease,
19
+ onResponderTerminate,
20
+ backgroundColor,
21
+ borderColor,
22
+ borderWidth,
23
+ borderBottomWidth,
24
+ borderBottomColor,
25
+ borderTopWidth,
26
+ borderTopColor,
27
+ borderLeftWidth,
28
+ borderLeftColor,
29
+ borderRightWidth,
30
+ borderRightColor,
31
+ borderRadius,
32
+ borderStyle,
33
+ height,
34
+ padding,
35
+ paddingHorizontal,
36
+ paddingVertical,
37
+ margin,
38
+ marginTop,
39
+ marginBottom,
40
+ marginLeft,
41
+ marginRight,
42
+ flexDirection,
43
+ alignItems,
44
+ justifyContent,
45
+ position,
46
+ top,
47
+ bottom,
48
+ left,
49
+ right,
50
+ width,
51
+ flex,
52
+ overflow,
53
+ zIndex,
54
+ hoverStyle,
55
+ pressStyle,
56
+ style,
57
+ "data-testid": dataTestId,
58
+ testID,
59
+ as,
60
+ src,
61
+ alt,
62
+ ...rest
63
+ }) => {
64
+ const getContainerStyle = (pressed) => ({
65
+ backgroundColor: pressed && pressStyle?.backgroundColor ? pressStyle.backgroundColor : backgroundColor,
66
+ borderColor,
67
+ borderWidth,
68
+ borderBottomWidth,
69
+ borderBottomColor,
70
+ borderTopWidth,
71
+ borderTopColor,
72
+ borderLeftWidth,
73
+ borderLeftColor,
74
+ borderRightWidth,
75
+ borderRightColor,
76
+ borderRadius,
77
+ borderStyle,
78
+ overflow,
79
+ zIndex,
80
+ height,
81
+ width,
82
+ padding,
83
+ paddingHorizontal,
84
+ paddingVertical,
85
+ margin,
86
+ marginTop,
87
+ marginBottom,
88
+ marginLeft,
89
+ marginRight,
90
+ flexDirection,
91
+ alignItems,
92
+ justifyContent,
93
+ position,
94
+ top,
95
+ bottom,
96
+ left,
97
+ right,
98
+ flex,
99
+ ...style
100
+ });
101
+ const finalTestID = dataTestId || testID;
102
+ const {
103
+ role,
104
+ tabIndex,
105
+ onKeyDown,
106
+ onKeyUp,
107
+ "aria-label": _ariaLabel,
108
+ "aria-labelledby": _ariaLabelledBy,
109
+ "aria-current": _ariaCurrent,
110
+ "aria-disabled": _ariaDisabled,
111
+ "aria-live": _ariaLive,
112
+ className,
113
+ "data-testid": _dataTestId,
114
+ ...nativeRest
115
+ } = rest;
116
+ if (as === "img" && src) {
117
+ const imageStyle = {
118
+ width,
119
+ height,
120
+ borderRadius,
121
+ position,
122
+ top,
123
+ bottom,
124
+ left,
125
+ right,
126
+ ...style
127
+ };
128
+ return /* @__PURE__ */ jsx(
129
+ Image,
130
+ {
131
+ source: { uri: src },
132
+ style: imageStyle,
133
+ testID: finalTestID,
134
+ resizeMode: "cover",
135
+ ...nativeRest
136
+ }
137
+ );
138
+ }
139
+ if (onPress) {
140
+ return /* @__PURE__ */ jsx(
141
+ Pressable,
142
+ {
143
+ onPress,
144
+ onLayout,
145
+ onMoveShouldSetResponder,
146
+ onResponderGrant,
147
+ onResponderMove,
148
+ onResponderRelease,
149
+ onResponderTerminate,
150
+ style: ({ pressed }) => getContainerStyle(pressed),
151
+ testID: finalTestID,
152
+ ...nativeRest,
153
+ children
154
+ }
155
+ );
156
+ }
157
+ return /* @__PURE__ */ jsx(
158
+ View,
159
+ {
160
+ style: getContainerStyle(),
161
+ testID: finalTestID,
162
+ onLayout,
163
+ onMoveShouldSetResponder,
164
+ onResponderGrant,
165
+ onResponderMove,
166
+ onResponderRelease,
167
+ onResponderTerminate,
168
+ ...nativeRest,
169
+ children
170
+ }
171
+ );
172
+ };
173
+
174
+ // ../primitives-native/src/Text.tsx
175
+ import { Text as RNText } from "react-native";
176
+ import { jsx as jsx2 } from "react/jsx-runtime";
177
+ var roleMap = {
178
+ alert: "alert",
179
+ heading: "header",
180
+ button: "button",
181
+ link: "link",
182
+ text: "text"
183
+ };
184
+ var Text = ({
185
+ children,
186
+ color,
187
+ fontSize,
188
+ fontWeight,
189
+ fontFamily,
190
+ id,
191
+ role,
192
+ ...props
193
+ }) => {
194
+ let resolvedFontFamily = fontFamily ? fontFamily.split(",")[0].replace(/['"]/g, "").trim() : void 0;
195
+ if (resolvedFontFamily === "Pilat Wide Bold") {
196
+ resolvedFontFamily = void 0;
197
+ }
198
+ const style = {
199
+ color,
200
+ fontSize: typeof fontSize === "number" ? fontSize : void 0,
201
+ fontWeight,
202
+ fontFamily: resolvedFontFamily,
203
+ textDecorationLine: props.textDecoration
204
+ };
205
+ const accessibilityRole = role ? roleMap[role] : void 0;
206
+ return /* @__PURE__ */ jsx2(RNText, { style, testID: id, accessibilityRole, children });
207
+ };
208
+
209
+ // ../primitives-native/src/Spinner.tsx
210
+ import { ActivityIndicator, View as View2 } from "react-native";
211
+ import { jsx as jsx3 } from "react/jsx-runtime";
212
+ var Spinner = ({
213
+ color,
214
+ size,
215
+ role,
216
+ "aria-label": ariaLabel,
217
+ "aria-live": ariaLive,
218
+ "aria-describedby": ariaDescribedBy,
219
+ testID
220
+ }) => {
221
+ return /* @__PURE__ */ jsx3(
222
+ View2,
223
+ {
224
+ accessible: true,
225
+ accessibilityRole: role === "status" ? "none" : void 0,
226
+ accessibilityLabel: ariaLabel,
227
+ accessibilityLiveRegion: ariaLive === "polite" ? "polite" : ariaLive === "assertive" ? "assertive" : "none",
228
+ testID,
229
+ children: /* @__PURE__ */ jsx3(
230
+ ActivityIndicator,
231
+ {
232
+ color,
233
+ size: typeof size === "number" ? size : "small"
234
+ }
235
+ )
236
+ }
237
+ );
238
+ };
239
+ Spinner.displayName = "Spinner";
240
+
241
+ // ../primitives-native/src/Icon.tsx
242
+ import React from "react";
243
+ import { View as View3 } from "react-native";
244
+ import { jsx as jsx4 } from "react/jsx-runtime";
245
+
246
+ // ../primitives-native/src/Divider.tsx
247
+ import { View as View4 } from "react-native";
248
+ import { jsx as jsx5 } from "react/jsx-runtime";
249
+
250
+ // ../primitives-native/src/Input.tsx
251
+ import { forwardRef } from "react";
252
+ import { TextInput as RNTextInput } from "react-native";
253
+ import { jsx as jsx6 } from "react/jsx-runtime";
254
+ var keyboardTypeMap = {
255
+ text: "default",
256
+ number: "numeric",
257
+ email: "email-address",
258
+ tel: "phone-pad",
259
+ url: "url",
260
+ decimal: "decimal-pad"
261
+ };
262
+ var inputModeToKeyboardType = {
263
+ none: "default",
264
+ text: "default",
265
+ decimal: "decimal-pad",
266
+ numeric: "number-pad",
267
+ tel: "phone-pad",
268
+ search: "default",
269
+ email: "email-address",
270
+ url: "url"
271
+ };
272
+ var autoCompleteToTextContentType = {
273
+ "one-time-code": "oneTimeCode",
274
+ email: "emailAddress",
275
+ username: "username",
276
+ password: "password",
277
+ "new-password": "newPassword",
278
+ tel: "telephoneNumber",
279
+ "postal-code": "postalCode",
280
+ name: "name"
281
+ };
282
+ var InputPrimitive = forwardRef(
283
+ ({
284
+ value,
285
+ placeholder,
286
+ onChange,
287
+ onChangeText,
288
+ onFocus,
289
+ onBlur,
290
+ onKeyDown,
291
+ disabled,
292
+ secureTextEntry,
293
+ style,
294
+ color,
295
+ fontSize,
296
+ placeholderTextColor,
297
+ maxLength,
298
+ name,
299
+ type,
300
+ inputMode,
301
+ autoComplete,
302
+ id,
303
+ "aria-invalid": ariaInvalid,
304
+ "aria-describedby": ariaDescribedBy,
305
+ "aria-labelledby": ariaLabelledBy,
306
+ "aria-label": ariaLabel,
307
+ "aria-disabled": ariaDisabled,
308
+ "data-testid": dataTestId
309
+ }, ref) => {
310
+ const handleChangeText = (text) => {
311
+ onChangeText?.(text);
312
+ if (onChange) {
313
+ const syntheticEvent = {
314
+ target: { value: text },
315
+ currentTarget: { value: text },
316
+ type: "change",
317
+ nativeEvent: { text },
318
+ preventDefault: () => {
319
+ },
320
+ stopPropagation: () => {
321
+ },
322
+ isTrusted: false
323
+ };
324
+ onChange(syntheticEvent);
325
+ }
326
+ };
327
+ const keyboardType = inputMode ? inputModeToKeyboardType[inputMode] || "default" : type ? keyboardTypeMap[type] || "default" : "default";
328
+ const textContentType = autoComplete ? autoCompleteToTextContentType[autoComplete] : void 0;
329
+ return /* @__PURE__ */ jsx6(
330
+ RNTextInput,
331
+ {
332
+ ref,
333
+ value,
334
+ placeholder,
335
+ onChangeText: handleChangeText,
336
+ onFocus,
337
+ onBlur,
338
+ onKeyPress: (e) => {
339
+ if (onKeyDown) {
340
+ onKeyDown({
341
+ key: e.nativeEvent.key,
342
+ preventDefault: () => {
343
+ }
344
+ });
345
+ }
346
+ },
347
+ editable: !disabled,
348
+ secureTextEntry: secureTextEntry || type === "password",
349
+ keyboardType,
350
+ textContentType,
351
+ style: [
352
+ {
353
+ color,
354
+ fontSize: typeof fontSize === "number" ? fontSize : void 0,
355
+ flex: 1,
356
+ padding: 0,
357
+ textAlign: style?.textAlign || "left"
358
+ },
359
+ style
360
+ ],
361
+ placeholderTextColor,
362
+ maxLength,
363
+ testID: dataTestId || id,
364
+ accessibilityLabel: ariaLabel,
365
+ accessibilityHint: ariaDescribedBy,
366
+ accessibilityState: {
367
+ disabled: disabled || ariaDisabled
368
+ },
369
+ accessible: true
370
+ }
371
+ );
372
+ }
373
+ );
374
+ InputPrimitive.displayName = "InputPrimitive";
375
+
376
+ // ../primitives-native/src/TextArea.tsx
377
+ import { forwardRef as forwardRef2 } from "react";
378
+ import { TextInput as RNTextInput2 } from "react-native";
379
+ import { jsx as jsx7 } from "react/jsx-runtime";
380
+ var TextAreaPrimitive = forwardRef2(
381
+ ({
382
+ value,
383
+ placeholder,
384
+ onChange,
385
+ onChangeText,
386
+ onFocus,
387
+ onBlur,
388
+ onKeyDown,
389
+ disabled,
390
+ style,
391
+ color,
392
+ fontSize,
393
+ placeholderTextColor,
394
+ maxLength,
395
+ rows,
396
+ id,
397
+ "aria-invalid": ariaInvalid,
398
+ "aria-describedby": ariaDescribedBy,
399
+ "aria-labelledby": ariaLabelledBy,
400
+ "aria-label": ariaLabel,
401
+ "aria-disabled": ariaDisabled,
402
+ "data-testid": dataTestId
403
+ }, ref) => {
404
+ const handleChangeText = (text) => {
405
+ onChangeText?.(text);
406
+ if (onChange) {
407
+ const syntheticEvent = {
408
+ target: { value: text },
409
+ currentTarget: { value: text },
410
+ type: "change",
411
+ nativeEvent: { text },
412
+ preventDefault: () => {
413
+ },
414
+ stopPropagation: () => {
415
+ },
416
+ isTrusted: false
417
+ };
418
+ onChange(syntheticEvent);
419
+ }
420
+ };
421
+ return /* @__PURE__ */ jsx7(
422
+ RNTextInput2,
423
+ {
424
+ ref,
425
+ value,
426
+ placeholder,
427
+ onChangeText: handleChangeText,
428
+ onFocus,
429
+ onBlur,
430
+ onKeyPress: (e) => {
431
+ if (onKeyDown) {
432
+ onKeyDown({
433
+ key: e.nativeEvent.key,
434
+ preventDefault: () => {
435
+ }
436
+ });
437
+ }
438
+ },
439
+ editable: !disabled,
440
+ multiline: true,
441
+ numberOfLines: rows || 4,
442
+ style: [
443
+ {
444
+ color,
445
+ fontSize: typeof fontSize === "number" ? fontSize : void 0,
446
+ flex: 1,
447
+ padding: 0,
448
+ textAlignVertical: "top",
449
+ textAlign: style?.textAlign || "left"
450
+ },
451
+ style
452
+ ],
453
+ placeholderTextColor,
454
+ maxLength,
455
+ testID: dataTestId || id,
456
+ accessibilityLabel: ariaLabel,
457
+ accessibilityHint: ariaDescribedBy,
458
+ accessibilityState: {
459
+ disabled: disabled || ariaDisabled
460
+ },
461
+ accessible: true
462
+ }
463
+ );
464
+ }
465
+ );
466
+ TextAreaPrimitive.displayName = "TextAreaPrimitive";
467
+
468
+ // src/InputPin.tsx
469
+ import { useDesignSystem, useId } from "@xsolla/xui-core";
470
+ import { jsx as jsx8, jsxs } from "react/jsx-runtime";
471
+ var InputPin = ({
472
+ value = "",
473
+ onChange,
474
+ onComplete,
475
+ codeLength,
476
+ length = 4,
477
+ size = "m",
478
+ state: externalState,
479
+ disabled: disabledProp,
480
+ error: errorProp,
481
+ label,
482
+ errorMessage,
483
+ errorLabel,
484
+ secureTextEntry = false,
485
+ showPlaceholderDots = true,
486
+ flexibleWidth = false,
487
+ isComplete: isCompleteProp,
488
+ currentFocus,
489
+ addRef,
490
+ testID,
491
+ "aria-label": ariaLabel
492
+ }) => {
493
+ const { theme } = useDesignSystem();
494
+ const uniqueId = useId();
495
+ const [focusedIndex, setFocusedIndex] = useState(null);
496
+ const [internalValue, setInternalValue] = useState(value);
497
+ const actualLength = codeLength ?? length;
498
+ const errorText = errorMessage ?? errorLabel;
499
+ const isError = errorProp ?? (!!errorText || externalState === "error");
500
+ const isDisabled = disabledProp ?? externalState === "disable";
501
+ const isHover = externalState === "hover";
502
+ const sizeStyles = theme.sizing.inputPin(size);
503
+ const inputColors = theme.colors.control.input;
504
+ const inputs = useRef([]);
505
+ useEffect(() => {
506
+ setInternalValue(value);
507
+ }, [value]);
508
+ useEffect(() => {
509
+ if (currentFocus !== void 0 && currentFocus >= 0 && currentFocus < actualLength) {
510
+ inputs.current[currentFocus]?.focus();
511
+ }
512
+ }, [currentFocus, actualLength]);
513
+ const checkIsComplete = useCallback(
514
+ (val) => val.length >= actualLength,
515
+ [actualLength]
516
+ );
517
+ const currentIsComplete = isCompleteProp ?? checkIsComplete(internalValue);
518
+ const labelId = `${uniqueId}-label`;
519
+ const errorId = `${uniqueId}-error`;
520
+ const handleTextChange = (text, index) => {
521
+ const sanitizedText = text.replace(/[^0-9a-zA-Z]/g, "");
522
+ if (sanitizedText.length > 1) {
523
+ const pastedData = sanitizedText.slice(0, actualLength - index);
524
+ const newValue2 = internalValue.split("");
525
+ while (newValue2.length < actualLength) {
526
+ newValue2.push("");
527
+ }
528
+ for (let i = 0; i < pastedData.length && index + i < actualLength; i++) {
529
+ newValue2[index + i] = pastedData[i];
530
+ }
531
+ const updatedValue2 = newValue2.slice(0, actualLength).join("");
532
+ setInternalValue(updatedValue2);
533
+ const complete2 = checkIsComplete(updatedValue2);
534
+ const changeProps2 = { isComplete: complete2, value: updatedValue2 };
535
+ onChange?.(changeProps2);
536
+ if (complete2) {
537
+ onComplete?.(changeProps2);
538
+ }
539
+ const nextIndex = Math.min(index + pastedData.length, actualLength - 1);
540
+ inputs.current[nextIndex]?.focus();
541
+ return;
542
+ }
543
+ const char = sanitizedText.slice(-1);
544
+ const newValue = internalValue.split("");
545
+ while (newValue.length < actualLength) {
546
+ newValue.push("");
547
+ }
548
+ newValue[index] = char;
549
+ const updatedValue = newValue.slice(0, actualLength).join("");
550
+ setInternalValue(updatedValue);
551
+ const complete = checkIsComplete(updatedValue);
552
+ const changeProps = { isComplete: complete, value: updatedValue };
553
+ onChange?.(changeProps);
554
+ if (complete) {
555
+ onComplete?.(changeProps);
556
+ }
557
+ if (char && index < actualLength - 1) {
558
+ inputs.current[index + 1]?.focus();
559
+ }
560
+ };
561
+ const handleKeyDown = (e, index) => {
562
+ if (e.key === "Backspace") {
563
+ if (!internalValue[index] && index > 0) {
564
+ inputs.current[index - 1]?.focus();
565
+ }
566
+ } else if (e.key === "ArrowLeft" && index > 0) {
567
+ inputs.current[index - 1]?.focus();
568
+ } else if (e.key === "ArrowRight" && index < actualLength - 1) {
569
+ inputs.current[index + 1]?.focus();
570
+ }
571
+ };
572
+ const handlePaste = (e) => {
573
+ e.preventDefault();
574
+ const pastedData = e.clipboardData.getData("text").replace(/[^0-9a-zA-Z]/g, "").slice(0, actualLength);
575
+ setInternalValue(pastedData);
576
+ const complete = checkIsComplete(pastedData);
577
+ const changeProps = { isComplete: complete, value: pastedData };
578
+ onChange?.(changeProps);
579
+ if (complete) {
580
+ onComplete?.(changeProps);
581
+ }
582
+ const nextIndex = Math.min(pastedData.length, actualLength - 1);
583
+ inputs.current[nextIndex]?.focus();
584
+ };
585
+ const handleRef = (index) => (element) => {
586
+ inputs.current[index] = element;
587
+ addRef?.(index)(element);
588
+ };
589
+ const pinItems = Array.from({ length: actualLength }).map((_, index) => {
590
+ const char = internalValue[index] || "";
591
+ const hasValue = !!char;
592
+ const isFocused = focusedIndex === index;
593
+ let backgroundColor = inputColors.bg;
594
+ let borderColor = inputColors.border;
595
+ if (isDisabled) {
596
+ backgroundColor = inputColors.bgDisable;
597
+ borderColor = inputColors.borderDisable;
598
+ } else if (isError) {
599
+ borderColor = theme.colors.border.alert;
600
+ backgroundColor = isFocused ? theme.colors.control.focus.bg : inputColors.bg;
601
+ } else if (isFocused) {
602
+ backgroundColor = theme.colors.control.focus.bg;
603
+ borderColor = theme.colors.content.brand.secondary;
604
+ } else if (isHover) {
605
+ backgroundColor = inputColors.bgHover;
606
+ borderColor = inputColors.borderHover;
607
+ } else {
608
+ backgroundColor = inputColors.bg;
609
+ borderColor = inputColors.border;
610
+ }
611
+ const textColor = isDisabled ? inputColors.textDisable : inputColors.text;
612
+ return /* @__PURE__ */ jsx8(
613
+ Box,
614
+ {
615
+ width: flexibleWidth ? void 0 : sizeStyles.size,
616
+ height: sizeStyles.size,
617
+ flex: flexibleWidth ? 1 : void 0,
618
+ backgroundColor,
619
+ borderColor,
620
+ borderWidth: sizeStyles.borderWidth,
621
+ borderRadius: sizeStyles.radius,
622
+ alignItems: "center",
623
+ justifyContent: "center",
624
+ hoverStyle: !isDisabled && !isFocused && !isError ? {
625
+ backgroundColor: inputColors.bgHover,
626
+ borderColor: inputColors.borderHover
627
+ } : void 0,
628
+ children: /* @__PURE__ */ jsx8(
629
+ InputPrimitive,
630
+ {
631
+ ref: addRef ? handleRef(index) : (el) => inputs.current[index] = el,
632
+ value: char,
633
+ placeholder: showPlaceholderDots && !isFocused ? "\u2022" : void 0,
634
+ onChangeText: (text) => handleTextChange(text, index),
635
+ onFocus: () => setFocusedIndex(index),
636
+ onBlur: () => setFocusedIndex(null),
637
+ onKeyDown: (e) => handleKeyDown(e, index),
638
+ disabled: isDisabled,
639
+ secureTextEntry,
640
+ color: textColor,
641
+ placeholderTextColor: inputColors.placeholder,
642
+ fontSize: sizeStyles.fontSize,
643
+ style: { textAlign: "center" },
644
+ maxLength: 1,
645
+ inputMode: "numeric",
646
+ autoComplete: "one-time-code",
647
+ "aria-label": ariaLabel ? `${ariaLabel} digit ${index + 1}` : `PIN digit ${index + 1}`,
648
+ "aria-invalid": isError,
649
+ "aria-describedby": isError && errorText ? errorId : void 0,
650
+ "data-testid": testID ? `${testID}-input-${index}` : `input-pin-input-${index}`
651
+ }
652
+ )
653
+ },
654
+ index
655
+ );
656
+ });
657
+ return /* @__PURE__ */ jsxs(
658
+ Box,
659
+ {
660
+ flexDirection: "column",
661
+ gap: 8,
662
+ "data-testid": testID || "input-pin",
663
+ role: "group",
664
+ "aria-labelledby": label ? labelId : void 0,
665
+ "aria-label": !label && ariaLabel ? ariaLabel : void 0,
666
+ children: [
667
+ label && /* @__PURE__ */ jsx8(
668
+ Text,
669
+ {
670
+ id: labelId,
671
+ color: theme.colors.content.secondary,
672
+ fontSize: sizeStyles.fontSize - 2,
673
+ fontWeight: "500",
674
+ children: label
675
+ }
676
+ ),
677
+ /* @__PURE__ */ jsx8(
678
+ Box,
679
+ {
680
+ flexDirection: "row",
681
+ gap: sizeStyles.gap,
682
+ onPaste: handlePaste,
683
+ width: flexibleWidth ? "100%" : void 0,
684
+ children: pinItems
685
+ }
686
+ ),
687
+ isError && errorText && /* @__PURE__ */ jsx8(
688
+ Text,
689
+ {
690
+ id: errorId,
691
+ role: "alert",
692
+ color: theme.colors.content.alert.primary,
693
+ fontSize: sizeStyles.fontSize - 2,
694
+ children: errorText
695
+ }
696
+ )
697
+ ]
698
+ }
699
+ );
700
+ };
701
+ export {
702
+ InputPin
703
+ };
704
+ //# sourceMappingURL=index.mjs.map