@oxy.so/bloom 4.30.1 → 4.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/adoption-matrix.mdx +4 -3
- package/docs/date-picker.mdx +37 -0
- package/docs/input-otp.mdx +22 -5
- package/lib/commonjs/date-picker/Calendar.js +2 -0
- package/lib/commonjs/date-picker/Calendar.js.map +1 -1
- package/lib/commonjs/date-picker/CalendarMonth.js +3 -2
- package/lib/commonjs/date-picker/CalendarMonth.js.map +1 -1
- package/lib/commonjs/date-picker/DatePicker.js +3 -0
- package/lib/commonjs/date-picker/DatePicker.js.map +1 -1
- package/lib/commonjs/date-picker/DateRangePicker.js +14 -6
- package/lib/commonjs/date-picker/DateRangePicker.js.map +1 -1
- package/lib/commonjs/date-picker/MeetingScheduler.js +1 -0
- package/lib/commonjs/date-picker/MeetingScheduler.js.map +1 -1
- package/lib/commonjs/date-picker/PickerShell.js +5 -3
- package/lib/commonjs/date-picker/PickerShell.js.map +1 -1
- package/lib/commonjs/date-picker/calendar-grid.js +9 -1
- package/lib/commonjs/date-picker/calendar-grid.js.map +1 -1
- package/lib/commonjs/input-otp/InputOtp.js +71 -18
- package/lib/commonjs/input-otp/InputOtp.js.map +1 -1
- package/lib/commonjs/input-otp/index.js +6 -0
- package/lib/commonjs/input-otp/index.js.map +1 -1
- package/lib/module/date-picker/Calendar.js +2 -0
- package/lib/module/date-picker/Calendar.js.map +1 -1
- package/lib/module/date-picker/CalendarMonth.js +3 -2
- package/lib/module/date-picker/CalendarMonth.js.map +1 -1
- package/lib/module/date-picker/DatePicker.js +3 -0
- package/lib/module/date-picker/DatePicker.js.map +1 -1
- package/lib/module/date-picker/DateRangePicker.js +14 -6
- package/lib/module/date-picker/DateRangePicker.js.map +1 -1
- package/lib/module/date-picker/MeetingScheduler.js +1 -0
- package/lib/module/date-picker/MeetingScheduler.js.map +1 -1
- package/lib/module/date-picker/PickerShell.js +4 -4
- package/lib/module/date-picker/PickerShell.js.map +1 -1
- package/lib/module/date-picker/calendar-grid.js +9 -1
- package/lib/module/date-picker/calendar-grid.js.map +1 -1
- package/lib/module/input-otp/InputOtp.js +70 -18
- package/lib/module/input-otp/InputOtp.js.map +1 -1
- package/lib/module/input-otp/index.js +1 -1
- package/lib/module/input-otp/index.js.map +1 -1
- package/lib/typescript/commonjs/date-picker/Calendar.d.ts.map +1 -1
- package/lib/typescript/commonjs/date-picker/CalendarMonth.d.ts +3 -2
- package/lib/typescript/commonjs/date-picker/CalendarMonth.d.ts.map +1 -1
- package/lib/typescript/commonjs/date-picker/DatePicker.d.ts +1 -1
- package/lib/typescript/commonjs/date-picker/DatePicker.d.ts.map +1 -1
- package/lib/typescript/commonjs/date-picker/DateRangePicker.d.ts +1 -1
- package/lib/typescript/commonjs/date-picker/DateRangePicker.d.ts.map +1 -1
- package/lib/typescript/commonjs/date-picker/MeetingScheduler.d.ts.map +1 -1
- package/lib/typescript/commonjs/date-picker/PickerShell.d.ts +3 -1
- package/lib/typescript/commonjs/date-picker/PickerShell.d.ts.map +1 -1
- package/lib/typescript/commonjs/date-picker/calendar-grid.d.ts +3 -2
- package/lib/typescript/commonjs/date-picker/calendar-grid.d.ts.map +1 -1
- package/lib/typescript/commonjs/date-picker/index.d.ts +1 -1
- package/lib/typescript/commonjs/date-picker/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/date-picker/types.d.ts +39 -1
- package/lib/typescript/commonjs/date-picker/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/input-otp/InputOtp.d.ts +4 -2
- package/lib/typescript/commonjs/input-otp/InputOtp.d.ts.map +1 -1
- package/lib/typescript/commonjs/input-otp/index.d.ts +2 -2
- package/lib/typescript/commonjs/input-otp/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/input-otp/types.d.ts +14 -2
- package/lib/typescript/commonjs/input-otp/types.d.ts.map +1 -1
- package/lib/typescript/module/date-picker/Calendar.d.ts.map +1 -1
- package/lib/typescript/module/date-picker/CalendarMonth.d.ts +3 -2
- package/lib/typescript/module/date-picker/CalendarMonth.d.ts.map +1 -1
- package/lib/typescript/module/date-picker/DatePicker.d.ts +1 -1
- package/lib/typescript/module/date-picker/DatePicker.d.ts.map +1 -1
- package/lib/typescript/module/date-picker/DateRangePicker.d.ts +1 -1
- package/lib/typescript/module/date-picker/DateRangePicker.d.ts.map +1 -1
- package/lib/typescript/module/date-picker/MeetingScheduler.d.ts.map +1 -1
- package/lib/typescript/module/date-picker/PickerShell.d.ts +3 -1
- package/lib/typescript/module/date-picker/PickerShell.d.ts.map +1 -1
- package/lib/typescript/module/date-picker/calendar-grid.d.ts +3 -2
- package/lib/typescript/module/date-picker/calendar-grid.d.ts.map +1 -1
- package/lib/typescript/module/date-picker/index.d.ts +1 -1
- package/lib/typescript/module/date-picker/index.d.ts.map +1 -1
- package/lib/typescript/module/date-picker/types.d.ts +39 -1
- package/lib/typescript/module/date-picker/types.d.ts.map +1 -1
- package/lib/typescript/module/input-otp/InputOtp.d.ts +4 -2
- package/lib/typescript/module/input-otp/InputOtp.d.ts.map +1 -1
- package/lib/typescript/module/input-otp/index.d.ts +2 -2
- package/lib/typescript/module/input-otp/index.d.ts.map +1 -1
- package/lib/typescript/module/input-otp/types.d.ts +14 -2
- package/lib/typescript/module/input-otp/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/date-picker/Calendar.tsx +10 -1
- package/src/date-picker/CalendarMonth.tsx +5 -3
- package/src/date-picker/DatePicker.tsx +3 -0
- package/src/date-picker/DateRangePicker.tsx +13 -5
- package/src/date-picker/MeetingScheduler.tsx +1 -0
- package/src/date-picker/PickerShell.tsx +5 -2
- package/src/date-picker/calendar-grid.ts +13 -11
- package/src/date-picker/index.ts +4 -0
- package/src/date-picker/types.ts +51 -1
- package/src/input-otp/InputOtp.tsx +66 -19
- package/src/input-otp/index.ts +2 -2
- package/src/input-otp/types.ts +15 -2
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* internal state. The time of day on an input is ignored.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import type { DateRange, WeekStart } from './types';
|
|
13
|
+
import type { DateRange, DateRangePreset, WeekStart } from './types';
|
|
14
14
|
|
|
15
15
|
const MS_PER_DAY = 86_400_000;
|
|
16
16
|
|
|
@@ -334,20 +334,22 @@ export function clampDate(date: Date, { minDate, maxDate }: DateConstraints): Da
|
|
|
334
334
|
return startOfDay(date);
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
-
/** Quick-select presets, relative to `now`. */
|
|
338
|
-
export function quickSelectPresets(
|
|
337
|
+
/** Quick-select presets, relative to `now`. `label` is the English default. */
|
|
338
|
+
export function quickSelectPresets(
|
|
339
|
+
now: Date = today(),
|
|
340
|
+
): { key: DateRangePreset; label: string; range: DateRange }[] {
|
|
339
341
|
const lastMonth = addMonths(now, -1);
|
|
340
342
|
const lastYear = new Date(now.getFullYear() - 1, now.getMonth(), 1);
|
|
341
343
|
const yesterday = addDays(now, -1);
|
|
342
344
|
return [
|
|
343
|
-
{ label: 'Today', range: { start: now, end: now } },
|
|
344
|
-
{ label: 'Yesterday', range: { start: yesterday, end: yesterday } },
|
|
345
|
-
{ label: 'Last week', range: { start: addDays(now, -7), end: yesterday } },
|
|
346
|
-
{ label: 'This month', range: { start: startOfMonth(now), end: endOfMonth(now) } },
|
|
347
|
-
{ label: 'Last month', range: { start: startOfMonth(lastMonth), end: endOfMonth(lastMonth) } },
|
|
348
|
-
{ label: 'This year', range: { start: startOfYear(now), end: endOfYear(now) } },
|
|
349
|
-
{ label: 'Last year', range: { start: startOfYear(lastYear), end: endOfYear(lastYear) } },
|
|
350
|
-
{ label: 'All time', range: { start: addMonths(now, -120), end: now } },
|
|
345
|
+
{ key: 'today', label: 'Today', range: { start: now, end: now } },
|
|
346
|
+
{ key: 'yesterday', label: 'Yesterday', range: { start: yesterday, end: yesterday } },
|
|
347
|
+
{ key: 'lastWeek', label: 'Last week', range: { start: addDays(now, -7), end: yesterday } },
|
|
348
|
+
{ key: 'thisMonth', label: 'This month', range: { start: startOfMonth(now), end: endOfMonth(now) } },
|
|
349
|
+
{ key: 'lastMonth', label: 'Last month', range: { start: startOfMonth(lastMonth), end: endOfMonth(lastMonth) } },
|
|
350
|
+
{ key: 'thisYear', label: 'This year', range: { start: startOfYear(now), end: endOfYear(now) } },
|
|
351
|
+
{ key: 'lastYear', label: 'Last year', range: { start: startOfYear(lastYear), end: endOfYear(lastYear) } },
|
|
352
|
+
{ key: 'allTime', label: 'All time', range: { start: addMonths(now, -120), end: now } },
|
|
351
353
|
];
|
|
352
354
|
}
|
|
353
355
|
|
package/src/date-picker/index.ts
CHANGED
|
@@ -8,10 +8,14 @@ export { TimeField } from './TimeField';
|
|
|
8
8
|
|
|
9
9
|
export type {
|
|
10
10
|
CalendarConstraintProps,
|
|
11
|
+
CalendarLabels,
|
|
11
12
|
CalendarProps,
|
|
13
|
+
DatePickerLabels,
|
|
12
14
|
DatePickerProps,
|
|
13
15
|
DateRange,
|
|
16
|
+
DateRangePickerLabels,
|
|
14
17
|
DateRangePickerProps,
|
|
18
|
+
DateRangePreset,
|
|
15
19
|
HourFormat,
|
|
16
20
|
MeetingSchedulerDetails,
|
|
17
21
|
MeetingSchedulerHost,
|
package/src/date-picker/types.ts
CHANGED
|
@@ -23,11 +23,23 @@ export interface CalendarConstraintProps {
|
|
|
23
23
|
locale?: string;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Fixed strings of the month panels, overridable for localisation. Month and
|
|
28
|
+
* weekday names already follow `locale`; these are the rest.
|
|
29
|
+
*/
|
|
30
|
+
export interface CalendarLabels {
|
|
31
|
+
/** Names the previous-month chevron. Default `'Previous month'`. */
|
|
32
|
+
previousMonth?: string;
|
|
33
|
+
/** Names the next-month chevron. Default `'Next month'`. */
|
|
34
|
+
nextMonth?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
26
37
|
interface CalendarBaseProps extends CalendarConstraintProps {
|
|
27
38
|
/** Month shown first. Defaults to the selected day's month, else today's. */
|
|
28
39
|
defaultMonth?: Date;
|
|
29
40
|
/** Names the calendar grid. Defaults to the month title. */
|
|
30
41
|
accessibilityLabel?: string;
|
|
42
|
+
labels?: CalendarLabels;
|
|
31
43
|
style?: StyleProp<ViewStyle>;
|
|
32
44
|
testID?: string;
|
|
33
45
|
}
|
|
@@ -68,6 +80,40 @@ interface PickerBaseProps extends CalendarConstraintProps {
|
|
|
68
80
|
testID?: string;
|
|
69
81
|
}
|
|
70
82
|
|
|
83
|
+
/** `DatePicker`'s fixed strings, overridable for localisation. */
|
|
84
|
+
export interface DatePickerLabels extends CalendarLabels {
|
|
85
|
+
/** The footer's dismiss button. Default `'Cancel'`. */
|
|
86
|
+
cancel?: string;
|
|
87
|
+
/** The footer's commit button. Default `'Apply'`. */
|
|
88
|
+
apply?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The quick-select column's rows, in the order they are drawn. */
|
|
92
|
+
export type DateRangePreset =
|
|
93
|
+
| 'today'
|
|
94
|
+
| 'yesterday'
|
|
95
|
+
| 'lastWeek'
|
|
96
|
+
| 'thisMonth'
|
|
97
|
+
| 'lastMonth'
|
|
98
|
+
| 'thisYear'
|
|
99
|
+
| 'lastYear'
|
|
100
|
+
| 'allTime';
|
|
101
|
+
|
|
102
|
+
/** `DateRangePicker`'s fixed strings, overridable for localisation. */
|
|
103
|
+
export interface DateRangePickerLabels extends DatePickerLabels {
|
|
104
|
+
/**
|
|
105
|
+
* The footer pill. Receives the count so the app pluralises with its own
|
|
106
|
+
* i18n rules. Default `(n) => \`${n} day${n === 1 ? '' : 's'} selected\``.
|
|
107
|
+
*/
|
|
108
|
+
daysSelected?: (days: number) => string;
|
|
109
|
+
/** Names the start-date chip. Default `'Start date'`. */
|
|
110
|
+
startDate?: string;
|
|
111
|
+
/** Names the end-date chip. Default `'End date'`. */
|
|
112
|
+
endDate?: string;
|
|
113
|
+
/** Quick-select rows; any left out keep their English text (`'Today'`, `'Last week'`, …). */
|
|
114
|
+
presets?: Partial<Record<DateRangePreset, string>>;
|
|
115
|
+
}
|
|
116
|
+
|
|
71
117
|
export interface DatePickerProps extends PickerBaseProps {
|
|
72
118
|
/** Committed day (controlled). `null` for none. */
|
|
73
119
|
value?: Date | null;
|
|
@@ -79,6 +125,8 @@ export interface DatePickerProps extends PickerBaseProps {
|
|
|
79
125
|
placeholder?: string;
|
|
80
126
|
/** Names the popup and its calendar. Defaults to `'Date'`. */
|
|
81
127
|
accessibilityLabel?: string;
|
|
128
|
+
/** Footer buttons and month chevrons. */
|
|
129
|
+
labels?: DatePickerLabels;
|
|
82
130
|
}
|
|
83
131
|
|
|
84
132
|
export interface DateRangePickerProps extends PickerBaseProps {
|
|
@@ -94,6 +142,8 @@ export interface DateRangePickerProps extends PickerBaseProps {
|
|
|
94
142
|
accessibilityLabel?: string;
|
|
95
143
|
/** Show the quick-select column (Today, Last week, …). Defaults to `true`. */
|
|
96
144
|
quickSelect?: boolean;
|
|
145
|
+
/** Footer buttons and pill, date chips, quick-select rows and month chevrons. */
|
|
146
|
+
labels?: DateRangePickerLabels;
|
|
97
147
|
}
|
|
98
148
|
|
|
99
149
|
/** The person the meeting is booked with. */
|
|
@@ -136,7 +186,7 @@ export type HourFormat = '12h' | '24h';
|
|
|
136
186
|
export type MeetingSchedulerHourFormat = HourFormat;
|
|
137
187
|
|
|
138
188
|
/** Fixed strings, overridable for localisation. */
|
|
139
|
-
export interface MeetingSchedulerLabels {
|
|
189
|
+
export interface MeetingSchedulerLabels extends CalendarLabels {
|
|
140
190
|
/** Default `'Send meeting'`. */
|
|
141
191
|
send?: string;
|
|
142
192
|
/** Chip text before a time is picked. Default `'Select a time'`. */
|
|
@@ -19,13 +19,14 @@ import {
|
|
|
19
19
|
TEXT_FIELD_TRANSITION_MS,
|
|
20
20
|
} from '../text-field/shared';
|
|
21
21
|
import { TYPE_SCALE } from '../typography/scale';
|
|
22
|
-
import type { InputOtpProps } from './types';
|
|
22
|
+
import type { InputOtpProps, InputOtpType } from './types';
|
|
23
23
|
import { useFieldMembership } from '../field/membership';
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* `InputOtp`: one box per digit, side by side, behaving as ONE value.
|
|
27
27
|
*
|
|
28
|
-
* box 48 × 48, radius 10, 1px border, shadow-xs
|
|
28
|
+
* box 48 × 48, radius 10, 1px border, shadow-xs; narrower (never
|
|
29
|
+
* wider) when the row does not fit its container
|
|
29
30
|
* digit mono, title-3-medium 18/26 500, centred, tabular numerals
|
|
30
31
|
* gap 8 between boxes, +12 before each `groupEvery` group
|
|
31
32
|
* focus accent-500 border + 2px accent-500 ring (outside the border)
|
|
@@ -46,12 +47,19 @@ import { useFieldMembership } from '../field/membership';
|
|
|
46
47
|
* Digits are monospace so the boxes stay optically even — in a proportional
|
|
47
48
|
* face a `1` is visibly narrower than an `8`.
|
|
48
49
|
*
|
|
50
|
+
* `type="alphanumeric"` is the same field for codes that carry letters: every
|
|
51
|
+
* character is upper-cased, anything outside `A`–`Z`/`0`–`9` is dropped (so a
|
|
52
|
+
* code printed as `ABCDE-12345` pastes whole), and the keyboard is a letters
|
|
53
|
+
* one with auto-capitalisation instead of the number pad.
|
|
54
|
+
*
|
|
49
55
|
* Focus paints on EVERY focus, not only keyboard focus: a text input matches
|
|
50
56
|
* `:focus-visible` on a pointer focus too, so the ring shows on click, and
|
|
51
57
|
* state (not CSS) carries it here so native paints the same ring.
|
|
52
58
|
*/
|
|
53
59
|
|
|
54
|
-
const
|
|
60
|
+
const NOT_DIGIT = /\D/g;
|
|
61
|
+
/** Filtered BEFORE upper-casing: `'ß'.toUpperCase()` is `SS`, `'ı'` becomes `I`. */
|
|
62
|
+
const NOT_ALPHANUMERIC = /[^A-Za-z0-9]/g;
|
|
55
63
|
const BOX_SIZE = 48;
|
|
56
64
|
const BOX_GAP = 8;
|
|
57
65
|
const GROUP_GAP = 12;
|
|
@@ -141,7 +149,34 @@ export function resolveInputOtpBoxPaint(
|
|
|
141
149
|
return { backgroundColor, borderColor, color, boxShadow };
|
|
142
150
|
}
|
|
143
151
|
|
|
144
|
-
|
|
152
|
+
/** The characters `type` accepts, in order; everything else is dropped. Pure. */
|
|
153
|
+
export function cleanInputOtpValue(raw: string, type: InputOtpType = 'numeric'): string {
|
|
154
|
+
return type === 'alphanumeric' ? raw.replace(NOT_ALPHANUMERIC, '').toUpperCase() : raw.replace(NOT_DIGIT, '');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const clean = (raw: string, length: number, type: InputOtpType) => cleanInputOtpValue(raw, type).slice(0, length);
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Keyboard and input hints per `type`. The one-time-code autofill hint is
|
|
161
|
+
* shared (below); only what the keyboard offers differs. Android has no
|
|
162
|
+
* `ascii-capable`, and its `visible-password` is the letters keyboard without
|
|
163
|
+
* suggestions or autocorrect — both of which would rewrite a code.
|
|
164
|
+
*
|
|
165
|
+
* Alphanumeric sets `inputMode` on web ONLY: React Native gives `inputMode`
|
|
166
|
+
* precedence over `keyboardType`, so `inputMode: 'text'` on native would open
|
|
167
|
+
* the ordinary keyboard instead of `ascii-capable` / `visible-password`.
|
|
168
|
+
*/
|
|
169
|
+
const KEYBOARD_PROPS: Record<InputOtpType, Record<string, unknown>> = {
|
|
170
|
+
numeric: { inputMode: 'numeric', keyboardType: 'number-pad' },
|
|
171
|
+
alphanumeric: {
|
|
172
|
+
...(IS_WEB
|
|
173
|
+
? { inputMode: 'text' }
|
|
174
|
+
: { keyboardType: Platform.OS === 'android' ? 'visible-password' : 'ascii-capable' }),
|
|
175
|
+
autoCapitalize: 'characters',
|
|
176
|
+
autoCorrect: false,
|
|
177
|
+
spellCheck: false,
|
|
178
|
+
},
|
|
179
|
+
};
|
|
145
180
|
|
|
146
181
|
const WEB_BOX_STYLE: TextStyle | undefined = IS_WEB
|
|
147
182
|
? ({
|
|
@@ -155,6 +190,7 @@ const WEB_BOX_STYLE: TextStyle | undefined = IS_WEB
|
|
|
155
190
|
|
|
156
191
|
export function InputOtp({
|
|
157
192
|
length = 6,
|
|
193
|
+
type = 'numeric',
|
|
158
194
|
value,
|
|
159
195
|
defaultValue = '',
|
|
160
196
|
onChange,
|
|
@@ -186,21 +222,24 @@ export function InputOtp({
|
|
|
186
222
|
const groupName = membership.accessibilityLabel ?? 'One-time code';
|
|
187
223
|
|
|
188
224
|
const inputsRef = useRef<Array<TextInput | null>>([]);
|
|
189
|
-
const [internal, setInternal] = useState(() => clean(defaultValue, length));
|
|
225
|
+
const [internal, setInternal] = useState(() => clean(defaultValue, length, type));
|
|
190
226
|
const [focusedIndex, setFocusedIndex] = useState<number | null>(null);
|
|
191
227
|
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
|
192
228
|
|
|
193
229
|
const controlled = value !== undefined;
|
|
194
|
-
const code = clean(controlled ? value : internal, length);
|
|
230
|
+
const code = clean(controlled ? value : internal, length, type);
|
|
231
|
+
const unit = type === 'alphanumeric' ? 'Character' : 'Digit';
|
|
195
232
|
|
|
196
233
|
const commit = useCallback(
|
|
197
234
|
(next: string) => {
|
|
198
|
-
|
|
235
|
+
// `next` is already cleaned per character (a cleared box is a space
|
|
236
|
+
// placeholder, which `clean` drops along with anything else).
|
|
237
|
+
const cleaned = clean(next, length, type);
|
|
199
238
|
if (!controlled) setInternal(cleaned);
|
|
200
239
|
onChange?.(cleaned);
|
|
201
240
|
if (cleaned.length === length) onComplete?.(cleaned);
|
|
202
241
|
},
|
|
203
|
-
[controlled, length, onChange, onComplete],
|
|
242
|
+
[controlled, length, type, onChange, onComplete],
|
|
204
243
|
);
|
|
205
244
|
|
|
206
245
|
const focusBox = useCallback(
|
|
@@ -210,9 +249,9 @@ export function InputOtp({
|
|
|
210
249
|
[length],
|
|
211
250
|
);
|
|
212
251
|
|
|
213
|
-
/** Writes `
|
|
214
|
-
const writeFrom = (index: number,
|
|
215
|
-
const incoming =
|
|
252
|
+
/** Writes `text` starting at `index`, which covers typing, paste and autofill. */
|
|
253
|
+
const writeFrom = (index: number, text: string) => {
|
|
254
|
+
const incoming = cleanInputOtpValue(text, type);
|
|
216
255
|
if (incoming === '') return;
|
|
217
256
|
const chars = code.padEnd(length, ' ').split('');
|
|
218
257
|
for (let offset = 0; offset < incoming.length && index + offset < length; offset += 1) {
|
|
@@ -281,16 +320,18 @@ export function InputOtp({
|
|
|
281
320
|
} as Record<string, unknown>)
|
|
282
321
|
: {})}
|
|
283
322
|
testID={testID ? `${testID}-${index}` : undefined}
|
|
284
|
-
// `text` with a numeric keyboard rather than a number
|
|
285
|
-
// input brings spinners, accepts `e` and `-`, and
|
|
286
|
-
// value for anything it considers malformed.
|
|
287
|
-
|
|
288
|
-
keyboardType="number-pad"
|
|
323
|
+
// Numeric is `text` with a numeric keyboard rather than a number
|
|
324
|
+
// input: a number input brings spinners, accepts `e` and `-`, and
|
|
325
|
+
// reports an empty value for anything it considers malformed.
|
|
326
|
+
{...KEYBOARD_PROPS[type]}
|
|
289
327
|
autoComplete={Platform.OS === 'android' ? 'sms-otp' : 'one-time-code'}
|
|
290
328
|
textContentType="oneTimeCode"
|
|
291
329
|
autoFocus={autoFocus && index === 0}
|
|
292
|
-
// Long enough to accept a full autofilled code in one box
|
|
293
|
-
maxLength
|
|
330
|
+
// Long enough to accept a full autofilled or pasted code in one box,
|
|
331
|
+
// separators included: a browser truncates a paste to `maxLength`
|
|
332
|
+
// BEFORE the change handler sees it, so `length` alone lost the
|
|
333
|
+
// last character of `ABCDE-12345`.
|
|
334
|
+
maxLength={length * 2}
|
|
294
335
|
// Typing into a filled box must REPLACE its digit, so the digit is
|
|
295
336
|
// selected on focus. Web's `selectTextOnFocus` does that. Android's
|
|
296
337
|
// does not: it selects on the input's next layout, and focusing a box
|
|
@@ -300,7 +341,7 @@ export function InputOtp({
|
|
|
300
341
|
selectTextOnFocus={IS_WEB}
|
|
301
342
|
caretHidden={false}
|
|
302
343
|
editable={!disabled}
|
|
303
|
-
accessibilityLabel={
|
|
344
|
+
accessibilityLabel={`${unit} ${index + 1} of ${length}`}
|
|
304
345
|
aria-invalid={invalid || undefined}
|
|
305
346
|
aria-disabled={disabled || undefined}
|
|
306
347
|
value={digit === ' ' ? '' : digit}
|
|
@@ -315,6 +356,12 @@ export function InputOtp({
|
|
|
315
356
|
style={[
|
|
316
357
|
{
|
|
317
358
|
width: BOX_SIZE,
|
|
359
|
+
// A box shrinks (never grows) when the row is narrower than
|
|
360
|
+
// its natural width, so ten boxes still fit a phone. `minWidth`
|
|
361
|
+
// 0 is what lets it: a flex item's automatic minimum is its
|
|
362
|
+
// specified width, and an `<input>` would otherwise overflow.
|
|
363
|
+
flexShrink: 1,
|
|
364
|
+
minWidth: 0,
|
|
318
365
|
height: BOX_SIZE,
|
|
319
366
|
marginLeft: gapBefore ? GROUP_GAP : 0,
|
|
320
367
|
padding: 0,
|
package/src/input-otp/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { InputOtp } from './InputOtp';
|
|
2
|
-
export type { InputOtpProps } from './types';
|
|
1
|
+
export { InputOtp, cleanInputOtpValue } from './InputOtp';
|
|
2
|
+
export type { InputOtpProps, InputOtpType } from './types';
|
package/src/input-otp/types.ts
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import type { ViewStyleProp } from '../styles';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* What a box accepts. `numeric` (the default) keeps digits only;
|
|
5
|
+
* `alphanumeric` upper-cases and keeps `A`–`Z` and `0`–`9`.
|
|
6
|
+
*/
|
|
7
|
+
export type InputOtpType = 'numeric' | 'alphanumeric';
|
|
8
|
+
|
|
3
9
|
export interface InputOtpProps extends ViewStyleProp {
|
|
4
|
-
/** Number of
|
|
10
|
+
/** Number of boxes, default `6`. */
|
|
5
11
|
length?: number;
|
|
6
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* `numeric` (default): digits only, number pad. `alphanumeric`: letters and
|
|
14
|
+
* digits, upper-cased, on a letters keyboard with auto-capitalisation.
|
|
15
|
+
* Anything else — a dash, a space — is dropped either way, so a pasted
|
|
16
|
+
* `ABCDE-12345` fills ten boxes.
|
|
17
|
+
*/
|
|
18
|
+
type?: InputOtpType;
|
|
19
|
+
/** Controlled value. Characters the `type` does not accept are dropped and longer strings truncated to `length`. */
|
|
7
20
|
value?: string;
|
|
8
21
|
/** Initial value when uncontrolled. */
|
|
9
22
|
defaultValue?: string;
|