digitojs 1.2.0 → 1.2.1

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.
@@ -112,6 +112,11 @@ export type UseOTPResult = {
112
112
  setError: (isError: boolean) => void
113
113
  /** Enable or disable the field at runtime. */
114
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
115
120
  /** Programmatically move focus to a slot index. */
116
121
  focus: (slotIndex: number) => void
117
122
  /**
@@ -195,12 +200,14 @@ export function useOTP(options: SvelteOTPOptions = {}): UseOTPResult {
195
200
  const store = writable(digito.state)
196
201
  const timerStore = writable(timerSecs)
197
202
  const isDisabledStore = writable(initialDisabled)
203
+ const isReadOnlyStore = writable(readOnlyOpt)
198
204
  const separatorAfterStore = writable(separatorAfterOpt)
199
205
  const separatorStore = writable(separatorOpt)
200
206
  const maskedStore = writable(maskedOpt)
201
207
  const maskCharStore = writable(maskCharOpt)
202
208
 
203
- let inputEl: HTMLInputElement | null = null
209
+ let inputEl: HTMLInputElement | null = null
210
+ let isReadOnly: boolean = readOnlyOpt
204
211
 
205
212
  // ── sync() ─────────────────────────────────────────────────────────────────
206
213
  function sync(suppressOnChange = false): void {
@@ -280,14 +287,14 @@ export function useOTP(options: SvelteOTPOptions = {}): UseOTPResult {
280
287
  const pos = node.selectionStart ?? 0
281
288
  if (e.key === 'Backspace') {
282
289
  e.preventDefault()
283
- if (readOnlyOpt) return
290
+ if (isReadOnly) return
284
291
  digito.deleteChar(pos)
285
292
  sync()
286
293
  const next = digito.state.activeSlot
287
294
  requestAnimationFrame(() => node.setSelectionRange(next, next))
288
295
  } else if (e.key === 'Delete') {
289
296
  e.preventDefault()
290
- if (readOnlyOpt) return
297
+ if (isReadOnly) return
291
298
  digito.clearSlot(pos)
292
299
  sync()
293
300
  requestAnimationFrame(() => node.setSelectionRange(pos, pos))
@@ -321,7 +328,7 @@ export function useOTP(options: SvelteOTPOptions = {}): UseOTPResult {
321
328
  }
322
329
 
323
330
  function onChange(e: Event): void {
324
- if (get(isDisabledStore) || readOnlyOpt) return
331
+ if (get(isDisabledStore) || isReadOnly) return
325
332
  const raw = (e.target as HTMLInputElement).value
326
333
  if (!raw) {
327
334
  digito.resetState()
@@ -344,7 +351,7 @@ export function useOTP(options: SvelteOTPOptions = {}): UseOTPResult {
344
351
  }
345
352
 
346
353
  function onPaste(e: ClipboardEvent): void {
347
- if (get(isDisabledStore) || readOnlyOpt) return
354
+ if (get(isDisabledStore) || isReadOnly) return
348
355
  e.preventDefault()
349
356
  const text = e.clipboardData?.getData('text') ?? ''
350
357
  const pos = node.selectionStart ?? 0
@@ -423,6 +430,19 @@ export function useOTP(options: SvelteOTPOptions = {}): UseOTPResult {
423
430
  digito.setDisabled(value)
424
431
  }
425
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
+
426
446
  function focus(slotIndex: number): void {
427
447
  digito.moveFocusTo(slotIndex)
428
448
  inputEl?.focus()
@@ -442,12 +462,12 @@ export function useOTP(options: SvelteOTPOptions = {}): UseOTPResult {
442
462
 
443
463
  // Derived wrapper data attributes for CSS/Tailwind targeting
444
464
  const wrapperAttrs = derived(
445
- [store, isDisabledStore],
446
- ([$s, $dis]: [DigitoState, boolean]) => ({
465
+ [store, isDisabledStore, isReadOnlyStore],
466
+ ([$s, $dis, $ro]: [DigitoState, boolean, boolean]) => ({
447
467
  ...($s.isComplete ? { 'data-complete': '' } : {}),
448
468
  ...($s.hasError ? { 'data-invalid': '' } : {}),
449
469
  ...($dis ? { 'data-disabled': '' } : {}),
450
- ...(readOnlyOpt ? { 'data-readonly': '' } : {}),
470
+ ...($ro ? { 'data-readonly': '' } : {}),
451
471
  })
452
472
  )
453
473
 
@@ -470,6 +490,7 @@ export function useOTP(options: SvelteOTPOptions = {}): UseOTPResult {
470
490
  reset,
471
491
  setError,
472
492
  setDisabled,
493
+ setReadOnly,
473
494
  setValue,
474
495
  focus,
475
496
  }
@@ -63,6 +63,12 @@ export type DigitoInstance = {
63
63
  * verification to prevent the user from modifying the code mid-request.
64
64
  */
65
65
  setDisabled: (isDisabled: boolean) => void
66
+ /**
67
+ * Toggle readOnly at runtime. When `true`, all slot mutations are blocked
68
+ * but focus, navigation, and copy remain fully functional.
69
+ * Distinct from `disabled` — no opacity/cursor change, `aria-readonly` is set.
70
+ */
71
+ setReadOnly: (isReadOnly: boolean) => void
66
72
  /** Returns the current joined code string. */
67
73
  getCode: () => string
68
74
  /** Programmatically move focus to a slot index (focuses the hidden input). */
@@ -238,7 +244,7 @@ function mountOnWrapper(
238
244
  const onExpire = options.onExpire
239
245
  const pattern = options.pattern
240
246
  let isDisabled = options.disabled ?? false
241
- const isReadOnly = options.readOnly ?? false
247
+ let isReadOnly = options.readOnly ?? false
242
248
  const defaultValue = options.defaultValue ?? ''
243
249
 
244
250
  // New options
@@ -717,6 +723,17 @@ function mountOnWrapper(
717
723
  })
718
724
  }
