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,254 @@
1
+ /*! Digito v1.0.0 | MIT | Olawale Balo — Product Designer + Design Engineer */
2
+ "use strict";(()=>{var it=Object.defineProperty,st=Object.defineProperties;var nt=Object.getOwnPropertyDescriptors;var Y=Object.getOwnPropertySymbols;var ot=Object.prototype.hasOwnProperty,rt=Object.prototype.propertyIsEnumerable;var J=(i,a,t)=>a in i?it(i,a,{enumerable:!0,configurable:!0,writable:!0,value:t}):i[a]=t,R=(i,a)=>{for(var t in a||(a={}))ot.call(a,t)&&J(i,t,a[t]);if(Y)for(var t of Y(a))rt.call(a,t)&&J(i,t,a[t]);return i},j=(i,a)=>st(i,nt(a));function M(i,a,t){if(!i||i.length!==1)return"";if(t!==void 0)return t.global&&(t.lastIndex=0),t.test(i)?i:"";switch(a){case"numeric":return/^[0-9]$/.test(i)?i:"";case"alphabet":return/^[a-zA-Z]$/.test(i)?i:"";case"alphanumeric":return/^[a-zA-Z0-9]$/.test(i)?i:"";case"any":return i;default:return""}}function O(i,a,t){return Array.from(i).filter(e=>M(e,a,t)!=="").join("")}function q(i){let{totalSeconds:a,onTick:t,onExpire:e}=i,s=a,o=null;function u(){o!==null&&(clearInterval(o),o=null)}function h(){u(),s=a}function S(){if(u(),a<=0){e==null||e();return}o=setInterval(()=>{s-=1,t==null||t(s),s<=0&&(u(),e==null||e())},1e3)}function l(){h(),S()}return{start:S,stop:u,reset:h,restart:l}}function P(){var i;try{(i=navigator==null?void 0:navigator.vibrate)==null||i.call(navigator,10)}catch(a){}}function Q(){try{let i=new AudioContext,a=i.createOscillator(),t=i.createGain();a.connect(t),t.connect(i.destination),a.frequency.value=880,t.gain.setValueAtTime(.08,i.currentTime),t.gain.exponentialRampToValueAtTime(.001,i.currentTime+.08),a.start(),a.stop(i.currentTime+.08),a.onended=()=>{i.close().catch(()=>{})}}catch(i){}}function X(i){return i.every(a=>a.length===1)}function H(i,a,t){return Math.max(a,Math.min(t,i))}function U(i){return i.join("")}function Z(i={}){var D,z,G;let a=(D=i.length)!=null?D:6,t=isNaN(a)?6:Math.max(1,Math.floor(a)),{type:e="numeric",pattern:s,onComplete:o,onInvalidChar:u,haptic:h=!0,sound:S=!1,pasteTransformer:l}=i,d=(z=i.disabled)!=null?z:!1,n={slotValues:Array(t).fill(""),activeSlot:0,hasError:!1,isComplete:!1,timerSeconds:(G=i.timer)!=null?G:0},p=new Set;function b(r){if(n=R(R({},n),r),p.size>0){let v=j(R({},n),{slotValues:[...n.slotValues]});p.forEach(C=>C(v))}return n}let g=null;function w(r){if(!X(r)||!o)return;h&&P(),S&&Q();let v=U(r);g!==null&&clearTimeout(g),g=setTimeout(()=>{g=null,o(v)},10)}function c(){g!==null&&(clearTimeout(g),g=null)}function N(r,v){if(d||r<0||r>=t)return n;let C=M(v,e,s);if(!C)return v.length===1&&(u==null||u(v,r)),n.activeSlot!==r?b({activeSlot:r}):n;let A=[...n.slotValues];A[r]=C;let k=r<t-1?r+1:t-1,L=b({slotValues:A,activeSlot:k,hasError:!1,isComplete:X(A)});return w(A),L}function B(r){if(d||r<0||r>=t)return n;let v=[...n.slotValues];if(v[r])return v[r]="",b({slotValues:v,activeSlot:r,isComplete:!1});let C=H(r-1,0,t-1);return v[C]="",b({slotValues:v,activeSlot:C,isComplete:!1})}function V(r){return b({activeSlot:H(r-1,0,t-1)})}function y(r){return b({activeSlot:H(r+1,0,t-1)})}function m(r,v){if(d)return n;let C;try{C=l?l(v):v}catch(T){console.warn("[digito] pasteTransformer threw \u2014 using raw paste text.",T),C=v}if(u&&C){let T=r;for(let W of Array.from(C))M(W,e,s)?T=(T+1)%t:u(W,T)}let A=O(C,e,s);if(!A)return n;let k=[...n.slotValues],L=r;for(let T=0;T<A.length&&T<t;T++)k[L]=A[T],L=(L+1)%t;let K=Math.min(A.length,t),tt=K>=t?t-1:(r+K)%t,et=b({slotValues:k,activeSlot:tt,hasError:!1,isComplete:X(k)});return w(k),et}function f(r){return r&&h&&P(),b({hasError:r})}function E(){var r;return g!==null&&(clearTimeout(g),g=null),b({slotValues:Array(t).fill(""),activeSlot:0,hasError:!1,isComplete:!1,timerSeconds:(r=i.timer)!=null?r:0})}function F(r){return b({activeSlot:H(r,0,t-1)})}function x(r){d=r}function _(r){return p.add(r),()=>{p.delete(r)}}return{get state(){return n},inputChar:N,deleteChar:B,moveFocusLeft:V,moveFocusRight:y,pasteString:m,setError:f,resetState:E,moveFocusTo:F,cancelPendingComplete:c,setDisabled:x,getCode:()=>U(n.slotValues),getSnapshot:()=>j(R({},n),{slotValues:[...n.slotValues]}),subscribe:_,getState:()=>j(R({},n),{slotValues:[...n.slotValues]})}}var at=`
3
+ :host {
4
+ display: inline-block;
5
+ position: relative;
6
+ line-height: 1;
7
+ }
8
+
9
+ .digito-wc-root {
10
+ position: relative;
11
+ display: inline-block;
12
+ }
13
+
14
+ .digito-wc-slots {
15
+ display: inline-flex;
16
+ gap: var(--digito-gap, 12px);
17
+ align-items: center;
18
+ position: relative;
19
+ }
20
+
21
+ .digito-wc-hidden {
22
+ position: absolute;
23
+ inset: 0;
24
+ width: 100%;
25
+ height: 100%;
26
+ opacity: 0;
27
+ border: none;
28
+ outline: none;
29
+ background: transparent;
30
+ color: transparent;
31
+ caret-color: transparent;
32
+ z-index: 1;
33
+ cursor: text;
34
+ font-size: 1px;
35
+ }
36
+
37
+ .digito-wc-slot {
38
+ position: relative;
39
+ width: var(--digito-size, 56px);
40
+ height: var(--digito-size, 56px);
41
+ border: 1px solid var(--digito-border-color, #E5E5E5);
42
+ border-radius: var(--digito-radius, 10px);
43
+ font-size: var(--digito-font-size, 24px);
44
+ font-weight: 600;
45
+ display: flex;
46
+ align-items: center;
47
+ justify-content: center;
48
+ background: var(--digito-bg, #FAFAFA);
49
+ color: var(--digito-color, #0A0A0A);
50
+ font-family: inherit;
51
+ cursor: text;
52
+ user-select: none;
53
+ transition: border-color 150ms ease, box-shadow 150ms ease, background 150ms ease, opacity 150ms ease;
54
+ }
55
+ .digito-wc-slot.is-active {
56
+ border-color: var(--digito-active-color, #3D3D3D);
57
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--digito-active-color, #3D3D3D) 10%, transparent);
58
+ background: var(--digito-bg-filled, #FFFFFF);
59
+ }
60
+ .digito-wc-slot.is-filled { background: var(--digito-bg-filled, #FFFFFF); }
61
+ .digito-wc-slot.is-error {
62
+ border-color: var(--digito-error-color, #FB2C36);
63
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--digito-error-color, #FB2C36) 12%, transparent);
64
+ }
65
+ .digito-wc-slot.is-success {
66
+ border-color: var(--digito-success-color, #00C950);
67
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--digito-success-color, #00C950) 12%, transparent);
68
+ }
69
+ .digito-wc-slot.is-disabled {
70
+ opacity: 0.45;
71
+ cursor: not-allowed;
72
+ pointer-events: none;
73
+ }
74
+ .digito-wc-slot:not(.is-filled) {
75
+ font-size: var(--digito-placeholder-size, 16px);
76
+ color: var(--digito-placeholder-color, #D3D3D3);
77
+ }
78
+ .digito-wc-slot.is-masked.is-filled {
79
+ font-size: var(--digito-masked-size, 16px);
80
+ }
81
+
82
+ .digito-wc-separator {
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ color: var(--digito-separator-color, #A1A1A1);
87
+ font-size: var(--digito-separator-size, 18px);
88
+ font-weight: 400;
89
+ user-select: none;
90
+ flex-shrink: 0;
91
+ padding: 0 2px;
92
+ }
93
+
94
+ .digito-wc-caret {
95
+ position: absolute;
96
+ width: 2px;
97
+ height: 52%;
98
+ background: var(--digito-caret-color, #3D3D3D);
99
+ border-radius: 1px;
100
+ animation: wc-blink 1s step-start infinite;
101
+ pointer-events: none;
102
+ display: none;
103
+ }
104
+ @keyframes wc-blink { 0%,100%{opacity:1} 50%{opacity:0} }
105
+
106
+ .digito-wc-timer {
107
+ display: flex;
108
+ align-items: center;
109
+ gap: 8px;
110
+ font-size: 14px;
111
+ padding: 12px 0 0;
112
+ }
113
+ .digito-wc-timer-label {
114
+ color: var(--digito-timer-color, #5C5C5C);
115
+ font-size: 14px;
116
+ }
117
+ .digito-wc-timer-badge {
118
+ display: inline-flex;
119
+ align-items: center;
120
+ background: color-mix(in srgb, var(--digito-error-color, #FB2C36) 10%, transparent);
121
+ color: var(--digito-error-color, #FB2C36);
122
+ font-weight: 500;
123
+ font-size: 14px;
124
+ padding: 2px 10px;
125
+ border-radius: 99px;
126
+ height: 24px;
127
+ font-variant-numeric: tabular-nums;
128
+ }
129
+
130
+ .digito-wc-resend {
131
+ display: none;
132
+ align-items: center;
133
+ gap: 8px;
134
+ font-size: 14px;
135
+ color: var(--digito-timer-color, #5C5C5C);
136
+ padding: 12px 0 0;
137
+ }
138
+ .digito-wc-resend.is-visible { display: flex; }
139
+ .digito-wc-resend-btn {
140
+ display: inline-flex;
141
+ align-items: center;
142
+ background: #E8E8E8;
143
+ border: none;
144
+ padding: 2px 10px;
145
+ border-radius: 99px;
146
+ color: #0A0A0A;
147
+ font-weight: 500;
148
+ font-size: 14px;
149
+ transition: background 150ms ease;
150
+ cursor: pointer;
151
+ height: 24px;
152
+ font-family: inherit;
153
+ }
154
+ .digito-wc-resend-btn:hover { background: #E5E5E5; }
155
+ .digito-wc-resend-btn:disabled { color: #A1A1A1; cursor: not-allowed; background: #F5F5F5; }
156
+ `;function I(i){let a=Math.floor(i/60),t=i%60;return a>0?`${a}:${String(t).padStart(2,"0")}`:`0:${String(t).padStart(2,"0")}`}var $=class extends HTMLElement{constructor(){super();this.slotEls=[];this.caretEls=[];this.hiddenInput=null;this.timerEl=null;this.timerBadgeEl=null;this.resendEl=null;this.timerCtrl=null;this.resendCountdown=null;this.digito=null;this._isDisabled=!1;this._isSuccess=!1;this._pattern=void 0;this._pasteTransformer=void 0;this._onComplete=void 0;this._onResend=void 0;this._onFocus=void 0;this._onBlur=void 0;this._onInvalidChar=void 0;this.shadow=this.attachShadow({mode:"open"})}set onComplete(t){if(t!==void 0&&typeof t!="function"){console.warn("[digito] onComplete must be a function, got:",typeof t);return}this._onComplete=t}set onResend(t){if(t!==void 0&&typeof t!="function"){console.warn("[digito] onResend must be a function, got:",typeof t);return}this._onResend=t}set onFocus(t){if(t!==void 0&&typeof t!="function"){console.warn("[digito] onFocus must be a function, got:",typeof t);return}this._onFocus=t}set onBlur(t){if(t!==void 0&&typeof t!="function"){console.warn("[digito] onBlur must be a function, got:",typeof t);return}this._onBlur=t}set onInvalidChar(t){if(t!==void 0&&typeof t!="function"){console.warn("[digito] onInvalidChar must be a function, got:",typeof t);return}this._onInvalidChar=t,this.shadow.children.length>0&&this.build()}set pattern(t){if(t!==void 0&&!(t instanceof RegExp)){console.warn("[digito] pattern must be a RegExp, got:",typeof t);return}this._pattern=t,this.shadow.children.length>0&&this.build()}set pasteTransformer(t){if(t!==void 0&&typeof t!="function"){console.warn("[digito] pasteTransformer must be a function, got:",typeof t);return}this._pasteTransformer=t,this.shadow.children.length>0&&this.build()}connectedCallback(){this.build()}disconnectedCallback(){var t,e,s;(t=this.timerCtrl)==null||t.stop(),(e=this.resendCountdown)==null||e.stop(),(s=this.digito)==null||s.resetState()}attributeChangedCallback(){this.shadow.children.length>0&&this.build()}get _length(){var e;let t=parseInt((e=this.getAttribute("length"))!=null?e:"6",10);return isNaN(t)||t<1?6:Math.floor(t)}get _type(){var t;return(t=this.getAttribute("type"))!=null?t:"numeric"}get _timer(){var e;let t=parseInt((e=this.getAttribute("timer"))!=null?e:"0",10);return isNaN(t)||t<0?0:Math.floor(t)}get _resendAfter(){var e;let t=parseInt((e=this.getAttribute("resend-after"))!=null?e:"30",10);return isNaN(t)||t<1?30:Math.floor(t)}get _disabledAttr(){return this.hasAttribute("disabled")}get _separatorAfter(){let t=this.getAttribute("separator-after");return t?t.split(",").map(e=>parseInt(e.trim(),10)).filter(e=>!isNaN(e)&&e>0):[]}get _separator(){var t;return(t=this.getAttribute("separator"))!=null?t:"\u2014"}get _masked(){return this.hasAttribute("masked")}get _maskChar(){var t;return(t=this.getAttribute("mask-char"))!=null?t:"\u25CF"}get _name(){var t;return(t=this.getAttribute("name"))!=null?t:""}get _placeholder(){var t;return(t=this.getAttribute("placeholder"))!=null?t:""}get _autoFocus(){return!this.hasAttribute("auto-focus")||this.getAttribute("auto-focus")!=="false"}get _selectOnFocus(){return this.hasAttribute("select-on-focus")}get _blurOnComplete(){return this.hasAttribute("blur-on-complete")}build(){var N,B,V;let t=this._length,e=this._type,s=this._timer,o=this._resendAfter,u=this._separatorAfter,h=this._separator,S=this._masked,l=this._name,d=this._autoFocus,n=this._selectOnFocus,p=this._blurOnComplete;for(this._isDisabled=this._disabledAttr,(N=this.timerCtrl)==null||N.stop(),(B=this.resendCountdown)==null||B.stop(),(V=this.digito)==null||V.resetState();this.shadow.firstChild;)this.shadow.removeChild(this.shadow.firstChild);this.slotEls=[],this.caretEls=[],this.timerEl=null,this.timerBadgeEl=null,this.resendEl=null,this.timerCtrl=null,this.resendCountdown=null;let b=document.createElement("style");b.textContent=at,this.shadow.appendChild(b);let g=document.createElement("div");g.className="digito-wc-root";let w=document.createElement("div");w.className="digito-wc-slots";for(let y=0;y<t;y++){let m=document.createElement("div");m.className="digito-wc-slot",m.setAttribute("aria-hidden","true");let f=document.createElement("div");if(f.className="digito-wc-caret",m.appendChild(f),this.caretEls.push(f),this.slotEls.push(m),w.appendChild(m),u.some(E=>y===E-1)){let E=document.createElement("div");E.className="digito-wc-separator",E.textContent=h,E.setAttribute("aria-hidden","true"),w.appendChild(E)}}let c=document.createElement("input");if(c.type=S?"password":"text",c.inputMode=e==="numeric"?"numeric":"text",c.autocomplete="one-time-code",c.maxLength=t,c.disabled=this._isDisabled,c.className="digito-wc-hidden",c.setAttribute("aria-label",`Enter your ${t}-${e==="numeric"?"digit":"character"} code`),c.setAttribute("spellcheck","false"),c.setAttribute("autocorrect","off"),c.setAttribute("autocapitalize","off"),l&&(c.name=l),this.hiddenInput=c,g.appendChild(w),g.appendChild(c),this.shadow.appendChild(g),this.digito=Z({length:t,type:e,pattern:this._pattern,pasteTransformer:this._pasteTransformer,onInvalidChar:this._onInvalidChar,onComplete:y=>{var m;(m=this._onComplete)==null||m.call(this,y),this.dispatchEvent(new CustomEvent("complete",{detail:{code:y},bubbles:!0,composed:!0}))}}),s>0){let y=document.createElement("div");y.className="digito-wc-timer",this.timerEl=y;let m=document.createElement("span");m.className="digito-wc-timer-label",m.textContent="Code expires in";let f=document.createElement("span");f.className="digito-wc-timer-badge",f.textContent=I(s),this.timerBadgeEl=f,y.appendChild(m),y.appendChild(f),g.appendChild(y);let E=document.createElement("div");E.className="digito-wc-resend",this.resendEl=E;let F=document.createElement("span");F.textContent="Didn\u2019t receive the code?";let x=document.createElement("button");x.className="digito-wc-resend-btn",x.textContent="Resend",x.type="button",E.appendChild(F),E.appendChild(x),g.appendChild(E),this.timerCtrl=q({totalSeconds:s,onTick:_=>{this.timerBadgeEl&&(this.timerBadgeEl.textContent=I(_))},onExpire:()=>{this.timerEl&&(this.timerEl.style.display="none"),this.resendEl&&this.resendEl.classList.add("is-visible"),this.dispatchEvent(new CustomEvent("expire",{bubbles:!0,composed:!0}))}}),this.timerCtrl.start(),x.addEventListener("click",()=>{var _,D;!this.timerEl||!this.timerBadgeEl||!this.resendEl||(this.resendEl.classList.remove("is-visible"),this.timerEl.style.display="flex",this.timerBadgeEl.textContent=I(o),(_=this.resendCountdown)==null||_.stop(),this.resendCountdown=q({totalSeconds:o,onTick:z=>{this.timerBadgeEl&&(this.timerBadgeEl.textContent=I(z))},onExpire:()=>{this.timerEl&&(this.timerEl.style.display="none"),this.resendEl&&this.resendEl.classList.add("is-visible")}}),this.resendCountdown.start(),(D=this._onResend)==null||D.call(this))})}this.attachEvents(n,p),this._isDisabled&&this.applyDisabledDOM(!0),c.addEventListener("click",y=>{var F,x,_;if(this._isDisabled)return;let m=this.slotEls.length-1;for(let D=0;D<this.slotEls.length;D++)if(y.clientX<=this.slotEls[D].getBoundingClientRect().right){m=D;break}let f=Math.min(m,c.value.length);(F=this.digito)==null||F.moveFocusTo(f);let E=(_=(x=this.digito)==null?void 0:x.state.slotValues[f])!=null?_:"";n&&E?c.setSelectionRange(f,f+1):c.setSelectionRange(f,f),this.syncSlotsToDOM()}),requestAnimationFrame(()=>{!this._isDisabled&&d&&c.focus(),c.setSelectionRange(0,0),this.syncSlotsToDOM()})}syncSlotsToDOM(){if(!this.digito||!this.hiddenInput)return;let{slotValues:t,activeSlot:e,hasError:s}=this.digito.state,o=this.shadow.activeElement===this.hiddenInput;this.slotEls.forEach((h,S)=>{var p;let l=(p=t[S])!=null?p:"",d=S===e&&o,n=h.childNodes[1];(!n||n.nodeType!==Node.TEXT_NODE)&&(n=document.createTextNode(""),h.appendChild(n)),n.nodeValue=this._masked&&l?this._maskChar:l||this._placeholder,h.classList.toggle("is-active",d&&!this._isDisabled),h.classList.toggle("is-filled",!!l),h.classList.toggle("is-masked",this._masked),h.classList.toggle("is-error",s),h.classList.toggle("is-success",this._isSuccess),h.classList.toggle("is-disabled",this._isDisabled),this.caretEls[S].style.display=d&&!l&&!this._isDisabled?"block":"none"});let u=t.join("");this.hiddenInput.value!==u&&(this.hiddenInput.value=u)}applyDisabledDOM(t){this.hiddenInput&&(this.hiddenInput.disabled=t),this.slotEls.forEach(e=>e.classList.toggle("is-disabled",t))}attachEvents(t,e){let s=this.hiddenInput,o=this.digito,u=this._length,h=this._type,S=this._pattern;s.addEventListener("keydown",l=>{var n;if(this._isDisabled)return;let d=(n=s.selectionStart)!=null?n:0;if(l.key==="Backspace"){l.preventDefault(),o.deleteChar(d),this.syncSlotsToDOM(),this.dispatchChange();let p=o.state.activeSlot;requestAnimationFrame(()=>s.setSelectionRange(p,p))}else if(l.key==="ArrowLeft")l.preventDefault(),o.moveFocusLeft(d),this.syncSlotsToDOM(),requestAnimationFrame(()=>s.setSelectionRange(o.state.activeSlot,o.state.activeSlot));else if(l.key==="ArrowRight")l.preventDefault(),o.moveFocusRight(d),this.syncSlotsToDOM(),requestAnimationFrame(()=>s.setSelectionRange(o.state.activeSlot,o.state.activeSlot));else if(l.key==="Tab"){if(l.shiftKey){if(d===0)return;l.preventDefault(),o.moveFocusLeft(d)}else{if(!o.state.slotValues[d]||d>=u-1)return;l.preventDefault(),o.moveFocusRight(d)}this.syncSlotsToDOM();let p=o.state.activeSlot;requestAnimationFrame(()=>s.setSelectionRange(p,p))}}),s.addEventListener("input",()=>{if(this._isDisabled)return;let l=s.value;if(!l){o.resetState(),s.value="",s.setSelectionRange(0,0),this.syncSlotsToDOM(),this.dispatchChange();return}let d=O(l,h,S).slice(0,u);o.resetState();for(let p=0;p<d.length;p++)o.inputChar(p,d[p]);let n=Math.min(d.length,u-1);s.value=d,s.setSelectionRange(n,n),o.moveFocusTo(n),this.syncSlotsToDOM(),this.dispatchChange(),e&&o.state.isComplete&&requestAnimationFrame(()=>s.blur())}),s.addEventListener("paste",l=>{var g,w,c;if(this._isDisabled)return;l.preventDefault();let d=(w=(g=l.clipboardData)==null?void 0:g.getData("text"))!=null?w:"",n=(c=s.selectionStart)!=null?c:0;o.pasteString(n,d);let{slotValues:p,activeSlot:b}=o.state;s.value=p.join(""),s.setSelectionRange(b,b),this.syncSlotsToDOM(),this.dispatchChange(),e&&o.state.isComplete&&requestAnimationFrame(()=>s.blur())}),s.addEventListener("focus",()=>{var l;(l=this._onFocus)==null||l.call(this),requestAnimationFrame(()=>{let d=o.state.activeSlot,n=o.state.slotValues[d];t&&n?s.setSelectionRange(d,d+1):s.setSelectionRange(d,d),this.syncSlotsToDOM()})}),s.addEventListener("blur",()=>{var l;(l=this._onBlur)==null||l.call(this),this.slotEls.forEach(d=>{d.classList.remove("is-active")}),this.caretEls.forEach(d=>{d.style.display="none"})})}dispatchChange(){var t,e;this.dispatchEvent(new CustomEvent("change",{detail:{code:(e=(t=this.digito)==null?void 0:t.getCode())!=null?e:""},bubbles:!0,composed:!0}))}reset(){var t,e,s;this._isSuccess=!1,(t=this.digito)==null||t.resetState(),this.hiddenInput&&(this.hiddenInput.value="",this._isDisabled||this.hiddenInput.focus(),this.hiddenInput.setSelectionRange(0,0)),this.timerBadgeEl&&(this.timerBadgeEl.textContent=I(this._timer)),this.timerEl&&(this.timerEl.style.display="flex"),this.resendEl&&this.resendEl.classList.remove("is-visible"),(e=this.resendCountdown)==null||e.stop(),(s=this.timerCtrl)==null||s.restart(),this.syncSlotsToDOM()}setError(t){var e;t&&(this._isSuccess=!1),(e=this.digito)==null||e.setError(t),this.syncSlotsToDOM()}setSuccess(t){var e,s,o;this._isSuccess=t,t&&((e=this.digito)==null||e.setError(!1),(s=this.timerCtrl)==null||s.stop(),(o=this.resendCountdown)==null||o.stop(),this.timerEl&&(this.timerEl.style.display="none"),this.resendEl&&(this.resendEl.style.display="none")),this.syncSlotsToDOM()}setDisabled(t){var e;this._isDisabled=t,(e=this.digito)==null||e.setDisabled(t),this.applyDisabledDOM(t),this.syncSlotsToDOM(),!t&&this.hiddenInput&&requestAnimationFrame(()=>{var s,o,u,h,S,l;(s=this.hiddenInput)==null||s.focus(),(l=this.hiddenInput)==null||l.setSelectionRange((u=(o=this.digito)==null?void 0:o.state.activeSlot)!=null?u:0,(S=(h=this.digito)==null?void 0:h.state.activeSlot)!=null?S:0)})}getCode(){var t,e;return(e=(t=this.digito)==null?void 0:t.getCode())!=null?e:""}};$.observedAttributes=["length","type","timer","resend-after","disabled","separator-after","separator","masked","mask-char","name","placeholder","auto-focus","select-on-focus","blur-on-complete"];typeof customElements!="undefined"&&!customElements.get("digito-input")&&customElements.define("digito-input",$);})();
157
+ /**
158
+ * digito/core/filter
159
+ * ─────────────────────────────────────────────────────────────────────────────
160
+ * Character filtering utilities — exported for use by all adapters.
161
+ *
162
+ * @author Olawale Balo — Product Designer + Design Engineer
163
+ * @license MIT
164
+ */
165
+ /**
166
+ * digito/core/timer
167
+ * ─────────────────────────────────────────────────────────────────────────────
168
+ * Standalone countdown timer — re-exported from core for use by adapters and
169
+ * developers who want to drive their own timer UI.
170
+ *
171
+ * @author Olawale Balo — Product Designer + Design Engineer
172
+ * @license MIT
173
+ */
174
+ /**
175
+ * digito/core/feedback
176
+ * ─────────────────────────────────────────────────────────────────────────────
177
+ * Optional sensory feedback utilities — exported so consumers can call them
178
+ * in their own event handlers without reimplementing the Web Audio / vibration
179
+ * boilerplate ("bring your own feedback" pattern).
180
+ *
181
+ * Used internally by the core machine when `haptic` / `sound` options are set.
182
+ *
183
+ * @author Olawale Balo — Product Designer + Design Engineer
184
+ * @license MIT
185
+ */
186
+ /**
187
+ * digito/core/machine
188
+ * ─────────────────────────────────────────────────────────────────────────────
189
+ * Pure OTP state machine — zero DOM, zero framework, zero side effects.
190
+ * All adapters import `createDigito` from here (via core/index.ts).
191
+ *
192
+ * Subscription system: pass a listener to `subscribe()` to be notified after
193
+ * every state mutation. Compatible with XState / Zustand-style patterns:
194
+ *
195
+ * const otp = createDigito({ length: 6 })
196
+ * const unsub = otp.subscribe(state => console.log(state))
197
+ * // ... later:
198
+ * unsub()
199
+ *
200
+ * @author Olawale Balo — Product Designer + Design Engineer
201
+ * @license MIT
202
+ */
203
+ /**
204
+ * digito/core
205
+ * ─────────────────────────────────────────────────────────────────────────────
206
+ * Pure OTP state machine — zero DOM, zero framework, zero side effects.
207
+ * All adapters (vanilla, React, Vue, Svelte, Alpine, Web Components) import
208
+ * from here. Nothing else is shared between them.
209
+ *
210
+ * @author Olawale Balo — Product Designer + Design Engineer
211
+ * @license MIT
212
+ */
213
+ /**
214
+ * digito/web-component
215
+ * ─────────────────────────────────────────────────────────────────────────────
216
+ * Framework-agnostic Web Component — <digito-input>
217
+ * Uses single hidden-input architecture for correct SMS autofill + a11y.
218
+ *
219
+ * Attributes:
220
+ * length Number of slots (default: 6)
221
+ * type Character set: numeric | alphabet | alphanumeric | any (default: numeric)
222
+ * timer Countdown seconds (default: 0 = no timer)
223
+ * resend-after Cooldown seconds before the built-in Resend re-enables (default: 30)
224
+ * disabled Boolean attribute — disables all input when present
225
+ * separator-after Slot index (1-based) or comma-separated list, e.g. "3" or "2,4" (default: none)
226
+ * separator Separator character to render (default: —)
227
+ * name Sets the hidden input's name attr for native form submission
228
+ * placeholder Character shown in empty slots (e.g. "○" or "_")
229
+ * auto-focus Boolean attribute — focus input on mount (default: true when absent)
230
+ * select-on-focus Boolean attribute — selects the current slot char on focus
231
+ * blur-on-complete Boolean attribute — blurs the input when all slots are filled
232
+ *
233
+ * Events:
234
+ * complete CustomEvent<{ code: string }> — fired when all slots filled
235
+ * expire CustomEvent — fired when timer reaches zero
236
+ * change CustomEvent<{ code: string }> — fired on every input change
237
+ *
238
+ * DOM API:
239
+ * el.reset()
240
+ * el.setError(boolean)
241
+ * el.setSuccess(boolean)
242
+ * el.setDisabled(boolean)
243
+ * el.getCode() -> string
244
+ * el.pattern = /^[0-9A-F]$/ (JS property, not attribute)
245
+ * el.pasteTransformer = fn (JS property)
246
+ * el.onComplete = code => {} (JS property)
247
+ * el.onResend = () => {} (JS property)
248
+ * el.onFocus = () => {} (JS property)
249
+ * el.onBlur = () => {} (JS property)
250
+ * el.onInvalidChar = (char, i) => {} (JS property)
251
+ *
252
+ * @author Olawale Balo — Product Designer + Design Engineer
253
+ * @license MIT
254
+ */
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/core/filter.ts", "../src/core/timer.ts", "../src/core/feedback.ts", "../src/core/machine.ts", "../src/adapters/web-component.ts"],
4
+ "sourcesContent": ["/**\n * digito/core/filter\n * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n * Character filtering utilities \u2014 exported for use by all adapters.\n *\n * @author Olawale Balo \u2014 Product Designer + Design Engineer\n * @license MIT\n */\n\nimport type { InputType } from './types.js'\n\n/**\n * Returns `char` if it is valid for `type` (and optional `pattern`), otherwise `''`.\n * Single character input only \u2014 multi-char strings always return `''`.\n *\n * When `pattern` is provided it takes precedence over `type` for validation.\n */\nexport function filterChar(char: string, type: InputType, pattern?: RegExp): string {\n if (!char || char.length !== 1) return ''\n if (pattern !== undefined) {\n // A pattern with the /g flag has stateful lastIndex. Reset it before every\n // test so the same pattern can be reused safely across multiple characters\n // without alternating between matches.\n if (pattern.global) pattern.lastIndex = 0\n return pattern.test(char) ? char : ''\n }\n switch (type) {\n case 'numeric': return /^[0-9]$/.test(char) ? char : ''\n case 'alphabet': return /^[a-zA-Z]$/.test(char) ? char : ''\n case 'alphanumeric': return /^[a-zA-Z0-9]$/.test(char) ? char : ''\n case 'any': return char\n default: return ''\n }\n}\n\n/**\n * Filters every character in `str` using `filterChar`.\n * Used to sanitize pasted strings and controlled-value inputs before distribution.\n *\n * When `pattern` is provided it takes precedence over `type` for each character.\n */\nexport function filterString(str: string, type: InputType, pattern?: RegExp): string {\n // Array.from iterates over Unicode code points, not UTF-16 code units.\n // str.split('') would split emoji and other supplementary-plane characters\n // into surrogate pairs (two strings of length 1 each), causing filterChar\n // to accept broken half-surrogates into slots for type:'any'.\n return Array.from(str).filter(c => filterChar(c, type, pattern) !== '').join('')\n}\n", "/**\n * digito/core/timer\n * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n * Standalone countdown timer \u2014 re-exported from core for use by adapters and\n * developers who want to drive their own timer UI.\n *\n * @author Olawale Balo \u2014 Product Designer + Design Engineer\n * @license MIT\n */\n\nimport type { TimerOptions, TimerControls } from './types.js'\n\n/**\n * Create a 1-second countdown timer.\n *\n * Lifecycle notes:\n * - `start()` is idempotent \u2014 it stops any running interval before starting a\n * new one, so calling it twice never produces double-ticking.\n * - If `totalSeconds <= 0`, `onExpire` fires synchronously on `start()` and no\n * interval is created (avoids decrementing to -1 and passing invalid values).\n * - `reset()` stops and restores remaining seconds without restarting.\n * - `restart()` is shorthand for `reset()` followed immediately by `start()`.\n * Used by the vanilla adapter's \"Resend\" button to reset the countdown.\n */\nexport function createTimer(options: TimerOptions): TimerControls {\n const { totalSeconds, onTick, onExpire } = options\n\n let remainingSeconds = totalSeconds\n let intervalId: ReturnType<typeof setInterval> | null = null\n\n /** Stop the running interval. No-op if already stopped. */\n function stop(): void {\n if (intervalId !== null) {\n clearInterval(intervalId)\n intervalId = null\n }\n }\n\n /** Stop the interval and restore `remainingSeconds` to `totalSeconds`. Does not restart. */\n function reset(): void {\n stop()\n remainingSeconds = totalSeconds\n }\n\n /**\n * Start ticking. Stops any existing interval first to prevent double-ticking.\n * If `totalSeconds <= 0`, fires `onExpire` immediately without creating an interval.\n */\n function start(): void {\n stop()\n // Guard: if totalSeconds is zero or negative, fire onExpire immediately\n // without starting an interval. Without this, the first tick would decrement\n // to -1 and pass an invalid value to onTick before calling onExpire.\n if (totalSeconds <= 0) {\n onExpire?.()\n return\n }\n intervalId = setInterval(() => {\n remainingSeconds -= 1\n onTick?.(remainingSeconds)\n if (remainingSeconds <= 0) {\n stop()\n onExpire?.()\n }\n }, 1000)\n }\n\n /** Reset to `totalSeconds` and immediately start ticking. */\n function restart(): void {\n reset()\n start()\n }\n\n return { start, stop, reset, restart }\n}\n", "/**\n * digito/core/feedback\n * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n * Optional sensory feedback utilities \u2014 exported so consumers can call them\n * in their own event handlers without reimplementing the Web Audio / vibration\n * boilerplate (\"bring your own feedback\" pattern).\n *\n * Used internally by the core machine when `haptic` / `sound` options are set.\n *\n * @author Olawale Balo \u2014 Product Designer + Design Engineer\n * @license MIT\n */\n\n/**\n * Trigger a short haptic pulse via `navigator.vibrate`.\n * Silently no-ops in environments that don't support the Vibration API\n * (e.g. desktop browsers, Safari, Node.js).\n */\nexport function triggerHapticFeedback(): void {\n try { navigator?.vibrate?.(10) } catch { /* not supported \u2014 fail silently */ }\n}\n\n/**\n * Play a brief 880 Hz tone via the Web Audio API.\n * The AudioContext is closed immediately after the tone ends to prevent\n * Chrome's ~6-concurrent-context limit from being reached across calls.\n * Silently no-ops where Web Audio is unavailable.\n */\nexport function triggerSoundFeedback(): void {\n try {\n const audioCtx = new AudioContext()\n const oscillator = audioCtx.createOscillator()\n const gainNode = audioCtx.createGain()\n oscillator.connect(gainNode)\n gainNode.connect(audioCtx.destination)\n oscillator.frequency.value = 880\n gainNode.gain.setValueAtTime(0.08, audioCtx.currentTime)\n gainNode.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.08)\n oscillator.start()\n oscillator.stop(audioCtx.currentTime + 0.08)\n // Close the context after the tone ends \u2014 prevents AudioContext instance leak\n oscillator.onended = () => { audioCtx.close().catch(() => { /* ignore */ }) }\n } catch { /* Web Audio not available \u2014 fail silently */ }\n}\n", "/**\n * digito/core/machine\n * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n * Pure OTP state machine \u2014 zero DOM, zero framework, zero side effects.\n * All adapters import `createDigito` from here (via core/index.ts).\n *\n * Subscription system: pass a listener to `subscribe()` to be notified after\n * every state mutation. Compatible with XState / Zustand-style patterns:\n *\n * const otp = createDigito({ length: 6 })\n * const unsub = otp.subscribe(state => console.log(state))\n * // ... later:\n * unsub()\n *\n * @author Olawale Balo \u2014 Product Designer + Design Engineer\n * @license MIT\n */\n\nimport type { DigitoOptions, DigitoState, StateListener, InputType } from './types.js'\nimport { filterChar, filterString } from './filter.js'\nimport { triggerHapticFeedback, triggerSoundFeedback } from './feedback.js'\n\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// INTERNAL HELPERS\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Returns `true` when every slot contains exactly one character. */\nfunction allSlotsFilled(slotValues: string[]): boolean {\n return slotValues.every(v => v.length === 1)\n}\n\n/** Clamp `index` to the inclusive range `[min, max]`. */\nfunction clampIndex(index: number, min: number, max: number): number {\n return Math.max(min, Math.min(max, index))\n}\n\n/** Join all slot values into a single string (the OTP code). */\nfunction joinSlots(slotValues: string[]): string {\n return slotValues.join('')\n}\n\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// FACTORY\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Creates a pure OTP state machine.\n * Returns action functions, a `state` getter, a `subscribe` method, and a\n * `getState` snapshot helper \u2014 no DOM, no side effects.\n */\nexport function createDigito(options: DigitoOptions = {}) {\n // Security: guard against invalid length values.\n // Negative numbers would throw a RangeError from Array(); zero would produce\n // a permanently-incomplete input with no slots. Clamp to a safe minimum of 1.\n const rawLength = options.length ?? 6\n // Guard against NaN (e.g. parseInt('', 10) from a missing data-attribute).\n // NaN would propagate through Math.floor and crash Array() with RangeError.\n const length = isNaN(rawLength) ? 6 : Math.max(1, Math.floor(rawLength))\n\n const {\n type = 'numeric' as InputType,\n pattern,\n onComplete,\n onInvalidChar,\n haptic = true,\n sound = false,\n pasteTransformer,\n } = options\n\n // `disabled` is mutable so setDisabled() can toggle it at runtime without\n // requiring the instance to be recreated. Adapters that pass disabled at\n // construction time still work \u2014 the initial value is honoured.\n let disabled = options.disabled ?? false\n\n let state: DigitoState = {\n slotValues: Array(length).fill('') as string[],\n activeSlot: 0,\n hasError: false,\n isComplete: false,\n timerSeconds: options.timer ?? 0,\n }\n\n // \u2500\u2500 Subscription set \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const listeners = new Set<StateListener>()\n\n function applyState(patch: Partial<DigitoState>): DigitoState {\n state = { ...state, ...patch }\n // Notify all subscribers with a deep copy of slotValues so they cannot\n // mutate the live array. A simple { ...state } is a shallow copy \u2014 the\n // slotValues array reference would be shared, letting a subscriber silently\n // corrupt internal state.\n if (listeners.size > 0) {\n const snapshot = { ...state, slotValues: [...state.slotValues] }\n listeners.forEach(fn => fn(snapshot))\n }\n return state\n }\n\n /**\n * Handle for the pending onComplete timeout.\n * Stored so resetState() can cancel it if the user clears the input\n * within the 10ms defer window (e.g. rapid type-then-delete).\n */\n let completeTimeoutId: ReturnType<typeof setTimeout> | null = null\n\n /** Fire onComplete after a short delay so DOM sync can finish first. */\n function notifyCompleteIfReady(slotValues: string[]): void {\n if (!allSlotsFilled(slotValues) || !onComplete) return\n if (haptic) triggerHapticFeedback()\n if (sound) triggerSoundFeedback()\n const code = joinSlots(slotValues)\n if (completeTimeoutId !== null) clearTimeout(completeTimeoutId)\n completeTimeoutId = setTimeout(() => {\n completeTimeoutId = null\n onComplete(code)\n }, 10)\n }\n\n /**\n * Cancel any pending onComplete callback without clearing slot state.\n * Use this after a programmatic fill (e.g. controlled-value sync in adapters)\n * to prevent a parent-driven pre-fill from triggering onComplete as if the\n * user had typed the code.\n */\n function cancelPendingComplete(): void {\n if (completeTimeoutId !== null) {\n clearTimeout(completeTimeoutId)\n completeTimeoutId = null\n }\n }\n\n\n // \u2500\u2500 Actions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Process a single character typed into `slotIndex`.\n * Invalid characters leave the slot unchanged and keep focus in place.\n * Out-of-bounds indices are silently ignored to prevent sparse-array corruption.\n */\n function inputChar(slotIndex: number, char: string): DigitoState {\n if (disabled) return state\n if (slotIndex < 0 || slotIndex >= length) return state\n const validChar = filterChar(char, type, pattern)\n if (!validChar) {\n // Fire onInvalidChar for single rejected characters (not empty/multi-char)\n if (char.length === 1) onInvalidChar?.(char, slotIndex)\n // Only notify subscribers if focus actually needs to move. Firing applyState\n // when activeSlot is already slotIndex causes a spurious state notification\n // on every invalid keystroke, which can trigger expensive re-renders.\n if (state.activeSlot !== slotIndex) {\n return applyState({ activeSlot: slotIndex })\n }\n return state\n }\n\n const slotValues = [...state.slotValues]\n slotValues[slotIndex] = validChar\n\n const nextSlot = slotIndex < length - 1 ? slotIndex + 1 : length - 1\n\n const newState = applyState({\n slotValues,\n activeSlot: nextSlot,\n hasError: false,\n isComplete: allSlotsFilled(slotValues),\n })\n\n notifyCompleteIfReady(slotValues)\n return newState\n }\n\n /**\n * Handle backspace at `slotIndex`.\n * Clears the current slot if filled, otherwise clears the previous slot and moves back.\n */\n function deleteChar(slotIndex: number): DigitoState {\n if (disabled) return state\n if (slotIndex < 0 || slotIndex >= length) return state\n const slotValues = [...state.slotValues]\n\n if (slotValues[slotIndex]) {\n slotValues[slotIndex] = ''\n return applyState({ slotValues, activeSlot: slotIndex, isComplete: false })\n }\n\n const prevSlot = clampIndex(slotIndex - 1, 0, length - 1)\n slotValues[prevSlot] = ''\n return applyState({ slotValues, activeSlot: prevSlot, isComplete: false })\n }\n\n /** Move focus one slot to the left, clamped to slot 0. */\n function moveFocusLeft(slotIndex: number): DigitoState {\n return applyState({ activeSlot: clampIndex(slotIndex - 1, 0, length - 1) })\n }\n\n /** Move focus one slot to the right, clamped to the last slot. */\n function moveFocusRight(slotIndex: number): DigitoState {\n return applyState({ activeSlot: clampIndex(slotIndex + 1, 0, length - 1) })\n }\n\n /**\n * Smart paste \u2014 distributes valid characters from `cursorSlot` forward,\n * wrapping around to slot 0 if the string is longer than the remaining slots.\n *\n * Examples (length = 6, type = numeric):\n * paste(0, '123456') \u2192 fills all slots\n * paste(5, '847291') \u2192 slot5='8', slot0='4', slot1='7', slot2='2', slot3='9', slot4='1'\n * paste(0, '84AB91') \u2192 filtered='8491', fills slots 0\u20133, slots 4\u20135 unchanged\n */\n function pasteString(cursorSlot: number, rawText: string): DigitoState {\n if (disabled) return state\n\n let transformed: string\n try {\n transformed = pasteTransformer ? pasteTransformer(rawText) : rawText\n } catch (err) {\n console.warn('[digito] pasteTransformer threw \u2014 using raw paste text.', err)\n transformed = rawText\n }\n\n // Report each rejected character so adapters can provide inline feedback.\n // Tracks the effective write cursor so the reported slot index matches where\n // the character would have landed if it had been valid.\n if (onInvalidChar && transformed) {\n let slotCursor = cursorSlot\n for (const char of Array.from(transformed)) {\n if (filterChar(char, type, pattern)) {\n slotCursor = (slotCursor + 1) % length\n } else {\n onInvalidChar(char, slotCursor)\n }\n }\n }\n\n const validChars = filterString(transformed, type, pattern)\n if (!validChars) return state\n\n const slotValues = [...state.slotValues]\n let writeSlot = cursorSlot\n\n for (let i = 0; i < validChars.length && i < length; i++) {\n slotValues[writeSlot] = validChars[i]\n writeSlot = (writeSlot + 1) % length\n }\n\n const charsWritten = Math.min(validChars.length, length)\n const nextActiveSlot = charsWritten >= length\n ? length - 1\n : (cursorSlot + charsWritten) % length\n\n const newState = applyState({\n slotValues,\n activeSlot: nextActiveSlot,\n hasError: false,\n isComplete: allSlotsFilled(slotValues),\n })\n\n notifyCompleteIfReady(slotValues)\n return newState\n }\n\n /** Set or clear the error state. Triggers haptic feedback when setting. */\n function setError(isError: boolean): DigitoState {\n if (isError && haptic) triggerHapticFeedback()\n return applyState({ hasError: isError })\n }\n\n /** Clear all slots and reset to initial state. Cancels any pending onComplete callback. */\n function resetState(): DigitoState {\n if (completeTimeoutId !== null) {\n clearTimeout(completeTimeoutId)\n completeTimeoutId = null\n }\n return applyState({\n slotValues: Array(length).fill('') as string[],\n activeSlot: 0,\n hasError: false,\n isComplete: false,\n timerSeconds: options.timer ?? 0,\n })\n }\n\n /** Move focus to a specific slot index. */\n function moveFocusTo(slotIndex: number): DigitoState {\n return applyState({ activeSlot: clampIndex(slotIndex, 0, length - 1) })\n }\n\n /**\n * Reactively enable or disable the input.\n * When `true`, inputChar / deleteChar / pasteString are silently ignored.\n * Navigation (moveFocusLeft/Right/To) is always allowed regardless of state.\n *\n * Prefer this over passing `disabled` at construction time whenever you need\n * to toggle disabled at runtime (e.g. during async verification).\n */\n function setDisabled(value: boolean): void {\n disabled = value\n }\n\n /**\n * Subscribe to state changes. The listener is called after every mutation\n * with a shallow copy of the new state.\n *\n * @returns An unsubscribe function \u2014 call it to stop receiving updates.\n *\n * @example\n * ```ts\n * const otp = createDigito({ length: 6 })\n * const unsub = otp.subscribe(state => console.log(state.slotValues))\n * // Later:\n * unsub()\n * ```\n */\n function subscribe(listener: StateListener): () => void {\n listeners.add(listener)\n return () => { listeners.delete(listener) }\n }\n\n return {\n /** Current state snapshot. */\n get state() { return state },\n\n // Input actions\n inputChar,\n deleteChar,\n moveFocusLeft,\n moveFocusRight,\n pasteString,\n\n // State control\n setError,\n resetState,\n moveFocusTo,\n\n /**\n * Cancel any pending onComplete callback without resetting slot state.\n * Intended for adapter-layer controlled-value syncs where a programmatic\n * fill should not be treated as a user completing the code.\n */\n cancelPendingComplete,\n\n /**\n * Toggle the disabled state at runtime.\n * Affects inputChar, deleteChar, and pasteString only.\n * Navigation actions are always allowed.\n */\n setDisabled,\n\n /** Returns the current joined code string. */\n getCode: () => joinSlots(state.slotValues),\n /**\n * Returns a copy of the current state with a cloned slotValues array.\n * Mutations to the returned object (including its slotValues array) will\n * not affect live state.\n */\n getSnapshot: (): DigitoState => ({ ...state, slotValues: [...state.slotValues] }),\n\n /**\n * Subscribe to state changes. Returns an unsubscribe function.\n * Compatible with XState / Zustand-style patterns.\n */\n subscribe,\n\n /**\n * Returns a copy of the current state with a cloned slotValues array.\n * Equivalent to `digito.getSnapshot()` \u2014 provided for ergonomic parity\n * with state-management libraries that expose a `getState()` method.\n */\n getState: (): DigitoState => ({ ...state, slotValues: [...state.slotValues] }),\n }\n}\n", "/**\n * digito/web-component\n * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n * Framework-agnostic Web Component \u2014 <digito-input>\n * Uses single hidden-input architecture for correct SMS autofill + a11y.\n *\n * Attributes:\n * length Number of slots (default: 6)\n * type Character set: numeric | alphabet | alphanumeric | any (default: numeric)\n * timer Countdown seconds (default: 0 = no timer)\n * resend-after Cooldown seconds before the built-in Resend re-enables (default: 30)\n * disabled Boolean attribute \u2014 disables all input when present\n * separator-after Slot index (1-based) or comma-separated list, e.g. \"3\" or \"2,4\" (default: none)\n * separator Separator character to render (default: \u2014)\n * name Sets the hidden input's name attr for native form submission\n * placeholder Character shown in empty slots (e.g. \"\u25CB\" or \"_\")\n * auto-focus Boolean attribute \u2014 focus input on mount (default: true when absent)\n * select-on-focus Boolean attribute \u2014 selects the current slot char on focus\n * blur-on-complete Boolean attribute \u2014 blurs the input when all slots are filled\n *\n * Events:\n * complete CustomEvent<{ code: string }> \u2014 fired when all slots filled\n * expire CustomEvent \u2014 fired when timer reaches zero\n * change CustomEvent<{ code: string }> \u2014 fired on every input change\n *\n * DOM API:\n * el.reset()\n * el.setError(boolean)\n * el.setSuccess(boolean)\n * el.setDisabled(boolean)\n * el.getCode() -> string\n * el.pattern = /^[0-9A-F]$/ (JS property, not attribute)\n * el.pasteTransformer = fn (JS property)\n * el.onComplete = code => {} (JS property)\n * el.onResend = () => {} (JS property)\n * el.onFocus = () => {} (JS property)\n * el.onBlur = () => {} (JS property)\n * el.onInvalidChar = (char, i) => {} (JS property)\n *\n * @author Olawale Balo \u2014 Product Designer + Design Engineer\n * @license MIT\n */\n\nimport {\n createDigito,\n createTimer,\n filterString,\n type InputType,\n} from '../core/index.js'\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// SHADOW DOM STYLES\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst STYLES = `\n :host {\n display: inline-block;\n position: relative;\n line-height: 1;\n }\n\n .digito-wc-root {\n position: relative;\n display: inline-block;\n }\n\n .digito-wc-slots {\n display: inline-flex;\n gap: var(--digito-gap, 12px);\n align-items: center;\n position: relative;\n }\n\n .digito-wc-hidden {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n opacity: 0;\n border: none;\n outline: none;\n background: transparent;\n color: transparent;\n caret-color: transparent;\n z-index: 1;\n cursor: text;\n font-size: 1px;\n }\n\n .digito-wc-slot {\n position: relative;\n width: var(--digito-size, 56px);\n height: var(--digito-size, 56px);\n border: 1px solid var(--digito-border-color, #E5E5E5);\n border-radius: var(--digito-radius, 10px);\n font-size: var(--digito-font-size, 24px);\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--digito-bg, #FAFAFA);\n color: var(--digito-color, #0A0A0A);\n font-family: inherit;\n cursor: text;\n user-select: none;\n transition: border-color 150ms ease, box-shadow 150ms ease, background 150ms ease, opacity 150ms ease;\n }\n .digito-wc-slot.is-active {\n border-color: var(--digito-active-color, #3D3D3D);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--digito-active-color, #3D3D3D) 10%, transparent);\n background: var(--digito-bg-filled, #FFFFFF);\n }\n .digito-wc-slot.is-filled { background: var(--digito-bg-filled, #FFFFFF); }\n .digito-wc-slot.is-error {\n border-color: var(--digito-error-color, #FB2C36);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--digito-error-color, #FB2C36) 12%, transparent);\n }\n .digito-wc-slot.is-success {\n border-color: var(--digito-success-color, #00C950);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--digito-success-color, #00C950) 12%, transparent);\n }\n .digito-wc-slot.is-disabled {\n opacity: 0.45;\n cursor: not-allowed;\n pointer-events: none;\n }\n .digito-wc-slot:not(.is-filled) {\n font-size: var(--digito-placeholder-size, 16px);\n color: var(--digito-placeholder-color, #D3D3D3);\n }\n .digito-wc-slot.is-masked.is-filled {\n font-size: var(--digito-masked-size, 16px);\n }\n\n .digito-wc-separator {\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--digito-separator-color, #A1A1A1);\n font-size: var(--digito-separator-size, 18px);\n font-weight: 400;\n user-select: none;\n flex-shrink: 0;\n padding: 0 2px;\n }\n\n .digito-wc-caret {\n position: absolute;\n width: 2px;\n height: 52%;\n background: var(--digito-caret-color, #3D3D3D);\n border-radius: 1px;\n animation: wc-blink 1s step-start infinite;\n pointer-events: none;\n display: none;\n }\n @keyframes wc-blink { 0%,100%{opacity:1} 50%{opacity:0} }\n\n .digito-wc-timer {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 14px;\n padding: 12px 0 0;\n }\n .digito-wc-timer-label {\n color: var(--digito-timer-color, #5C5C5C);\n font-size: 14px;\n }\n .digito-wc-timer-badge {\n display: inline-flex;\n align-items: center;\n background: color-mix(in srgb, var(--digito-error-color, #FB2C36) 10%, transparent);\n color: var(--digito-error-color, #FB2C36);\n font-weight: 500;\n font-size: 14px;\n padding: 2px 10px;\n border-radius: 99px;\n height: 24px;\n font-variant-numeric: tabular-nums;\n }\n\n .digito-wc-resend {\n display: none;\n align-items: center;\n gap: 8px;\n font-size: 14px;\n color: var(--digito-timer-color, #5C5C5C);\n padding: 12px 0 0;\n }\n .digito-wc-resend.is-visible { display: flex; }\n .digito-wc-resend-btn {\n display: inline-flex;\n align-items: center;\n background: #E8E8E8;\n border: none;\n padding: 2px 10px;\n border-radius: 99px;\n color: #0A0A0A;\n font-weight: 500;\n font-size: 14px;\n transition: background 150ms ease;\n cursor: pointer;\n height: 24px;\n font-family: inherit;\n }\n .digito-wc-resend-btn:hover { background: #E5E5E5; }\n .digito-wc-resend-btn:disabled { color: #A1A1A1; cursor: not-allowed; background: #F5F5F5; }\n`\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// HELPERS\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction formatCountdown(totalSeconds: number): string {\n const m = Math.floor(totalSeconds / 60)\n const s = totalSeconds % 60\n return m > 0 ? `${m}:${String(s).padStart(2, '0')}` : `0:${String(s).padStart(2, '0')}`\n}\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// WEB COMPONENT\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nclass DigitoInput extends HTMLElement {\n /**\n * HTML attribute names whose changes trigger `attributeChangedCallback`.\n * Any change to these attributes causes a full shadow DOM rebuild so the\n * component always reflects its attribute state without manual reconciliation.\n */\n static observedAttributes = ['length', 'type', 'timer', 'resend-after', 'disabled', 'separator-after', 'separator', 'masked', 'mask-char', 'name', 'placeholder', 'auto-focus', 'select-on-focus', 'blur-on-complete']\n\n // Shadow DOM references \u2014 rebuilt in full on every attributeChangedCallback.\n private slotEls: HTMLDivElement[] = []\n private caretEls: HTMLDivElement[] = []\n private hiddenInput: HTMLInputElement | null = null\n private timerEl: HTMLDivElement | null = null\n private timerBadgeEl: HTMLSpanElement | null = null\n private resendEl: HTMLDivElement | null = null\n private timerCtrl: ReturnType<typeof createTimer> | null = null\n private resendCountdown: ReturnType<typeof createTimer> | null = null\n private digito: ReturnType<typeof createDigito> | null = null\n private shadow: ShadowRoot\n\n // Runtime mutable state \u2014 toggled by setDisabled() without a full rebuild.\n private _isDisabled = false\n private _isSuccess = false\n\n // JS-property-only options. These cannot be expressed as HTML attributes\n // (RegExp and functions are not serialisable to strings), so they are stored\n // here and applied on every build().\n private _pattern: RegExp | undefined = undefined\n private _pasteTransformer: ((raw: string) => string) | undefined = undefined\n private _onComplete: ((code: string) => void) | undefined = undefined\n private _onResend: (() => void) | undefined = undefined\n private _onFocus: (() => void) | undefined = undefined\n private _onBlur: (() => void) | undefined = undefined\n private _onInvalidChar: ((char: string, index: number) => void) | undefined = undefined\n\n /** Called when all slots are filled. Also dispatches the `complete` CustomEvent. */\n set onComplete(fn: ((code: string) => void) | undefined) {\n if (fn !== undefined && typeof fn !== 'function') {\n console.warn('[digito] onComplete must be a function, got:', typeof fn); return\n }\n this._onComplete = fn\n }\n /** Called when the built-in Resend button is clicked. */\n set onResend(fn: (() => void) | undefined) {\n if (fn !== undefined && typeof fn !== 'function') {\n console.warn('[digito] onResend must be a function, got:', typeof fn); return\n }\n this._onResend = fn\n }\n /** Fires when the hidden input receives focus. Set as JS property. */\n set onFocus(fn: (() => void) | undefined) {\n if (fn !== undefined && typeof fn !== 'function') {\n console.warn('[digito] onFocus must be a function, got:', typeof fn); return\n }\n this._onFocus = fn\n }\n /** Fires when the hidden input loses focus. Set as JS property. */\n set onBlur(fn: (() => void) | undefined) {\n if (fn !== undefined && typeof fn !== 'function') {\n console.warn('[digito] onBlur must be a function, got:', typeof fn); return\n }\n this._onBlur = fn\n }\n /**\n * Fires when a typed character is rejected by type/pattern validation.\n * Receives the character and the slot index it was attempted on.\n * Set as JS property.\n */\n set onInvalidChar(fn: ((char: string, index: number) => void) | undefined) {\n if (fn !== undefined && typeof fn !== 'function') {\n console.warn('[digito] onInvalidChar must be a function, got:', typeof fn); return\n }\n this._onInvalidChar = fn\n if (this.shadow.children.length > 0) this.build()\n }\n\n /**\n * Arbitrary per-character regex. When set, each typed/pasted character must\n * match to be accepted. Takes precedence over the type attribute for\n * character validation. Cannot be expressed as an HTML attribute \u2014 set as a\n * JS property instead.\n * @example el.pattern = /^[0-9A-F]$/\n */\n set pattern(re: RegExp | undefined) {\n if (re !== undefined && !(re instanceof RegExp)) {\n console.warn('[digito] pattern must be a RegExp, got:', typeof re); return\n }\n this._pattern = re\n if (this.shadow.children.length > 0) this.build()\n }\n\n /**\n * Optional paste transformer function. Applied to raw clipboard text before\n * filtering. Use to strip formatting (e.g. `\"G-123456\"` \u2192 `\"123456\"`).\n * Cannot be expressed as an HTML attribute \u2014 set as a JS property.\n * @example el.pasteTransformer = (raw) => raw.replace(/\\s+|-/g, '')\n */\n set pasteTransformer(fn: ((raw: string) => string) | undefined) {\n if (fn !== undefined && typeof fn !== 'function') {\n console.warn('[digito] pasteTransformer must be a function, got:', typeof fn); return\n }\n this._pasteTransformer = fn\n if (this.shadow.children.length > 0) this.build()\n }\n\n constructor() {\n super()\n // Open shadow root so external CSS custom properties (--digito-*) cascade in.\n this.shadow = this.attachShadow({ mode: 'open' })\n }\n\n /** Called when the element is inserted into the DOM. Triggers the initial build. */\n connectedCallback(): void { this.build() }\n\n /**\n * Called when the element is removed from the DOM.\n * Stops both timers and cancels any pending `onComplete` timeout to avoid\n * callbacks firing after the element is detached.\n */\n disconnectedCallback(): void {\n this.timerCtrl?.stop()\n this.resendCountdown?.stop()\n this.digito?.resetState()\n }\n\n /**\n * Called when any observed attribute changes after the initial connection.\n * Guards on `shadow.children.length > 0` so it does not fire before\n * `connectedCallback` has completed the first build.\n */\n attributeChangedCallback(): void {\n if (this.shadow.children.length > 0) this.build()\n }\n\n // \u2500\u2500 Attribute accessors \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Each getter reads directly from the live attribute to stay in sync with\n // external attribute mutations. All values are snapshotted at the top of\n // build() so a single rebuild is always internally consistent.\n\n private get _length(): number {\n const v = parseInt(this.getAttribute('length') ?? '6', 10)\n return isNaN(v) || v < 1 ? 6 : Math.floor(v)\n }\n private get _type(): InputType { return (this.getAttribute('type') ?? 'numeric') as InputType }\n private get _timer(): number {\n const v = parseInt(this.getAttribute('timer') ?? '0', 10)\n return isNaN(v) || v < 0 ? 0 : Math.floor(v)\n }\n private get _resendAfter(): number {\n const v = parseInt(this.getAttribute('resend-after') ?? '30', 10)\n return isNaN(v) || v < 1 ? 30 : Math.floor(v)\n }\n private get _disabledAttr(): boolean { return this.hasAttribute('disabled') }\n /** Parses `separator-after=\"2,4\"` into `[2, 4]`. Filters NaN and zero values. */\n private get _separatorAfter(): number[] {\n const v = this.getAttribute('separator-after')\n if (!v) return []\n return v.split(',').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n) && n > 0)\n }\n private get _separator(): string { return this.getAttribute('separator') ?? '\u2014' }\n /** `masked` is a boolean attribute \u2014 present means true, absent means false. */\n private get _masked(): boolean { return this.hasAttribute('masked') }\n private get _maskChar(): string { return this.getAttribute('mask-char') ?? '\\u25CF' }\n private get _name(): string { return this.getAttribute('name') ?? '' }\n private get _placeholder(): string { return this.getAttribute('placeholder') ?? '' }\n /**\n * `auto-focus` defaults to `true` when the attribute is absent.\n * Setting `auto-focus=\"false\"` explicitly suppresses focus on mount.\n */\n private get _autoFocus(): boolean { return !this.hasAttribute('auto-focus') || this.getAttribute('auto-focus') !== 'false' }\n private get _selectOnFocus(): boolean { return this.hasAttribute('select-on-focus') }\n private get _blurOnComplete(): boolean { return this.hasAttribute('blur-on-complete') }\n\n // \u2500\u2500 Build \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /**\n * Constructs the entire shadow DOM from scratch.\n *\n * Called on first connect, on every observed attribute change, and when\n * certain JS-property setters (`pattern`, `pasteTransformer`, `onInvalidChar`)\n * are assigned after mount. Tears down any running timer and resets the\n * state machine before rebuilding to prevent duplicate intervals or stale\n * closure references from the previous build.\n */\n private build(): void {\n const length = this._length\n const type = this._type\n const timerSecs = this._timer\n const resendCooldown = this._resendAfter\n const separatorPositions = this._separatorAfter\n const separator = this._separator\n const masked = this._masked\n const inputName = this._name\n const autoFocus = this._autoFocus\n const selectOnFocus = this._selectOnFocus\n const blurOnComplete = this._blurOnComplete\n this._isDisabled = this._disabledAttr\n\n this.timerCtrl?.stop()\n this.resendCountdown?.stop()\n this.digito?.resetState()\n\n // Clear shadow DOM using safe child removal\n while (this.shadow.firstChild) this.shadow.removeChild(this.shadow.firstChild)\n this.slotEls = []\n this.caretEls = []\n this.timerEl = null\n this.timerBadgeEl = null\n this.resendEl = null\n this.timerCtrl = null\n this.resendCountdown = null\n\n // Styles\n const styleEl = document.createElement('style')\n styleEl.textContent = STYLES\n this.shadow.appendChild(styleEl)\n\n // Root\n const rootEl = document.createElement('div')\n rootEl.className = 'digito-wc-root'\n\n // Slot row\n const slotRowEl = document.createElement('div')\n slotRowEl.className = 'digito-wc-slots'\n\n // Visual slots + optional separator\n for (let i = 0; i < length; i++) {\n const slotEl = document.createElement('div')\n slotEl.className = 'digito-wc-slot'\n slotEl.setAttribute('aria-hidden', 'true')\n\n const caretEl = document.createElement('div')\n caretEl.className = 'digito-wc-caret'\n slotEl.appendChild(caretEl)\n\n this.caretEls.push(caretEl)\n this.slotEls.push(slotEl)\n slotRowEl.appendChild(slotEl)\n\n if (separatorPositions.some(pos => i === pos - 1)) {\n const sepEl = document.createElement('div')\n sepEl.className = 'digito-wc-separator'\n sepEl.textContent = separator\n sepEl.setAttribute('aria-hidden', 'true')\n slotRowEl.appendChild(sepEl)\n }\n }\n\n // Hidden input\n const hiddenInput = document.createElement('input')\n hiddenInput.type = masked ? 'password' : 'text'\n hiddenInput.inputMode = type === 'numeric' ? 'numeric' : 'text'\n hiddenInput.autocomplete = 'one-time-code'\n hiddenInput.maxLength = length\n hiddenInput.disabled = this._isDisabled\n hiddenInput.className = 'digito-wc-hidden'\n hiddenInput.setAttribute('aria-label', `Enter your ${length}-${type === 'numeric' ? 'digit' : 'character'} code`)\n hiddenInput.setAttribute('spellcheck', 'false')\n hiddenInput.setAttribute('autocorrect', 'off')\n hiddenInput.setAttribute('autocapitalize', 'off')\n if (inputName) hiddenInput.name = inputName\n this.hiddenInput = hiddenInput\n\n rootEl.appendChild(slotRowEl)\n rootEl.appendChild(hiddenInput)\n this.shadow.appendChild(rootEl)\n\n // Core\n this.digito = createDigito({\n length,\n type,\n pattern: this._pattern,\n pasteTransformer: this._pasteTransformer,\n onInvalidChar: this._onInvalidChar,\n onComplete: (code) => {\n // Call JS property setter AND dispatch CustomEvent\n this._onComplete?.(code)\n this.dispatchEvent(\n new CustomEvent('complete', { detail: { code }, bubbles: true, composed: true })\n )\n },\n })\n\n // \u2500\u2500 Built-in timer + resend (mirrors vanilla/alpine adapters) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (timerSecs > 0) {\n // Timer footer \u2014 \"Code expires in [0:45]\"\n const timerFooterEl = document.createElement('div')\n timerFooterEl.className = 'digito-wc-timer'\n this.timerEl = timerFooterEl\n\n const timerLabel = document.createElement('span')\n timerLabel.className = 'digito-wc-timer-label'\n timerLabel.textContent = 'Code expires in'\n\n const timerBadge = document.createElement('span')\n timerBadge.className = 'digito-wc-timer-badge'\n timerBadge.textContent = formatCountdown(timerSecs)\n this.timerBadgeEl = timerBadge\n\n timerFooterEl.appendChild(timerLabel)\n timerFooterEl.appendChild(timerBadge)\n rootEl.appendChild(timerFooterEl)\n\n // Resend row \u2014 \"Didn't receive the code? [Resend]\"\n const resendRowEl = document.createElement('div')\n resendRowEl.className = 'digito-wc-resend'\n this.resendEl = resendRowEl\n\n const resendLabel = document.createElement('span')\n resendLabel.textContent = 'Didn\\u2019t receive the code?'\n\n const resendBtn = document.createElement('button')\n resendBtn.className = 'digito-wc-resend-btn'\n resendBtn.textContent = 'Resend'\n resendBtn.type = 'button'\n\n resendRowEl.appendChild(resendLabel)\n resendRowEl.appendChild(resendBtn)\n rootEl.appendChild(resendRowEl)\n\n // Main countdown\n this.timerCtrl = createTimer({\n totalSeconds: timerSecs,\n onTick: (r) => { if (this.timerBadgeEl) this.timerBadgeEl.textContent = formatCountdown(r) },\n onExpire: () => {\n if (this.timerEl) this.timerEl.style.display = 'none'\n if (this.resendEl) this.resendEl.classList.add('is-visible')\n this.dispatchEvent(new CustomEvent('expire', { bubbles: true, composed: true }))\n },\n })\n this.timerCtrl.start()\n\n // Resend button click \u2014 restart with resend cooldown\n resendBtn.addEventListener('click', () => {\n if (!this.timerEl || !this.timerBadgeEl || !this.resendEl) return\n this.resendEl.classList.remove('is-visible')\n this.timerEl.style.display = 'flex'\n this.timerBadgeEl.textContent = formatCountdown(resendCooldown)\n this.resendCountdown?.stop()\n this.resendCountdown = createTimer({\n totalSeconds: resendCooldown,\n onTick: (r) => { if (this.timerBadgeEl) this.timerBadgeEl.textContent = formatCountdown(r) },\n onExpire: () => {\n if (this.timerEl) this.timerEl.style.display = 'none'\n if (this.resendEl) this.resendEl.classList.add('is-visible')\n },\n })\n this.resendCountdown.start()\n this._onResend?.()\n })\n }\n\n this.attachEvents(selectOnFocus, blurOnComplete)\n\n if (this._isDisabled) this.applyDisabledDOM(true)\n\n hiddenInput.addEventListener('click', (e: MouseEvent) => {\n if (this._isDisabled) return\n // click fires after the browser places cursor (always 0 due to font-size:1px).\n // Coordinate hit-test determines which slot was visually clicked, then\n // setSelectionRange overrides the browser's placement.\n let rawSlot = this.slotEls.length - 1\n for (let i = 0; i < this.slotEls.length; i++) {\n if (e.clientX <= this.slotEls[i].getBoundingClientRect().right) { rawSlot = i; break }\n }\n // Clamp to filled count so the visual active slot matches the actual cursor position.\n const clickedSlot = Math.min(rawSlot, hiddenInput.value.length)\n this.digito?.moveFocusTo(clickedSlot)\n const char = this.digito?.state.slotValues[clickedSlot] ?? ''\n if (selectOnFocus && char) {\n hiddenInput.setSelectionRange(clickedSlot, clickedSlot + 1)\n } else {\n hiddenInput.setSelectionRange(clickedSlot, clickedSlot)\n }\n this.syncSlotsToDOM()\n })\n\n requestAnimationFrame(() => {\n if (!this._isDisabled && autoFocus) hiddenInput.focus()\n hiddenInput.setSelectionRange(0, 0)\n this.syncSlotsToDOM()\n })\n }\n\n // \u2500\u2500 DOM sync \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /**\n * Reconcile the shadow slot divs with the current core state using CSS class\n * toggles. Called after every user action (input, keydown, paste, focus, click).\n *\n * Uses `this.shadow.activeElement` instead of `document.activeElement` to\n * correctly detect focus within the shadow root across all browsers \u2014 the\n * document active element is the host `<digito-input>` element, not the\n * internal hidden input.\n */\n private syncSlotsToDOM(): void {\n if (!this.digito || !this.hiddenInput) return\n const { slotValues, activeSlot, hasError } = this.digito.state\n const focused = this.shadow.activeElement === this.hiddenInput\n\n this.slotEls.forEach((slotEl, i) => {\n const char = slotValues[i] ?? ''\n const isActive = i === activeSlot && focused\n\n let textNode = slotEl.childNodes[1] as Text | undefined\n if (!textNode || textNode.nodeType !== Node.TEXT_NODE) {\n textNode = document.createTextNode('')\n slotEl.appendChild(textNode)\n }\n textNode.nodeValue = this._masked && char ? this._maskChar : char || this._placeholder\n\n slotEl.classList.toggle('is-active', isActive && !this._isDisabled)\n slotEl.classList.toggle('is-filled', !!char)\n slotEl.classList.toggle('is-masked', this._masked)\n slotEl.classList.toggle('is-error', hasError)\n slotEl.classList.toggle('is-success', this._isSuccess)\n slotEl.classList.toggle('is-disabled', this._isDisabled)\n\n this.caretEls[i].style.display = isActive && !char && !this._isDisabled ? 'block' : 'none'\n })\n\n // Only update value when it actually differs \u2014 assigning the same string\n // resets selectionStart/End in some browsers, clobbering the cursor.\n const newValue = slotValues.join('')\n if (this.hiddenInput.value !== newValue) this.hiddenInput.value = newValue\n }\n\n /**\n * Apply or remove the disabled state directly on existing DOM nodes without\n * triggering a full rebuild. Used by both `build()` (initial disabled attr)\n * and `setDisabled()` (runtime toggle).\n */\n private applyDisabledDOM(value: boolean): void {\n if (this.hiddenInput) this.hiddenInput.disabled = value\n this.slotEls.forEach(s => s.classList.toggle('is-disabled', value))\n }\n\n // \u2500\u2500 Events \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /**\n * Wire all event listeners to the hidden input element.\n * Called once at the end of each `build()`. Because `build()` creates a fresh\n * `hiddenInput` element, there is no need to `removeEventListener` \u2014 the old\n * element is discarded and its listeners are garbage-collected with it.\n *\n * @param selectOnFocus When `true`, focusing a filled slot selects its character.\n * @param blurOnComplete When `true`, blurs the input after the last slot is filled.\n */\n private attachEvents(selectOnFocus: boolean, blurOnComplete: boolean): void {\n const input = this.hiddenInput!\n const digito = this.digito!\n const length = this._length\n const type = this._type\n const pattern = this._pattern\n\n input.addEventListener('keydown', (e) => {\n if (this._isDisabled) return\n const pos = input.selectionStart ?? 0\n if (e.key === 'Backspace') {\n e.preventDefault()\n digito.deleteChar(pos)\n this.syncSlotsToDOM()\n this.dispatchChange()\n const next = digito.state.activeSlot\n requestAnimationFrame(() => input.setSelectionRange(next, next))\n } else if (e.key === 'ArrowLeft') {\n e.preventDefault()\n digito.moveFocusLeft(pos)\n this.syncSlotsToDOM()\n requestAnimationFrame(() => input.setSelectionRange(digito.state.activeSlot, digito.state.activeSlot))\n } else if (e.key === 'ArrowRight') {\n e.preventDefault()\n digito.moveFocusRight(pos)\n this.syncSlotsToDOM()\n requestAnimationFrame(() => input.setSelectionRange(digito.state.activeSlot, digito.state.activeSlot))\n } else if (e.key === 'Tab') {\n if (e.shiftKey) {\n if (pos === 0) return\n e.preventDefault()\n digito.moveFocusLeft(pos)\n } else {\n if (!digito.state.slotValues[pos]) return\n if (pos >= length - 1) return\n e.preventDefault()\n digito.moveFocusRight(pos)\n }\n this.syncSlotsToDOM()\n const next = digito.state.activeSlot\n requestAnimationFrame(() => input.setSelectionRange(next, next))\n }\n })\n\n input.addEventListener('input', () => {\n if (this._isDisabled) return\n const raw = input.value\n if (!raw) {\n digito.resetState()\n input.value = ''\n input.setSelectionRange(0, 0)\n this.syncSlotsToDOM()\n this.dispatchChange()\n return\n }\n const valid = filterString(raw, type, pattern).slice(0, length)\n digito.resetState()\n for (let i = 0; i < valid.length; i++) digito.inputChar(i, valid[i])\n const next = Math.min(valid.length, length - 1)\n input.value = valid\n input.setSelectionRange(next, next)\n digito.moveFocusTo(next)\n this.syncSlotsToDOM()\n this.dispatchChange()\n if (blurOnComplete && digito.state.isComplete) {\n requestAnimationFrame(() => input.blur())\n }\n })\n\n input.addEventListener('paste', (e) => {\n if (this._isDisabled) return\n e.preventDefault()\n const text = e.clipboardData?.getData('text') ?? ''\n const pos = input.selectionStart ?? 0\n digito.pasteString(pos, text)\n const { slotValues, activeSlot } = digito.state\n input.value = slotValues.join('')\n input.setSelectionRange(activeSlot, activeSlot)\n this.syncSlotsToDOM()\n this.dispatchChange()\n if (blurOnComplete && digito.state.isComplete) {\n requestAnimationFrame(() => input.blur())\n }\n })\n\n input.addEventListener('focus', () => {\n this._onFocus?.()\n requestAnimationFrame(() => {\n const pos = digito.state.activeSlot\n const char = digito.state.slotValues[pos]\n if (selectOnFocus && char) {\n input.setSelectionRange(pos, pos + 1)\n } else {\n input.setSelectionRange(pos, pos)\n }\n this.syncSlotsToDOM()\n })\n })\n\n input.addEventListener('blur', () => {\n this._onBlur?.()\n this.slotEls.forEach(s => { s.classList.remove('is-active') })\n this.caretEls.forEach(c => { c.style.display = 'none' })\n })\n\n }\n\n /**\n * Dispatch a `change` CustomEvent carrying the current code string.\n * Fired after every input, paste, and backspace action.\n * `composed: true` lets the event cross the shadow root boundary so host-page\n * listeners registered with `el.addEventListener('change', ...)` receive it.\n */\n private dispatchChange(): void {\n this.dispatchEvent(new CustomEvent('change', {\n detail: { code: this.digito?.getCode() ?? '' },\n bubbles: true,\n composed: true,\n }))\n }\n\n // \u2500\u2500 Public DOM API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Clear all slots, reset the timer display, and re-focus the hidden input. */\n reset(): void {\n this._isSuccess = false\n this.digito?.resetState()\n if (this.hiddenInput) {\n this.hiddenInput.value = ''\n if (!this._isDisabled) this.hiddenInput.focus()\n this.hiddenInput.setSelectionRange(0, 0)\n }\n if (this.timerBadgeEl) this.timerBadgeEl.textContent = formatCountdown(this._timer)\n if (this.timerEl) this.timerEl.style.display = 'flex'\n if (this.resendEl) this.resendEl.classList.remove('is-visible')\n this.resendCountdown?.stop()\n this.timerCtrl?.restart()\n this.syncSlotsToDOM()\n }\n\n /** Apply or clear the error state on all visual slots. */\n setError(isError: boolean): void {\n if (isError) this._isSuccess = false\n this.digito?.setError(isError)\n this.syncSlotsToDOM()\n }\n\n /** Apply or clear the success state on all visual slots. Stops the timer on success. */\n setSuccess(isSuccess: boolean): void {\n this._isSuccess = isSuccess\n if (isSuccess) {\n this.digito?.setError(false)\n this.timerCtrl?.stop()\n this.resendCountdown?.stop()\n if (this.timerEl) this.timerEl.style.display = 'none'\n if (this.resendEl) this.resendEl.style.display = 'none'\n }\n this.syncSlotsToDOM()\n }\n\n /**\n * Enable or disable the input at runtime.\n * Equivalent to toggling the `disabled` HTML attribute but without triggering\n * a full rebuild. Re-enabling automatically restores focus to the active slot.\n */\n setDisabled(value: boolean): void {\n this._isDisabled = value\n this.digito?.setDisabled(value)\n this.applyDisabledDOM(value)\n this.syncSlotsToDOM()\n if (!value && this.hiddenInput) {\n requestAnimationFrame(() => {\n this.hiddenInput?.focus()\n this.hiddenInput?.setSelectionRange(this.digito?.state.activeSlot ?? 0, this.digito?.state.activeSlot ?? 0)\n })\n }\n }\n\n /** Returns the current code as a joined string (e.g. `\"123456\"`). */\n getCode(): string {\n return this.digito?.getCode() ?? ''\n }\n}\n\nif (typeof customElements !== 'undefined' && !customElements.get('digito-input')) {\n customElements.define('digito-input', DigitoInput)\n}\n\nexport { DigitoInput }\n"],
5
+ "mappings": "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",
6
+ "names": ["filterChar", "char", "type", "pattern", "filterString", "str", "c", "createTimer", "options", "totalSeconds", "onTick", "onExpire", "remainingSeconds", "intervalId", "stop", "reset", "start", "restart", "triggerHapticFeedback", "_a", "e", "triggerSoundFeedback", "audioCtx", "oscillator", "gainNode", "allSlotsFilled", "slotValues", "v", "clampIndex", "index", "min", "max", "joinSlots", "createDigito", "options", "_a", "_b", "_c", "rawLength", "length", "type", "pattern", "onComplete", "onInvalidChar", "haptic", "sound", "pasteTransformer", "disabled", "state", "listeners", "applyState", "patch", "__spreadValues", "snapshot", "__spreadProps", "fn", "completeTimeoutId", "notifyCompleteIfReady", "triggerHapticFeedback", "triggerSoundFeedback", "code", "cancelPendingComplete", "inputChar", "slotIndex", "char", "validChar", "filterChar", "nextSlot", "newState", "deleteChar", "prevSlot", "moveFocusLeft", "moveFocusRight", "pasteString", "cursorSlot", "rawText", "transformed", "err", "slotCursor", "validChars", "filterString", "writeSlot", "i", "charsWritten", "nextActiveSlot", "setError", "isError", "resetState", "moveFocusTo", "setDisabled", "value", "subscribe", "listener", "STYLES", "formatCountdown", "totalSeconds", "m", "s", "DigitoInput", "fn", "re", "_a", "_b", "_c", "v", "n", "length", "type", "timerSecs", "resendCooldown", "separatorPositions", "separator", "masked", "inputName", "autoFocus", "selectOnFocus", "blurOnComplete", "styleEl", "rootEl", "slotRowEl", "i", "slotEl", "caretEl", "pos", "sepEl", "hiddenInput", "createDigito", "code", "timerFooterEl", "timerLabel", "timerBadge", "resendRowEl", "resendLabel", "resendBtn", "createTimer", "r", "e", "rawSlot", "clickedSlot", "char", "slotValues", "activeSlot", "hasError", "focused", "isActive", "textNode", "newValue", "value", "input", "digito", "pattern", "next", "raw", "valid", "filterString", "text", "c", "isError", "isSuccess", "_d", "_e", "_f"]
7
+ }
@@ -0,0 +1,91 @@
1
+ /*! Digito v1.0.0 | MIT | Olawale Balo — Product Designer + Design Engineer */
2
+ "use strict";(()=>{var Ze=Object.defineProperty,Je=Object.defineProperties;var Ue=Object.getOwnPropertyDescriptors;var Ie=Object.getOwnPropertySymbols;var Ye=Object.prototype.hasOwnProperty,Qe=Object.prototype.propertyIsEnumerable;var Ne=(e,t,i)=>t in e?Ze(e,t,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[t]=i,j=(e,t)=>{for(var i in t||(t={}))Ye.call(t,i)&&Ne(e,i,t[i]);if(Ie)for(var i of Ie(t))Qe.call(t,i)&&Ne(e,i,t[i]);return e},U=(e,t)=>Je(e,Ue(t));function G(e,t,i){if(!e||e.length!==1)return"";if(i!==void 0)return i.global&&(i.lastIndex=0),i.test(e)?e:"";switch(t){case"numeric":return/^[0-9]$/.test(e)?e:"";case"alphabet":return/^[a-zA-Z]$/.test(e)?e:"";case"alphanumeric":return/^[a-zA-Z0-9]$/.test(e)?e:"";case"any":return e;default:return""}}function B(e,t,i){return Array.from(e).filter(f=>G(f,t,i)!=="").join("")}function K(e){let{totalSeconds:t,onTick:i,onExpire:f}=e,C=t,F=null;function y(){F!==null&&(clearInterval(F),F=null)}function w(){y(),C=t}function L(){if(y(),t<=0){f==null||f();return}F=setInterval(()=>{C-=1,i==null||i(C),C<=0&&(y(),f==null||f())},1e3)}function R(){w(),L()}return{start:L,stop:y,reset:w,restart:R}}function ne(){var e;try{(e=navigator==null?void 0:navigator.vibrate)==null||e.call(navigator,10)}catch(t){}}function He(){try{let e=new AudioContext,t=e.createOscillator(),i=e.createGain();t.connect(i),i.connect(e.destination),t.frequency.value=880,i.gain.setValueAtTime(.08,e.currentTime),i.gain.exponentialRampToValueAtTime(.001,e.currentTime+.08),t.start(),t.stop(e.currentTime+.08),t.onended=()=>{e.close().catch(()=>{})}}catch(e){}}function oe(e){return e.every(t=>t.length===1)}function Y(e,t,i){return Math.max(t,Math.min(i,e))}function ze(e){return e.join("")}function re(e={}){var k,H,o;let t=(k=e.length)!=null?k:6,i=isNaN(t)?6:Math.max(1,Math.floor(t)),{type:f="numeric",pattern:C,onComplete:F,onInvalidChar:y,haptic:w=!0,sound:L=!1,pasteTransformer:R}=e,O=(H=e.disabled)!=null?H:!1,u={slotValues:Array(i).fill(""),activeSlot:0,hasError:!1,isComplete:!1,timerSeconds:(o=e.timer)!=null?o:0},_=new Set;function D(s){if(u=j(j({},u),s),_.size>0){let c=U(j({},u),{slotValues:[...u.slotValues]});_.forEach(g=>g(c))}return u}let E=null;function I(s){if(!oe(s)||!F)return;w&&ne(),L&&He();let c=ze(s);E!==null&&clearTimeout(E),E=setTimeout(()=>{E=null,F(c)},10)}function Q(){E!==null&&(clearTimeout(E),E=null)}function Z(s,c){if(O||s<0||s>=i)return u;let g=G(c,f,C);if(!g)return c.length===1&&(y==null||y(c,s)),u.activeSlot!==s?D({activeSlot:s}):u;let v=[...u.slotValues];v[s]=g;let b=s<i-1?s+1:i-1,m=D({slotValues:v,activeSlot:b,hasError:!1,isComplete:oe(v)});return I(v),m}function J(s){if(O||s<0||s>=i)return u;let c=[...u.slotValues];if(c[s])return c[s]="",D({slotValues:c,activeSlot:s,isComplete:!1});let g=Y(s-1,0,i-1);return c[g]="",D({slotValues:c,activeSlot:g,isComplete:!1})}function P(s){return D({activeSlot:Y(s-1,0,i-1)})}function ee(s){return D({activeSlot:Y(s+1,0,i-1)})}function te(s,c){if(O)return u;let g;try{g=R?R(c):c}catch(A){console.warn("[digito] pasteTransformer threw \u2014 using raw paste text.",A),g=c}if(y&&g){let A=s;for(let $ of Array.from(g))G($,f,C)?A=(A+1)%i:y($,A)}let v=B(g,f,C);if(!v)return u;let b=[...u.slotValues],m=s;for(let A=0;A<v.length&&A<i;A++)b[m]=v[A],m=(m+1)%i;let S=Math.min(v.length,i),V=S>=i?i-1:(s+S)%i,x=D({slotValues:b,activeSlot:V,hasError:!1,isComplete:oe(b)});return I(b),x}function q(s){return s&&w&&ne(),D({hasError:s})}function ie(){var s;return E!==null&&(clearTimeout(E),E=null),D({slotValues:Array(i).fill(""),activeSlot:0,hasError:!1,isComplete:!1,timerSeconds:(s=e.timer)!=null?s:0})}function a(s){return D({activeSlot:Y(s,0,i-1)})}function N(s){O=s}function M(s){return _.add(s),()=>{_.delete(s)}}return{get state(){return u},inputChar:Z,deleteChar:J,moveFocusLeft:P,moveFocusRight:ee,pasteString:te,setError:q,resetState:ie,moveFocusTo:a,cancelPendingComplete:Q,setDisabled:N,getCode:()=>ze(u.slotValues),getSnapshot:()=>U(j({},u),{slotValues:[...u.slotValues]}),subscribe:M,getState:()=>U(j({},u),{slotValues:[...u.slotValues]})}}var je="digito-styles";function et(){if(typeof document=="undefined"||document.getElementById(je))return;let e=document.createElement("style");e.id=je,e.textContent=[".digito-element{position:relative;display:inline-block;line-height:1}",".digito-hidden-input{position:absolute;inset:0;width:100%;height:100%;opacity:0;border:none;outline:none;background:transparent;color:transparent;caret-color:transparent;z-index:1;cursor:text;font-size:1px}",".digito-content{display:inline-flex;gap:var(--digito-gap,12px);align-items:center;padding:24px 0 0px;position:relative}",".digito-slot{position:relative;width:var(--digito-size,56px);height:var(--digito-size,56px);border:1px solid var(--digito-border-color,#E5E5E5);border-radius:var(--digito-radius,10px);font-size:var(--digito-font-size,24px);font-weight:600;display:flex;align-items:center;justify-content:center;background:var(--digito-bg,#FAFAFA);color:var(--digito-color,#0A0A0A);transition:border-color 150ms ease,box-shadow 150ms ease,background 150ms ease;user-select:none;-webkit-user-select:none;cursor:text;font-family:inherit}",".digito-slot.is-active{border-color:var(--digito-active-color,#3D3D3D);box-shadow:0 0 0 3px color-mix(in srgb,var(--digito-active-color,#3D3D3D) 10%,transparent);background:var(--digito-bg-filled,#FFFFFF)}",".digito-slot.is-filled{background:var(--digito-bg-filled,#FFFFFF)}",".digito-slot.is-error{border-color:var(--digito-error-color,#FB2C36);box-shadow:0 0 0 3px color-mix(in srgb,var(--digito-error-color,#FB2C36) 12%,transparent)}",".digito-slot.is-success{border-color:var(--digito-success-color,#00C950);box-shadow:0 0 0 3px color-mix(in srgb,var(--digito-success-color,#00C950) 12%,transparent)}",".digito-slot.is-disabled{opacity:0.45;cursor:not-allowed;pointer-events:none}",".digito-caret{position:absolute;width:2px;height:52%;background:var(--digito-caret-color,#3D3D3D);border-radius:1px;animation:digito-blink 1s step-start infinite;pointer-events:none}","@keyframes digito-blink{0%,100%{opacity:1}50%{opacity:0}}",".digito-separator{display:flex;align-items:center;justify-content:center;color:var(--digito-separator-color,#A1A1A1);font-size:var(--digito-separator-size,18px);font-weight:400;user-select:none;flex-shrink:0;}",".digito-slot:not(.is-filled){font-size:var(--digito-placeholder-size,16px);color:var(--digito-placeholder-color,#D3D3D3)}",".digito-slot.is-masked.is-filled{font-size:var(--digito-masked-size,16px)}",".digito-timer{display:flex;align-items:center;gap:8px;font-size:14px;padding:20px 0 0}",".digito-timer-label{color:var(--digito-timer-color,#5C5C5C);font-size:14px}",".digito-timer-badge{display:inline-flex;align-items:center;background:color-mix(in srgb,var(--digito-error-color,#FB2C36) 10%,transparent);color:var(--digito-error-color,#FB2C36);font-weight:500;font-size:14px;padding:2px 10px;border-radius:99px;height: 24px}",".digito-resend{display:none;align-items:center;gap:8px;font-size:14px;color:var(--digito-timer-color,#5C5C5C);padding:20px 0 0}",".digito-resend.is-visible{display:flex}",".digito-resend-btn{display:inline-flex;align-items:center;background:#E8E8E8;border:none;padding:2px 10px;border-radius:99px;color:#0A0A0A;font-weight:500;font-size:14px;transition:background 150ms ease;cursor:pointer;height: 24px}",".digito-resend-btn:hover{background:#E5E5E5}",".digito-resend-btn:disabled{color:#A1A1A1;cursor:not-allowed;background:#F5F5F5}"].join(""),document.head.appendChild(e)}function tt(e=".digito-wrapper",t={}){return et(),(typeof e=="string"?Array.from(document.querySelectorAll(e)):[e]).map(f=>it(f,t))}function it(e,t){var ue,fe,ge,me,pe,ve,be,he,ye,Se,xe,Ce,Ee,Te,De,Ae,Fe,ke,Le,Re,Oe,we;e.querySelector(".digito-element")&&console.warn("[digito] initDigito() called on an already-initialised wrapper \u2014 call instance.destroy() first to avoid leaks.");let i=(fe=t.length)!=null?fe:parseInt((ue=e.dataset.length)!=null?ue:"6",10),f=(me=(ge=t.type)!=null?ge:e.dataset.type)!=null?me:"numeric",C=(ve=t.timer)!=null?ve:parseInt((pe=e.dataset.timer)!=null?pe:"0",10),F=(he=t.resendAfter)!=null?he:parseInt((be=e.dataset.resend)!=null?be:"30",10),y=t.onResend,w=t.onComplete,L=t.onTick,R=t.onExpire,O=t.pattern,u=(ye=t.disabled)!=null?ye:!1,_=t.autoFocus!==!1,D=t.name,E=t.onFocus,I=t.onBlur,Q=(Se=t.placeholder)!=null?Se:"",Z=(xe=t.selectOnFocus)!=null?xe:!1,J=(Ce=t.blurOnComplete)!=null?Ce:!1,P=(Ee=t.separatorAfter)!=null?Ee:e.dataset.separatorAfter!==void 0?e.dataset.separatorAfter.split(",").map(n=>parseInt(n.trim(),10)).filter(n=>!isNaN(n)&&n>0):[],ee=Array.isArray(P)?P:[P],te=(De=(Te=t.separator)!=null?Te:e.dataset.separator)!=null?De:"\u2014",q=(Ae=t.masked)!=null?Ae:e.dataset.masked==="true",ie=(ke=(Fe=t.maskChar)!=null?Fe:e.dataset.maskChar)!=null?ke:"\u25CF",a=re({length:i,type:f,timer:C,resendAfter:F,onComplete:w,onTick:L,onExpire:R,pattern:O,pasteTransformer:t.pasteTransformer,onInvalidChar:t.onInvalidChar,sound:(Le=t.sound)!=null?Le:!1,haptic:(Re=t.haptic)!=null?Re:!0,disabled:u});for(;e.firstChild;)e.removeChild(e.firstChild);let N=document.createElement("div");N.className="digito-element";let M=document.createElement("div");M.className="digito-content";let k=[],H=[];for(let n=0;n<i;n++){let r=document.createElement("div");r.className="digito-slot",r.setAttribute("aria-hidden","true"),r.setAttribute("data-slot",String(n));let d=document.createElement("div");if(d.className="digito-caret",d.style.display="none",r.appendChild(d),H.push(d),k.push(r),M.appendChild(r),ee.some(l=>l>0&&n===l-1)){let l=document.createElement("div");l.className="digito-separator",l.textContent=te,l.setAttribute("aria-hidden","true"),M.appendChild(l)}}let o=document.createElement("input");o.type=q?"password":"text",o.inputMode=f==="numeric"?"numeric":"text",o.autocomplete="one-time-code",o.maxLength=i,o.className="digito-hidden-input",D&&(o.name=D),o.setAttribute("aria-label",`Enter your ${i}-${f==="numeric"?"digit":"character"} code`),o.setAttribute("spellcheck","false"),o.setAttribute("autocorrect","off"),o.setAttribute("autocapitalize","off"),N.appendChild(M),N.appendChild(o),e.appendChild(N);let s=()=>{};requestAnimationFrame(()=>{let n=M.getBoundingClientRect().width||0;s=rt(o,n)});let c=null,g=null,v=null,b=null,m=null,S=null;if((Oe=e.__digitoFooterEl)==null||Oe.remove(),(we=e.__digitoResendRowEl)==null||we.remove(),e.__digitoFooterEl=null,e.__digitoResendRowEl=null,C>0){let n=!L;if(n){m=document.createElement("div"),m.className="digito-timer";let r=document.createElement("span");r.className="digito-timer-label",r.textContent="Code expires in",c=document.createElement("span"),c.className="digito-timer-badge",c.textContent=X(C),m.appendChild(r),m.appendChild(c),e.insertAdjacentElement("afterend",m),S=document.createElement("div"),S.className="digito-resend";let d=document.createElement("span");d.textContent="Didn\u2019t receive the code?",g=document.createElement("button"),g.className="digito-resend-btn",g.textContent="Resend",g.type="button",S.appendChild(d),S.appendChild(g),m.insertAdjacentElement("afterend",S),e.__digitoFooterEl=m,e.__digitoResendRowEl=S}v=K({totalSeconds:C,onTick:r=>{c&&(c.textContent=X(r)),L==null||L(r)},onExpire:()=>{m&&(m.style.display="none"),S&&S.classList.add("is-visible"),R==null||R()}}),v.start(),n&&g&&g.addEventListener("click",()=>{!g||!c||!m||!S||(S.classList.remove("is-visible"),m.style.display="flex",c.textContent=X(F),b==null||b.stop(),b=K({totalSeconds:F,onTick:r=>{c&&(c.textContent=X(r))},onExpire:()=>{m&&(m.style.display="none"),S&&S.classList.add("is-visible")}}),b.start(),y==null||y())})}let V=null;typeof navigator!="undefined"&&"credentials"in navigator&&(V=new AbortController,navigator.credentials.get({otp:{transport:["sms"]},signal:V.signal}).then(n=>{if(!(n!=null&&n.code))return;let r=B(n.code,f,O).slice(0,i);if(!r)return;a.resetState();for(let h=0;h<r.length;h++)r[h]&&a.inputChar(h,r[h]);let d=r.length,l=Math.min(d,i-1);o.value=r,o.setSelectionRange(l,l),a.moveFocusTo(l),x()}).catch(()=>{}));function x(){let{slotValues:n,activeSlot:r,hasError:d}=a.state,l=document.activeElement===o;k.forEach((T,p)=>{var _e;let z=(_e=n[p])!=null?_e:"",Me=p===r&&l,Ve=z.length===1,W=T.childNodes[1];(!W||W.nodeType!==Node.TEXT_NODE)&&(W=document.createTextNode(""),T.appendChild(W)),W.nodeValue=q&&z?ie:z||Q,T.classList.toggle("is-active",Me),T.classList.toggle("is-filled",Ve),T.classList.toggle("is-masked",q),T.classList.toggle("is-error",d),d&&T.classList.remove("is-success"),H[p].style.display=Me&&!Ve?"block":"none"});let h=n.join("");o.value!==h&&(o.value=h)}function A(n){var d;let r=(d=o.selectionStart)!=null?d:0;if(n.key==="Backspace")n.preventDefault(),a.deleteChar(r),x(),o.setSelectionRange(a.state.activeSlot,a.state.activeSlot);else if(n.key==="Tab"){if(n.shiftKey){if(r===0)return;n.preventDefault(),a.moveFocusLeft(r)}else{if(!a.state.slotValues[r]||r>=i-1)return;n.preventDefault(),a.moveFocusRight(r)}let l=a.state.activeSlot;o.setSelectionRange(l,l),x()}else if(n.key==="ArrowLeft"){n.preventDefault(),a.moveFocusLeft(r);let l=a.state.activeSlot;o.setSelectionRange(l,l),x()}else if(n.key==="ArrowRight"){n.preventDefault(),a.moveFocusRight(r);let l=a.state.activeSlot;o.setSelectionRange(l,l),x()}}function $(n){let r=o.value;if(!r){a.resetState(),o.value="",o.setSelectionRange(0,0),x();return}let d=B(r,f,O),l=Array(i).fill("");for(let p=0;p<Math.min(d.length,i);p++)l[p]=d[p];a.resetState();for(let p=0;p<l.length;p++)l[p]&&a.inputChar(p,l[p]);let h=l.filter(p=>p!=="").length,T=Math.min(h,i-1);o.value=d.slice(0,i),o.setSelectionRange(T,T),a.moveFocusTo(T),x(),J&&a.state.isComplete&&requestAnimationFrame(()=>o.blur())}function se(n){var T,p,z;n.preventDefault();let r=(p=(T=n.clipboardData)==null?void 0:T.getData("text"))!=null?p:"",d=(z=o.selectionStart)!=null?z:0;a.pasteString(d,r);let{slotValues:l,activeSlot:h}=a.state;o.value=l.join(""),o.setSelectionRange(h,h),x(),J&&a.state.isComplete&&requestAnimationFrame(()=>o.blur())}function ae(){E==null||E(),requestAnimationFrame(()=>{let n=a.state.activeSlot,r=a.state.slotValues[n];Z&&r?o.setSelectionRange(n,n+1):o.setSelectionRange(n,n),x()})}function le(){I==null||I(),k.forEach(n=>n.classList.remove("is-active")),H.forEach(n=>{n.style.display="none"})}o.addEventListener("keydown",A),o.addEventListener("input",$),o.addEventListener("paste",se),o.addEventListener("focus",ae),o.addEventListener("blur",le),o.addEventListener("click",ce);function ce(n){if(u)return;let r=k.length-1;for(let h=0;h<k.length;h++)if(n.clientX<=k[h].getBoundingClientRect().right){r=h;break}let d=Math.min(r,o.value.length);a.moveFocusTo(d);let l=a.state.slotValues[d];Z&&l?o.setSelectionRange(d,d+1):o.setSelectionRange(d,d),x()}requestAnimationFrame(()=>{!u&&_&&o.focus(),o.setSelectionRange(0,0),x()});function de(){a.resetState(),o.value="",c&&(c.textContent=X(C)),m&&(m.style.display="flex"),S&&S.classList.remove("is-visible"),b==null||b.stop(),v==null||v.restart(),requestAnimationFrame(()=>{o.focus(),o.setSelectionRange(0,0),x()})}function Pe(){de(),y==null||y()}function qe(n){a.setError(n),x()}function $e(n){k.forEach(r=>{r.classList.toggle("is-success",n),n&&r.classList.remove("is-error")})}function We(n){u=n,a.setDisabled(n),o.disabled=n,k.forEach(r=>{r.classList.toggle("is-disabled",n),r.style.pointerEvents=n?"none":""})}function Ge(){return a.getCode()}function Ke(n){a.moveFocusTo(n),o.focus(),o.setSelectionRange(n,n),x()}function Xe(){o.removeEventListener("keydown",A),o.removeEventListener("input",$),o.removeEventListener("paste",se),o.removeEventListener("focus",ae),o.removeEventListener("blur",le),o.removeEventListener("click",ce),v==null||v.stop(),b==null||b.stop(),s(),V==null||V.abort(),e.__digitoFooterEl=null,e.__digitoResendRowEl=null}return{reset:de,resend:Pe,setError:qe,setSuccess:$e,setDisabled:We,getCode:Ge,focus:Ke,destroy:Xe}}function X(e){let t=Math.floor(e/60),i=e%60;return t>0?`${t}:${String(i).padStart(2,"0")}`:`0:${String(i).padStart(2,"0")}`}var nt=["[data-lastpass-icon-root]","[data-lastpass-root]","[data-op-autofill]","[data-1p-ignore]","[data-dashlane-rid]","[data-dashlane-label]","[data-kwimpalastatus]","[data-bwautofill]","com-bitwarden-browser-arctic-modal"],ot=40;function Be(){return typeof document=="undefined"?!1:nt.some(e=>{try{return document.querySelector(e)!==null}catch(t){return!1}})}function rt(e,t){if(typeof MutationObserver=="undefined")return()=>{};function i(){e.style.width=`${t+ot}px`}if(Be())return i(),()=>{};let f=new MutationObserver(()=>{Be()&&(i(),f.disconnect())});return f.observe(document.documentElement,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["data-lastpass-icon-root","data-lastpass-root","data-1p-ignore","data-dashlane-rid","data-kwimpalastatus"]}),()=>f.disconnect()}typeof window!="undefined"&&(window.Digito={init:tt});})();
3
+ /**
4
+ * digito/core/filter
5
+ * ─────────────────────────────────────────────────────────────────────────────
6
+ * Character filtering utilities — exported for use by all adapters.
7
+ *
8
+ * @author Olawale Balo — Product Designer + Design Engineer
9
+ * @license MIT
10
+ */
11
+ /**
12
+ * digito/core/timer
13
+ * ─────────────────────────────────────────────────────────────────────────────
14
+ * Standalone countdown timer — re-exported from core for use by adapters and
15
+ * developers who want to drive their own timer UI.
16
+ *
17
+ * @author Olawale Balo — Product Designer + Design Engineer
18
+ * @license MIT
19
+ */
20
+ /**
21
+ * digito/core/feedback
22
+ * ─────────────────────────────────────────────────────────────────────────────
23
+ * Optional sensory feedback utilities — exported so consumers can call them
24
+ * in their own event handlers without reimplementing the Web Audio / vibration
25
+ * boilerplate ("bring your own feedback" pattern).
26
+ *
27
+ * Used internally by the core machine when `haptic` / `sound` options are set.
28
+ *
29
+ * @author Olawale Balo — Product Designer + Design Engineer
30
+ * @license MIT
31
+ */
32
+ /**
33
+ * digito/core/machine
34
+ * ─────────────────────────────────────────────────────────────────────────────
35
+ * Pure OTP state machine — zero DOM, zero framework, zero side effects.
36
+ * All adapters import `createDigito` from here (via core/index.ts).
37
+ *
38
+ * Subscription system: pass a listener to `subscribe()` to be notified after
39
+ * every state mutation. Compatible with XState / Zustand-style patterns:
40
+ *
41
+ * const otp = createDigito({ length: 6 })
42
+ * const unsub = otp.subscribe(state => console.log(state))
43
+ * // ... later:
44
+ * unsub()
45
+ *
46
+ * @author Olawale Balo — Product Designer + Design Engineer
47
+ * @license MIT
48
+ */
49
+ /**
50
+ * digito/core
51
+ * ─────────────────────────────────────────────────────────────────────────────
52
+ * Pure OTP state machine — zero DOM, zero framework, zero side effects.
53
+ * All adapters (vanilla, React, Vue, Svelte, Alpine, Web Components) import
54
+ * from here. Nothing else is shared between them.
55
+ *
56
+ * @author Olawale Balo — Product Designer + Design Engineer
57
+ * @license MIT
58
+ */
59
+ /**
60
+ * digito/vanilla
61
+ * ─────────────────────────────────────────────────────────────────────────────
62
+ * DOM adapter using the single-hidden-input architecture.
63
+ *
64
+ * Architecture:
65
+ * One real <input> sits invisibly behind the visual slot divs.
66
+ * It captures ALL keyboard input, paste, and native SMS autofill.
67
+ * The visual slot <div>s are pure mirrors — they display characters
68
+ * from the hidden input's value, show a fake caret on the active slot,
69
+ * and forward click events to focus the real input.
70
+ *
71
+ * Why this is better than multiple inputs:
72
+ * - autocomplete="one-time-code" works as native single-input autofill
73
+ * - iOS SMS autofill works without any hacks
74
+ * - Screen readers see one real input — perfect a11y
75
+ * - No focus-juggling between inputs on every keystroke
76
+ * - Password managers can't confuse the slots for separate fields
77
+ *
78
+ * Web OTP API:
79
+ * When supported (Android Chrome), navigator.credentials.get is called
80
+ * automatically to intercept the SMS OTP code without any user interaction.
81
+ * The AbortController is wired to destroy() so the request is cancelled
82
+ * on cleanup. Falls back gracefully in all other environments.
83
+ *
84
+ * Two timer modes:
85
+ * Built-in UI — omit onTick. Digito renders "Code expires in [0:60]"
86
+ * and "Didn't receive the code? Resend" automatically.
87
+ * Custom UI — pass onTick. Digito fires the callback and skips its timer.
88
+ *
89
+ * @author Olawale Balo — Product Designer + Design Engineer
90
+ * @license MIT
91
+ */