digitojs 1.0.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/LICENSE +21 -0
  3. package/README.md +753 -0
  4. package/dist/adapters/alpine.d.ts +71 -0
  5. package/dist/adapters/alpine.d.ts.map +1 -0
  6. package/dist/adapters/alpine.js +560 -0
  7. package/dist/adapters/alpine.js.map +1 -0
  8. package/dist/adapters/react.d.ts +223 -0
  9. package/dist/adapters/react.d.ts.map +1 -0
  10. package/dist/adapters/react.js +337 -0
  11. package/dist/adapters/react.js.map +1 -0
  12. package/dist/adapters/svelte.d.ts +139 -0
  13. package/dist/adapters/svelte.d.ts.map +1 -0
  14. package/dist/adapters/svelte.js +295 -0
  15. package/dist/adapters/svelte.js.map +1 -0
  16. package/dist/adapters/vanilla.d.ts +110 -0
  17. package/dist/adapters/vanilla.d.ts.map +1 -0
  18. package/dist/adapters/vanilla.js +650 -0
  19. package/dist/adapters/vanilla.js.map +1 -0
  20. package/dist/adapters/vue.d.ts +163 -0
  21. package/dist/adapters/vue.d.ts.map +1 -0
  22. package/dist/adapters/vue.js +298 -0
  23. package/dist/adapters/vue.js.map +1 -0
  24. package/dist/adapters/web-component.d.ts +192 -0
  25. package/dist/adapters/web-component.d.ts.map +1 -0
  26. package/dist/adapters/web-component.js +832 -0
  27. package/dist/adapters/web-component.js.map +1 -0
  28. package/dist/core/feedback.d.ts +26 -0
  29. package/dist/core/feedback.d.ts.map +1 -0
  30. package/dist/core/feedback.js +47 -0
  31. package/dist/core/feedback.js.map +1 -0
  32. package/dist/core/filter.d.ts +24 -0
  33. package/dist/core/filter.d.ts.map +1 -0
  34. package/dist/core/filter.js +47 -0
  35. package/dist/core/filter.js.map +1 -0
  36. package/dist/core/index.d.ts +16 -0
  37. package/dist/core/index.d.ts.map +1 -0
  38. package/dist/core/index.js +15 -0
  39. package/dist/core/index.js.map +1 -0
  40. package/dist/core/machine.d.ts +67 -0
  41. package/dist/core/machine.d.ts.map +1 -0
  42. package/dist/core/machine.js +328 -0
  43. package/dist/core/machine.js.map +1 -0
  44. package/dist/core/timer.d.ts +24 -0
  45. package/dist/core/timer.d.ts.map +1 -0
  46. package/dist/core/timer.js +67 -0
  47. package/dist/core/timer.js.map +1 -0
  48. package/dist/core/types.d.ts +162 -0
  49. package/dist/core/types.d.ts.map +1 -0
  50. package/dist/core/types.js +10 -0
  51. package/dist/core/types.js.map +1 -0
  52. package/dist/digito-wc.min.js +254 -0
  53. package/dist/digito-wc.min.js.map +7 -0
  54. package/dist/digito.min.js +91 -0
  55. package/dist/digito.min.js.map +7 -0
  56. package/dist/index.d.ts +18 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +25 -0
  59. package/dist/index.js.map +1 -0
  60. package/package.json +109 -0
  61. package/src/adapters/alpine.ts +666 -0
  62. package/src/adapters/react.tsx +603 -0
  63. package/src/adapters/svelte.ts +444 -0
  64. package/src/adapters/vanilla.ts +810 -0
  65. package/src/adapters/vue.ts +462 -0
  66. package/src/adapters/web-component.ts +858 -0
  67. package/src/core/feedback.ts +44 -0
  68. package/src/core/filter.ts +48 -0
  69. package/src/core/index.ts +16 -0
  70. package/src/core/machine.ts +373 -0
  71. package/src/core/timer.ts +75 -0
  72. package/src/core/types.ts +167 -0
  73. package/src/index.ts +51 -0
