@reliability-design/web 1.4.5 → 1.4.7
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/CHANGELOG.md +211 -49
- package/dist/components/rel-accordion/README.md +132 -132
- package/dist/components/rel-alert/README.md +145 -141
- package/dist/components/rel-autocomplete/README.md +156 -61
- package/dist/components/rel-avatar/README.md +116 -130
- package/dist/components/rel-badge/README.md +98 -93
- package/dist/components/rel-breadcrumb/README.md +96 -102
- package/dist/components/rel-button/README.md +160 -130
- package/dist/components/rel-calendar/README.md +109 -109
- package/dist/components/rel-card/README.md +75 -94
- package/dist/components/rel-cardmedia/README.md +100 -127
- package/dist/components/rel-carousel/README.md +184 -166
- package/dist/components/rel-checkbox/README.md +93 -75
- package/dist/components/rel-chip/README.md +132 -121
- package/dist/components/rel-container/README.md +107 -99
- package/dist/components/rel-date-picker/README.md +200 -289
- package/dist/components/rel-divider/README.md +78 -100
- package/dist/components/rel-drawer/README.md +187 -200
- package/dist/components/rel-kbd/README.md +141 -151
- package/dist/components/rel-menu/README.md +178 -151
- package/dist/components/rel-menubar/README.md +218 -329
- package/dist/components/rel-otp/README.md +170 -440
- package/dist/components/rel-pagination/README.md +100 -424
- package/dist/components/rel-popover/README.md +203 -568
- package/dist/components/rel-progress/README.md +88 -110
- package/dist/components/rel-radio/README.md +131 -135
- package/dist/components/rel-range-picker/README.md +162 -0
- package/dist/components/rel-resizable/README.md +199 -140
- package/dist/components/rel-segmented-button/README.md +155 -141
- package/dist/components/rel-select/README.md +154 -93
- package/dist/components/rel-sheet/README.md +168 -224
- package/dist/components/rel-skeleton/README.md +103 -0
- package/dist/components/rel-slider/README.md +130 -135
- package/dist/components/rel-snackbar/README.md +162 -134
- package/dist/components/rel-stepper/README.md +158 -160
- package/dist/components/rel-switch/README.md +108 -102
- package/dist/components/rel-tab/README.md +171 -163
- package/dist/components/rel-textfield/README.md +159 -69
- package/dist/components/rel-time-picker/README.md +122 -157
- package/dist/components/rel-toast/README.md +157 -148
- package/dist/components/rel-tooltip/README.md +108 -103
- package/dist/components/rel-upload/README.md +220 -0
- package/dist/index.d.mts +1387 -350
- package/dist/index.d.ts +1387 -350
- package/dist/index.js +5863 -3600
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5864 -3592
- package/dist/index.mjs.map +1 -1
- package/dist/styles/vars.css +877 -866
- package/dist/{vars-XNMTSRVN.css → vars-JHTUGNRG.css} +877 -866
- package/package.json +4 -3
- package/dist/components/rel-accordion/rel-accordion.ts +0 -336
- package/dist/components/rel-alert/rel-alert.ts +0 -305
- package/dist/components/rel-autocomplete/rel-autocomplete.ts +0 -689
- package/dist/components/rel-avatar/rel-avatar.ts +0 -142
- package/dist/components/rel-badge/rel-badge.ts +0 -170
- package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +0 -106
- package/dist/components/rel-button/rel-button.ts +0 -560
- package/dist/components/rel-calendar/index.ts +0 -1
- package/dist/components/rel-calendar/rel-calendar.ts +0 -519
- package/dist/components/rel-card/rel-card.ts +0 -88
- package/dist/components/rel-cardmedia/rel-cardmedia.ts +0 -88
- package/dist/components/rel-carousel/index.ts +0 -1
- package/dist/components/rel-carousel/rel-carousel.ts +0 -700
- package/dist/components/rel-chart/README.md +0 -286
- package/dist/components/rel-chart/index.ts +0 -1
- package/dist/components/rel-chart/rel-chart.ts +0 -754
- package/dist/components/rel-checkbox/rel-checkbox.ts +0 -382
- package/dist/components/rel-chip/rel-chip.ts +0 -472
- package/dist/components/rel-container/rel-container.ts +0 -95
- package/dist/components/rel-date-picker/date-format.test.ts +0 -43
- package/dist/components/rel-date-picker/date-format.ts +0 -47
- package/dist/components/rel-date-picker/index.ts +0 -1
- package/dist/components/rel-date-picker/rel-date-picker.ts +0 -1237
- package/dist/components/rel-divider/rel-divider.ts +0 -54
- package/dist/components/rel-drawer/index.ts +0 -1
- package/dist/components/rel-drawer/rel-drawer-footer.ts +0 -27
- package/dist/components/rel-drawer/rel-drawer.ts +0 -604
- package/dist/components/rel-kbd/rel-kbd.ts +0 -188
- package/dist/components/rel-layout/rel-layout.ts +0 -49
- package/dist/components/rel-menu/rel-menu.ts +0 -535
- package/dist/components/rel-menubar/index.ts +0 -2
- package/dist/components/rel-menubar/rel-menubar-menu.css +0 -3
- package/dist/components/rel-menubar/rel-menubar-menu.ts +0 -17
- package/dist/components/rel-menubar/rel-menubar.ts +0 -777
- package/dist/components/rel-nav-menu/README.md +0 -289
- package/dist/components/rel-nav-menu/index.ts +0 -3
- package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +0 -243
- package/dist/components/rel-nav-menu/rel-nav-menu-panel.ts +0 -72
- package/dist/components/rel-nav-menu/rel-nav-menu.ts +0 -270
- package/dist/components/rel-navbar/README.md +0 -126
- package/dist/components/rel-navbar/rel-navbar.ts +0 -105
- package/dist/components/rel-otp/index.ts +0 -1
- package/dist/components/rel-otp/rel-otp.ts +0 -517
- package/dist/components/rel-pagination/index.ts +0 -1
- package/dist/components/rel-pagination/rel-pagination.ts +0 -509
- package/dist/components/rel-popover/index.ts +0 -2
- package/dist/components/rel-popover/rel-popover-content.ts +0 -81
- package/dist/components/rel-popover/rel-popover.ts +0 -444
- package/dist/components/rel-progress/rel-progress.ts +0 -225
- package/dist/components/rel-radio/rel-radio.ts +0 -305
- package/dist/components/rel-range-picker/rel-range-picker.ts +0 -1331
- package/dist/components/rel-resizable/index.ts +0 -3
- package/dist/components/rel-resizable/rel-resizable-group.ts +0 -405
- package/dist/components/rel-resizable/rel-resizable-handle.ts +0 -150
- package/dist/components/rel-resizable/rel-resizable-panel.ts +0 -58
- package/dist/components/rel-segmented-button/rel-segmented-button.ts +0 -213
- package/dist/components/rel-select/rel-select.ts +0 -1385
- package/dist/components/rel-sheet/rel-sheet.ts +0 -465
- package/dist/components/rel-sidebar/README.md +0 -209
- package/dist/components/rel-sidebar/rel-sidebar.ts +0 -1185
- package/dist/components/rel-skeleton/rel-skeleton.ts +0 -134
- package/dist/components/rel-slider/rel-slider.ts +0 -670
- package/dist/components/rel-snackbar/rel-snackbar.ts +0 -325
- package/dist/components/rel-stepper/rel-stepper.ts +0 -565
- package/dist/components/rel-switch/rel-switch.ts +0 -197
- package/dist/components/rel-tab/rel-tab.ts +0 -264
- package/dist/components/rel-table/README.md +0 -178
- package/dist/components/rel-table/rel-table-body.ts +0 -22
- package/dist/components/rel-table/rel-table-caption.ts +0 -30
- package/dist/components/rel-table/rel-table-cell.ts +0 -43
- package/dist/components/rel-table/rel-table-footer.ts +0 -24
- package/dist/components/rel-table/rel-table-head.ts +0 -47
- package/dist/components/rel-table/rel-table-header.ts +0 -22
- package/dist/components/rel-table/rel-table-row.ts +0 -37
- package/dist/components/rel-table/rel-table.ts +0 -62
- package/dist/components/rel-textfield/rel-textfield.ts +0 -866
- package/dist/components/rel-time-picker/rel-time-picker.ts +0 -1126
- package/dist/components/rel-toast/rel-toast.ts +0 -204
- package/dist/components/rel-tooltip/rel-tooltip.ts +0 -190
- package/dist/components/rel-topbar/rel-topbar-actions.ts +0 -27
- package/dist/components/rel-topbar/rel-topbar-brand.ts +0 -54
- package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +0 -69
- package/dist/components/rel-topbar/rel-topbar-dropdown.ts +0 -31
- package/dist/components/rel-topbar/rel-topbar-item.ts +0 -324
- package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +0 -27
- package/dist/components/rel-topbar/rel-topbar-profile.ts +0 -243
- package/dist/components/rel-topbar/rel-topbar-utilities.ts +0 -27
- package/dist/components/rel-topbar/rel-topbar.ts +0 -446
- package/dist/components/rel-upload/UPLOAD-DESIGN-CHANGES.md +0 -134
- package/dist/components/rel-upload/rel-upload-actions.ts +0 -163
- package/dist/components/rel-upload/rel-upload.ts +0 -943
|
@@ -1,440 +1,170 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
<rel-otp
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
otpInput.invalid = true;
|
|
172
|
-
}
|
|
173
|
-
</script>
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### Programmatic Control
|
|
177
|
-
|
|
178
|
-
```javascript
|
|
179
|
-
const otp = document.querySelector('rel-otp');
|
|
180
|
-
|
|
181
|
-
// Set value programmatically
|
|
182
|
-
otp.value = "123456";
|
|
183
|
-
|
|
184
|
-
// Clear value
|
|
185
|
-
otp.value = "";
|
|
186
|
-
|
|
187
|
-
// Mark as invalid
|
|
188
|
-
otp.invalid = true;
|
|
189
|
-
|
|
190
|
-
// Disable
|
|
191
|
-
otp.disabled = true;
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### Without Separators
|
|
195
|
-
|
|
196
|
-
```html
|
|
197
|
-
<rel-otp
|
|
198
|
-
length="6"
|
|
199
|
-
show-separator="false">
|
|
200
|
-
</rel-otp>
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
### Different Sizes
|
|
204
|
-
|
|
205
|
-
```html
|
|
206
|
-
<!-- Small -->
|
|
207
|
-
<rel-otp size="sm" length="4"></rel-otp>
|
|
208
|
-
|
|
209
|
-
<!-- Medium (default) -->
|
|
210
|
-
<rel-otp size="md" length="6"></rel-otp>
|
|
211
|
-
|
|
212
|
-
<!-- Large -->
|
|
213
|
-
<rel-otp size="lg" length="6"></rel-otp>
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
## Mobile OTP Autofill
|
|
217
|
-
|
|
218
|
-
The component supports native mobile OTP autofill on iOS and Android:
|
|
219
|
-
|
|
220
|
-
- Sets `autocomplete="one-time-code"` on the first input
|
|
221
|
-
- When the browser/OS injects the full code into the first cell, the component automatically distributes it across all cells
|
|
222
|
-
- Works with SMS-based verification codes
|
|
223
|
-
|
|
224
|
-
```html
|
|
225
|
-
<rel-otp
|
|
226
|
-
length="6"
|
|
227
|
-
autocomplete="one-time-code">
|
|
228
|
-
</rel-otp>
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
## Accessibility
|
|
232
|
-
|
|
233
|
-
- **Role**: The container has `role="group"` with a descriptive `aria-label`
|
|
234
|
-
- **Cell Labels**: Each input has `aria-label` like "Digit 1 of 6"
|
|
235
|
-
- **Input Mode**: Sets appropriate `inputmode` for mobile keyboards
|
|
236
|
-
- `numeric` type → `inputmode="numeric"`
|
|
237
|
-
- `alphanumeric` type → `inputmode="text"`
|
|
238
|
-
- **Focus Management**: Auto-advances focus, keyboard navigation supported
|
|
239
|
-
- **States**: `disabled`, `readonly`, `required`, and `invalid` states properly reflected
|
|
240
|
-
|
|
241
|
-
```html
|
|
242
|
-
<rel-otp
|
|
243
|
-
aria-label="Enter your 6-digit verification code"
|
|
244
|
-
length="6">
|
|
245
|
-
</rel-otp>
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
## Styling
|
|
249
|
-
|
|
250
|
-
The OTP component uses design system tokens for all styling. Customize via CSS parts:
|
|
251
|
-
|
|
252
|
-
```css
|
|
253
|
-
rel-otp::part(cell) {
|
|
254
|
-
border-color: var(--rel-bg-info);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
rel-otp::part(cell):focus {
|
|
258
|
-
box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.2);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
rel-otp::part(separator) {
|
|
262
|
-
color: var(--rel-bg-info-light);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/* Custom invalid styling */
|
|
266
|
-
rel-otp[invalid]::part(cell) {
|
|
267
|
-
border-color: var(--rel-text-danger);
|
|
268
|
-
animation: shake 0.3s;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
@keyframes shake {
|
|
272
|
-
0%, 100% { transform: translateX(0); }
|
|
273
|
-
25% { transform: translateX(-4px); }
|
|
274
|
-
75% { transform: translateX(4px); }
|
|
275
|
-
}
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
## CSS Parts
|
|
279
|
-
|
|
280
|
-
| Part | Description |
|
|
281
|
-
|------|-------------|
|
|
282
|
-
| `root` | Root container element |
|
|
283
|
-
| `cell` | Individual input cell |
|
|
284
|
-
| `separator` | Separator between groups |
|
|
285
|
-
| `hidden-input` | Hidden input for form submission |
|
|
286
|
-
|
|
287
|
-
## Framework Integration
|
|
288
|
-
|
|
289
|
-
### React
|
|
290
|
-
|
|
291
|
-
```jsx
|
|
292
|
-
import { RelOtp } from '@reliability-design/web';
|
|
293
|
-
|
|
294
|
-
function OtpForm() {
|
|
295
|
-
const [otp, setOtp] = useState('');
|
|
296
|
-
const [isInvalid, setIsInvalid] = useState(false);
|
|
297
|
-
|
|
298
|
-
const handleChange = (e) => {
|
|
299
|
-
setOtp(e.detail.value);
|
|
300
|
-
setIsInvalid(false);
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
const handleComplete = async (e) => {
|
|
304
|
-
const code = e.detail.value;
|
|
305
|
-
const valid = await verifyOtp(code);
|
|
306
|
-
setIsInvalid(!valid);
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
return (
|
|
310
|
-
<RelOtp
|
|
311
|
-
length={6}
|
|
312
|
-
onRelChange={handleChange}
|
|
313
|
-
onRelComplete={handleComplete}
|
|
314
|
-
invalid={isInvalid}
|
|
315
|
-
/>
|
|
316
|
-
);
|
|
317
|
-
}
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
### Angular
|
|
321
|
-
|
|
322
|
-
```html
|
|
323
|
-
<rel-otp
|
|
324
|
-
[length]="6"
|
|
325
|
-
[group-size]="3"
|
|
326
|
-
(rel-complete)="onOtpComplete($event)"
|
|
327
|
-
[invalid]="isInvalid">
|
|
328
|
-
</rel-otp>
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
```typescript
|
|
332
|
-
export class OtpComponent {
|
|
333
|
-
isInvalid = false;
|
|
334
|
-
|
|
335
|
-
async onOtpComplete(event: CustomEvent) {
|
|
336
|
-
const code = event.detail.value;
|
|
337
|
-
const valid = await this.verifyOtp(code);
|
|
338
|
-
this.isInvalid = !valid;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
### Vue
|
|
344
|
-
|
|
345
|
-
```vue
|
|
346
|
-
<template>
|
|
347
|
-
<rel-otp
|
|
348
|
-
:length="6"
|
|
349
|
-
:group-size="3"
|
|
350
|
-
:invalid="isInvalid"
|
|
351
|
-
@rel-complete="handleComplete"
|
|
352
|
-
/>
|
|
353
|
-
</template>
|
|
354
|
-
|
|
355
|
-
<script setup>
|
|
356
|
-
import { ref } from 'vue';
|
|
357
|
-
|
|
358
|
-
const isInvalid = ref(false);
|
|
359
|
-
|
|
360
|
-
async function handleComplete(event) {
|
|
361
|
-
const code = event.detail.value;
|
|
362
|
-
const valid = await verifyOtp(code);
|
|
363
|
-
isInvalid.value = !valid;
|
|
364
|
-
}
|
|
365
|
-
</script>
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
## Validation
|
|
369
|
-
|
|
370
|
-
The component provides basic character validation (numeric or alphanumeric) but does not validate the actual OTP code. Implement your own verification logic:
|
|
371
|
-
|
|
372
|
-
```javascript
|
|
373
|
-
const otp = document.querySelector('rel-otp');
|
|
374
|
-
|
|
375
|
-
otp.addEventListener('rel-complete', async (e) => {
|
|
376
|
-
const code = e.detail.value;
|
|
377
|
-
|
|
378
|
-
try {
|
|
379
|
-
const response = await fetch('/api/verify-otp', {
|
|
380
|
-
method: 'POST',
|
|
381
|
-
body: JSON.stringify({ code }),
|
|
382
|
-
headers: { 'Content-Type': 'application/json' }
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
if (response.ok) {
|
|
386
|
-
// Success
|
|
387
|
-
console.log('OTP verified!');
|
|
388
|
-
} else {
|
|
389
|
-
// Invalid
|
|
390
|
-
otp.invalid = true;
|
|
391
|
-
otp.value = ''; // Clear for retry
|
|
392
|
-
}
|
|
393
|
-
} catch (error) {
|
|
394
|
-
console.error('Verification failed:', error);
|
|
395
|
-
}
|
|
396
|
-
});
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
## Common Patterns
|
|
400
|
-
|
|
401
|
-
### Resend Timer
|
|
402
|
-
|
|
403
|
-
```html
|
|
404
|
-
<rel-otp id="otp" length="6"></rel-otp>
|
|
405
|
-
<button id="resend" disabled>Resend code in <span id="timer">60</span>s</button>
|
|
406
|
-
|
|
407
|
-
<script>
|
|
408
|
-
let timeLeft = 60;
|
|
409
|
-
const timer = document.getElementById('timer');
|
|
410
|
-
const resendBtn = document.getElementById('resend');
|
|
411
|
-
|
|
412
|
-
const countdown = setInterval(() => {
|
|
413
|
-
timeLeft--;
|
|
414
|
-
timer.textContent = timeLeft;
|
|
415
|
-
|
|
416
|
-
if (timeLeft <= 0) {
|
|
417
|
-
clearInterval(countdown);
|
|
418
|
-
resendBtn.disabled = false;
|
|
419
|
-
resendBtn.textContent = 'Resend code';
|
|
420
|
-
}
|
|
421
|
-
}, 1000);
|
|
422
|
-
</script>
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
### Auto-Submit on Complete
|
|
426
|
-
|
|
427
|
-
```javascript
|
|
428
|
-
const otp = document.querySelector('rel-otp');
|
|
429
|
-
|
|
430
|
-
otp.addEventListener('rel-complete', async (e) => {
|
|
431
|
-
// Show loading state
|
|
432
|
-
otp.disabled = true;
|
|
433
|
-
|
|
434
|
-
// Auto-submit
|
|
435
|
-
await submitOtp(e.detail.value);
|
|
436
|
-
|
|
437
|
-
// Re-enable if failed
|
|
438
|
-
otp.disabled = false;
|
|
439
|
-
});
|
|
440
|
-
```
|
|
1
|
+
# rel-otp
|
|
2
|
+
|
|
3
|
+
A one-time-password input implementing the Figma "OTP Input" cell: one single-character `<input>` per position, with auto-advance, smart backspace, paste distribution, mobile OTP autofill and optional group separators. The cells accept digits only (`input-mode="numeric"`, the default) or digits plus ASCII letters (`input-mode="alphanumeric"`), and can be masked password-style. Use it for SMS / authenticator verification codes and PINs; for free-form text use `rel-textfield`.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<!-- 6-digit numeric code in two groups of three -->
|
|
9
|
+
<rel-otp length="6" group-size="3"></rel-otp>
|
|
10
|
+
|
|
11
|
+
<!-- Alphanumeric, masked, four per group, custom accessible name -->
|
|
12
|
+
<rel-otp
|
|
13
|
+
length="8"
|
|
14
|
+
input-mode="alphanumeric"
|
|
15
|
+
mask
|
|
16
|
+
group-size="4"
|
|
17
|
+
aria-label="Enter 8-character code">
|
|
18
|
+
</rel-otp>
|
|
19
|
+
|
|
20
|
+
<!-- Sizes: small 32px / medium 40px (default) / large 48px cells -->
|
|
21
|
+
<rel-otp size="small" length="4"></rel-otp>
|
|
22
|
+
<rel-otp size="medium" length="6"></rel-otp>
|
|
23
|
+
<rel-otp size="large" length="6"></rel-otp>
|
|
24
|
+
|
|
25
|
+
<!-- Form integration: `name` renders a hidden input carrying the value -->
|
|
26
|
+
<form id="verify-form">
|
|
27
|
+
<rel-otp id="otp-form" name="verification_code" length="6" required></rel-otp>
|
|
28
|
+
<button type="submit">Verify</button>
|
|
29
|
+
</form>
|
|
30
|
+
<script>
|
|
31
|
+
const form = document.getElementById('verify-form');
|
|
32
|
+
const otp = document.getElementById('otp-form');
|
|
33
|
+
|
|
34
|
+
form.addEventListener('submit', (e) => {
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
console.log('OTP:', new FormData(form).get('verification_code'));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
otp.addEventListener('rel-change', (e) => {
|
|
40
|
+
console.log('Value:', e.detail.value, 'Complete:', e.detail.complete);
|
|
41
|
+
otp.invalid = false;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
otp.addEventListener('rel-complete', async (e) => {
|
|
45
|
+
otp.disabled = true;
|
|
46
|
+
const ok = await verifyOtp(e.detail.value);
|
|
47
|
+
otp.disabled = false;
|
|
48
|
+
if (!ok) {
|
|
49
|
+
otp.invalid = true; // danger border + aria-invalid
|
|
50
|
+
otp.value = ''; // clear for retry
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<!-- No separators: show-separator is a boolean attribute (presence = true),
|
|
56
|
+
so hide the dashes by setting the property -->
|
|
57
|
+
<rel-otp id="otp-plain" length="6"></rel-otp>
|
|
58
|
+
<script>
|
|
59
|
+
document.getElementById('otp-plain').showSeparator = false;
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<!-- States -->
|
|
63
|
+
<rel-otp value="123456" readonly></rel-otp>
|
|
64
|
+
<rel-otp value="12" disabled></rel-otp>
|
|
65
|
+
<rel-otp invalid length="6"></rel-otp>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Deprecated aliases
|
|
69
|
+
|
|
70
|
+
```html
|
|
71
|
+
<!-- type= is a deprecated alias of input-mode; input-mode wins when both are set.
|
|
72
|
+
Logs once per page:
|
|
73
|
+
[rel-otp] "type" is deprecated and will be removed in 3.0 — use "input-mode" instead. It describes the accepted characters, not a semantic input type. -->
|
|
74
|
+
<rel-otp type="alphanumeric" length="8"></rel-otp>
|
|
75
|
+
|
|
76
|
+
<!-- sm / md / lg are deprecated aliases of size="small" / "medium" / "large" (no console notice) -->
|
|
77
|
+
<rel-otp size="lg" length="6"></rel-otp>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
<!-- readme-gen:start -->
|
|
81
|
+
|
|
82
|
+
<!-- Generated from rel-otp.ts — do not edit; run readme-generate.mjs -->
|
|
83
|
+
|
|
84
|
+
## API
|
|
85
|
+
|
|
86
|
+
### Attributes / Properties
|
|
87
|
+
|
|
88
|
+
| Property | Attribute | Type | Default | Description |
|
|
89
|
+
| --- | --- | --- | --- | --- |
|
|
90
|
+
| `value` | `value` | `string` | `""` | The concatenated code (all cells joined). Set it to fill the cells programmatically; it never exceeds `length` characters. Reflected. |
|
|
91
|
+
| `length` | `length` | `number` | `6` | Total number of cells (characters in the code). |
|
|
92
|
+
| `groupSize` | `group-size` | `number` | `3` | Cells per visual group; a separator renders between groups. `0` (or `show-separator` off) renders one ungrouped row. |
|
|
93
|
+
| `placeholder` | `placeholder` | `string` | `""` | Placeholder character shown in every empty cell. |
|
|
94
|
+
| `inputMode` | `input-mode` | `"numeric" \| "alphanumeric" \| ""` | `""` | Accepted character set (Trello 5). Describes what the field ACCEPTS, not a semantic input type — hence the name. Explicitly set it wins over the deprecated `type`; unset (`""`, like the native `inputMode` accessor it shadows on the host), `type` (default `"numeric"`) applies. Note the lowercase `inputmode` prop is a different thing: the raw native keyboard hint forwarded to each cell. |
|
|
95
|
+
| `mask` | `mask` | `boolean` | `false` | Renders the cells as `type="password"` so entered characters are masked. |
|
|
96
|
+
| `disabled` | `disabled` | `boolean` | `false` | Disables every cell and dims the control to 0.6 opacity. Reflected. |
|
|
97
|
+
| `readonly` | `readonly` | `boolean` | `false` | Makes every cell read-only: the value is shown but typing, paste and keyboard editing are ignored. Reflected. |
|
|
98
|
+
| `required` | `required` | `boolean` | `false` | Marks the first cell `required` for native form validation. |
|
|
99
|
+
| `name` | `name` | `string \| null` | `null` | When set, renders a hidden `<input name="…">` (part `hidden-input`) carrying `value` for native form submission. |
|
|
100
|
+
| `autocomplete` | `autocomplete` | `string` | `"one-time-code"` | Reserved autocomplete hint. Currently every cell always renders `autocomplete="one-time-code"` regardless of this value. |
|
|
101
|
+
| `inputmode` | `inputmode` | `"numeric" \| "text" \| null` | `null` | Raw native `inputmode` keyboard hint forwarded to every cell. Unset, it follows the accepted character set: `numeric` for numeric, `text` for alphanumeric. |
|
|
102
|
+
| `pattern` | `pattern` | `string \| null` | `null` | Native `pattern` attribute forwarded to every cell (per-character regex for form validation). |
|
|
103
|
+
| `ariaLabel` | `aria-label` | `string \| null` | `null` | Accessible name of the whole group; defaults to "One-time password input". Each cell is additionally labelled "Digit/Character N of M". |
|
|
104
|
+
| `invalid` | `invalid` | `boolean` | `false` | Error styling (danger border on every cell) and `aria-invalid="true"`. Reflected. |
|
|
105
|
+
| `showSeparator` | `show-separator` | `boolean` | `true` | Renders the dash separator between groups. Boolean attribute: its presence means true — set the property to `false` to hide separators. |
|
|
106
|
+
| `size` | `size` | `"small" \| "medium" \| "large" \| "sm" \| "md" \| "lg"` | `"medium"` | Cell size on the shared field scale (square, border-box): `small` 32px, `medium` 40px, `large` 48px (the Figma OTP Input cell). `sm` / `md` / `lg` are deprecated aliases of the full words. Reflected. |
|
|
107
|
+
|
|
108
|
+
#### Deprecated (still supported — removal targeted for 3.0)
|
|
109
|
+
|
|
110
|
+
| Property | Attribute | Type | Use instead |
|
|
111
|
+
| --- | --- | --- | --- |
|
|
112
|
+
| `type` | `type` | `"numeric" \| "alphanumeric"` | Deprecated alias of `inputMode` (attribute `input-mode`); use `input-mode` instead. Kept working in 1.x and logs a one-time console notice. |
|
|
113
|
+
|
|
114
|
+
## Events
|
|
115
|
+
|
|
116
|
+
| Event | Detail | Bubbles / composed | Description |
|
|
117
|
+
| --- | --- | --- | --- |
|
|
118
|
+
| `rel-change` | `{ value: newValue, complete }` | bubbles / composed | Fired on every value change (typing, backspace, delete, paste). Detail: `{ value: string, complete: boolean }` — `complete` is true when all cells hold a valid character. |
|
|
119
|
+
| `rel-complete` | `{ value: newValue }` | bubbles / composed | Fired after `rel-change` once every cell holds a valid character. Detail: `{ value: string }` (the full code). |
|
|
120
|
+
|
|
121
|
+
## Slots
|
|
122
|
+
|
|
123
|
+
| Slot | Description |
|
|
124
|
+
| --- | --- |
|
|
125
|
+
| — | This element has no slots. |
|
|
126
|
+
|
|
127
|
+
## CSS Parts
|
|
128
|
+
|
|
129
|
+
| Part | Description |
|
|
130
|
+
| --- | --- |
|
|
131
|
+
| `base` | The `role="group"` root container (same element as `root`) |
|
|
132
|
+
| `root` | Alias of `base`, kept for existing `::part(root)` selectors |
|
|
133
|
+
| `input` | Each single-character cell input (same element as `cell`) |
|
|
134
|
+
| `cell` | Alias of `input`, kept for existing `::part(cell)` selectors |
|
|
135
|
+
| `separator` | The dash rendered between cell groups |
|
|
136
|
+
| `hidden-input` | The hidden form input rendered when `name` is set |
|
|
137
|
+
|
|
138
|
+
<!-- readme-gen:end -->
|
|
139
|
+
|
|
140
|
+
## Accessibility
|
|
141
|
+
|
|
142
|
+
- The root renders `role="group"` with `aria-label` from the `aria-label` attribute (default "One-time password input").
|
|
143
|
+
- Every cell is a native `<input maxlength="1">` labelled "Digit N of M" (numeric) or "Character N of M" (alphanumeric), so screen readers announce the position while focus auto-advances.
|
|
144
|
+
- `invalid` sets `aria-invalid="true"` on every cell; `disabled`, `readonly` and `required` (first cell only) are the native attributes.
|
|
145
|
+
- The native `inputmode` hint on each cell follows the accepted character set — `numeric` for `input-mode="numeric"`, `text` for `alphanumeric` — unless the raw `inputmode` prop overrides it. Every cell carries `autocomplete="one-time-code"` so iOS / Android can inject an SMS code; a multi-character injection into any cell is distributed like a paste.
|
|
146
|
+
- Focus management: focusing a cell selects its content (replace-on-type); a pointer-down on a cell beyond the first empty one is redirected to that empty cell so the code cannot be entered out of order; after paste focus lands on the next empty cell (or the last cell when full).
|
|
147
|
+
- Live-region announcements (via the shared `announce()` utility): "Code complete" when every cell holds a valid character, and "N digits/characters entered" after a paste that does not fill the code.
|
|
148
|
+
|
|
149
|
+
| Key | Action |
|
|
150
|
+
| --- | --- |
|
|
151
|
+
| `0-9` (numeric) / `0-9` `A-Z` `a-z` (alphanumeric) | Enter the character and advance to the next cell; other characters are rejected |
|
|
152
|
+
| `Backspace` | Clear the current cell; if it is already empty, move to the previous cell and clear it |
|
|
153
|
+
| `Delete` | Clear the current cell and stay |
|
|
154
|
+
| `ArrowLeft` / `ArrowRight` | Move to the previous / next cell |
|
|
155
|
+
| `Home` / `End` | Jump to the first / last cell |
|
|
156
|
+
| `ArrowUp` / `ArrowDown` | Ignored (prevented so the page does not scroll) |
|
|
157
|
+
| `Ctrl/Cmd+V` | Paste: valid characters are distributed from the current cell onward, overflow past `length` is dropped |
|
|
158
|
+
| `Tab` / `Shift+Tab` | Move focus between cells (each cell is a tab stop) |
|
|
159
|
+
|
|
160
|
+
## Notes / Edge Cases
|
|
161
|
+
|
|
162
|
+
- `value` is truncated to `length` characters when synced into the cells; changing `length` re-initialises the cells from `value`.
|
|
163
|
+
- `rel-change` fires on every edit; `rel-complete` fires right after it when all cells hold a valid character for the current mode — validate the code server-side, the component only checks the character set.
|
|
164
|
+
- `input-mode` unset (`""`) falls back to the deprecated `type` prop (default `"numeric"`); set `input-mode` explicitly and it always wins.
|
|
165
|
+
- The lowercase `inputmode` prop is the raw native keyboard hint, not the accepted character set — normally leave it unset.
|
|
166
|
+
- `show-separator` is a boolean attribute: `show-separator="false"` still enables separators. Hide them with `el.showSeparator = false` or `group-size="0"`.
|
|
167
|
+
- `required` is applied to the first cell only, so native form validation blocks submission when the code is empty but not when it is partial — check `e.detail.complete` yourself.
|
|
168
|
+
- The `autocomplete` prop currently has no effect: every cell always renders `autocomplete="one-time-code"`.
|
|
169
|
+
- `disabled` and `readonly` also suppress paste and keyboard handling; the value stays visible.
|
|
170
|
+
- Style cells via `::part(cell)` (alias `::part(input)`) and the root via `::part(base)` (alias `::part(root)`); the two names in each pair target the same element.
|