@coreui/react-pro 5.22.0 → 5.23.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/README.md +1 -1
- package/dist/cjs/components/autocomplete/CAutocomplete.js +2 -2
- package/dist/cjs/components/autocomplete/CAutocomplete.js.map +1 -1
- package/dist/cjs/components/index.d.ts +1 -0
- package/dist/cjs/components/one-time-password-input/COneTimePassword.d.ts +73 -0
- package/dist/cjs/components/one-time-password-input/COneTimePassword.js +178 -0
- package/dist/cjs/components/one-time-password-input/COneTimePassword.js.map +1 -0
- package/dist/cjs/components/one-time-password-input/COneTimePasswordInput.d.ts +8 -0
- package/dist/cjs/components/one-time-password-input/COneTimePasswordInput.js +14 -0
- package/dist/cjs/components/one-time-password-input/COneTimePasswordInput.js.map +1 -0
- package/dist/cjs/components/one-time-password-input/index.d.ts +5 -0
- package/dist/cjs/components/one-time-password-input/utils.d.ts +2 -0
- package/dist/cjs/components/one-time-password-input/utils.js +28 -0
- package/dist/cjs/components/one-time-password-input/utils.js.map +1 -0
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/components/autocomplete/CAutocomplete.js +2 -2
- package/dist/esm/components/autocomplete/CAutocomplete.js.map +1 -1
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/components/one-time-password-input/COneTimePassword.d.ts +73 -0
- package/dist/esm/components/one-time-password-input/COneTimePassword.js +176 -0
- package/dist/esm/components/one-time-password-input/COneTimePassword.js.map +1 -0
- package/dist/esm/components/one-time-password-input/COneTimePasswordInput.d.ts +8 -0
- package/dist/esm/components/one-time-password-input/COneTimePasswordInput.js +12 -0
- package/dist/esm/components/one-time-password-input/COneTimePasswordInput.js.map +1 -0
- package/dist/esm/components/one-time-password-input/index.d.ts +5 -0
- package/dist/esm/components/one-time-password-input/utils.d.ts +2 -0
- package/dist/esm/components/one-time-password-input/utils.js +25 -0
- package/dist/esm/components/one-time-password-input/utils.js.map +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/package.json +9 -9
- package/src/components/autocomplete/CAutocomplete.tsx +18 -16
- package/src/components/index.ts +1 -0
- package/src/components/one-time-password-input/COneTimePassword.tsx +462 -0
- package/src/components/one-time-password-input/COneTimePasswordInput.tsx +25 -0
- package/src/components/one-time-password-input/__tests__/COneTimePassword.spec.tsx +361 -0
- package/src/components/one-time-password-input/__tests__/__snapshots__/COneTimePassword.spec.tsx.snap +130 -0
- package/src/components/one-time-password-input/index.ts +6 -0
- package/src/components/one-time-password-input/utils.ts +23 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
|
3
|
+
import '@testing-library/jest-dom'
|
|
4
|
+
import { COneTimePassword } from '../COneTimePassword'
|
|
5
|
+
import { COneTimePasswordInput } from '../COneTimePasswordInput'
|
|
6
|
+
|
|
7
|
+
// Test helpers
|
|
8
|
+
const customAriaLabel = (index: number, total: number) => `Code digit ${index + 1} of ${total}`
|
|
9
|
+
|
|
10
|
+
// Helper to render OTP with default inputs
|
|
11
|
+
const renderOTP = (props = {}) => {
|
|
12
|
+
return render(
|
|
13
|
+
<COneTimePassword {...props}>
|
|
14
|
+
<COneTimePasswordInput />
|
|
15
|
+
<COneTimePasswordInput />
|
|
16
|
+
<COneTimePasswordInput />
|
|
17
|
+
<COneTimePasswordInput />
|
|
18
|
+
</COneTimePassword>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('COneTimePassword', () => {
|
|
23
|
+
// Snapshot tests
|
|
24
|
+
test('loads and displays COneTimePassword component', () => {
|
|
25
|
+
const { container } = renderOTP()
|
|
26
|
+
expect(container).toMatchSnapshot()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('COneTimePassword customize props', () => {
|
|
30
|
+
const { container } = renderOTP({
|
|
31
|
+
className: 'custom-otp',
|
|
32
|
+
size: 'lg',
|
|
33
|
+
disabled: true,
|
|
34
|
+
readOnly: true,
|
|
35
|
+
placeholder: '•',
|
|
36
|
+
type: 'text',
|
|
37
|
+
})
|
|
38
|
+
expect(container).toMatchSnapshot()
|
|
39
|
+
expect(container.querySelector('.form-otp')).toHaveClass('custom-otp')
|
|
40
|
+
expect(container.querySelector('.form-otp')).toHaveClass('form-otp-lg')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// Controlled pattern tests
|
|
44
|
+
test('works as controlled component with value prop', async () => {
|
|
45
|
+
const onChange = jest.fn()
|
|
46
|
+
const { rerender } = renderOTP({ value: '12', onChange })
|
|
47
|
+
|
|
48
|
+
const inputs = screen.getAllByRole('textbox')
|
|
49
|
+
expect(inputs[0]).toHaveValue('1')
|
|
50
|
+
expect(inputs[1]).toHaveValue('2')
|
|
51
|
+
expect(inputs[2]).toHaveValue('')
|
|
52
|
+
expect(inputs[3]).toHaveValue('')
|
|
53
|
+
|
|
54
|
+
// Update value prop
|
|
55
|
+
rerender(
|
|
56
|
+
<COneTimePassword value="1234" onChange={onChange}>
|
|
57
|
+
<COneTimePasswordInput />
|
|
58
|
+
<COneTimePasswordInput />
|
|
59
|
+
<COneTimePasswordInput />
|
|
60
|
+
<COneTimePasswordInput />
|
|
61
|
+
</COneTimePassword>
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
expect(inputs[0]).toHaveValue('1')
|
|
65
|
+
expect(inputs[1]).toHaveValue('2')
|
|
66
|
+
expect(inputs[2]).toHaveValue('3')
|
|
67
|
+
expect(inputs[3]).toHaveValue('4')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('controlled component calls onChange when value changes', () => {
|
|
71
|
+
const onChange = jest.fn()
|
|
72
|
+
renderOTP({ value: '', onChange })
|
|
73
|
+
|
|
74
|
+
const inputs = screen.getAllByRole('textbox')
|
|
75
|
+
fireEvent.input(inputs[0], { target: { value: '5' } })
|
|
76
|
+
|
|
77
|
+
expect(onChange).toHaveBeenCalledWith('5')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// Uncontrolled pattern tests
|
|
81
|
+
test('works as uncontrolled component without value prop', () => {
|
|
82
|
+
const onChange = jest.fn()
|
|
83
|
+
renderOTP({ onChange })
|
|
84
|
+
|
|
85
|
+
const inputs = screen.getAllByRole('textbox')
|
|
86
|
+
expect(inputs[0]).toHaveValue('')
|
|
87
|
+
|
|
88
|
+
fireEvent.input(inputs[0], { target: { value: '7' } })
|
|
89
|
+
expect(inputs[0]).toHaveValue('7')
|
|
90
|
+
expect(onChange).toHaveBeenCalledWith('7')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('works with defaultValue prop for uncontrolled mode', () => {
|
|
94
|
+
const onChange = jest.fn()
|
|
95
|
+
renderOTP({ defaultValue: '9876', onChange })
|
|
96
|
+
|
|
97
|
+
const inputs = screen.getAllByRole('textbox')
|
|
98
|
+
expect(inputs[0]).toHaveValue('9')
|
|
99
|
+
expect(inputs[1]).toHaveValue('8')
|
|
100
|
+
expect(inputs[2]).toHaveValue('7')
|
|
101
|
+
expect(inputs[3]).toHaveValue('6')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('defaultValue works with partial values', () => {
|
|
105
|
+
renderOTP({ defaultValue: '42' })
|
|
106
|
+
|
|
107
|
+
const inputs = screen.getAllByRole('textbox')
|
|
108
|
+
expect(inputs[0]).toHaveValue('4')
|
|
109
|
+
expect(inputs[1]).toHaveValue('2')
|
|
110
|
+
expect(inputs[2]).toHaveValue('')
|
|
111
|
+
expect(inputs[3]).toHaveValue('')
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test('controlled mode overrides defaultValue', () => {
|
|
115
|
+
renderOTP({ defaultValue: '1111', value: '2222' })
|
|
116
|
+
|
|
117
|
+
const inputs = screen.getAllByRole('textbox')
|
|
118
|
+
expect(inputs[0]).toHaveValue('2')
|
|
119
|
+
expect(inputs[1]).toHaveValue('2')
|
|
120
|
+
expect(inputs[2]).toHaveValue('2')
|
|
121
|
+
expect(inputs[3]).toHaveValue('2')
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
// Focus and navigation tests
|
|
125
|
+
test('auto-focuses next input on valid entry', () => {
|
|
126
|
+
renderOTP()
|
|
127
|
+
|
|
128
|
+
const inputs = screen.getAllByRole('textbox')
|
|
129
|
+
fireEvent.input(inputs[0], { target: { value: '1' } })
|
|
130
|
+
|
|
131
|
+
expect(inputs[1]).toHaveFocus()
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test('backspace moves to previous input when current is empty', () => {
|
|
135
|
+
renderOTP({ defaultValue: '12' })
|
|
136
|
+
|
|
137
|
+
const inputs = screen.getAllByRole('textbox')
|
|
138
|
+
inputs[2].focus()
|
|
139
|
+
|
|
140
|
+
fireEvent.keyDown(inputs[2], { key: 'Backspace' })
|
|
141
|
+
expect(inputs[1]).toHaveFocus()
|
|
142
|
+
expect(inputs[1]).toHaveValue('')
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test('arrow key navigation works', () => {
|
|
146
|
+
renderOTP({ defaultValue: '1234' })
|
|
147
|
+
|
|
148
|
+
const inputs = screen.getAllByRole('textbox')
|
|
149
|
+
inputs[1].focus()
|
|
150
|
+
|
|
151
|
+
fireEvent.keyDown(inputs[1], { key: 'ArrowRight' })
|
|
152
|
+
expect(inputs[2]).toHaveFocus()
|
|
153
|
+
|
|
154
|
+
fireEvent.keyDown(inputs[2], { key: 'ArrowLeft' })
|
|
155
|
+
expect(inputs[1]).toHaveFocus()
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('linear mode prevents out-of-order focus', () => {
|
|
159
|
+
renderOTP({ linear: true })
|
|
160
|
+
|
|
161
|
+
const inputs = screen.getAllByRole('textbox')
|
|
162
|
+
fireEvent.focus(inputs[2]) // Try to focus third input
|
|
163
|
+
|
|
164
|
+
expect(inputs[0]).toHaveFocus() // Should focus first empty input instead
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
// Paste functionality tests
|
|
168
|
+
test('paste distributes characters across inputs', () => {
|
|
169
|
+
const onChange = jest.fn()
|
|
170
|
+
renderOTP({ onChange })
|
|
171
|
+
|
|
172
|
+
const inputs = screen.getAllByRole('textbox')
|
|
173
|
+
inputs[0].focus()
|
|
174
|
+
|
|
175
|
+
fireEvent.paste(inputs[0], { clipboardData: { getData: () => '5678' } })
|
|
176
|
+
|
|
177
|
+
expect(inputs[0]).toHaveValue('5')
|
|
178
|
+
expect(inputs[1]).toHaveValue('6')
|
|
179
|
+
expect(inputs[2]).toHaveValue('7')
|
|
180
|
+
expect(inputs[3]).toHaveValue('8')
|
|
181
|
+
expect(onChange).toHaveBeenLastCalledWith('5678')
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test('paste from middle input distributes remaining characters', () => {
|
|
185
|
+
renderOTP({ defaultValue: '12' })
|
|
186
|
+
|
|
187
|
+
const inputs = screen.getAllByRole('textbox')
|
|
188
|
+
inputs[1].focus()
|
|
189
|
+
|
|
190
|
+
fireEvent.paste(inputs[1], { clipboardData: { getData: () => '99' } })
|
|
191
|
+
|
|
192
|
+
expect(inputs[0]).toHaveValue('1')
|
|
193
|
+
expect(inputs[1]).toHaveValue('9')
|
|
194
|
+
expect(inputs[2]).toHaveValue('9')
|
|
195
|
+
expect(inputs[3]).toHaveValue('')
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
test('paste respects input type validation', () => {
|
|
199
|
+
renderOTP({ type: 'number' })
|
|
200
|
+
|
|
201
|
+
const inputs = screen.getAllByRole('textbox')
|
|
202
|
+
inputs[0].focus()
|
|
203
|
+
|
|
204
|
+
fireEvent.paste(inputs[0], { clipboardData: { getData: () => 'a1b2' } })
|
|
205
|
+
|
|
206
|
+
expect(inputs[0]).toHaveValue('1')
|
|
207
|
+
expect(inputs[1]).toHaveValue('2')
|
|
208
|
+
expect(inputs[2]).toHaveValue('')
|
|
209
|
+
expect(inputs[3]).toHaveValue('')
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
// Validation and type tests
|
|
213
|
+
test('number type only accepts digits', () => {
|
|
214
|
+
renderOTP({ type: 'number' })
|
|
215
|
+
|
|
216
|
+
const inputs = screen.getAllByRole('textbox')
|
|
217
|
+
fireEvent.input(inputs[0], { target: { value: 'a' } })
|
|
218
|
+
|
|
219
|
+
expect(inputs[0]).toHaveValue('')
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
test('text type accepts any character', () => {
|
|
223
|
+
renderOTP({ type: 'text' })
|
|
224
|
+
|
|
225
|
+
const inputs = screen.getAllByRole('textbox')
|
|
226
|
+
fireEvent.input(inputs[0], { target: { value: 'a' } })
|
|
227
|
+
|
|
228
|
+
expect(inputs[0]).toHaveValue('a')
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
// Completion and submission tests
|
|
232
|
+
test('calls onComplete when all fields are filled', () => {
|
|
233
|
+
const onComplete = jest.fn()
|
|
234
|
+
renderOTP({ onComplete })
|
|
235
|
+
|
|
236
|
+
const inputs = screen.getAllByRole('textbox')
|
|
237
|
+
fireEvent.input(inputs[0], { target: { value: '1' } })
|
|
238
|
+
fireEvent.input(inputs[1], { target: { value: '2' } })
|
|
239
|
+
fireEvent.input(inputs[2], { target: { value: '3' } })
|
|
240
|
+
fireEvent.input(inputs[3], { target: { value: '4' } })
|
|
241
|
+
|
|
242
|
+
expect(onComplete).toHaveBeenCalledWith('1234')
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
test('autoSubmit submits form when complete', async () => {
|
|
246
|
+
const onSubmit = jest.fn((e) => e.preventDefault())
|
|
247
|
+
|
|
248
|
+
render(
|
|
249
|
+
<form onSubmit={onSubmit}>
|
|
250
|
+
<COneTimePassword autoSubmit>
|
|
251
|
+
<COneTimePasswordInput />
|
|
252
|
+
<COneTimePasswordInput />
|
|
253
|
+
<COneTimePasswordInput />
|
|
254
|
+
<COneTimePasswordInput />
|
|
255
|
+
</COneTimePassword>
|
|
256
|
+
</form>
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
const inputs = screen.getAllByRole('textbox')
|
|
260
|
+
fireEvent.input(inputs[0], { target: { value: '1' } })
|
|
261
|
+
fireEvent.input(inputs[1], { target: { value: '2' } })
|
|
262
|
+
fireEvent.input(inputs[2], { target: { value: '3' } })
|
|
263
|
+
fireEvent.input(inputs[3], { target: { value: '4' } })
|
|
264
|
+
|
|
265
|
+
await waitFor(() => {
|
|
266
|
+
expect(onSubmit).toHaveBeenCalled()
|
|
267
|
+
})
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
// Disabled and readonly tests
|
|
271
|
+
test('disabled prop disables all inputs', () => {
|
|
272
|
+
renderOTP({ disabled: true })
|
|
273
|
+
|
|
274
|
+
const inputs = screen.getAllByRole('textbox')
|
|
275
|
+
inputs.forEach((input) => {
|
|
276
|
+
expect(input).toBeDisabled()
|
|
277
|
+
})
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
test('readOnly prop makes all inputs readonly', () => {
|
|
281
|
+
renderOTP({ readOnly: true })
|
|
282
|
+
|
|
283
|
+
const inputs = screen.getAllByRole('textbox')
|
|
284
|
+
inputs.forEach((input) => {
|
|
285
|
+
expect(input).toHaveAttribute('readonly')
|
|
286
|
+
})
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
// Masked input tests
|
|
290
|
+
test('masked prop sets input type to password', () => {
|
|
291
|
+
renderOTP({ masked: true })
|
|
292
|
+
|
|
293
|
+
const inputs = document.querySelectorAll('input[type="password"]')
|
|
294
|
+
expect(inputs).toHaveLength(4)
|
|
295
|
+
inputs.forEach((input) => {
|
|
296
|
+
expect(input).toHaveAttribute('type', 'password')
|
|
297
|
+
})
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
// Hidden input tests
|
|
301
|
+
test('creates hidden input with combined value', () => {
|
|
302
|
+
renderOTP({ name: 'otp', id: 'test-otp' })
|
|
303
|
+
|
|
304
|
+
const hiddenInput = document.querySelector('input[type="hidden"]')
|
|
305
|
+
expect(hiddenInput).toBeInTheDocument()
|
|
306
|
+
expect(hiddenInput).toHaveAttribute('name', 'otp')
|
|
307
|
+
expect(hiddenInput).toHaveAttribute('id', 'test-otp')
|
|
308
|
+
|
|
309
|
+
const inputs = screen.getAllByRole('textbox')
|
|
310
|
+
fireEvent.input(inputs[0], { target: { value: '9' } })
|
|
311
|
+
|
|
312
|
+
expect(hiddenInput).toHaveValue('9')
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
// Accessibility tests
|
|
316
|
+
test('provides proper ARIA labels', () => {
|
|
317
|
+
renderOTP()
|
|
318
|
+
|
|
319
|
+
const inputs = screen.getAllByRole('textbox')
|
|
320
|
+
expect(inputs[0]).toHaveAttribute('aria-label', 'Digit 1 of 4')
|
|
321
|
+
expect(inputs[1]).toHaveAttribute('aria-label', 'Digit 2 of 4')
|
|
322
|
+
expect(inputs[2]).toHaveAttribute('aria-label', 'Digit 3 of 4')
|
|
323
|
+
expect(inputs[3]).toHaveAttribute('aria-label', 'Digit 4 of 4')
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
test('custom ariaLabel function works', () => {
|
|
327
|
+
renderOTP({ ariaLabel: customAriaLabel })
|
|
328
|
+
|
|
329
|
+
const inputs = screen.getAllByRole('textbox')
|
|
330
|
+
expect(inputs[0]).toHaveAttribute('aria-label', 'Code digit 1 of 4')
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
test('applies form validation classes', () => {
|
|
334
|
+
renderOTP({ invalid: true })
|
|
335
|
+
|
|
336
|
+
const inputs = screen.getAllByRole('textbox')
|
|
337
|
+
inputs.forEach((input) => {
|
|
338
|
+
expect(input).toHaveClass('is-invalid')
|
|
339
|
+
})
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
// Placeholder tests
|
|
343
|
+
test('single character placeholder applies to all inputs', () => {
|
|
344
|
+
renderOTP({ placeholder: '•' })
|
|
345
|
+
|
|
346
|
+
const inputs = screen.getAllByRole('textbox')
|
|
347
|
+
inputs.forEach((input) => {
|
|
348
|
+
expect(input).toHaveAttribute('placeholder', '•')
|
|
349
|
+
})
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
test('multi-character placeholder distributes by index', () => {
|
|
353
|
+
renderOTP({ placeholder: 'ABCD' })
|
|
354
|
+
|
|
355
|
+
const inputs = screen.getAllByRole('textbox')
|
|
356
|
+
expect(inputs[0]).toHaveAttribute('placeholder', 'A')
|
|
357
|
+
expect(inputs[1]).toHaveAttribute('placeholder', 'B')
|
|
358
|
+
expect(inputs[2]).toHaveAttribute('placeholder', 'C')
|
|
359
|
+
expect(inputs[3]).toHaveAttribute('placeholder', 'D')
|
|
360
|
+
})
|
|
361
|
+
})
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`COneTimePassword COneTimePassword customize props 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<div
|
|
6
|
+
class="form-otp form-otp-lg custom-otp"
|
|
7
|
+
role="group"
|
|
8
|
+
>
|
|
9
|
+
<input
|
|
10
|
+
aria-label="Digit 1 of 4"
|
|
11
|
+
autocomplete="off"
|
|
12
|
+
class="form-otp-control"
|
|
13
|
+
disabled=""
|
|
14
|
+
inputmode="text"
|
|
15
|
+
maxlength="1"
|
|
16
|
+
pattern=".*"
|
|
17
|
+
placeholder="•"
|
|
18
|
+
readonly=""
|
|
19
|
+
tabindex="0"
|
|
20
|
+
type="text"
|
|
21
|
+
value=""
|
|
22
|
+
/>
|
|
23
|
+
<input
|
|
24
|
+
aria-label="Digit 2 of 4"
|
|
25
|
+
autocomplete="off"
|
|
26
|
+
class="form-otp-control"
|
|
27
|
+
disabled=""
|
|
28
|
+
inputmode="text"
|
|
29
|
+
maxlength="1"
|
|
30
|
+
pattern=".*"
|
|
31
|
+
placeholder="•"
|
|
32
|
+
readonly=""
|
|
33
|
+
tabindex="-1"
|
|
34
|
+
type="text"
|
|
35
|
+
value=""
|
|
36
|
+
/>
|
|
37
|
+
<input
|
|
38
|
+
aria-label="Digit 3 of 4"
|
|
39
|
+
autocomplete="off"
|
|
40
|
+
class="form-otp-control"
|
|
41
|
+
disabled=""
|
|
42
|
+
inputmode="text"
|
|
43
|
+
maxlength="1"
|
|
44
|
+
pattern=".*"
|
|
45
|
+
placeholder="•"
|
|
46
|
+
readonly=""
|
|
47
|
+
tabindex="-1"
|
|
48
|
+
type="text"
|
|
49
|
+
value=""
|
|
50
|
+
/>
|
|
51
|
+
<input
|
|
52
|
+
aria-label="Digit 4 of 4"
|
|
53
|
+
autocomplete="off"
|
|
54
|
+
class="form-otp-control"
|
|
55
|
+
disabled=""
|
|
56
|
+
inputmode="text"
|
|
57
|
+
maxlength="1"
|
|
58
|
+
pattern=".*"
|
|
59
|
+
placeholder="•"
|
|
60
|
+
readonly=""
|
|
61
|
+
tabindex="-1"
|
|
62
|
+
type="text"
|
|
63
|
+
value=""
|
|
64
|
+
/>
|
|
65
|
+
<input
|
|
66
|
+
disabled=""
|
|
67
|
+
type="hidden"
|
|
68
|
+
value=""
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
exports[`COneTimePassword loads and displays COneTimePassword component 1`] = `
|
|
75
|
+
<div>
|
|
76
|
+
<div
|
|
77
|
+
class="form-otp"
|
|
78
|
+
role="group"
|
|
79
|
+
>
|
|
80
|
+
<input
|
|
81
|
+
aria-label="Digit 1 of 4"
|
|
82
|
+
autocomplete="off"
|
|
83
|
+
class="form-otp-control"
|
|
84
|
+
inputmode="numeric"
|
|
85
|
+
maxlength="1"
|
|
86
|
+
pattern="[0-9]*"
|
|
87
|
+
tabindex="0"
|
|
88
|
+
type="text"
|
|
89
|
+
value=""
|
|
90
|
+
/>
|
|
91
|
+
<input
|
|
92
|
+
aria-label="Digit 2 of 4"
|
|
93
|
+
autocomplete="off"
|
|
94
|
+
class="form-otp-control"
|
|
95
|
+
inputmode="numeric"
|
|
96
|
+
maxlength="1"
|
|
97
|
+
pattern="[0-9]*"
|
|
98
|
+
tabindex="-1"
|
|
99
|
+
type="text"
|
|
100
|
+
value=""
|
|
101
|
+
/>
|
|
102
|
+
<input
|
|
103
|
+
aria-label="Digit 3 of 4"
|
|
104
|
+
autocomplete="off"
|
|
105
|
+
class="form-otp-control"
|
|
106
|
+
inputmode="numeric"
|
|
107
|
+
maxlength="1"
|
|
108
|
+
pattern="[0-9]*"
|
|
109
|
+
tabindex="-1"
|
|
110
|
+
type="text"
|
|
111
|
+
value=""
|
|
112
|
+
/>
|
|
113
|
+
<input
|
|
114
|
+
aria-label="Digit 4 of 4"
|
|
115
|
+
autocomplete="off"
|
|
116
|
+
class="form-otp-control"
|
|
117
|
+
inputmode="numeric"
|
|
118
|
+
maxlength="1"
|
|
119
|
+
pattern="[0-9]*"
|
|
120
|
+
tabindex="-1"
|
|
121
|
+
type="text"
|
|
122
|
+
value=""
|
|
123
|
+
/>
|
|
124
|
+
<input
|
|
125
|
+
type="hidden"
|
|
126
|
+
value=""
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
`;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { COneTimePassword } from './COneTimePassword'
|
|
2
|
+
import { COneTimePasswordInput } from './COneTimePasswordInput'
|
|
3
|
+
|
|
4
|
+
export { COneTimePassword, COneTimePasswordInput }
|
|
5
|
+
export type { COneTimePasswordProps } from './COneTimePassword'
|
|
6
|
+
export type { COneTimePasswordInputProps } from './COneTimePasswordInput'
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const isValidInput = (inputValue: string, type: 'number' | 'text'): boolean => {
|
|
2
|
+
if (inputValue.length !== 1) return false
|
|
3
|
+
|
|
4
|
+
switch (type) {
|
|
5
|
+
case 'number': {
|
|
6
|
+
return /^\d$/.test(inputValue)
|
|
7
|
+
}
|
|
8
|
+
default: {
|
|
9
|
+
return /^.$/.test(inputValue)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const extractValidChars = (text: string, type: 'number' | 'text'): string => {
|
|
15
|
+
switch (type) {
|
|
16
|
+
case 'number': {
|
|
17
|
+
return text.replaceAll(/\D/g, '')
|
|
18
|
+
}
|
|
19
|
+
default: {
|
|
20
|
+
return text
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|