digitojs 1.2.2 → 1.2.4
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 +5 -5
- package/dist/digito-wc.min.js +1 -1
- package/dist/digito.min.js +1 -1
- package/package.json +2 -3
- package/src/adapters/alpine.ts +0 -691
- package/src/adapters/react.tsx +0 -648
- package/src/adapters/svelte.ts +0 -497
- package/src/adapters/vanilla.ts +0 -845
- package/src/adapters/vue.ts +0 -491
- package/src/adapters/web-component.ts +0 -898
- package/src/cdn.ts +0 -24
- package/src/core/feedback.ts +0 -44
- package/src/core/filter.ts +0 -48
- package/src/core/index.ts +0 -16
- package/src/core/machine.ts +0 -405
- package/src/core/timer.ts +0 -90
- package/src/core/types.ts +0 -199
- package/src/index.ts +0 -50
package/src/adapters/svelte.ts
DELETED
|
@@ -1,497 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* digito/svelte
|
|
3
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
-
* Svelte adapter — useOTP store + action (single hidden-input architecture)
|
|
5
|
-
*
|
|
6
|
-
* @author Olawale Balo — Product Designer + Design Engineer
|
|
7
|
-
* @license MIT
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { writable, derived, get, type Readable, type Writable } from 'svelte/store'
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
createDigito,
|
|
14
|
-
createTimer,
|
|
15
|
-
filterString,
|
|
16
|
-
type DigitoOptions,
|
|
17
|
-
type DigitoState,
|
|
18
|
-
type InputType,
|
|
19
|
-
} from '../core/index.js'
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
23
|
-
// TYPES
|
|
24
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Extended options for the Svelte useOTP composable.
|
|
28
|
-
* Adds controlled-input, separator, and disabled support on top of DigitoOptions.
|
|
29
|
-
*/
|
|
30
|
-
export type SvelteOTPOptions = DigitoOptions & {
|
|
31
|
-
/**
|
|
32
|
-
* Controlled value — drives the slot state from outside the composable.
|
|
33
|
-
* Pass a string of up to length characters to pre-fill or sync the field.
|
|
34
|
-
*/
|
|
35
|
-
value?: string
|
|
36
|
-
/**
|
|
37
|
-
* Fires exactly ONCE per user interaction with the current joined code string.
|
|
38
|
-
* Receives partial values too — not just when the code is complete.
|
|
39
|
-
*/
|
|
40
|
-
onChange?: (code: string) => void
|
|
41
|
-
/**
|
|
42
|
-
* Insert a purely visual separator after this slot index (0-based).
|
|
43
|
-
* Accepts a single position or an array for multiple separators.
|
|
44
|
-
* aria-hidden, never part of the value, no effect on the state machine.
|
|
45
|
-
* Default: 0 (no separator).
|
|
46
|
-
* @example separatorAfter: 3 -> [*][*][*] — [*][*][*]
|
|
47
|
-
* @example separatorAfter: [2, 4] -> [*][*] — [*][*] — [*][*]
|
|
48
|
-
*/
|
|
49
|
-
separatorAfter?: number | number[]
|
|
50
|
-
/**
|
|
51
|
-
* The character or string to render as the separator.
|
|
52
|
-
* Default: '—'
|
|
53
|
-
*/
|
|
54
|
-
separator?: string
|
|
55
|
-
/**
|
|
56
|
-
* When `true`, slot templates should display a mask glyph instead of the real
|
|
57
|
-
* character. The hidden input switches to `type="password"` via the action.
|
|
58
|
-
*
|
|
59
|
-
* `getCode()` and `onComplete` always return real characters.
|
|
60
|
-
* Use for PIN entry or any sensitive input flow.
|
|
61
|
-
*
|
|
62
|
-
* Default: `false`.
|
|
63
|
-
*/
|
|
64
|
-
masked?: boolean
|
|
65
|
-
/**
|
|
66
|
-
* The glyph displayed in filled slots when `masked` is `true`.
|
|
67
|
-
* Returned as a `writable` store so Svelte templates can subscribe to it.
|
|
68
|
-
*
|
|
69
|
-
* Default: `'●'` (U+25CF BLACK CIRCLE).
|
|
70
|
-
* @example maskChar: '*'
|
|
71
|
-
*/
|
|
72
|
-
maskChar?: string
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export type UseOTPResult = {
|
|
76
|
-
/** Subscribe to the full state store. */
|
|
77
|
-
subscribe: Writable<DigitoState>['subscribe']
|
|
78
|
-
/** Derived — joined code string. */
|
|
79
|
-
value: Readable<string>
|
|
80
|
-
/** Derived — completion boolean. */
|
|
81
|
-
isComplete: Readable<boolean>
|
|
82
|
-
/** Derived — error boolean. */
|
|
83
|
-
hasError: Readable<boolean>
|
|
84
|
-
/** Derived — active slot index. */
|
|
85
|
-
activeSlot: Readable<number>
|
|
86
|
-
/** Remaining timer seconds store. */
|
|
87
|
-
timerSeconds: Writable<number>
|
|
88
|
-
/** Whether the field is currently disabled. */
|
|
89
|
-
isDisabled: Writable<boolean>
|
|
90
|
-
/** The separator slot index store. */
|
|
91
|
-
separatorAfter: Writable<number | number[]>
|
|
92
|
-
/** The separator character store. */
|
|
93
|
-
separator: Writable<string>
|
|
94
|
-
/** Whether masked mode is active. When true, templates should render `maskChar` instead of char. */
|
|
95
|
-
masked: Writable<boolean>
|
|
96
|
-
/**
|
|
97
|
-
* The configured mask glyph store. Use in templates instead of a hard-coded `●`:
|
|
98
|
-
* `{$otp.masked && char ? $otp.maskChar : char}`
|
|
99
|
-
*/
|
|
100
|
-
maskChar: Writable<string>
|
|
101
|
-
/** The placeholder character for empty slots. Empty string when not set. */
|
|
102
|
-
placeholder: string
|
|
103
|
-
/** Derived — spread onto the wrapper element as data attributes for CSS/Tailwind targeting. */
|
|
104
|
-
wrapperAttrs: Readable<Record<string, string | undefined>>
|
|
105
|
-
/** Svelte action to bind to the single hidden input. */
|
|
106
|
-
action: (node: HTMLInputElement) => { destroy: () => void }
|
|
107
|
-
/** Returns the current joined code string. */
|
|
108
|
-
getCode: () => string
|
|
109
|
-
/** Clear all slots, restart timer, return focus to input. */
|
|
110
|
-
reset: () => void
|
|
111
|
-
/** Apply or clear the error state. */
|
|
112
|
-
setError: (isError: boolean) => void
|
|
113
|
-
/** Enable or disable the field at runtime. */
|
|
114
|
-
setDisabled: (value: boolean) => void
|
|
115
|
-
/**
|
|
116
|
-
* Toggle readOnly at runtime. When `true`, all slot mutations are blocked
|
|
117
|
-
* but focus, navigation, and copy remain fully functional.
|
|
118
|
-
*/
|
|
119
|
-
setReadOnly: (value: boolean) => void
|
|
120
|
-
/** Programmatically move focus to a slot index. */
|
|
121
|
-
focus: (slotIndex: number) => void
|
|
122
|
-
/**
|
|
123
|
-
* Programmatically set the field value without triggering `onComplete`.
|
|
124
|
-
* Pass `undefined` to no-op. Filters the incoming string through the current
|
|
125
|
-
* `type`/`pattern` before distribution, identical to controlled-value sync.
|
|
126
|
-
*/
|
|
127
|
-
setValue: (v: string | undefined) => void
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
132
|
-
// COMPOSABLE
|
|
133
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Svelte composable for OTP input — single hidden-input architecture.
|
|
137
|
-
*
|
|
138
|
-
* @example
|
|
139
|
-
* ```svelte
|
|
140
|
-
* <script>
|
|
141
|
-
* import { useOTP } from 'digito/svelte'
|
|
142
|
-
* const otp = useOTP({ length: 6, onComplete: (code) => verify(code) })
|
|
143
|
-
* $: state = $otp
|
|
144
|
-
* </script>
|
|
145
|
-
*
|
|
146
|
-
* <div style="position:relative; display:inline-flex; gap:8px; align-items:center">
|
|
147
|
-
* <input
|
|
148
|
-
* use:otp.action
|
|
149
|
-
* style="position:absolute;inset:0;opacity:0;z-index:1;cursor:text"
|
|
150
|
-
* />
|
|
151
|
-
* {#each state.slotValues as char, i}
|
|
152
|
-
* {#if $otp.separatorAfter > 0 && i === $otp.separatorAfter}
|
|
153
|
-
* <span aria-hidden="true">{$otp.separator}</span>
|
|
154
|
-
* {/if}
|
|
155
|
-
* <div class="slot"
|
|
156
|
-
* class:is-active={i === state.activeSlot}
|
|
157
|
-
* class:is-filled={!!char}
|
|
158
|
-
* class:is-error={state.hasError}
|
|
159
|
-
* class:is-disabled={$otp.isDisabled}
|
|
160
|
-
* >{char}</div>
|
|
161
|
-
* {/each}
|
|
162
|
-
* </div>
|
|
163
|
-
* ```
|
|
164
|
-
*/
|
|
165
|
-
export function useOTP(options: SvelteOTPOptions = {}): UseOTPResult {
|
|
166
|
-
const {
|
|
167
|
-
length = 6,
|
|
168
|
-
type = 'numeric' as InputType,
|
|
169
|
-
timer: timerSecs = 0,
|
|
170
|
-
disabled: initialDisabled = false,
|
|
171
|
-
onComplete,
|
|
172
|
-
onExpire,
|
|
173
|
-
onResend,
|
|
174
|
-
haptic = true,
|
|
175
|
-
sound = false,
|
|
176
|
-
pattern,
|
|
177
|
-
pasteTransformer,
|
|
178
|
-
onInvalidChar,
|
|
179
|
-
value: controlledValue,
|
|
180
|
-
defaultValue,
|
|
181
|
-
readOnly: readOnlyOpt = false,
|
|
182
|
-
onChange: onChangeProp,
|
|
183
|
-
onFocus: onFocusProp,
|
|
184
|
-
onBlur: onBlurProp,
|
|
185
|
-
separatorAfter: separatorAfterOpt = 0,
|
|
186
|
-
separator: separatorOpt = '—',
|
|
187
|
-
masked: maskedOpt = false,
|
|
188
|
-
maskChar: maskCharOpt = '\u25CF',
|
|
189
|
-
autoFocus: autoFocusOpt = true,
|
|
190
|
-
name: nameOpt,
|
|
191
|
-
placeholder: placeholderOpt = '',
|
|
192
|
-
selectOnFocus: selectOnFocusOpt = false,
|
|
193
|
-
blurOnComplete: blurOnCompleteOpt = false,
|
|
194
|
-
} = options
|
|
195
|
-
|
|
196
|
-
// ── Core instance ──────────────────────────────────────────────────────────
|
|
197
|
-
const digito = createDigito({ length, type, pattern, pasteTransformer, onInvalidChar, onComplete, onExpire, onResend, haptic, sound, readOnly: readOnlyOpt })
|
|
198
|
-
|
|
199
|
-
// ── Stores ─────────────────────────────────────────────────────────────────
|
|
200
|
-
const store = writable(digito.state)
|
|
201
|
-
const timerStore = writable(timerSecs)
|
|
202
|
-
const isDisabledStore = writable(initialDisabled)
|
|
203
|
-
const isReadOnlyStore = writable(readOnlyOpt)
|
|
204
|
-
const separatorAfterStore = writable(separatorAfterOpt)
|
|
205
|
-
const separatorStore = writable(separatorOpt)
|
|
206
|
-
const maskedStore = writable(maskedOpt)
|
|
207
|
-
const maskCharStore = writable(maskCharOpt)
|
|
208
|
-
|
|
209
|
-
let inputEl: HTMLInputElement | null = null
|
|
210
|
-
let isReadOnly: boolean = readOnlyOpt
|
|
211
|
-
|
|
212
|
-
// ── sync() ─────────────────────────────────────────────────────────────────
|
|
213
|
-
function sync(suppressOnChange = false): void {
|
|
214
|
-
const s = digito.state
|
|
215
|
-
store.set({ ...s })
|
|
216
|
-
if (!suppressOnChange) {
|
|
217
|
-
onChangeProp?.(s.slotValues.join(''))
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// ── Controlled value sync ──────────────────────────────────────────────────
|
|
222
|
-
function setValue(incoming: string | undefined): void {
|
|
223
|
-
if (incoming === undefined) return
|
|
224
|
-
const filtered = filterString(incoming.slice(0, length), type, pattern)
|
|
225
|
-
const current = digito.state.slotValues.join('')
|
|
226
|
-
if (filtered === current) return
|
|
227
|
-
|
|
228
|
-
digito.resetState()
|
|
229
|
-
for (let i = 0; i < filtered.length; i++) {
|
|
230
|
-
digito.inputChar(i, filtered[i])
|
|
231
|
-
}
|
|
232
|
-
// Prevent a programmatic fill from triggering onComplete as if the user
|
|
233
|
-
// had typed the code. cancelPendingComplete cancels the 10ms deferred
|
|
234
|
-
// callback scheduled by the final inputChar above.
|
|
235
|
-
digito.cancelPendingComplete()
|
|
236
|
-
sync(true)
|
|
237
|
-
if (inputEl) {
|
|
238
|
-
inputEl.value = filtered
|
|
239
|
-
inputEl.setSelectionRange(filtered.length, filtered.length)
|
|
240
|
-
}
|
|
241
|
-
onChangeProp?.(filtered)
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (controlledValue !== undefined) {
|
|
245
|
-
setValue(controlledValue)
|
|
246
|
-
} else if (defaultValue) {
|
|
247
|
-
// Apply defaultValue once — no onComplete, no onChange
|
|
248
|
-
const filtered = filterString(defaultValue.slice(0, length), type, pattern)
|
|
249
|
-
if (filtered) {
|
|
250
|
-
for (let i = 0; i < filtered.length; i++) digito.inputChar(i, filtered[i])
|
|
251
|
-
digito.cancelPendingComplete()
|
|
252
|
-
sync(true)
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// ── Timer ──────────────────────────────────────────────────────────────────
|
|
257
|
-
let timerControls: ReturnType<typeof createTimer> | null = null
|
|
258
|
-
|
|
259
|
-
if (timerSecs > 0) {
|
|
260
|
-
timerControls = createTimer({
|
|
261
|
-
totalSeconds: timerSecs,
|
|
262
|
-
onTick: (r) => timerStore.set(r),
|
|
263
|
-
onExpire: () => { timerStore.set(0); onExpire?.() },
|
|
264
|
-
})
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// ── Svelte Action ──────────────────────────────────────────────────────────
|
|
268
|
-
function action(node: HTMLInputElement): { destroy: () => void } {
|
|
269
|
-
inputEl = node
|
|
270
|
-
|
|
271
|
-
node.type = maskedOpt ? 'password' : 'text'
|
|
272
|
-
node.inputMode = type === 'numeric' ? 'numeric' : 'text'
|
|
273
|
-
node.autocomplete = 'one-time-code'
|
|
274
|
-
node.maxLength = length
|
|
275
|
-
node.disabled = get(isDisabledStore)
|
|
276
|
-
node.spellcheck = false
|
|
277
|
-
if (nameOpt) node.name = nameOpt
|
|
278
|
-
node.setAttribute('aria-label', `Enter your ${length}-${type === 'numeric' ? 'digit' : 'character'} code`)
|
|
279
|
-
node.setAttribute('autocorrect', 'off')
|
|
280
|
-
node.setAttribute('autocapitalize', 'off')
|
|
281
|
-
if (readOnlyOpt) node.setAttribute('aria-readonly', 'true')
|
|
282
|
-
|
|
283
|
-
const unsubDisabled = isDisabledStore.subscribe((v: boolean) => { node.disabled = v })
|
|
284
|
-
|
|
285
|
-
function onKeydown(e: KeyboardEvent): void {
|
|
286
|
-
if (get(isDisabledStore)) return
|
|
287
|
-
const pos = node.selectionStart ?? 0
|
|
288
|
-
if (e.key === 'Backspace') {
|
|
289
|
-
e.preventDefault()
|
|
290
|
-
if (isReadOnly) return
|
|
291
|
-
digito.deleteChar(pos)
|
|
292
|
-
sync()
|
|
293
|
-
const next = digito.state.activeSlot
|
|
294
|
-
requestAnimationFrame(() => node.setSelectionRange(next, next))
|
|
295
|
-
} else if (e.key === 'Delete') {
|
|
296
|
-
e.preventDefault()
|
|
297
|
-
if (isReadOnly) return
|
|
298
|
-
digito.clearSlot(pos)
|
|
299
|
-
sync()
|
|
300
|
-
requestAnimationFrame(() => node.setSelectionRange(pos, pos))
|
|
301
|
-
} else if (e.key === 'ArrowLeft') {
|
|
302
|
-
e.preventDefault()
|
|
303
|
-
digito.moveFocusLeft(pos)
|
|
304
|
-
sync()
|
|
305
|
-
const next = digito.state.activeSlot
|
|
306
|
-
requestAnimationFrame(() => node.setSelectionRange(next, next))
|
|
307
|
-
} else if (e.key === 'ArrowRight') {
|
|
308
|
-
e.preventDefault()
|
|
309
|
-
digito.moveFocusRight(pos)
|
|
310
|
-
sync()
|
|
311
|
-
const next = digito.state.activeSlot
|
|
312
|
-
requestAnimationFrame(() => node.setSelectionRange(next, next))
|
|
313
|
-
} else if (e.key === 'Tab') {
|
|
314
|
-
if (e.shiftKey) {
|
|
315
|
-
if (pos === 0) return
|
|
316
|
-
e.preventDefault()
|
|
317
|
-
digito.moveFocusLeft(pos)
|
|
318
|
-
} else {
|
|
319
|
-
if (!digito.state.slotValues[pos]) return
|
|
320
|
-
if (pos >= length - 1) return
|
|
321
|
-
e.preventDefault()
|
|
322
|
-
digito.moveFocusRight(pos)
|
|
323
|
-
}
|
|
324
|
-
sync()
|
|
325
|
-
const next = digito.state.activeSlot
|
|
326
|
-
requestAnimationFrame(() => node.setSelectionRange(next, next))
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function onChange(e: Event): void {
|
|
331
|
-
if (get(isDisabledStore) || isReadOnly) return
|
|
332
|
-
const raw = (e.target as HTMLInputElement).value
|
|
333
|
-
if (!raw) {
|
|
334
|
-
digito.resetState()
|
|
335
|
-
node.value = ''
|
|
336
|
-
node.setSelectionRange(0, 0)
|
|
337
|
-
sync()
|
|
338
|
-
return
|
|
339
|
-
}
|
|
340
|
-
const valid = filterString(raw, type, pattern).slice(0, length)
|
|
341
|
-
digito.resetState()
|
|
342
|
-
for (let i = 0; i < valid.length; i++) digito.inputChar(i, valid[i])
|
|
343
|
-
const next = Math.min(valid.length, length - 1)
|
|
344
|
-
node.value = valid
|
|
345
|
-
node.setSelectionRange(next, next)
|
|
346
|
-
digito.moveFocusTo(next)
|
|
347
|
-
sync()
|
|
348
|
-
if (blurOnCompleteOpt && digito.state.isComplete) {
|
|
349
|
-
requestAnimationFrame(() => node.blur())
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
function onPaste(e: ClipboardEvent): void {
|
|
354
|
-
if (get(isDisabledStore) || isReadOnly) return
|
|
355
|
-
e.preventDefault()
|
|
356
|
-
const text = e.clipboardData?.getData('text') ?? ''
|
|
357
|
-
const pos = node.selectionStart ?? 0
|
|
358
|
-
digito.pasteString(pos, text)
|
|
359
|
-
const { slotValues, activeSlot } = digito.state
|
|
360
|
-
node.value = slotValues.join('')
|
|
361
|
-
node.setSelectionRange(activeSlot, activeSlot)
|
|
362
|
-
sync()
|
|
363
|
-
if (blurOnCompleteOpt && digito.state.isComplete) {
|
|
364
|
-
requestAnimationFrame(() => node.blur())
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function onFocus(): void {
|
|
369
|
-
onFocusProp?.()
|
|
370
|
-
const pos = digito.state.activeSlot
|
|
371
|
-
requestAnimationFrame(() => {
|
|
372
|
-
const char = digito.state.slotValues[pos]
|
|
373
|
-
if (selectOnFocusOpt && char) {
|
|
374
|
-
node.setSelectionRange(pos, pos + 1)
|
|
375
|
-
} else {
|
|
376
|
-
node.setSelectionRange(pos, pos)
|
|
377
|
-
}
|
|
378
|
-
})
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function onBlur(): void {
|
|
382
|
-
onBlurProp?.()
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
node.addEventListener('keydown', onKeydown)
|
|
386
|
-
node.addEventListener('input', onChange)
|
|
387
|
-
node.addEventListener('paste', onPaste)
|
|
388
|
-
node.addEventListener('focus', onFocus)
|
|
389
|
-
node.addEventListener('blur', onBlur)
|
|
390
|
-
|
|
391
|
-
if (autoFocusOpt && !get(isDisabledStore)) {
|
|
392
|
-
requestAnimationFrame(() => node.focus())
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// Start timer now that the component is mounted and the input element is
|
|
396
|
-
// available — matching Vue's onMounted pattern.
|
|
397
|
-
timerControls?.start()
|
|
398
|
-
|
|
399
|
-
return {
|
|
400
|
-
destroy() {
|
|
401
|
-
node.removeEventListener('keydown', onKeydown)
|
|
402
|
-
node.removeEventListener('input', onChange)
|
|
403
|
-
node.removeEventListener('paste', onPaste)
|
|
404
|
-
node.removeEventListener('focus', onFocus)
|
|
405
|
-
node.removeEventListener('blur', onBlur)
|
|
406
|
-
unsubDisabled()
|
|
407
|
-
timerControls?.stop()
|
|
408
|
-
inputEl = null
|
|
409
|
-
},
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// ── Public API ─────────────────────────────────────────────────────────────
|
|
414
|
-
|
|
415
|
-
function reset(): void {
|
|
416
|
-
digito.resetState()
|
|
417
|
-
if (inputEl) { inputEl.value = ''; inputEl.focus(); inputEl.setSelectionRange(0, 0) }
|
|
418
|
-
timerStore.set(timerSecs)
|
|
419
|
-
timerControls?.restart()
|
|
420
|
-
sync()
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
function setError(isError: boolean): void {
|
|
424
|
-
digito.setError(isError)
|
|
425
|
-
sync(true)
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
function setDisabled(value: boolean): void {
|
|
429
|
-
isDisabledStore.set(value)
|
|
430
|
-
digito.setDisabled(value)
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
function setReadOnly(value: boolean): void {
|
|
434
|
-
isReadOnly = value
|
|
435
|
-
isReadOnlyStore.set(value)
|
|
436
|
-
digito.setReadOnly(value)
|
|
437
|
-
if (inputEl) {
|
|
438
|
-
if (value) {
|
|
439
|
-
inputEl.setAttribute('aria-readonly', 'true')
|
|
440
|
-
} else {
|
|
441
|
-
inputEl.removeAttribute('aria-readonly')
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
function focus(slotIndex: number): void {
|
|
447
|
-
digito.moveFocusTo(slotIndex)
|
|
448
|
-
inputEl?.focus()
|
|
449
|
-
requestAnimationFrame(() => inputEl?.setSelectionRange(slotIndex, slotIndex))
|
|
450
|
-
sync(true)
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
function getCode(): string {
|
|
454
|
-
return digito.getCode()
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// Derived stores
|
|
458
|
-
const value = derived(store, ($s: DigitoState) => $s.slotValues.join(''))
|
|
459
|
-
const isComplete = derived(store, ($s: DigitoState) => $s.isComplete)
|
|
460
|
-
const hasError = derived(store, ($s: DigitoState) => $s.hasError)
|
|
461
|
-
const activeSlot = derived(store, ($s: DigitoState) => $s.activeSlot)
|
|
462
|
-
|
|
463
|
-
// Derived wrapper data attributes for CSS/Tailwind targeting
|
|
464
|
-
const wrapperAttrs = derived(
|
|
465
|
-
[store, isDisabledStore, isReadOnlyStore],
|
|
466
|
-
([$s, $dis, $ro]: [DigitoState, boolean, boolean]) => ({
|
|
467
|
-
...($s.isComplete ? { 'data-complete': '' } : {}),
|
|
468
|
-
...($s.hasError ? { 'data-invalid': '' } : {}),
|
|
469
|
-
...($dis ? { 'data-disabled': '' } : {}),
|
|
470
|
-
...($ro ? { 'data-readonly': '' } : {}),
|
|
471
|
-
})
|
|
472
|
-
)
|
|
473
|
-
|
|
474
|
-
return {
|
|
475
|
-
subscribe: store.subscribe,
|
|
476
|
-
value,
|
|
477
|
-
isComplete,
|
|
478
|
-
hasError,
|
|
479
|
-
activeSlot,
|
|
480
|
-
timerSeconds: timerStore,
|
|
481
|
-
isDisabled: isDisabledStore,
|
|
482
|
-
separatorAfter: separatorAfterStore,
|
|
483
|
-
separator: separatorStore,
|
|
484
|
-
masked: maskedStore,
|
|
485
|
-
maskChar: maskCharStore,
|
|
486
|
-
placeholder: placeholderOpt,
|
|
487
|
-
wrapperAttrs,
|
|
488
|
-
action,
|
|
489
|
-
getCode,
|
|
490
|
-
reset,
|
|
491
|
-
setError,
|
|
492
|
-
setDisabled,
|
|
493
|
-
setReadOnly,
|
|
494
|
-
setValue,
|
|
495
|
-
focus,
|
|
496
|
-
}
|
|
497
|
-
}
|