@@ -0,0 +1,328 @@
1
+ /**
2
+ * digito/core/machine
3
+ * ─────────────────────────────────────────────────────────────────────────────
4
+ * Pure OTP state machine — zero DOM, zero framework, zero side effects.
5
+ * All adapters import `createDigito` from here (via core/index.ts).
6
+ *
7
+ * Subscription system: pass a listener to `subscribe()` to be notified after
8
+ * every state mutation. Compatible with XState / Zustand-style patterns:
9
+ *
10
+ * const otp = createDigito({ length: 6 })
11
+ * const unsub = otp.subscribe(state => console.log(state))
12
+ * // ... later:
13
+ * unsub()
14
+ *
15
+ * @author Olawale Balo — Product Designer + Design Engineer
16
+ * @license MIT
17
+ */
18
+ import { filterChar, filterString } from './filter.js';
19
+ import { triggerHapticFeedback, triggerSoundFeedback } from './feedback.js';
20
+ // ─────────────────────────────────────────────────────────────────────────────
21
+ // INTERNAL HELPERS
22
+ // ─────────────────────────────────────────────────────────────────────────────
23
+ /** Returns `true` when every slot contains exactly one character. */
24
+ function allSlotsFilled(slotValues) {
25
+ return slotValues.every(v => v.length === 1);
26
+ }
27
+ /** Clamp `index` to the inclusive range `[min, max]`. */
28
+ function clampIndex(index, min, max) {
29
+ return Math.max(min, Math.min(max, index));
30
+ }
31
+ /** Join all slot values into a single string (the OTP code). */
32
+ function joinSlots(slotValues) {
33
+ return slotValues.join('');
34
+ }
35
+ // ─────────────────────────────────────────────────────────────────────────────
36
+ // FACTORY
37
+ // ─────────────────────────────────────────────────────────────────────────────
38
+ /**
39
+ * Creates a pure OTP state machine.
40
+ * Returns action functions, a `state` getter, a `subscribe` method, and a
41
+ * `getState` snapshot helper — no DOM, no side effects.
42
+ */
43
+ export function createDigito(options = {}) {
44
+ // Security: guard against invalid length values.
45
+ // Negative numbers would throw a RangeError from Array(); zero would produce
46
+ // a permanently-incomplete input with no slots. Clamp to a safe minimum of 1.
47
+ const rawLength = options.length ?? 6;
48
+ // Guard against NaN (e.g. parseInt('', 10) from a missing data-attribute).
49
+ // NaN would propagate through Math.floor and crash Array() with RangeError.
50
+ const length = isNaN(rawLength) ? 6 : Math.max(1, Math.floor(rawLength));
51
+ const { type = 'numeric', pattern, onComplete, onInvalidChar, haptic = true, sound = false, pasteTransformer, } = options;
52
+ // `disabled` is mutable so setDisabled() can toggle it at runtime without
53
+ // requiring the instance to be recreated. Adapters that pass disabled at
54
+ // construction time still work — the initial value is honoured.
55
+ let disabled = options.disabled ?? false;
56
+ let state = {
57
+ slotValues: Array(length).fill(''),
58
+ activeSlot: 0,
59
+ hasError: false,
60
+ isComplete: false,
61
+ timerSeconds: options.timer ?? 0,
62
+ };
63
+ // ── Subscription set ──────────────────────────────────────────────────────
64
+ const listeners = new Set();
65
+ function applyState(patch) {
66
+ state = { ...state, ...patch };
67
+ // Notify all subscribers with a deep copy of slotValues so they cannot
68
+ // mutate the live array. A simple { ...state } is a shallow copy — the
69
+ // slotValues array reference would be shared, letting a subscriber silently
70
+ // corrupt internal state.
71
+ if (listeners.size > 0) {
72
+ const snapshot = { ...state, slotValues: [...state.slotValues] };
73
+ listeners.forEach(fn => fn(snapshot));
74
+ }
75
+ return state;
76
+ }
77
+ /**
78
+ * Handle for the pending onComplete timeout.
79
+ * Stored so resetState() can cancel it if the user clears the input
80
+ * within the 10ms defer window (e.g. rapid type-then-delete).
81
+ */
82
+ let completeTimeoutId = null;
83
+ /** Fire onComplete after a short delay so DOM sync can finish first. */
84
+ function notifyCompleteIfReady(slotValues) {
85
+ if (!allSlotsFilled(slotValues) || !onComplete)
86
+ return;
87
+ if (haptic)
88
+ triggerHapticFeedback();
89
+ if (sound)
90
+ triggerSoundFeedback();
91
+ const code = joinSlots(slotValues);
92
+ if (completeTimeoutId !== null)
93
+ clearTimeout(completeTimeoutId);
94
+ completeTimeoutId = setTimeout(() => {
95
+ completeTimeoutId = null;
96
+ onComplete(code);
97
+ }, 10);
98
+ }
99
+ /**
100
+ * Cancel any pending onComplete callback without clearing slot state.
101
+ * Use this after a programmatic fill (e.g. controlled-value sync in adapters)
102
+ * to prevent a parent-driven pre-fill from triggering onComplete as if the
103
+ * user had typed the code.
104
+ */
105
+ function cancelPendingComplete() {
106
+ if (completeTimeoutId !== null) {
107
+ clearTimeout(completeTimeoutId);
108
+ completeTimeoutId = null;
109
+ }
110
+ }
111
+ // ── Actions ────────────────────────────────────────────────────────────────
112
+ /**
113
+ * Process a single character typed into `slotIndex`.
114
+ * Invalid characters leave the slot unchanged and keep focus in place.
115
+ * Out-of-bounds indices are silently ignored to prevent sparse-array corruption.
116
+ */
117
+ function inputChar(slotIndex, char) {
118
+ if (disabled)
119
+ return state;
120
+ if (slotIndex < 0 || slotIndex >= length)
121
+ return state;
122
+ const validChar = filterChar(char, type, pattern);
123
+ if (!validChar) {
124
+ // Fire onInvalidChar for single rejected characters (not empty/multi-char)
125
+ if (char.length === 1)
126
+ onInvalidChar?.(char, slotIndex);
127
+ // Only notify subscribers if focus actually needs to move. Firing applyState
128
+ // when activeSlot is already slotIndex causes a spurious state notification
129
+ // on every invalid keystroke, which can trigger expensive re-renders.
130
+ if (state.activeSlot !== slotIndex) {
131
+ return applyState({ activeSlot: slotIndex });
132
+ }
133
+ return state;
134
+ }
135
+ const slotValues = [...state.slotValues];
136
+ slotValues[slotIndex] = validChar;
137
+ const nextSlot = slotIndex < length - 1 ? slotIndex + 1 : length - 1;
138
+ const newState = applyState({
139
+ slotValues,
140
+ activeSlot: nextSlot,
141
+ hasError: false,
142
+ isComplete: allSlotsFilled(slotValues),
143
+ });
144
+ notifyCompleteIfReady(slotValues);
145
+ return newState;
146
+ }
147
+ /**
148
+ * Handle backspace at `slotIndex`.
149
+ * Clears the current slot if filled, otherwise clears the previous slot and moves back.
150
+ */
151
+ function deleteChar(slotIndex) {
152
+ if (disabled)
153
+ return state;
154
+ if (slotIndex < 0 || slotIndex >= length)
155
+ return state;
156
+ const slotValues = [...state.slotValues];
157
+ if (slotValues[slotIndex]) {
158
+ slotValues[slotIndex] = '';
159
+ return applyState({ slotValues, activeSlot: slotIndex, isComplete: false });
160
+ }
161
+ const prevSlot = clampIndex(slotIndex - 1, 0, length - 1);
162
+ slotValues[prevSlot] = '';
163
+ return applyState({ slotValues, activeSlot: prevSlot, isComplete: false });
164
+ }
165
+ /** Move focus one slot to the left, clamped to slot 0. */
166
+ function moveFocusLeft(slotIndex) {
167
+ return applyState({ activeSlot: clampIndex(slotIndex - 1, 0, length - 1) });
168
+ }
169
+ /** Move focus one slot to the right, clamped to the last slot. */
170
+ function moveFocusRight(slotIndex) {
171
+ return applyState({ activeSlot: clampIndex(slotIndex + 1, 0, length - 1) });
172
+ }
173
+ /**
174
+ * Smart paste — distributes valid characters from `cursorSlot` forward,
175
+ * wrapping around to slot 0 if the string is longer than the remaining slots.
176
+ *
177
+ * Examples (length = 6, type = numeric):
178
+ * paste(0, '123456') → fills all slots
179
+ * paste(5, '847291') → slot5='8', slot0='4', slot1='7', slot2='2', slot3='9', slot4='1'
180
+ * paste(0, '84AB91') → filtered='8491', fills slots 0–3, slots 4–5 unchanged
181
+ */
182
+ function pasteString(cursorSlot, rawText) {
183
+ if (disabled)
184
+ return state;
185
+ let transformed;
186
+ try {
187
+ transformed = pasteTransformer ? pasteTransformer(rawText) : rawText;
188
+ }
189
+ catch (err) {
190
+ console.warn('[digito] pasteTransformer threw — using raw paste text.', err);
191
+ transformed = rawText;
192
+ }
193
+ // Report each rejected character so adapters can provide inline feedback.
194
+ // Tracks the effective write cursor so the reported slot index matches where
195
+ // the character would have landed if it had been valid.
196
+ if (onInvalidChar && transformed) {
197
+ let slotCursor = cursorSlot;
198
+ for (const char of Array.from(transformed)) {
199
+ if (filterChar(char, type, pattern)) {
200
+ slotCursor = (slotCursor + 1) % length;
201
+ }
202
+ else {
203
+ onInvalidChar(char, slotCursor);
204
+ }
205
+ }
206
+ }
207
+ const validChars = filterString(transformed, type, pattern);
208
+ if (!validChars)
209
+ return state;
210
+ const slotValues = [...state.slotValues];
211
+ let writeSlot = cursorSlot;
212
+ for (let i = 0; i < validChars.length && i < length; i++) {
213
+ slotValues[writeSlot] = validChars[i];
214
+ writeSlot = (writeSlot + 1) % length;
215
+ }
216
+ const charsWritten = Math.min(validChars.length, length);
217
+ const nextActiveSlot = charsWritten >= length
218
+ ? length - 1
219
+ : (cursorSlot + charsWritten) % length;
220
+ const newState = applyState({
221
+ slotValues,
222
+ activeSlot: nextActiveSlot,
223
+ hasError: false,
224
+ isComplete: allSlotsFilled(slotValues),
225
+ });
226
+ notifyCompleteIfReady(slotValues);
227
+ return newState;
228
+ }
229
+ /** Set or clear the error state. Triggers haptic feedback when setting. */
230
+ function setError(isError) {
231
+ if (isError && haptic)
232
+ triggerHapticFeedback();
233
+ return applyState({ hasError: isError });
234
+ }
235
+ /** Clear all slots and reset to initial state. Cancels any pending onComplete callback. */
236
+ function resetState() {
237
+ if (completeTimeoutId !== null) {
238
+ clearTimeout(completeTimeoutId);
239
+ completeTimeoutId = null;
240
+ }
241
+ return applyState({
242
+ slotValues: Array(length).fill(''),
243
+ activeSlot: 0,
244
+ hasError: false,
245
+ isComplete: false,
246
+ timerSeconds: options.timer ?? 0,
247
+ });
248
+ }
249
+ /** Move focus to a specific slot index. */
250
+ function moveFocusTo(slotIndex) {
251
+ return applyState({ activeSlot: clampIndex(slotIndex, 0, length - 1) });
252
+ }
253
+ /**
254
+ * Reactively enable or disable the input.
255
+ * When `true`, inputChar / deleteChar / pasteString are silently ignored.
256
+ * Navigation (moveFocusLeft/Right/To) is always allowed regardless of state.
257
+ *
258
+ * Prefer this over passing `disabled` at construction time whenever you need
259
+ * to toggle disabled at runtime (e.g. during async verification).
260
+ */
261
+ function setDisabled(value) {
262
+ disabled = value;
263
+ }
264
+ /**
265
+ * Subscribe to state changes. The listener is called after every mutation
266
+ * with a shallow copy of the new state.
267
+ *
268
+ * @returns An unsubscribe function — call it to stop receiving updates.
269
+ *
270
+ * @example
271
+ * ```ts
272
+ * const otp = createDigito({ length: 6 })
273
+ * const unsub = otp.subscribe(state => console.log(state.slotValues))
274
+ * // Later:
275
+ * unsub()
276
+ * ```
277
+ */
278
+ function subscribe(listener) {
279
+ listeners.add(listener);
280
+ return () => { listeners.delete(listener); };
281
+ }
282
+ return {
283
+ /** Current state snapshot. */
284
+ get state() { return state; },
285
+ // Input actions
286
+ inputChar,
287
+ deleteChar,
288
+ moveFocusLeft,
289
+ moveFocusRight,
290
+ pasteString,
291
+ // State control
292
+ setError,
293
+ resetState,
294
+ moveFocusTo,
295
+ /**
296
+ * Cancel any pending onComplete callback without resetting slot state.
297
+ * Intended for adapter-layer controlled-value syncs where a programmatic
298
+ * fill should not be treated as a user completing the code.
299
+ */
300
+ cancelPendingComplete,
301
+ /**
302
+ * Toggle the disabled state at runtime.
303
+ * Affects inputChar, deleteChar, and pasteString only.
304
+ * Navigation actions are always allowed.
305
+ */
306
+ setDisabled,
307
+ /** Returns the current joined code string. */
308
+ getCode: () => joinSlots(state.slotValues),
309
+ /**
310
+ * Returns a copy of the current state with a cloned slotValues array.
311
+ * Mutations to the returned object (including its slotValues array) will
312
+ * not affect live state.
313
+ */
314
+ getSnapshot: () => ({ ...state, slotValues: [...state.slotValues] }),
315
+ /**
316
+ * Subscribe to state changes. Returns an unsubscribe function.
317
+ * Compatible with XState / Zustand-style patterns.
318
+ */
319
+ subscribe,
320
+ /**
321
+ * Returns a copy of the current state with a cloned slotValues array.
322
+ * Equivalent to `digito.getSnapshot()` — provided for ergonomic parity
323
+ * with state-management libraries that expose a `getState()` method.
324
+ */
325
+ getState: () => ({ ...state, slotValues: [...state.slotValues] }),
326
+ };
327
+ }
328
+ //# sourceMappingURL=machine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"machine.js","sourceRoot":"","sources":["../../src/core/machine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAyC,aAAa,CAAA;AACzF,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAsB,eAAe,CAAA;AAG3F,gFAAgF;AAChF,mBAAmB;AACnB,gFAAgF;AAEhF,qEAAqE;AACrE,SAAS,cAAc,CAAC,UAAoB;IAC1C,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAA;AAC9C,CAAC;AAED,yDAAyD;AACzD,SAAS,UAAU,CAAC,KAAa,EAAE,GAAW,EAAE,GAAW;IACzD,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAA;AAC5C,CAAC;AAED,gEAAgE;AAChE,SAAS,SAAS,CAAC,UAAoB;IACrC,OAAO,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AAC5B,CAAC;AAGD,gFAAgF;AAChF,UAAU;AACV,gFAAgF;AAEhF;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,UAAyB,EAAE;IACtD,iDAAiD;IACjD,6EAA6E;IAC7E,8EAA8E;IAC9E,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CAAA;IACrC,2EAA2E;IAC3E,4EAA4E;IAC5E,MAAM,MAAM,GAAM,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAA;IAE3E,MAAM,EACJ,IAAI,GAAe,SAAsB,EACzC,OAAO,EACP,UAAU,EACV,aAAa,EACb,MAAM,GAAa,IAAI,EACvB,KAAK,GAAc,KAAK,EACxB,gBAAgB,GACjB,GAAG,OAAO,CAAA;IAEX,0EAA0E;IAC1E,yEAAyE;IACzE,gEAAgE;IAChE,IAAI,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,KAAK,CAAA;IAExC,IAAI,KAAK,GAAgB;QACvB,UAAU,EAAI,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAa;QAChD,UAAU,EAAI,CAAC;QACf,QAAQ,EAAM,KAAK;QACnB,UAAU,EAAI,KAAK;QACnB,YAAY,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;KACjC,CAAA;IAED,6EAA6E;IAC7E,MAAM,SAAS,GAAG,IAAI,GAAG,EAAiB,CAAA;IAE1C,SAAS,UAAU,CAAC,KAA2B;QAC7C,KAAK,GAAG,EAAE,GAAG,KAAK,EAAE,GAAG,KAAK,EAAE,CAAA;QAC9B,uEAAuE;QACvE,uEAAuE;QACvE,4EAA4E;QAC5E,0BAA0B;QAC1B,IAAI,SAAS,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,QAAQ,GAAG,EAAE,GAAG,KAAK,EAAE,UAAU,EAAE,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,EAAE,CAAA;YAChE,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;QACvC,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IAED;;;;OAIG;IACH,IAAI,iBAAiB,GAAyC,IAAI,CAAA;IAElE,wEAAwE;IACxE,SAAS,qBAAqB,CAAC,UAAoB;QACjD,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU;YAAE,OAAM;QACtD,IAAI,MAAM;YAAE,qBAAqB,EAAE,CAAA;QACnC,IAAI,KAAK;YAAG,oBAAoB,EAAE,CAAA;QAClC,MAAM,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC,CAAA;QAClC,IAAI,iBAAiB,KAAK,IAAI;YAAE,YAAY,CAAC,iBAAiB,CAAC,CAAA;QAC/D,iBAAiB,GAAG,UAAU,CAAC,GAAG,EAAE;YAClC,iBAAiB,GAAG,IAAI,CAAA;YACxB,UAAU,CAAC,IAAI,CAAC,CAAA;QAClB,CAAC,EAAE,EAAE,CAAC,CAAA;IACR,CAAC;IAED;;;;;OAKG;IACH,SAAS,qBAAqB;QAC5B,IAAI,iBAAiB,KAAK,IAAI,EAAE,CAAC;YAC/B,YAAY,CAAC,iBAAiB,CAAC,CAAA;YAC/B,iBAAiB,GAAG,IAAI,CAAA;QAC1B,CAAC;IACH,CAAC;IAGD,8EAA8E;IAE9E;;;;OAIG;IACH,SAAS,SAAS,CAAC,SAAiB,EAAE,IAAY;QAChD,IAAI,QAAQ;YAAE,OAAO,KAAK,CAAA;QAC1B,IAAI,SAAS,GAAG,CAAC,IAAI,SAAS,IAAI,MAAM;YAAE,OAAO,KAAK,CAAA;QACtD,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;QACjD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,2EAA2E;YAC3E,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;gBAAE,aAAa,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;YACvD,6EAA6E;YAC7E,4EAA4E;YAC5E,sEAAsE;YACtE,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;gBACnC,OAAO,UAAU,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAA;YAC9C,CAAC;YACD,OAAO,KAAK,CAAA;QACd,CAAC;QAED,MAAM,UAAU,GAAG,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,CAAA;QACxC,UAAU,CAAC,SAAS,CAAC,GAAG,SAAS,CAAA;QAEjC,MAAM,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAA;QAEpE,MAAM,QAAQ,GAAG,UAAU,CAAC;YAC1B,UAAU;YACV,UAAU,EAAG,QAAQ;YACrB,QAAQ,EAAK,KAAK;YAClB,UAAU,EAAG,cAAc,CAAC,UAAU,CAAC;SACxC,CAAC,CAAA;QAEF,qBAAqB,CAAC,UAAU,CAAC,CAAA;QACjC,OAAO,QAAQ,CAAA;IACjB,CAAC;IAED;;;OAGG;IACH,SAAS,UAAU,CAAC,SAAiB;QACnC,IAAI,QAAQ;YAAE,OAAO,KAAK,CAAA;QAC1B,IAAI,SAAS,GAAG,CAAC,IAAI,SAAS,IAAI,MAAM;YAAE,OAAO,KAAK,CAAA;QACtD,MAAM,UAAU,GAAG,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,CAAA;QAExC,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1B,UAAU,CAAC,SAAS,CAAC,GAAG,EAAE,CAAA;YAC1B,OAAO,UAAU,CAAC,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAA;QAC7E,CAAC;QAED,MAAM,QAAQ,GAAG,UAAU,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,CAAA;QACzD,UAAU,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAA;QACzB,OAAO,UAAU,CAAC,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAA;IAC5E,CAAC;IAED,0DAA0D;IAC1D,SAAS,aAAa,CAAC,SAAiB;QACtC,OAAO,UAAU,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,CAAA;IAC7E,CAAC;IAED,kEAAkE;IAClE,SAAS,cAAc,CAAC,SAAiB;QACvC,OAAO,UAAU,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,CAAA;IAC7E,CAAC;IAED;;;;;;;;OAQG;IACH,SAAS,WAAW,CAAC,UAAkB,EAAE,OAAe;QACtD,IAAI,QAAQ;YAAE,OAAO,KAAK,CAAA;QAE1B,IAAI,WAAmB,CAAA;QACvB,IAAI,CAAC;YACH,WAAW,GAAG,gBAAgB,CAAC,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAA;QACtE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,yDAAyD,EAAE,GAAG,CAAC,CAAA;YAC5E,WAAW,GAAG,OAAO,CAAA;QACvB,CAAC;QAED,0EAA0E;QAC1E,6EAA6E;QAC7E,wDAAwD;QACxD,IAAI,aAAa,IAAI,WAAW,EAAE,CAAC;YACjC,IAAI,UAAU,GAAG,UAAU,CAAA;YAC3B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC3C,IAAI,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC;oBACpC,UAAU,GAAG,CAAC,UAAU,GAAG,CAAC,CAAC,GAAG,MAAM,CAAA;gBACxC,CAAC;qBAAM,CAAC;oBACN,aAAa,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;gBACjC,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,UAAU,GAAI,YAAY,CAAC,WAAW,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;QAC5D,IAAI,CAAC,UAAU;YAAE,OAAO,KAAK,CAAA;QAE7B,MAAM,UAAU,GAAG,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,CAAA;QACxC,IAAM,SAAS,GAAI,UAAU,CAAA;QAE7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,IAAI,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACzD,UAAU,CAAC,SAAS,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;YACrC,SAAS,GAAG,CAAC,SAAS,GAAG,CAAC,CAAC,GAAG,MAAM,CAAA;QACtC,CAAC;QAED,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QACxD,MAAM,cAAc,GAAG,YAAY,IAAI,MAAM;YAC3C,CAAC,CAAC,MAAM,GAAG,CAAC;YACZ,CAAC,CAAC,CAAC,UAAU,GAAG,YAAY,CAAC,GAAG,MAAM,CAAA;QAExC,MAAM,QAAQ,GAAG,UAAU,CAAC;YAC1B,UAAU;YACV,UAAU,EAAG,cAAc;YAC3B,QAAQ,EAAK,KAAK;YAClB,UAAU,EAAG,cAAc,CAAC,UAAU,CAAC;SACxC,CAAC,CAAA;QAEF,qBAAqB,CAAC,UAAU,CAAC,CAAA;QACjC,OAAO,QAAQ,CAAA;IACjB,CAAC;IAED,2EAA2E;IAC3E,SAAS,QAAQ,CAAC,OAAgB;QAChC,IAAI,OAAO,IAAI,MAAM;YAAE,qBAAqB,EAAE,CAAA;QAC9C,OAAO,UAAU,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAA;IAC1C,CAAC;IAED,2FAA2F;IAC3F,SAAS,UAAU;QACjB,IAAI,iBAAiB,KAAK,IAAI,EAAE,CAAC;YAC/B,YAAY,CAAC,iBAAiB,CAAC,CAAA;YAC/B,iBAAiB,GAAG,IAAI,CAAA;QAC1B,CAAC;QACD,OAAO,UAAU,CAAC;YAChB,UAAU,EAAI,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAa;YAChD,UAAU,EAAI,CAAC;YACf,QAAQ,EAAM,KAAK;YACnB,UAAU,EAAI,KAAK;YACnB,YAAY,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;SACjC,CAAC,CAAA;IACJ,CAAC;IAED,2CAA2C;IAC3C,SAAS,WAAW,CAAC,SAAiB;QACpC,OAAO,UAAU,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,SAAS,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,CAAA;IACzE,CAAC;IAED;;;;;;;OAOG;IACH,SAAS,WAAW,CAAC,KAAc;QACjC,QAAQ,GAAG,KAAK,CAAA;IAClB,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,SAAS,SAAS,CAAC,QAAuB;QACxC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QACvB,OAAO,GAAG,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA,CAAC,CAAC,CAAA;IAC7C,CAAC;IAED,OAAO;QACL,8BAA8B;QAC9B,IAAI,KAAK,KAAK,OAAO,KAAK,CAAA,CAAC,CAAC;QAE5B,gBAAgB;QAChB,SAAS;QACT,UAAU;QACV,aAAa;QACb,cAAc;QACd,WAAW;QAEX,gBAAgB;QAChB,QAAQ;QACR,UAAU;QACV,WAAW;QAEX;;;;WAIG;QACH,qBAAqB;QAErB;;;;WAIG;QACH,WAAW;QAEX,8CAA8C;QAC9C,OAAO,EAAM,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,UAAU,CAAC;QAC9C;;;;WAIG;QACH,WAAW,EAAE,GAAgB,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,UAAU,EAAE,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;QAEjF;;;WAGG;QACH,SAAS;QAET;;;;WAIG;QACH,QAAQ,EAAE,GAAgB,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,UAAU,EAAE,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;KAC/E,CAAA;AACH,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * digito/core/timer
3
+ * ─────────────────────────────────────────────────────────────────────────────
4
+ * Standalone countdown timer — re-exported from core for use by adapters and
5
+ * developers who want to drive their own timer UI.
6
+ *
7
+ * @author Olawale Balo — Product Designer + Design Engineer
8
+ * @license MIT
9
+ */
10
+ import type { TimerOptions, TimerControls } from './types.js';
11
+ /**
12
+ * Create a 1-second countdown timer.
13
+ *
14
+ * Lifecycle notes:
15
+ * - `start()` is idempotent — it stops any running interval before starting a
16
+ * new one, so calling it twice never produces double-ticking.
17
+ * - If `totalSeconds <= 0`, `onExpire` fires synchronously on `start()` and no
18
+ * interval is created (avoids decrementing to -1 and passing invalid values).
19
+ * - `reset()` stops and restores remaining seconds without restarting.
20
+ * - `restart()` is shorthand for `reset()` followed immediately by `start()`.
21
+ * Used by the vanilla adapter's "Resend" button to reset the countdown.
22
+ */
23
+ export declare function createTimer(options: TimerOptions): TimerControls;
24
+ //# sourceMappingURL=timer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timer.d.ts","sourceRoot":"","sources":["../../src/core/timer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE7D;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,aAAa,CAkDhE"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * digito/core/timer
3
+ * ─────────────────────────────────────────────────────────────────────────────
4
+ * Standalone countdown timer — re-exported from core for use by adapters and
5
+ * developers who want to drive their own timer UI.
6
+ *
7
+ * @author Olawale Balo — Product Designer + Design Engineer
8
+ * @license MIT
9
+ */
10
+ /**
11
+ * Create a 1-second countdown timer.
12
+ *
13
+ * Lifecycle notes:
14
+ * - `start()` is idempotent — it stops any running interval before starting a
15
+ * new one, so calling it twice never produces double-ticking.
16
+ * - If `totalSeconds <= 0`, `onExpire` fires synchronously on `start()` and no
17
+ * interval is created (avoids decrementing to -1 and passing invalid values).
18
+ * - `reset()` stops and restores remaining seconds without restarting.
19
+ * - `restart()` is shorthand for `reset()` followed immediately by `start()`.
20
+ * Used by the vanilla adapter's "Resend" button to reset the countdown.
21
+ */
22
+ export function createTimer(options) {
23
+ const { totalSeconds, onTick, onExpire } = options;
24
+ let remainingSeconds = totalSeconds;
25
+ let intervalId = null;
26
+ /** Stop the running interval. No-op if already stopped. */
27
+ function stop() {
28
+ if (intervalId !== null) {
29
+ clearInterval(intervalId);
30
+ intervalId = null;
31
+ }
32
+ }
33
+ /** Stop the interval and restore `remainingSeconds` to `totalSeconds`. Does not restart. */
34
+ function reset() {
35
+ stop();
36
+ remainingSeconds = totalSeconds;
37
+ }
38
+ /**
39
+ * Start ticking. Stops any existing interval first to prevent double-ticking.
40
+ * If `totalSeconds <= 0`, fires `onExpire` immediately without creating an interval.
41
+ */
42
+ function start() {
43
+ stop();
44
+ // Guard: if totalSeconds is zero or negative, fire onExpire immediately
45
+ // without starting an interval. Without this, the first tick would decrement
46
+ // to -1 and pass an invalid value to onTick before calling onExpire.
47
+ if (totalSeconds <= 0) {
48
+ onExpire?.();
49
+ return;
50
+ }
51
+ intervalId = setInterval(() => {
52
+ remainingSeconds -= 1;
53
+ onTick?.(remainingSeconds);
54
+ if (remainingSeconds <= 0) {
55
+ stop();
56
+ onExpire?.();
57
+ }
58
+ }, 1000);
59
+ }
60
+ /** Reset to `totalSeconds` and immediately start ticking. */
61
+ function restart() {
62
+ reset();
63
+ start();
64
+ }
65
+ return { start, stop, reset, restart };
66
+ }
67
+ //# sourceMappingURL=timer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timer.js","sourceRoot":"","sources":["../../src/core/timer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,WAAW,CAAC,OAAqB;IAC/C,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAA;IAElD,IAAI,gBAAgB,GAAG,YAAY,CAAA;IACnC,IAAI,UAAU,GAA0C,IAAI,CAAA;IAE5D,2DAA2D;IAC3D,SAAS,IAAI;QACX,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;YACxB,aAAa,CAAC,UAAU,CAAC,CAAA;YACzB,UAAU,GAAG,IAAI,CAAA;QACnB,CAAC;IACH,CAAC;IAED,4FAA4F;IAC5F,SAAS,KAAK;QACZ,IAAI,EAAE,CAAA;QACN,gBAAgB,GAAG,YAAY,CAAA;IACjC,CAAC;IAED;;;OAGG;IACH,SAAS,KAAK;QACZ,IAAI,EAAE,CAAA;QACN,wEAAwE;QACxE,6EAA6E;QAC7E,qEAAqE;QACrE,IAAI,YAAY,IAAI,CAAC,EAAE,CAAC;YACtB,QAAQ,EAAE,EAAE,CAAA;YACZ,OAAM;QACR,CAAC;QACD,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE;YAC5B,gBAAgB,IAAI,CAAC,CAAA;YACrB,MAAM,EAAE,CAAC,gBAAgB,CAAC,CAAA;YAC1B,IAAI,gBAAgB,IAAI,CAAC,EAAE,CAAC;gBAC1B,IAAI,EAAE,CAAA;gBACN,QAAQ,EAAE,EAAE,CAAA;YACd,CAAC;QACH,CAAC,EAAE,IAAI,CAAC,CAAA;IACV,CAAC;IAED,6DAA6D;IAC7D,SAAS,OAAO;QACd,KAAK,EAAE,CAAA;QACP,KAAK,EAAE,CAAA;IACT,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;AACxC,CAAC"}
@@ -0,0 +1,162 @@
1
+ /**
2
+ * digito/core/types
3
+ * ─────────────────────────────────────────────────────────────────────────────
4
+ * Shared TypeScript interfaces and type aliases used across the core modules.
5
+ *
6
+ * @author Olawale Balo — Product Designer + Design Engineer
7
+ * @license MIT
8
+ */
9
+ /** The set of characters each slot will accept. */
10
+ export type InputType = 'numeric' | 'alphabet' | 'alphanumeric' | 'any';
11
+ /** Snapshot of the OTP field state at any point in time. */
12
+ export type DigitoState = {
13
+ /** Current value of each slot. Empty string means unfilled. */
14
+ slotValues: string[];
15
+ /** Index of the currently focused slot. */
16
+ activeSlot: number;
17
+ /** Whether an error state is active. */
18
+ hasError: boolean;
19
+ /** True when every slot contains a valid character. */
20
+ isComplete: boolean;
21
+ /**
22
+ * Mirrors the initial timer value — NOT a live countdown.
23
+ * The live countdown is managed by each adapter layer.
24
+ * Do not use this field to read remaining time; use the adapter's onTick callback instead.
25
+ */
26
+ timerSeconds: number;
27
+ };
28
+ /** Configuration options passed to `createDigito` or `initDigito`. */
29
+ export type DigitoOptions = {
30
+ /** Number of input slots. Default: `6`. */
31
+ length?: number;
32
+ /** Character set accepted by each slot. Default: `'numeric'`. */
33
+ type?: InputType;
34
+ /** Countdown duration in seconds. `0` disables the timer. Default: `0`. */
35
+ timer?: number;
36
+ /** Resend cooldown in seconds after the user clicks Resend. Default: `30`. */
37
+ resendAfter?: number;
38
+ /** Called with the joined code string when all slots are filled. */
39
+ onComplete?: (code: string) => void;
40
+ /** Called every second with the remaining seconds. Use to drive a custom timer UI. */
41
+ onTick?: (remainingSeconds: number) => void;
42
+ /** Called when the countdown reaches zero. */
43
+ onExpire?: () => void;
44
+ /** Called when the resend action is triggered. */
45
+ onResend?: () => void;
46
+ /** Vibrate on completion and error via `navigator.vibrate`. Default: `true`. */
47
+ haptic?: boolean;
48
+ /** Play a short tone on completion via Web Audio API. Default: `false`. */
49
+ sound?: boolean;
50
+ /**
51
+ * When `true`, all input actions (typing, backspace, paste) are silently ignored.
52
+ * Use this during async verification to prevent the user modifying the code.
53
+ * Default: `false`.
54
+ */
55
+ disabled?: boolean;
56
+ /**
57
+ * Arbitrary per-character regex. When provided, each typed/pasted character must
58
+ * match this pattern to be accepted into a slot.
59
+ *
60
+ * Takes precedence over the named `type` for character validation only —
61
+ * `type` still controls `inputMode` and ARIA labels on the hidden input.
62
+ *
63
+ * The regex should match a **single character**:
64
+ * @example pattern: /^[0-9A-F]$/ — uppercase hex only
65
+ * @example pattern: /^[2-9A-HJ-NP-Z]$/ — ambiguity-free alphanumeric (no 0/O, 1/I/L)
66
+ */
67
+ pattern?: RegExp;
68
+ /**
69
+ * Optional transform applied to the raw clipboard text before it is filtered
70
+ * and distributed into slots. Runs before `filterString` inside `pasteString()`.
71
+ *
72
+ * Use to strip formatting from pasted codes that real users copy from emails or
73
+ * SMS messages (e.g. `"G-123456"` → `"123456"`, `"123 456"` → `"123456"`).
74
+ *
75
+ * The return value is then passed through the normal `filterString` + `pattern`
76
+ * validation, so you only need to handle the structural formatting — character
77
+ * validity is still enforced automatically.
78
+ *
79
+ * @example pasteTransformer: (raw) => raw.replace(/\s+|-/g, '')
80
+ * @example pasteTransformer: (raw) => raw.toUpperCase()
81
+ */
82
+ pasteTransformer?: (raw: string) => string;
83
+ /**
84
+ * Auto-focus the hidden input when the component mounts.
85
+ * Set to `false` to prevent the field from stealing focus on load.
86
+ * Default: `true`.
87
+ */
88
+ autoFocus?: boolean;
89
+ /**
90
+ * The `name` attribute to set on the hidden input for native HTML form
91
+ * submission and `FormData` compatibility.
92
+ * @example name: 'otp' → FormData includes otp=123456
93
+ */
94
+ name?: string;
95
+ /**
96
+ * Called when the hidden input gains browser focus.
97
+ * Use to show contextual help or update surrounding UI.
98
+ */
99
+ onFocus?: () => void;
100
+ /**
101
+ * Called when the hidden input loses browser focus.
102
+ * Use to trigger validation or hide contextual help.
103
+ */
104
+ onBlur?: () => void;
105
+ /**
106
+ * Character to display in empty (unfilled) slots as a visual hint.
107
+ * Common choices: `'○'`, `'_'`, `'·'`, `'•'`.
108
+ * Default: `''` (blank — no placeholder).
109
+ * @example placeholder: '○'
110
+ */
111
+ placeholder?: string;
112
+ /**
113
+ * When `true`, focusing a slot that already contains a character selects that
114
+ * character so the next keystroke replaces it in-place.
115
+ * When `false` (default), the cursor is placed at the slot position and the
116
+ * existing character must be deleted before a new one can be entered.
117
+ * Default: `false`.
118
+ */
119
+ selectOnFocus?: boolean;
120
+ /**
121
+ * When `true`, the hidden input is automatically blurred when all slots are
122
+ * filled. Removes focus styling and hides the fake caret once the code is
123
+ * complete. Useful for flows that immediately submit or verify on completion.
124
+ * Default: `false`.
125
+ */
126
+ blurOnComplete?: boolean;
127
+ /**
128
+ * Called when the user types or pastes a character that is rejected by the
129
+ * current `type` or `pattern` filter.
130
+ *
131
+ * Receives the raw rejected character and the zero-based slot index where
132
+ * entry was attempted. Use to display inline feedback such as
133
+ * "Only digits are allowed" or highlight the offending slot.
134
+ *
135
+ * @example
136
+ * onInvalidChar: (char, index) => console.warn(`Rejected "${char}" at slot ${index}`)
137
+ */
138
+ onInvalidChar?: (char: string, index: number) => void;
139
+ };
140
+ /** Options for the standalone `createTimer` utility. */
141
+ export type TimerOptions = {
142
+ /** Total countdown duration in seconds. */
143
+ totalSeconds: number;
144
+ /** Called every second with the remaining seconds. */
145
+ onTick?: (remainingSeconds: number) => void;
146
+ /** Called when the countdown reaches zero. */
147
+ onExpire?: () => void;
148
+ };
149
+ /** Controls returned by `createTimer`. */
150
+ export type TimerControls = {
151
+ /** Start the countdown. */
152
+ start: () => void;
153
+ /** Stop and pause the countdown. */
154
+ stop: () => void;
155
+ /** Reset remaining time back to `totalSeconds` without starting. */
156
+ reset: () => void;
157
+ /** Reset and immediately start again. */
158
+ restart: () => void;
159
+ };
160
+ /** Listener function invoked after every state mutation in `createDigito`. */
161
+ export type StateListener = (state: DigitoState) => void;
162
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,mDAAmD;AACnD,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,UAAU,GAAG,cAAc,GAAG,KAAK,CAAA;AAEvE,4DAA4D;AAC5D,MAAM,MAAM,WAAW,GAAG;IACxB,+DAA+D;IAC/D,UAAU,EAAG,MAAM,EAAE,CAAA;IACrB,2CAA2C;IAC3C,UAAU,EAAG,MAAM,CAAA;IACnB,wCAAwC;IACxC,QAAQ,EAAK,OAAO,CAAA;IACpB,uDAAuD;IACvD,UAAU,EAAG,OAAO,CAAA;IACpB;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,sEAAsE;AACtE,MAAM,MAAM,aAAa,GAAG;IAC1B,2CAA2C;IAC3C,MAAM,CAAC,EAAQ,MAAM,CAAA;IACrB,iEAAiE;IACjE,IAAI,CAAC,EAAU,SAAS,CAAA;IACxB,2EAA2E;IAC3E,KAAK,CAAC,EAAS,MAAM,CAAA;IACrB,8EAA8E;IAC9E,WAAW,CAAC,EAAG,MAAM,CAAA;IACrB,oEAAoE;IACpE,UAAU,CAAC,EAAI,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACrC,sFAAsF;IACtF,MAAM,CAAC,EAAQ,CAAC,gBAAgB,EAAE,MAAM,KAAK,IAAI,CAAA;IACjD,8CAA8C;IAC9C,QAAQ,CAAC,EAAM,MAAM,IAAI,CAAA;IACzB,kDAAkD;IAClD,QAAQ,CAAC,EAAM,MAAM,IAAI,CAAA;IACzB,gFAAgF;IAChF,MAAM,CAAC,EAAQ,OAAO,CAAA;IACtB,2EAA2E;IAC3E,KAAK,CAAC,EAAS,OAAO,CAAA;IACtB;;;;OAIG;IACH,QAAQ,CAAC,EAAM,OAAO,CAAA;IACtB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,EAAO,MAAM,CAAA;IACrB;;;;;;;;;;;;;OAaG;IACH,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;IAC1C;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACtD,CAAA;AAED,wDAAwD;AACxD,MAAM,MAAM,YAAY,GAAG;IACzB,2CAA2C;IAC3C,YAAY,EAAI,MAAM,CAAA;IACtB,sDAAsD;IACtD,MAAM,CAAC,EAAS,CAAC,gBAAgB,EAAE,MAAM,KAAK,IAAI,CAAA;IAClD,8CAA8C;IAC9C,QAAQ,CAAC,EAAO,MAAM,IAAI,CAAA;CAC3B,CAAA;AAED,0CAA0C;AAC1C,MAAM,MAAM,aAAa,GAAG;IAC1B,2BAA2B;IAC3B,KAAK,EAAI,MAAM,IAAI,CAAA;IACnB,oCAAoC;IACpC,IAAI,EAAK,MAAM,IAAI,CAAA;IACnB,oEAAoE;IACpE,KAAK,EAAI,MAAM,IAAI,CAAA;IACnB,yCAAyC;IACzC,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,CAAA;AAED,8EAA8E;AAC9E,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * digito/core/types
3
+ * ─────────────────────────────────────────────────────────────────────────────
4
+ * Shared TypeScript interfaces and type aliases used across the core modules.
5
+ *
6
+ * @author Olawale Balo — Product Designer + Design Engineer
7
+ * @license MIT
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}