719
725
 
726
+ function setReadOnly(value: boolean): void {
727
+ isReadOnly = value
728
+ otpCore.setReadOnly(value)
729
+ if (value) {
730
+ hiddenInputEl.setAttribute('aria-readonly', 'true')
731
+ } else {
732
+ hiddenInputEl.removeAttribute('aria-readonly')
733
+ }
734
+ syncSlotsToDOM()
735
+ }
736
+
720
737
  function getCode(): string {
721
738
  return otpCore.getCode()
722
739
  }
@@ -743,7 +760,7 @@ function mountOnWrapper(
743
760
  wrapperEl.__digitoResendRowEl = null
744
761
  }
745
762
 
746
- return { reset, resend, setError, setSuccess, setDisabled, getCode, focus, destroy }
763
+ return { reset, resend, setError, setSuccess, setDisabled, setReadOnly, getCode, focus, destroy }
747
764
  }
748
765
 
749
766
 
@@ -867,6 +867,24 @@ class DigitoInput extends HTMLElement {
867
867
  }
868
868
  }
869
869
 
870
+ /**
871
+ * Toggle readOnly at runtime. When `true`, all slot mutations are blocked
872
+ * but focus, navigation, and copy remain fully functional.
873
+ * Distinct from `disabled` — no opacity/cursor change, `aria-readonly` is set.
874
+ */
875
+ setReadOnly(value: boolean): void {
876
+ this._isReadOnly = value
877
+ this.digito?.setReadOnly(value)
878
+ if (this.hiddenInput) {
879
+ if (value) {
880
+ this.hiddenInput.setAttribute('aria-readonly', 'true')
881
+ } else {
882
+ this.hiddenInput.removeAttribute('aria-readonly')
883
+ }
884
+ }
885
+ this.syncSlotsToDOM()
886
+ }
887
+
870
888
  /** Returns the current code as a joined string (e.g. `"123456"`). */
871
889
  getCode(): string {
872
890
  return this.digito?.getCode() ?? ''