mother-mask 2.0.4 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,12 @@
1
1
  # mother-mask
2
2
 
3
- Lightweight input mask library for browsers. Zero dependencies, TypeScript-first, ships ESM + CJS + UMD.
3
+ Lightweight input mask library for browsers. Zero runtime dependencies, TypeScript-first, ships **ESM**, **CJS**, and **UMD**.
4
+
5
+ Published as [`mother-mask` on npm](https://www.npmjs.com/package/mother-mask).
6
+
7
+ ## Live demo
8
+
9
+ **[Try it on StackBlitz →](https://stackblitz.com/edit/mother-mask-simple-demo?file=src%2Fmain.ts)**
4
10
 
5
11
  ## Install
6
12
 
@@ -12,9 +18,13 @@ pnpm add mother-mask
12
18
 
13
19
  ## Usage
14
20
 
15
- ### `bind(input, mask, callback?)` — DOM binding
21
+ ### `bind(input, mask, options?)`
16
22
 
17
- Attach a mask to any input element. Idempotent calling `bind()` twice on the same element has no effect.
23
+ Attach a mask to any input element — this is the main API.
24
+
25
+ - **Idempotent** — calling `bind()` again on the same element does nothing (the element is marked with `data-masked`).
26
+ - **Returns a dispose function** — call it to remove listeners and attributes so you can bind again later.
27
+ - Sets sensible defaults when missing: `autocomplete`, `autocorrect`, `autocapitalize`, `spellcheck`, and `maxlength` from the mask.
18
28
 
19
29
  ```ts
20
30
  import { bind } from 'mother-mask'
@@ -22,48 +32,129 @@ import { bind } from 'mother-mask'
22
32
  const input = document.getElementById('phone') as HTMLInputElement
23
33
 
24
34
  // Fixed mask
25
- bind(input, '(99) 99999-9999')
35
+ const dispose = bind(input, '(99) 99999-9999')
26
36
 
27
- // Dynamic mask — switches automatically as the user types
37
+ // Dynamic mask — picks the pattern from an ordered list (shortest → longest)
28
38
  bind(input, ['(99) 9999-9999', '(99) 99999-9999'])
29
39
 
30
- // Optional callback receives the masked value on every change
40
+ // Callback after paste or keyboard-driven changes
31
41
  bind(input, '999.999.999-99', (value) => {
32
42
  console.log(value) // e.g. "123.456.789-01"
33
43
  })
44
+
45
+ // Or options object (same as callback for a single `onChange`)
46
+ bind(input, '999.999.999-99', { onChange: (value) => console.log(value) })
47
+
48
+ // Later: allow rebinding
49
+ dispose()
34
50
  ```
35
51
 
36
- ### `process(value, mask)` pure function
52
+ ### Segmented masks (default) vs. flat/reflow masks
37
53
 
38
- Apply a mask to a raw string without touching the DOM. Useful for formatting stored values.
54
+ By default, every mask treats its literal separators as hard boundaries
55
+ between independent fields. Selecting "12" in `25/12/2025` and typing a
56
+ shorter or longer replacement stays scoped to the month — it never bleeds
57
+ digits into the year, and deleting a whole field just leaves it empty
58
+ instead of pulling the next field left across the separator:
39
59
 
40
60
  ```ts
41
- import { process } from 'mother-mask'
61
+ bind(input, '99/99/9999') // segmented by default
62
+ ```
63
+
64
+ This is the right behavior for masks made of independent fields — dates,
65
+ times — and it's a safe default everywhere else too, since it never
66
+ *discards* data (typing past a field's capacity still flows forward into
67
+ the next one; only backward bleed is blocked). A fully raw, unformatted
68
+ paste (e.g. `25122025`) still fills every field in one pass, and array
69
+ masks that switch pattern width (see below) still reflow correctly.
42
70
 
43
- process('12345678901', '999.999.999-99') // "123.456.789-01"
44
- process('01012024', '99/99/9999') // "01/01/2024"
45
- process('01310100', '99999-999') // "01310-100"
46
- process('1AB2C3D45E6F78', 'AA.AAA.AAA/AAAA-99') // "1A.B2C.3D4/5E6F-78"
71
+ Pass `segmented: false` to opt into the classic flat/reflow behavior
72
+ instead, where deleting or replacing characters anywhere shifts everything
73
+ after it to close the gap — useful when a mask really is one continuous
74
+ number with cosmetic separators (e.g. formatting a running total) rather
75
+ than independent fields:
76
+
77
+ ```ts
78
+ bind(input, '999.999.999-99', { segmented: false })
47
79
  ```
48
80
 
81
+ `segmented` is also accepted by `applyMask`, `buildMask`, and `process` as
82
+ part of an options object.
83
+
84
+ ### `bindDecimal(input, options?)`
85
+
86
+ A second binder for decimal/currency-style inputs — numbers, not
87
+ pattern-slot strings. There's no fixed template: the integer part grows and
88
+ shrinks freely as the user types, and formatting (grouping, decimal places,
89
+ prefix/suffix) is driven entirely by `options`. Same contract as `bind()`:
90
+ idempotent, marked with `data-masked`, returns a dispose function.
91
+
92
+ Typing behaves like a normal number field, not a cents-first calculator:
93
+ digits fill the integer part until you type the decimal separator, and the
94
+ fixed-width fraction is always shown zero-padded. A lone placeholder "0" in
95
+ the integer part (an untouched field showing e.g. "$0.00") is overwritten,
96
+ not extended, by the next digit — typing "2" anywhere against that "0" gives
97
+ "$2.00", never "$20.00" or "$02.00".
98
+
99
+ ```ts
100
+ import { bindDecimal } from 'mother-mask'
101
+
102
+ const input = document.querySelector<HTMLInputElement>('#decimal')!
103
+
104
+ bindDecimal(input, {
105
+ decimalPlaces: 2, // fixed fractional digits — default 2
106
+ segmented: true, // group the integer part into thousands — default true
107
+ separator: ',', // thousands grouping separator — default ','
108
+ decimalSeparator: '.', // integer/fraction separator, and its typing trigger — default '.'
109
+ prefix: '$',
110
+ suffix: '',
111
+ allowNegative: false, // allow a leading "-" — default false
112
+ })
113
+
114
+ // Typing "42" → "$42.00"
115
+ // Typing "423" → "$423.00"
116
+ // Typing "423." → "$423.00" (the separator opens the fraction segment)
117
+ // Typing "423.4" → "$423.40"
118
+ // Typing "423.42" → "$423.42"
119
+
120
+ // Editing is segmented, same as bind(): shortening "423.42" to "423.4"
121
+ // (deleting the trailing "2") re-pads the fraction instead of reflowing
122
+ // digits from the integer part → "$423.40"
123
+
124
+ // Either "." or "," opens the fraction, regardless of decimalSeparator —
125
+ // handy since numeric keypads often only have ".".
126
+
127
+ // Callback receives both the masked string and its parsed numeric value
128
+ bindDecimal(input, {
129
+ prefix: '$',
130
+ onChange: (value, numericValue) => {
131
+ console.log(value, numericValue) // "$423.42", 423.42
132
+ },
133
+ })
134
+
135
+ // Or a bare callback (legacy style, same as bind())
136
+ bindDecimal(input, (value, numericValue) => console.log(value, numericValue))
137
+ ```
138
+
139
+ Locale note: `separator` and `decimalSeparator` are independent, so
140
+ `{ separator: '.', decimalSeparator: ',' }` gives the `1.234,56` format
141
+ common outside the US.
142
+
49
143
  ## Pattern syntax
50
144
 
51
- | Character | Matches |
52
- |-----------|----------------|
53
- | `9` | Digit (09) |
54
- | `Z` | Letter (az, AZ) |
55
- | `A` | Alphanumeric (0–9, a–z, A–Z) |
56
- | anything else | Literal — inserted automatically |
145
+ | Character | Matches |
146
+ |-----------|---------|
147
+ | `9` | Digit (`0`–`9`) |
148
+ | `Z` | Letter (`a`–`z`, `A`–`Z`) |
149
+ | `A` | Alphanumeric (digit or letter) |
150
+ | Anything else | Literal — inserted as the user fills slots |
57
151
 
58
152
  ## Array masks
59
153
 
60
- Pass an ordered array (shortest → longest) to support variable-length inputs. The mask is selected by comparing the current value length against each mask's total length.
154
+ Pass an ordered array **shortest → longest** for variable-length inputs. The active mask is chosen from the **count of alphanumeric “data” characters** in the current value, so it works for both progressively masked input and fast typing.
61
155
 
62
156
  ```ts
63
- // Brazilian phone: 8-digit → 9-digit landline / mobile
64
157
  bind(input, ['(99) 9999-9999', '(99) 99999-9999'])
65
-
66
- // CPF / CNPJ alfanumérico
67
158
  bind(input, ['999.999.999-99', 'AA.AAA.AAA/AAAA-99'])
68
159
  ```
69
160
 
@@ -72,51 +163,88 @@ bind(input, ['999.999.999-99', 'AA.AAA.AAA/AAAA-99'])
72
163
  ```html
73
164
  <script src="https://unpkg.com/mother-mask/dist/mother-mask.umd.js"></script>
74
165
  <script>
75
- MotherMask.bind(document.getElementById('cpf'), '999.999.999-99')
166
+ const dispose = MotherMask.bind(document.getElementById('cpf'), '999.999.999-99')
76
167
  </script>
77
168
  ```
78
169
 
170
+ The global name is **`MotherMask`**.
171
+
79
172
  ## API reference
80
173
 
81
- ```ts
82
- // Apply mask to a string — no DOM required
83
- process(value: string, mask: MaskPattern): string
174
+ ### `bind` (primary)
84
175
 
85
- // Bind a mask to an input element (idempotent)
86
- bind(
87
- input: HTMLInputElement | Element,
88
- mask: MaskPattern,
89
- callback?: ((value: string) => void) | null,
90
- ): void
176
+ | | |
177
+ |--|--|
178
+ | **Signature** | `bind(input, mask, options?)` |
179
+ | **Returns** | `() => void` — call to remove listeners and attributes so the input can be bound again |
180
+ | **Third argument** | `{ onChange?: (value: string) => void }`, or a legacy `(value) => void` callback |
91
181
 
92
- // Build a Mask instance directly (advanced use)
93
- buildMask(value: string, mask: MaskPattern, caret?: number): Mask
182
+ ### Other exports
94
183
 
95
- // Maximum allowed input length for a given mask
96
- getMaxLength(mask: MaskPattern): number
184
+ | Export | Description |
185
+ |--------|-------------|
186
+ | `buildMask(value, mask, caret?, options?)` | Build a `Mask` instance (array `mask` is resolved to one string first). |
187
+ | `getMaxLength(mask)` | Maximum string length for the mask (for array masks, the longest pattern). |
188
+ | `applyMask(value, mask, inputCaret?, options?)` | Low-level: apply a **single** mask string; returns `{ value, caret }`. |
189
+ | `process(value, mask, options?)` | Apply a mask pattern to a raw value and return just the masked string. |
97
190
 
98
- // Pattern type
99
- type MaskPattern = string | string[]
100
- ```
191
+ ### `Mask` class
101
192
 
102
- ## Development
193
+ `buildMask` returns a `Mask` for advanced use. The instance applies the pattern and keeps a `caret` position aligned with the masked output (see TypeScript definitions in the package).
103
194
 
104
- ```bash
105
- make install # install dependencies
106
- make test # run tests + coverage
107
- make build # build ESM + CJS + UMD
108
- make dev # watch mode
109
- make lint # lint source files
110
- ```
195
+ ### `bindDecimal`
111
196
 
112
- ## Release
197
+ | | |
198
+ |--|--|
199
+ | **Signature** | `bindDecimal(input, options?)` |
200
+ | **Returns** | `() => void` — call to remove listeners and attributes so the input can be bound again |
201
+ | **Second argument** | `DecimalMaskOptions & { onChange?: (value: string, numericValue: number) => void }`, or a legacy `(value, numericValue) => void` callback |
113
202
 
114
- ```bash
115
- make publish # bump patch, publish, commit, tag, push
116
- make publish BUMP=minor
117
- make publish BUMP=major
203
+ ### Other decimal exports
204
+
205
+ | Export | Description |
206
+ |--------|-------------|
207
+ | `applyDecimalMask(value, inputCaret?, options?)` | Low-level: format a raw/already-masked value; returns `{ value, caret }`. |
208
+ | `processDecimal(value, options?)` | Apply a decimal mask to a raw value and return just the masked string. |
209
+ | `unmaskDecimal(value, options?)` | Parse a raw or masked decimal string back into a JS `number` (`0` if it has no digits). |
210
+ | `formatDecimalValue(value, options?)` | Format a plain JS `number` into its masked display string — useful to pre-populate an input. |
211
+
212
+ ### Types
213
+
214
+ ```ts
215
+ type MaskPattern = string | string[]
216
+
217
+ interface MaskResult {
218
+ readonly value: string
219
+ readonly caret: number
220
+ }
221
+
222
+ interface ApplyMaskOptions {
223
+ /** Hard boundaries between fields — on by default. Pass `false` for flat/reflow. */
224
+ segmented?: boolean
225
+ }
226
+
227
+ interface BindOptions extends ApplyMaskOptions {
228
+ onChange?: (value: string) => void
229
+ }
230
+
231
+ interface DecimalMaskOptions {
232
+ decimalPlaces?: number // default 2
233
+ segmented?: boolean // group into thousands — default true
234
+ separator?: string // thousands separator — default ','
235
+ decimalSeparator?: string // default '.'
236
+ prefix?: string // default ''
237
+ suffix?: string // default ''
238
+ allowNegative?: boolean // default false
239
+ }
240
+
241
+ interface BindDecimalOptions extends DecimalMaskOptions {
242
+ onChange?: (value: string, numericValue: number) => void
243
+ }
118
244
  ```
119
245
 
246
+ `MaskPattern`, `MaskResult`, `ApplyMaskOptions`, `BindOptions`, `DecimalMaskOptions`, and `BindDecimalOptions` are exported as types.
247
+
120
248
  ## License
121
249
 
122
250
  MIT — [Danilo Celestino de Castro](https://github.com/dan2dev)
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){return e>=`0`&&e<=`9`}function t(e){return e>=`a`&&e<=`z`||e>=`A`&&e<=`Z`}function n(n,r){return r===`9`?e(n):r===`Z`?t(n):e(n)||t(n)}function r(e,t,r=0){if(!e)return{value:``,caret:0};let i=``,a=``,o=0,s=0,c=!1;for(let l=0;l<t.length;l++){let u=t[l];if(u!==`9`&&u!==`Z`&&u!==`A`){a+=u;continue}let d=!1;for(;o<e.length;){let t=e[o++];if(n(t,u)){i+=a+t,a=``,d=!0,c||(o<=r?s=i.length:c=!0);break}}if(!d)break}return c||(s=i.length),{value:i,caret:s}}function i(e){let t=0;for(let n of e)(n>=`0`&&n<=`9`||n>=`a`&&n<=`z`||n>=`A`&&n<=`Z`)&&t++;return t}function a(e){let t=0;for(let n of e)(n===`9`||n===`Z`||n===`A`)&&t++;return t}function o(e,t){if(!Array.isArray(t))return t;let n=i(e),r=0;for(;r<t.length-1&&n>a(t[r]);)r++;return t[r]}function s(e){return Array.isArray(e)?e.length>0?Math.max(...e.map(e=>e.length)):0:e.length}var c=class{caret;_value;_mask;constructor(e,t,n=0){this._value=e,this._mask=t,this.caret=n}process(){let e=r(this._value,this._mask,this.caret);return this.caret=e.caret,e.value}};function l(e,t,n=0){return new c(e,o(e,t),n)}function u(e,t){return l(e,t).process()}let d;function f(){return d===void 0&&(d=typeof navigator<`u`&&/iPad|iPhone|iPod/i.test(navigator.userAgent)),d}const p=`data-masked`;function m(e){return e==null?{}:typeof e==`function`?{onChange:e}:e}function h(e,t,n){if(e.getAttribute(p)!==null)return()=>{};let{onChange:r}=m(n),i=[],a=(t,n)=>{e.hasAttribute(t)||(e.setAttribute(t,n),i.push(t))};e.setAttribute(p,Array.isArray(t)?t.join(`|`):t),a(`autocomplete`,`off`),a(`autocorrect`,`off`),a(`autocapitalize`,`off`),a(`spellcheck`,`false`),a(`maxlength`,String(s(t)));let o=!1,c=f()?`keyup`:`keydown`,u=e=>{let n=e.target;requestAnimationFrame(()=>{n.value=l(n.value,t).process(),r?.(n.value)})},d=e=>{let n=e,i=n.target,a=i.value;if(!n.key){o=!0,requestAnimationFrame(()=>{let e=i.selectionStart??999,n=l(i.value,t,e);i.value=n.process(),i.setSelectionRange(n.caret,n.caret),requestAnimationFrame(()=>{o=!1})});return}if(n.key===`Meta`)return;let c=n.key===`Backspace`,u=n.key===`Delete`,d=n.key.length===1&&!n.ctrlKey&&!n.altKey&&!n.metaKey,p=n.key===`Unidentified`;if(d&&i.selectionStart===i.selectionEnd&&a.length>=s(t)&&!f()){n.preventDefault();return}if(o){n.preventDefault();return}requestAnimationFrame(()=>{let e=i.selectionStart??999,n=l(i.value,t,e);if(i.value=n.process(),p){let t=i.value.length>a.length?n.caret:e;i.setSelectionRange(t,t)}else if(u){let t=a.length===i.value.length?e+1:e;i.setSelectionRange(t,t)}else c?i.setSelectionRange(e,e):d&&i.setSelectionRange(n.caret,n.caret);r?.(i.value)})};return e.addEventListener(`paste`,u),e.addEventListener(c,d),()=>{e.removeEventListener(`paste`,u),e.removeEventListener(c,d),e.removeAttribute(p);for(let t of i)e.removeAttribute(t)}}exports.Mask=c,exports.applyMask=r,exports.bind=h,exports.buildMask=l,exports.getMaxLength=s,exports.process=u;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){return e>=`0`&&e<=`9`}function t(e){return e>=`a`&&e<=`z`||e>=`A`&&e<=`Z`}function n(e){return e===`9`||e===`Z`||e===`A`}function r(n,r){return r===`9`?e(n):r===`Z`?t(n):e(n)||t(n)}function i(e,t,n){let i=``,a=``,o=0,s=0,c=!1;for(let l=0;l<t.length;l++){let u=t[l];if(u!==`9`&&u!==`Z`&&u!==`A`){a+=u;continue}let d=!1;for(;o<e.length;){let t=e[o++];if(r(t,u)){i+=a+t,a=``,d=!0,c||(o<=n?s=i.length:c=!0);break}}if(!d)break}return c||(s=i.length),{value:i,caret:s}}function a(e){let t=[],r=0;for(;r<e.length;){let i=r,a=n(e[r]);for(;r<e.length&&n(e[r])===a;)r++;let o=e.slice(i,r);t.push(a?{kind:`slots`,chars:o}:{kind:`literal`,text:o})}return t}function o(e,t){let n=0;for(let r=t;r<e.length;r++){let t=e[r];t.kind===`slots`&&(n+=t.chars.length)}return n}function s(n,r){let i=0;for(let a=r;a<n.length;a++){let r=n[a];(e(r)||t(r))&&i++}return i}function c(e,t,n){let i=a(t),c=``,l=``,u=0,d=0,f=!1,p=!1;for(let t=0;t<i.length&&!p;t++){let a=i[t];if(a.kind===`literal`){l+=a.text,e.startsWith(a.text,u)&&(u+=a.text.length);continue}let m=i[t+1],h=o(i,t+1);for(let t=0;t<a.chars.length;t++){let i=a.chars[t],o=!1;for(;u<e.length;){let t=e[u];if(r(t,i)){u++,c+=l+t,l=``,o=!0,f||(u<=n?d=c.length:f=!0);break}if(m?.kind===`literal`&&e.startsWith(m.text,u)&&s(e,u)<=h)break;u++}if(!o){u>=e.length&&(p=!0);break}}}return f||(d=c.length),{value:c,caret:d}}function l(e,t,n=0,r){return e?r?.segmented===!1?i(e,t,n):c(e,t,n):{value:``,caret:0}}function u(e){let t=0;for(let n of e)(n>=`0`&&n<=`9`||n>=`a`&&n<=`z`||n>=`A`&&n<=`Z`)&&t++;return t}function d(e){let t=0;for(let n of e)(n===`9`||n===`Z`||n===`A`)&&t++;return t}function f(e,t){if(!Array.isArray(t))return t;let n=u(e),r=0;for(;r<t.length-1&&n>d(t[r]);)r++;return t[r]}function p(e){return Array.isArray(e)?e.length>0?Math.max(...e.map(e=>e.length)):0:e.length}var m=class{caret;_value;_mask;_options;constructor(e,t,n=0,r){this._value=e,this._mask=t,this.caret=n,this._options=r}process(){let e=l(this._value,this._mask,this.caret,this._options);return this.caret=e.caret,e.value}};function h(e,t,n=0,r){return new m(e,f(e,t),n,r)}function g(e,t,n){return h(e,t,0,n).process()}let _;function v(){return _===void 0&&(_=typeof navigator<`u`&&/iPad|iPhone|iPod/i.test(navigator.userAgent)),_}const y=`data-masked`;function b(e){return e==null?{}:typeof e==`function`?{onChange:e}:e}function x(e,t,n){if(e.getAttribute(y)!==null)return()=>{};let{onChange:r,segmented:i}=b(n),a=[],o=(t,n)=>{e.hasAttribute(t)||(e.setAttribute(t,n),a.push(t))};e.setAttribute(y,Array.isArray(t)?t.join(`|`):t),o(`autocomplete`,`off`),o(`autocorrect`,`off`),o(`autocapitalize`,`off`),o(`spellcheck`,`false`),o(`maxlength`,String(p(t)));let s=!1,c=v()?`keyup`:`keydown`,l=e=>{let n=e.target;requestAnimationFrame(()=>{n.value=h(n.value,t,0,{segmented:i}).process(),r?.(n.value)})},u=e=>{let n=e,a=n.target,o=a.value;if(!n.key){s=!0,requestAnimationFrame(()=>{let e=a.selectionStart??999,n=h(a.value,t,e,{segmented:i});a.value=n.process(),a.setSelectionRange(n.caret,n.caret),requestAnimationFrame(()=>{s=!1})});return}if(n.key===`Meta`)return;let c=n.key===`Backspace`,l=n.key===`Delete`,u=n.key.length===1&&!n.ctrlKey&&!n.altKey&&!n.metaKey,d=n.key===`Unidentified`;if(u&&a.selectionStart===a.selectionEnd&&o.length>=p(t)&&!v()){n.preventDefault();return}if(s){n.preventDefault();return}requestAnimationFrame(()=>{let e=a.selectionStart??999,n=h(a.value,t,e,{segmented:i});if(a.value=n.process(),d){let t=a.value.length>o.length?n.caret:e;a.setSelectionRange(t,t)}else if(l){let t=o.length===a.value.length?e+1:e;a.setSelectionRange(t,t)}else c?a.setSelectionRange(e,e):u&&a.setSelectionRange(n.caret,n.caret);r?.(a.value)})};return e.addEventListener(`paste`,l),e.addEventListener(c,u),()=>{e.removeEventListener(`paste`,l),e.removeEventListener(c,u),e.removeAttribute(y);for(let t of a)e.removeAttribute(t)}}function S(e){return e>=`0`&&e<=`9`}function C(e){let t=e?.decimalPlaces??2;return{decimalPlaces:Number.isFinite(t)?Math.max(0,Math.floor(t)):2,segmented:e?.segmented??!0,separator:e?.separator??`,`,decimalSeparator:e?.decimalSeparator??`.`,prefix:e?.prefix??``,suffix:e?.suffix??``,allowNegative:e?.allowNegative??!1}}function w(e){let t=0;for(;t<e.length-1&&e[t]===`0`;)t++;return e.slice(t)}function T(e,t){if(!t||e.length<=3)return e;let n=[],r=e.length;for(;r>3;)n.unshift(e.slice(r-3,r)),r-=3;return n.unshift(e.slice(0,r)),n.join(t)}function E(e,t){if(t<=0)return 0;let n=0;for(let r=0;r<e.length;r++)if(S(e[r])&&(n++,n===t))return r+1;return e.length}function D(e,t){let n=``,r=``,i=!1,a=!1,o=t.decimalPlaces>0;for(let s of e){if(S(s)){a?r.length<t.decimalPlaces&&(r+=s):n+=s;continue}if(o&&!a&&s===t.decimalSeparator){a=!0;continue}s===`-`&&t.allowNegative&&(i=!0)}return{isNegative:i,intDigits:n,fracDigits:r,hasSeparator:a}}function O(e,t,n){let r=n.decimalPlaces>0,i=!1,a=0;for(let o=0;o<t;o++){let t=e[o];if(S(t)){(!i||a<n.decimalPlaces)&&a++;continue}r&&!i&&t===n.decimalSeparator&&(i=!0,a=0)}return{inFraction:i,digitsBefore:a}}function k(e,t=0,n){let r=C(n);if(!e)return{value:``,caret:0};let{isNegative:i,intDigits:a,fracDigits:o,hasSeparator:s}=D(e,r);if(a===``&&o===``&&!s)return{value:``,caret:0};let c=w(a||`0`),l=r.segmented?T(c,r.separator):c,u=r.decimalPlaces>0?o.padEnd(r.decimalPlaces,`0`):``,d=l+(r.decimalPlaces>0?r.decimalSeparator+u:``),f=i?`-`:``,p=f+r.prefix+d+r.suffix,{inFraction:m,digitsBefore:h}=O(e,Math.max(0,Math.min(t,e.length)),r),g=f.length+r.prefix.length;return{value:p,caret:m?g+l.length+r.decimalSeparator.length+h:g+E(l,h)}}function A(e,t){return k(e,e.length,t).value}function j(e,t){let{isNegative:n,intDigits:r,fracDigits:i}=D(e,C(t)),a=Number(i?`${r||`0`}.${i}`:r||`0`);return n?-a:a}function M(e,t){let n=C(t);if(n.decimalPlaces<=0)return null;let{isNegative:r,intDigits:i,hasSeparator:a}=D(e,n);if(a||i.length<n.decimalPlaces+1)return null;let o=i.length-n.decimalPlaces,s=i.slice(o),c=i.slice(0,o-1),l=r?`-`:``;return k(l+c+n.decimalSeparator+s,l.length+c.length,n)}function N(e,t,n,r){let i=C(r),a=t-1;if(a<0||e[a]!==n)return null;let{isNegative:o,intDigits:s,fracDigits:c,hasSeparator:l}=D(e.slice(0,a)+e.slice(t),i);if(s!==`0`)return null;let u=+!!o+i.prefix.length;if(a<u||a>u+1)return null;let d=o?`-`:``;return k(d+n+(l?i.decimalSeparator+c:``),d.length+1,i)}function P(e,t){let n=C(t);if(!Number.isFinite(e))return``;let r=n.allowNegative&&e<0,i=Math.abs(e).toFixed(n.decimalPlaces),a=i.indexOf(`.`),o=a===-1?i:i.slice(0,a),s=a===-1?``:i.slice(a+1),c=w(o||`0`),l=(n.segmented?T(c,n.separator):c)+(n.decimalPlaces>0?n.decimalSeparator+s:``);return(r?`-`:``)+n.prefix+l+n.suffix}const F=`data-masked`;function I(e){return e==null?{}:typeof e==`function`?{onChange:e}:e}function L(e){return e.length===1&&e>=`0`&&e<=`9`}function R(e,t){if(e.getAttribute(F)!==null)return()=>{};let{onChange:n,...r}=I(t),i=r,{decimalSeparator:a,decimalPlaces:o}=C(i),s=[],c=(t,n)=>{e.hasAttribute(t)||(e.setAttribute(t,n),s.push(t))};e.setAttribute(F,`decimal`),c(`autocomplete`,`off`),c(`autocorrect`,`off`),c(`autocapitalize`,`off`),c(`spellcheck`,`false`);let l=!1,u=v()?`keyup`:`keydown`,d=(e,t)=>{e.value=t.value,e.setSelectionRange(t.caret,t.caret),n?.(t.value,j(t.value,i))},f=e=>{let t=e.target;requestAnimationFrame(()=>{d(t,k(t.value,t.value.length,i))})},p=e=>{let t=e,n=t.target;if(!t.key){l=!0,requestAnimationFrame(()=>{let e=n.selectionStart??n.value.length;d(n,k(n.value,e,i)),requestAnimationFrame(()=>{l=!1})});return}if(t.key===`Meta`)return;if(l){t.preventDefault();return}let r=t.key===`Backspace`,s=t.key===`Delete`,c=t.key.length===1&&!t.ctrlKey&&!t.altKey&&!t.metaKey;!r&&!s&&!c||requestAnimationFrame(()=>{let e=n.selectionStart??n.value.length;if(r){let e=M(n.value,i);if(e){d(n,e);return}}o>0&&(t.key===`.`||t.key===`,`)&&t.key!==a&&e>0&&n.value[e-1]===t.key&&(n.value=n.value.slice(0,e-1)+a+n.value.slice(e)),d(n,(L(t.key)?N(n.value,e,t.key,i):null)??k(n.value,e,i))})};return e.addEventListener(`paste`,f),e.addEventListener(u,p),()=>{e.removeEventListener(`paste`,f),e.removeEventListener(u,p),e.removeAttribute(F);for(let t of s)e.removeAttribute(t)}}exports.Mask=m,exports.applyDecimalMask=k,exports.applyMask=l,exports.bind=x,exports.bindDecimal=R,exports.buildMask=h,exports.formatDecimalValue=P,exports.getMaxLength=p,exports.process=g,exports.processDecimal=A,exports.unmaskDecimal=j;
2
2
  //# sourceMappingURL=mother-mask.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"mother-mask.cjs","names":[],"sources":["../src/apply-mask.ts","../src/pattern.ts","../src/mask.ts","../src/platform.ts","../src/bind.ts"],"sourcesContent":["import type { MaskResult } from './types'\n\n// ---------------------------------------------------------------------------\n// Character classification (no regex — avoids the empty-string pitfall)\n// ---------------------------------------------------------------------------\n\nfunction isDigitChar(ch: string): boolean {\n return ch >= '0' && ch <= '9'\n}\n\nfunction isLetterChar(ch: string): boolean {\n return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')\n}\n\nfunction matchesSlot(ch: string, slot: string): boolean {\n if (slot === '9') return isDigitChar(ch)\n if (slot === 'Z') return isLetterChar(ch)\n // slot === 'A' → alphanumeric\n return isDigitChar(ch) || isLetterChar(ch)\n}\n\n// ---------------------------------------------------------------------------\n// Core masking — pure function\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a single mask string to a value, producing the masked output and\n * a computed caret position.\n *\n * **Caret algorithm**: as the mask consumes characters from `value`, every\n * time a *matching* input character at a position *before* `inputCaret` is\n * written to the output (including any preceding pending literals that were\n * just flushed), the output caret is updated to the current output length.\n * This correctly handles literal insertion, middle-of-string edits, and\n * characters that are skipped because they don't match the current slot.\n */\nexport function applyMask(value: string, mask: string, inputCaret = 0): MaskResult {\n if (!value) return { value: '', caret: 0 }\n\n let output = ''\n let pending = ''\n let valueIdx = 0\n let outputCaret = 0\n let caretResolved = false\n\n for (let maskIdx = 0; maskIdx < mask.length; maskIdx++) {\n const maskCh = mask[maskIdx]\n\n if (maskCh !== '9' && maskCh !== 'Z' && maskCh !== 'A') {\n pending += maskCh\n continue\n }\n\n // Find next value character that matches this slot\n let found = false\n while (valueIdx < value.length) {\n const ch = value[valueIdx++]\n\n if (matchesSlot(ch, maskCh)) {\n // Flush pending literals then write the matched char\n output += pending + ch\n pending = ''\n found = true\n\n // Caret tracking: if this consumed char was before the input caret,\n // the output caret is (at least) at the current output length.\n if (!caretResolved) {\n if (valueIdx <= inputCaret) {\n outputCaret = output.length\n } else {\n caretResolved = true\n }\n }\n break\n }\n // Non-matching chars are silently skipped (iterative, no recursion).\n }\n\n if (!found) break\n }\n\n // If every matched char was before the input caret (or no chars matched at\n // all past the caret), place the output caret at the end of the output.\n if (!caretResolved) outputCaret = output.length\n\n return { value: output, caret: outputCaret }\n}\n","import type { MaskPattern } from './types'\n\n/**\n * Count alphanumeric characters in a value.\n *\n * Used by `resolveMask` so that both raw input (e.g. \"11999887766\") and\n * already-masked values (e.g. \"(11) 99988-7766\") produce the same data-char\n * count, enabling correct mask selection even when all keystrokes arrive\n * before the first rAF fires (fast typing).\n */\nfunction countDataChars(value: string): number {\n let n = 0\n for (const ch of value) {\n if (\n (ch >= '0' && ch <= '9') ||\n (ch >= 'a' && ch <= 'z') ||\n (ch >= 'A' && ch <= 'Z')\n )\n n++\n }\n return n\n}\n\n/** Count input slots (9, Z, A) in a mask string. */\nfunction countMaskSlots(mask: string): number {\n let n = 0\n for (const ch of mask) {\n if (ch === '9' || ch === 'Z' || ch === 'A') n++\n }\n return n\n}\n\n/**\n * Select the right mask string for the current value.\n *\n * Compares the number of alphanumeric data characters in `value` to the\n * slot capacity of each candidate mask. This correctly handles both\n * progressively-masked values (normal typing) and raw accumulated characters\n * (fast typing where multiple keystrokes arrive before any rAF fires).\n */\nexport function resolveMask(value: string, mask: MaskPattern): string {\n if (!Array.isArray(mask)) return mask\n const dataCount = countDataChars(value)\n let i = 0\n while (i < mask.length - 1 && dataCount > countMaskSlots(mask[i])) i++\n return mask[i]\n}\n\n/** Maximum allowed input length for the given mask. */\nexport function getMaxLength(mask: MaskPattern): number {\n if (Array.isArray(mask)) {\n return mask.length > 0 ? Math.max(...mask.map((m) => m.length)) : 0\n }\n return mask.length\n}\n","import { applyMask } from './apply-mask'\nimport { resolveMask } from './pattern'\nimport type { MaskPattern } from './types'\n\nexport { getMaxLength } from './pattern'\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** Low-level mask processor. Tracks caret position after masking. */\nexport class Mask {\n /** Caret position after `process()` runs. */\n caret: number\n\n private readonly _value: string\n private readonly _mask: string\n\n constructor(value: string, mask: string, caret = 0) {\n this._value = value\n this._mask = mask\n this.caret = caret\n }\n\n /** Apply the mask to the value and return the masked string. */\n process(): string {\n const result = applyMask(this._value, this._mask, this.caret)\n this.caret = result.caret\n return result.value\n }\n}\n\n/** Build a `Mask` instance, resolving array patterns by value length. */\nexport function buildMask(value: string, mask: MaskPattern, caret = 0): Mask {\n return new Mask(value, resolveMask(value, mask), caret)\n}\n\n/** Apply a mask pattern to a raw value string and return the masked result. */\nexport function process(value: string, mask: MaskPattern): string {\n return buildMask(value, mask).process()\n}\n","let cachedIsIos: boolean | undefined\n\n/** True when the runtime looks like iOS Safari / WebKit (affects key event choice in {@link bind}). */\nexport function isIos(): boolean {\n if (cachedIsIos !== undefined) return cachedIsIos\n cachedIsIos =\n typeof navigator !== 'undefined' && /iPad|iPhone|iPod/i.test(navigator.userAgent)\n return cachedIsIos\n}\n","import { buildMask, getMaxLength } from './mask'\nimport { isIos } from './platform'\nimport type { BindOptions, MaskPattern } from './types'\n\nconst MASKED_ATTR = 'data-masked'\n\nfunction toBindOptions(\n third: BindOptions | ((value: string) => void) | null | undefined,\n): BindOptions {\n if (third == null) return {}\n if (typeof third === 'function') return { onChange: third }\n return third\n}\n\n/**\n * Bind a mask pattern to an input element.\n *\n * Idempotent — calling `bind()` on an already-bound element has no effect.\n * The element receives a `data-masked` attribute marking it as bound.\n *\n * Returns a function that removes listeners and clears `data-masked` so the\n * element can be bound again later.\n *\n * @param input - Any `HTMLInputElement` or `Element` that behaves like one.\n * @param mask - A single pattern string or an ordered array (shortest → longest).\n * @param options - Optional `{ onChange }`, or pass a callback (legacy) as the third argument.\n */\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n options?: BindOptions | null,\n): () => void\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n onChange: ((value: string) => void) | null,\n): () => void\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n third?: BindOptions | ((value: string) => void) | null,\n): () => void {\n if (input.getAttribute(MASKED_ATTR) !== null) return () => {}\n\n const { onChange } = toBindOptions(third)\n\n /** Attribute names set by this bind call; removed on dispose so a later `bind()` can re-apply. */\n const attrsSetHere: string[] = []\n const setIfMissing = (name: string, value: string): void => {\n if (!input.hasAttribute(name)) {\n input.setAttribute(name, value)\n attrsSetHere.push(name)\n }\n }\n\n input.setAttribute(MASKED_ATTR, Array.isArray(mask) ? mask.join('|') : mask)\n setIfMissing('autocomplete', 'off')\n setIfMissing('autocorrect', 'off')\n setIfMissing('autocapitalize', 'off')\n setIfMissing('spellcheck', 'false')\n setIfMissing('maxlength', String(getMaxLength(mask)))\n\n let lockInput = false\n\n const keyEventName = isIos() ? 'keyup' : 'keydown'\n\n const onPaste = (e: Event): void => {\n const target = e.target as HTMLInputElement\n requestAnimationFrame(() => {\n const m = buildMask(target.value, mask)\n target.value = m.process()\n onChange?.(target.value)\n })\n }\n\n const onKey = (e: Event): void => {\n const ke = e as KeyboardEvent\n const target = ke.target as HTMLInputElement\n const oldValue = target.value\n\n // Older Android WebViews may fire key events without a `key` value.\n if (!(ke as { key?: string }).key) {\n lockInput = true\n requestAnimationFrame(() => {\n const pos = target.selectionStart ?? 999\n const m = buildMask(target.value, mask, pos)\n target.value = m.process()\n target.setSelectionRange(m.caret, m.caret)\n requestAnimationFrame(() => {\n lockInput = false\n })\n })\n return\n }\n\n if (ke.key === 'Meta') return\n\n const isBackspace = ke.key === 'Backspace'\n const isDelete = ke.key === 'Delete'\n const isCharInsert = ke.key.length === 1 && !ke.ctrlKey && !ke.altKey && !ke.metaKey\n const isUnidentified = ke.key === 'Unidentified'\n\n // Block inserting when mask is full (desktop only — iOS handles this natively)\n if (isCharInsert && target.selectionStart === target.selectionEnd) {\n if (oldValue.length >= getMaxLength(mask) && !isIos()) {\n ke.preventDefault()\n return\n }\n }\n\n // `lockInput` is only set by the Android WebView path above; block normal\n // key events while that asynchronous path is in flight.\n if (lockInput) {\n ke.preventDefault()\n return\n }\n\n requestAnimationFrame(() => {\n const pos = target.selectionStart ?? 999\n const m = buildMask(target.value, mask, pos)\n target.value = m.process()\n\n if (isUnidentified) {\n const newPos = target.value.length > oldValue.length ? m.caret : pos\n target.setSelectionRange(newPos, newPos)\n } else if (isDelete) {\n const newPos = oldValue.length === target.value.length ? pos + 1 : pos\n target.setSelectionRange(newPos, newPos)\n } else if (isBackspace) {\n target.setSelectionRange(pos, pos)\n } else if (isCharInsert) {\n target.setSelectionRange(m.caret, m.caret)\n }\n\n onChange?.(target.value)\n })\n }\n\n input.addEventListener('paste', onPaste)\n input.addEventListener(keyEventName, onKey)\n\n return () => {\n input.removeEventListener('paste', onPaste)\n input.removeEventListener(keyEventName, onKey)\n input.removeAttribute(MASKED_ATTR)\n for (const name of attrsSetHere) input.removeAttribute(name)\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mother-mask.cjs","names":["isDigitChar","MASKED_ATTR"],"sources":["../src/apply-mask.ts","../src/pattern.ts","../src/mask.ts","../src/platform.ts","../src/bind.ts","../src/decimal-mask.ts","../src/bind-decimal.ts"],"sourcesContent":["import type { ApplyMaskOptions, MaskResult } from './types'\n\n// ---------------------------------------------------------------------------\n// Character classification (no regex — avoids the empty-string pitfall)\n// ---------------------------------------------------------------------------\n\nfunction isDigitChar(ch: string): boolean {\n return ch >= '0' && ch <= '9'\n}\n\nfunction isLetterChar(ch: string): boolean {\n return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')\n}\n\nfunction isSlotChar(ch: string): boolean {\n return ch === '9' || ch === 'Z' || ch === 'A'\n}\n\nfunction matchesSlot(ch: string, slot: string): boolean {\n if (slot === '9') return isDigitChar(ch)\n if (slot === 'Z') return isLetterChar(ch)\n // slot === 'A' → alphanumeric\n return isDigitChar(ch) || isLetterChar(ch)\n}\n\n// ---------------------------------------------------------------------------\n// Flat masking (default) — treats the mask as one continuous character\n// stream. Best for continuous identifiers (phone numbers, CPF/CNPJ, credit\n// cards) where deleting/inserting a digit anywhere is expected to reflow\n// every digit after it — this is the classic mother-mask behavior and is\n// relied on by the majority of the test suite (paste, backspace, mid-string\n// insert, etc).\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a single mask string to a value, producing the masked output and\n * a computed caret position.\n *\n * **Caret algorithm**: as the mask consumes characters from `value`, every\n * time a *matching* input character at a position *before* `inputCaret` is\n * written to the output (including any preceding pending literals that were\n * just flushed), the output caret is updated to the current output length.\n * This correctly handles literal insertion, middle-of-string edits, and\n * characters that are skipped because they don't match the current slot.\n */\nfunction applyFlatMask(value: string, mask: string, inputCaret: number): MaskResult {\n let output = ''\n let pending = ''\n let valueIdx = 0\n let outputCaret = 0\n let caretResolved = false\n\n for (let maskIdx = 0; maskIdx < mask.length; maskIdx++) {\n const maskCh = mask[maskIdx]\n\n if (maskCh !== '9' && maskCh !== 'Z' && maskCh !== 'A') {\n pending += maskCh\n continue\n }\n\n // Find next value character that matches this slot\n let found = false\n while (valueIdx < value.length) {\n const ch = value[valueIdx++]\n\n if (matchesSlot(ch, maskCh)) {\n // Flush pending literals then write the matched char\n output += pending + ch\n pending = ''\n found = true\n\n // Caret tracking: if this consumed char was before the input caret,\n // the output caret is (at least) at the current output length.\n if (!caretResolved) {\n if (valueIdx <= inputCaret) {\n outputCaret = output.length\n } else {\n caretResolved = true\n }\n }\n break\n }\n // Non-matching chars are silently skipped (iterative, no recursion).\n }\n\n if (!found) break\n }\n\n // If every matched char was before the input caret (or no chars matched at\n // all past the caret), place the output caret at the end of the output.\n if (!caretResolved) outputCaret = output.length\n\n return { value: output, caret: outputCaret }\n}\n\n// ---------------------------------------------------------------------------\n// Segmented masking (opt-in) — treats literal separators as hard boundaries\n// between independent fields (e.g. day/month/year in \"99/99/9999\"). Editing\n// one segment never bleeds characters into a neighboring one, so replacing\n// the \"12\" in \"25/12/2025\" with a shorter or longer value keeps the year\n// exactly where it is instead of shifting digits across the \"/\".\n// ---------------------------------------------------------------------------\n\ntype MaskToken = { kind: 'literal'; text: string } | { kind: 'slots'; chars: string }\n\n/**\n * Split a mask into alternating literal and slot-run tokens (e.g. \"99/99/9999\"\n * → slots\"99\", literal\"/\", slots\"99\", literal\"/\", slots\"9999\"), so the masking\n * pass can reason about segment boundaries instead of a flat character stream.\n */\nfunction tokenizeMask(mask: string): MaskToken[] {\n const tokens: MaskToken[] = []\n let i = 0\n while (i < mask.length) {\n const start = i\n const wantSlots = isSlotChar(mask[i])\n while (i < mask.length && isSlotChar(mask[i]) === wantSlots) i++\n const text = mask.slice(start, i)\n tokens.push(wantSlots ? { kind: 'slots', chars: text } : { kind: 'literal', text })\n }\n return tokens\n}\n\n/** Total slot capacity from token index `from` (inclusive) to the end of `tokens`. */\nfunction slotCapacityFrom(tokens: MaskToken[], from: number): number {\n let capacity = 0\n for (let i = from; i < tokens.length; i++) {\n const token = tokens[i]\n if (token.kind === 'slots') capacity += token.chars.length\n }\n return capacity\n}\n\n/** Count of remaining slot-matchable (digit/letter) characters in `value` from `fromIdx` onward. */\nfunction remainingDataChars(value: string, fromIdx: number): number {\n let count = 0\n for (let i = fromIdx; i < value.length; i++) {\n const ch = value[i]\n if (isDigitChar(ch) || isLetterChar(ch)) count++\n }\n return count\n}\n\n/**\n * Same contract as {@link applyFlatMask}, but walks the mask one *segment* at\n * a time (a run of slots, or a literal) rather than one character at a time.\n * The rule that keeps an edit inside a segment from crossing into its\n * neighbor is an **early stop**: if the value hits the literal that ends the\n * current slot run before all of that run's slots are filled (e.g. only one\n * digit typed into a two-digit month slot), the run is left partially filled\n * instead of skipping past the separator to steal a digit from the next\n * segment.\n *\n * That stop is only taken when it's actually safe — i.e. every character\n * still to come in `value` fits in the slot capacity that remains *after*\n * this segment. If stopping here would strand more data than the rest of\n * the mask can hold (e.g. pasting into a later segment while an earlier one\n * still sits under-filled from before), the \"separator\" is treated as stray\n * noise instead and skipped, letting this segment take the extra slot it\n * needs so nothing at the end gets silently dropped. This is also what lets\n * an array mask grow or shrink its pattern (moving every literal after the\n * change point) reflow correctly instead of losing a digit at the boundary.\n */\nfunction applySegmentedMask(value: string, mask: string, inputCaret: number): MaskResult {\n const tokens = tokenizeMask(mask)\n\n let output = ''\n let pending = ''\n let valueIdx = 0\n let outputCaret = 0\n let caretResolved = false\n let exhausted = false\n\n for (let t = 0; t < tokens.length && !exhausted; t++) {\n const token = tokens[t]\n\n if (token.kind === 'literal') {\n pending += token.text\n if (value.startsWith(token.text, valueIdx)) valueIdx += token.text.length\n continue\n }\n\n const nextToken = tokens[t + 1]\n const capacityAfter = slotCapacityFrom(tokens, t + 1)\n\n for (let s = 0; s < token.chars.length; s++) {\n const slotCh = token.chars[s]\n let found = false\n\n while (valueIdx < value.length) {\n const ch = value[valueIdx]\n\n if (matchesSlot(ch, slotCh)) {\n valueIdx++\n output += pending + ch\n pending = ''\n found = true\n\n if (!caretResolved) {\n if (valueIdx <= inputCaret) {\n outputCaret = output.length\n } else {\n caretResolved = true\n }\n }\n break\n }\n\n // This segment's ending separator showed up before the run filled.\n // Stopping here is only safe if the rest of the value still fits in\n // whatever slot capacity remains after this segment — otherwise\n // stopping would strand data, so fall through and skip this char as\n // noise instead, letting the segment take the slot it needs.\n if (\n nextToken?.kind === 'literal' &&\n value.startsWith(nextToken.text, valueIdx) &&\n remainingDataChars(value, valueIdx) <= capacityAfter\n ) {\n break\n }\n\n valueIdx++ // stray/noise char — skip it\n }\n\n if (!found) {\n if (valueIdx >= value.length) exhausted = true\n break\n }\n }\n }\n\n if (!caretResolved) outputCaret = output.length\n\n return { value: output, caret: outputCaret }\n}\n\n// ---------------------------------------------------------------------------\n// Public entry point\n// ---------------------------------------------------------------------------\n\nexport function applyMask(\n value: string,\n mask: string,\n inputCaret = 0,\n options?: ApplyMaskOptions,\n): MaskResult {\n if (!value) return { value: '', caret: 0 }\n return options?.segmented === false\n ? applyFlatMask(value, mask, inputCaret)\n : applySegmentedMask(value, mask, inputCaret)\n}\n","import type { MaskPattern } from './types'\n\n/**\n * Count alphanumeric characters in a value.\n *\n * Used by `resolveMask` so that both raw input (e.g. \"11999887766\") and\n * already-masked values (e.g. \"(11) 99988-7766\") produce the same data-char\n * count, enabling correct mask selection even when all keystrokes arrive\n * before the first rAF fires (fast typing).\n */\nfunction countDataChars(value: string): number {\n let n = 0\n for (const ch of value) {\n if (\n (ch >= '0' && ch <= '9') ||\n (ch >= 'a' && ch <= 'z') ||\n (ch >= 'A' && ch <= 'Z')\n )\n n++\n }\n return n\n}\n\n/** Count input slots (9, Z, A) in a mask string. */\nfunction countMaskSlots(mask: string): number {\n let n = 0\n for (const ch of mask) {\n if (ch === '9' || ch === 'Z' || ch === 'A') n++\n }\n return n\n}\n\n/**\n * Select the right mask string for the current value.\n *\n * Compares the number of alphanumeric data characters in `value` to the\n * slot capacity of each candidate mask. This correctly handles both\n * progressively-masked values (normal typing) and raw accumulated characters\n * (fast typing where multiple keystrokes arrive before any rAF fires).\n */\nexport function resolveMask(value: string, mask: MaskPattern): string {\n if (!Array.isArray(mask)) return mask\n const dataCount = countDataChars(value)\n let i = 0\n while (i < mask.length - 1 && dataCount > countMaskSlots(mask[i])) i++\n return mask[i]\n}\n\n/** Maximum allowed input length for the given mask. */\nexport function getMaxLength(mask: MaskPattern): number {\n if (Array.isArray(mask)) {\n return mask.length > 0 ? Math.max(...mask.map((m) => m.length)) : 0\n }\n return mask.length\n}\n","import { applyMask } from './apply-mask'\nimport { resolveMask } from './pattern'\nimport type { ApplyMaskOptions, MaskPattern } from './types'\n\nexport { getMaxLength } from './pattern'\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** Low-level mask processor. Tracks caret position after masking. */\nexport class Mask {\n /** Caret position after `process()` runs. */\n caret: number\n\n private readonly _value: string\n private readonly _mask: string\n private readonly _options: ApplyMaskOptions | undefined\n\n constructor(value: string, mask: string, caret = 0, options?: ApplyMaskOptions) {\n this._value = value\n this._mask = mask\n this.caret = caret\n this._options = options\n }\n\n /** Apply the mask to the value and return the masked string. */\n process(): string {\n const result = applyMask(this._value, this._mask, this.caret, this._options)\n this.caret = result.caret\n return result.value\n }\n}\n\n/** Build a `Mask` instance, resolving array patterns by value length. */\nexport function buildMask(\n value: string,\n mask: MaskPattern,\n caret = 0,\n options?: ApplyMaskOptions,\n): Mask {\n return new Mask(value, resolveMask(value, mask), caret, options)\n}\n\n/** Apply a mask pattern to a raw value string and return the masked result. */\nexport function process(value: string, mask: MaskPattern, options?: ApplyMaskOptions): string {\n return buildMask(value, mask, 0, options).process()\n}\n","let cachedIsIos: boolean | undefined\n\n/** True when the runtime looks like iOS Safari / WebKit (affects key event choice in {@link bind}). */\nexport function isIos(): boolean {\n if (cachedIsIos !== undefined) return cachedIsIos\n cachedIsIos =\n typeof navigator !== 'undefined' && /iPad|iPhone|iPod/i.test(navigator.userAgent)\n return cachedIsIos\n}\n","import { buildMask, getMaxLength } from './mask'\nimport { isIos } from './platform'\nimport type { BindOptions, MaskPattern } from './types'\n\nconst MASKED_ATTR = 'data-masked'\n\nfunction toBindOptions(\n third: BindOptions | ((value: string) => void) | null | undefined,\n): BindOptions {\n if (third == null) return {}\n if (typeof third === 'function') return { onChange: third }\n return third\n}\n\n/**\n * Bind a mask pattern to an input element.\n *\n * Idempotent — calling `bind()` on an already-bound element has no effect.\n * The element receives a `data-masked` attribute marking it as bound.\n *\n * Returns a function that removes listeners and clears `data-masked` so the\n * element can be bound again later.\n *\n * @param input - Any `HTMLInputElement` or `Element` that behaves like one.\n * @param mask - A single pattern string or an ordered array (shortest → longest).\n * @param options - Optional `{ onChange }`, or pass a callback (legacy) as the third argument.\n */\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n options?: BindOptions | null,\n): () => void\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n onChange: ((value: string) => void) | null,\n): () => void\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n third?: BindOptions | ((value: string) => void) | null,\n): () => void {\n if (input.getAttribute(MASKED_ATTR) !== null) return () => {}\n\n const { onChange, segmented } = toBindOptions(third)\n\n /** Attribute names set by this bind call; removed on dispose so a later `bind()` can re-apply. */\n const attrsSetHere: string[] = []\n const setIfMissing = (name: string, value: string): void => {\n if (!input.hasAttribute(name)) {\n input.setAttribute(name, value)\n attrsSetHere.push(name)\n }\n }\n\n input.setAttribute(MASKED_ATTR, Array.isArray(mask) ? mask.join('|') : mask)\n setIfMissing('autocomplete', 'off')\n setIfMissing('autocorrect', 'off')\n setIfMissing('autocapitalize', 'off')\n setIfMissing('spellcheck', 'false')\n setIfMissing('maxlength', String(getMaxLength(mask)))\n\n let lockInput = false\n\n const keyEventName = isIos() ? 'keyup' : 'keydown'\n\n const onPaste = (e: Event): void => {\n const target = e.target as HTMLInputElement\n requestAnimationFrame(() => {\n const m = buildMask(target.value, mask, 0, { segmented })\n target.value = m.process()\n onChange?.(target.value)\n })\n }\n\n const onKey = (e: Event): void => {\n const ke = e as KeyboardEvent\n const target = ke.target as HTMLInputElement\n const oldValue = target.value\n\n // Older Android WebViews may fire key events without a `key` value.\n if (!(ke as { key?: string }).key) {\n lockInput = true\n requestAnimationFrame(() => {\n const pos = target.selectionStart ?? 999\n const m = buildMask(target.value, mask, pos, { segmented })\n target.value = m.process()\n target.setSelectionRange(m.caret, m.caret)\n requestAnimationFrame(() => {\n lockInput = false\n })\n })\n return\n }\n\n if (ke.key === 'Meta') return\n\n const isBackspace = ke.key === 'Backspace'\n const isDelete = ke.key === 'Delete'\n const isCharInsert = ke.key.length === 1 && !ke.ctrlKey && !ke.altKey && !ke.metaKey\n const isUnidentified = ke.key === 'Unidentified'\n\n // Block inserting when mask is full (desktop only — iOS handles this natively)\n if (isCharInsert && target.selectionStart === target.selectionEnd) {\n if (oldValue.length >= getMaxLength(mask) && !isIos()) {\n ke.preventDefault()\n return\n }\n }\n\n // `lockInput` is only set by the Android WebView path above; block normal\n // key events while that asynchronous path is in flight.\n if (lockInput) {\n ke.preventDefault()\n return\n }\n\n requestAnimationFrame(() => {\n const pos = target.selectionStart ?? 999\n const m = buildMask(target.value, mask, pos, { segmented })\n target.value = m.process()\n\n if (isUnidentified) {\n const newPos = target.value.length > oldValue.length ? m.caret : pos\n target.setSelectionRange(newPos, newPos)\n } else if (isDelete) {\n const newPos = oldValue.length === target.value.length ? pos + 1 : pos\n target.setSelectionRange(newPos, newPos)\n } else if (isBackspace) {\n target.setSelectionRange(pos, pos)\n } else if (isCharInsert) {\n target.setSelectionRange(m.caret, m.caret)\n }\n\n onChange?.(target.value)\n })\n }\n\n input.addEventListener('paste', onPaste)\n input.addEventListener(keyEventName, onKey)\n\n return () => {\n input.removeEventListener('paste', onPaste)\n input.removeEventListener(keyEventName, onKey)\n input.removeAttribute(MASKED_ATTR)\n for (const name of attrsSetHere) input.removeAttribute(name)\n }\n}\n","import type { DecimalMaskOptions, MaskResult } from './types'\n\n// ---------------------------------------------------------------------------\n// Character classification (no regex — mirrors apply-mask.ts)\n// ---------------------------------------------------------------------------\n\nfunction isDigitChar(ch: string): boolean {\n return ch >= '0' && ch <= '9'\n}\n\n// ---------------------------------------------------------------------------\n// Option resolution\n// ---------------------------------------------------------------------------\n\ninterface ResolvedDecimalOptions {\n decimalPlaces: number\n segmented: boolean\n separator: string\n decimalSeparator: string\n prefix: string\n suffix: string\n allowNegative: boolean\n}\n\n/** @internal exported for {@link bindDecimal}'s \".\" / \",\" key normalization */\nexport function resolveDecimalOptions(options?: DecimalMaskOptions): ResolvedDecimalOptions {\n const rawPlaces = options?.decimalPlaces ?? 2\n const decimalPlaces = Number.isFinite(rawPlaces) ? Math.max(0, Math.floor(rawPlaces)) : 2\n return {\n decimalPlaces,\n segmented: options?.segmented ?? true,\n separator: options?.separator ?? ',',\n decimalSeparator: options?.decimalSeparator ?? '.',\n prefix: options?.prefix ?? '',\n suffix: options?.suffix ?? '',\n allowNegative: options?.allowNegative ?? false,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Digit-stream helpers\n// ---------------------------------------------------------------------------\n\n/** Strip leading zeros from a digit string, always keeping at least one digit. */\nfunction stripLeadingZeros(s: string): string {\n let i = 0\n while (i < s.length - 1 && s[i] === '0') i++\n return s.slice(i)\n}\n\n/** Insert `sep` every 3 digits from the right (e.g. \"1234567\" → \"1,234,567\"). */\nfunction groupThousands(s: string, sep: string): string {\n if (!sep || s.length <= 3) return s\n const parts: string[] = []\n let i = s.length\n while (i > 3) {\n parts.unshift(s.slice(i - 3, i))\n i -= 3\n }\n parts.unshift(s.slice(0, i))\n return parts.join(sep)\n}\n\n/**\n * Find the position in `s` that leaves exactly `digitsBefore` digit\n * characters preceding it — the position immediately after that digit and\n * before any subsequent literal (grouping separator, ...), so the caret\n * stays glued to the last digit the user placed there.\n */\nfunction caretForDigitsBefore(s: string, digitsBefore: number): number {\n if (digitsBefore <= 0) return 0\n let count = 0\n for (let i = 0; i < s.length; i++) {\n if (isDigitChar(s[i])) {\n count++\n if (count === digitsBefore) return i + 1\n }\n }\n return s.length\n}\n\n// ---------------------------------------------------------------------------\n// Segmented parsing — integer digits before the decimal separator, fraction\n// digits after. Unlike a slot-pattern mask, the integer segment has no fixed\n// length; only the fraction is fixed-width (`decimalPlaces`), zero-padded on\n// the right so a shorter fraction reads as its low-order (trailing) digits\n// being zero rather than reflowing/shifting — e.g. editing \"423,42\" down to\n// \"423,4\" produces \"423,40\", not \"42,34\".\n//\n// The decimal separator only has meaning as the *first* occurrence of\n// `opts.decimalSeparator`; every other non-digit character (thousands\n// separator, prefix/suffix text, a second stray separator, ...) is noise and\n// is dropped. This keeps re-parsing an already-masked value idempotent.\n// ---------------------------------------------------------------------------\n\ninterface DecimalParts {\n isNegative: boolean\n intDigits: string\n fracDigits: string\n hasSeparator: boolean\n}\n\nfunction computeDecimalParts(raw: string, opts: ResolvedDecimalOptions): DecimalParts {\n let intDigits = ''\n let fracDigits = ''\n let isNegative = false\n let inFraction = false\n const canHaveFraction = opts.decimalPlaces > 0\n\n for (const ch of raw) {\n if (isDigitChar(ch)) {\n if (inFraction) {\n if (fracDigits.length < opts.decimalPlaces) fracDigits += ch\n } else {\n intDigits += ch\n }\n continue\n }\n if (canHaveFraction && !inFraction && ch === opts.decimalSeparator) {\n inFraction = true\n continue\n }\n if (ch === '-' && opts.allowNegative) isNegative = true\n // Anything else — thousands separator, prefix/suffix text, a repeated\n // separator, stray letters — is noise and is dropped.\n }\n\n return { isNegative, intDigits, fracDigits, hasSeparator: inFraction }\n}\n\n/**\n * Walk `raw[0:caret]` to find which segment the caret sits in (integer or\n * fraction) and how many digits of that segment precede it, so the same\n * position can be re-derived in the freshly formatted output.\n */\nfunction locateCaretSegment(\n raw: string,\n caret: number,\n opts: ResolvedDecimalOptions,\n): { inFraction: boolean; digitsBefore: number } {\n const canHaveFraction = opts.decimalPlaces > 0\n let inFraction = false\n let digitsBefore = 0\n\n for (let i = 0; i < caret; i++) {\n const ch = raw[i]\n if (isDigitChar(ch)) {\n if (!inFraction || digitsBefore < opts.decimalPlaces) digitsBefore++\n continue\n }\n if (canHaveFraction && !inFraction && ch === opts.decimalSeparator) {\n inFraction = true\n digitsBefore = 0\n }\n }\n\n return { inFraction, digitsBefore }\n}\n\n// ---------------------------------------------------------------------------\n// Public entry points\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a decimal/currency mask to a value, producing the masked output and\n * a computed caret position. Digits typed before the decimal separator\n * extend the integer part; the fraction only starts once the separator is\n * typed, and is always displayed zero-padded to `decimalPlaces` width.\n */\nexport function applyDecimalMask(\n value: string,\n inputCaret = 0,\n options?: DecimalMaskOptions,\n): MaskResult {\n const opts = resolveDecimalOptions(options)\n if (!value) return { value: '', caret: 0 }\n\n const { isNegative, intDigits, fracDigits, hasSeparator } = computeDecimalParts(value, opts)\n if (intDigits === '' && fracDigits === '' && !hasSeparator) return { value: '', caret: 0 }\n\n const intPart = stripLeadingZeros(intDigits || '0')\n const groupedInt = opts.segmented ? groupThousands(intPart, opts.separator) : intPart\n const fracPadded = opts.decimalPlaces > 0 ? fracDigits.padEnd(opts.decimalPlaces, '0') : ''\n const numberStr = groupedInt + (opts.decimalPlaces > 0 ? opts.decimalSeparator + fracPadded : '')\n const signStr = isNegative ? '-' : ''\n const output = signStr + opts.prefix + numberStr + opts.suffix\n\n const clampedCaret = Math.max(0, Math.min(inputCaret, value.length))\n const { inFraction, digitsBefore } = locateCaretSegment(value, clampedCaret, opts)\n const prefixLen = signStr.length + opts.prefix.length\n const caret = inFraction\n ? prefixLen + groupedInt.length + opts.decimalSeparator.length + digitsBefore\n : prefixLen + caretForDigitsBefore(groupedInt, digitsBefore)\n\n return { value: output, caret }\n}\n\n/** Apply a decimal mask to a raw value and return just the masked string. */\nexport function processDecimal(value: string, options?: DecimalMaskOptions): string {\n return applyDecimalMask(value, value.length, options).value\n}\n\n/**\n * Parse a raw or already-masked decimal value back into a JS number.\n * Ignores prefix/suffix/thousands separator; returns `0` for an empty or\n * digit-less value.\n */\nexport function unmaskDecimal(value: string, options?: DecimalMaskOptions): number {\n const opts = resolveDecimalOptions(options)\n const { isNegative, intDigits, fracDigits } = computeDecimalParts(value, opts)\n const n = Number(fracDigits ? `${intDigits || '0'}.${fracDigits}` : intDigits || '0')\n return isNegative ? -n : n\n}\n\n/**\n * After a Backspace removes the decimal separator itself, the integer and\n * fraction digit runs collapse into one continuous stream (e.g. \"25.00\"\n * with the caret right after \".\" → Backspace deletes the \".\" → \"2500\").\n * Left alone, that reads as one big integer (\"$2,500.00\"). This restores\n * the segment boundary instead: the trailing `decimalPlaces` digits are\n * still the fraction, and the digit right before them — the one that used\n * to sit at the end of the integer part — is the one Backspace actually\n * removed, so it's dropped (not kept) — \"$25.00\" → \"$2.00\".\n *\n * Every reformat re-appends `decimalSeparator` whenever `decimalPlaces > 0`,\n * so its absence from `value` is an unambiguous signal that this exact\n * keystroke just deleted it — no \"value before this keystroke\" snapshot is\n * needed. Returns `null` when there's nothing to restore (the separator is\n * still present, `decimalPlaces` is `0`, or too few digits remain), so the\n * caller falls through to a plain {@link applyDecimalMask} call.\n */\nexport function applyDecimalMaskUnmergingSeparator(\n value: string,\n options?: DecimalMaskOptions,\n): MaskResult | null {\n const opts = resolveDecimalOptions(options)\n if (opts.decimalPlaces <= 0) return null\n\n const { isNegative, intDigits, hasSeparator } = computeDecimalParts(value, opts)\n if (hasSeparator || intDigits.length < opts.decimalPlaces + 1) return null\n\n const preMergeIntLength = intDigits.length - opts.decimalPlaces\n const fracDigits = intDigits.slice(preMergeIntLength)\n const remainingInt = intDigits.slice(0, preMergeIntLength - 1)\n\n const signPart = isNegative ? '-' : ''\n const raw = signPart + remainingInt + opts.decimalSeparator + fracDigits\n return applyDecimalMask(raw, signPart.length + remainingInt.length, opts)\n}\n\n/**\n * Special-cases typing a single digit into a field whose integer part is\n * exactly the auto-inserted \"0\" placeholder: the new digit replaces that\n * zero instead of combining with it — e.g. \"$0.00\" with the caret anywhere\n * against that lone \"0\" and typing \"2\" gives \"$2.00\", not \"$20.00\"/\"$02.00\".\n * Any already-typed fraction is preserved.\n *\n * `value`/`caret` must be the state *after* the browser has already\n * inserted `digit` at `caret - 1` (the same post-insertion snapshot\n * `applyDecimalMask` itself expects from `bindDecimal`). Returns `null`\n * when the pattern doesn't apply — either the integer part has real digits\n * already, or the caret wasn't against that lone zero — so the caller falls\n * through to a plain {@link applyDecimalMask} call.\n */\nexport function applyDecimalMaskReplacingLoneZero(\n value: string,\n caret: number,\n digit: string,\n options?: DecimalMaskOptions,\n): MaskResult | null {\n const opts = resolveDecimalOptions(options)\n const insertIdx = caret - 1\n if (insertIdx < 0 || value[insertIdx] !== digit) return null\n\n const withoutDigit = value.slice(0, insertIdx) + value.slice(caret)\n const { isNegative, intDigits, fracDigits, hasSeparator } = computeDecimalParts(withoutDigit, opts)\n if (intDigits !== '0') return null\n\n const prefixLen = (isNegative ? 1 : 0) + opts.prefix.length\n if (insertIdx < prefixLen || insertIdx > prefixLen + 1) return null\n\n const signPart = isNegative ? '-' : ''\n const raw = signPart + digit + (hasSeparator ? opts.decimalSeparator + fracDigits : '')\n return applyDecimalMask(raw, signPart.length + 1, opts)\n}\n\n/** Format a plain JS number into its masked display string. */\nexport function formatDecimalValue(value: number, options?: DecimalMaskOptions): string {\n const opts = resolveDecimalOptions(options)\n if (!Number.isFinite(value)) return ''\n\n const isNegative = opts.allowNegative && value < 0\n const fixed = Math.abs(value).toFixed(opts.decimalPlaces)\n const dotIdx = fixed.indexOf('.')\n const intRaw = dotIdx === -1 ? fixed : fixed.slice(0, dotIdx)\n const fracPart = dotIdx === -1 ? '' : fixed.slice(dotIdx + 1)\n const intPart = stripLeadingZeros(intRaw || '0')\n\n const groupedInt = opts.segmented ? groupThousands(intPart, opts.separator) : intPart\n const numberStr = groupedInt + (opts.decimalPlaces > 0 ? opts.decimalSeparator + fracPart : '')\n\n return (isNegative ? '-' : '') + opts.prefix + numberStr + opts.suffix\n}\n","import {\n applyDecimalMask,\n applyDecimalMaskReplacingLoneZero,\n applyDecimalMaskUnmergingSeparator,\n resolveDecimalOptions,\n unmaskDecimal,\n} from './decimal-mask'\nimport { isIos } from './platform'\nimport type { BindDecimalOptions, DecimalMaskOptions, MaskResult } from './types'\n\nconst MASKED_ATTR = 'data-masked'\n\nfunction toBindDecimalOptions(\n second:\n | BindDecimalOptions\n | ((value: string, numericValue: number) => void)\n | null\n | undefined,\n): BindDecimalOptions {\n if (second == null) return {}\n if (typeof second === 'function') return { onChange: second }\n return second\n}\n\nfunction isDigitKey(key: string): boolean {\n return key.length === 1 && key >= '0' && key <= '9'\n}\n\n/**\n * Bind a decimal/currency mask to an input element.\n *\n * Same contract as {@link bind}: idempotent (marked with `data-masked`),\n * returns a dispose function, and reformats on paste and keyboard-driven\n * changes via `requestAnimationFrame`. Unlike the pattern masks, there is no\n * fixed pattern — the integer part grows and shrinks freely; formatting is\n * driven entirely by `options`.\n *\n * @param input - Any `HTMLInputElement` or `Element` that behaves like one.\n * @param options - `DecimalMaskOptions` plus an optional `onChange`, or pass a callback (legacy) directly.\n */\nexport function bindDecimal(\n input: HTMLInputElement | Element,\n options?: BindDecimalOptions | null,\n): () => void\nexport function bindDecimal(\n input: HTMLInputElement | Element,\n onChange: ((value: string, numericValue: number) => void) | null,\n): () => void\nexport function bindDecimal(\n input: HTMLInputElement | Element,\n second?: BindDecimalOptions | ((value: string, numericValue: number) => void) | null,\n): () => void {\n if (input.getAttribute(MASKED_ATTR) !== null) return () => {}\n\n const { onChange, ...maskOptions } = toBindDecimalOptions(second)\n const decimalOptions: DecimalMaskOptions = maskOptions\n const { decimalSeparator, decimalPlaces } = resolveDecimalOptions(decimalOptions)\n\n const attrsSetHere: string[] = []\n const setIfMissing = (name: string, value: string): void => {\n if (!input.hasAttribute(name)) {\n input.setAttribute(name, value)\n attrsSetHere.push(name)\n }\n }\n\n input.setAttribute(MASKED_ATTR, 'decimal')\n setIfMissing('autocomplete', 'off')\n setIfMissing('autocorrect', 'off')\n setIfMissing('autocapitalize', 'off')\n setIfMissing('spellcheck', 'false')\n\n let lockInput = false\n const keyEventName = isIos() ? 'keyup' : 'keydown'\n\n const applyResult = (target: HTMLInputElement, m: MaskResult): void => {\n target.value = m.value\n target.setSelectionRange(m.caret, m.caret)\n onChange?.(m.value, unmaskDecimal(m.value, decimalOptions))\n }\n\n const onPaste = (e: Event): void => {\n const target = e.target as HTMLInputElement\n requestAnimationFrame(() => {\n applyResult(target, applyDecimalMask(target.value, target.value.length, decimalOptions))\n })\n }\n\n const onKey = (e: Event): void => {\n const ke = e as KeyboardEvent\n const target = ke.target as HTMLInputElement\n\n // Older Android WebViews may fire key events without a `key` value.\n if (!(ke as { key?: string }).key) {\n lockInput = true\n requestAnimationFrame(() => {\n const pos = target.selectionStart ?? target.value.length\n applyResult(target, applyDecimalMask(target.value, pos, decimalOptions))\n requestAnimationFrame(() => {\n lockInput = false\n })\n })\n return\n }\n\n if (ke.key === 'Meta') return\n\n // `lockInput` is only set by the Android WebView path above; block normal\n // key events while that asynchronous path is in flight.\n if (lockInput) {\n ke.preventDefault()\n return\n }\n\n const isBackspace = ke.key === 'Backspace'\n const isDelete = ke.key === 'Delete'\n const isCharInsert = ke.key.length === 1 && !ke.ctrlKey && !ke.altKey && !ke.metaKey\n\n // Navigation (arrows, Home/End, Tab, ...), selection, and shortcut keys\n // (Ctrl/Cmd+A, Ctrl/Cmd+C, ...) don't change the text — leave the\n // browser's native caret/selection handling alone instead of recomputing\n // and overwriting it on every keystroke.\n if (!isBackspace && !isDelete && !isCharInsert) return\n\n // Everything below reads `target.value`/`selectionStart` inside the rAF\n // callback rather than synchronously here, since the browser's native\n // character insertion for this keystroke isn't guaranteed to have landed\n // yet at the point a keydown listener runs — only by the next frame.\n requestAnimationFrame(() => {\n const pos = target.selectionStart ?? target.value.length\n\n // Backspace that just deleted the decimal separator merges the\n // integer and fraction digit runs into one continuous stream —\n // restore the boundary instead of treating that as one big integer.\n if (isBackspace) {\n const unmerged = applyDecimalMaskUnmergingSeparator(target.value, decimalOptions)\n if (unmerged) {\n applyResult(target, unmerged)\n return\n }\n }\n\n // A numeric keypad (or a locale mismatch) may only offer \".\" or \",\".\n // Normalize whichever one the user just typed to the configured\n // `decimalSeparator` so it reliably opens the fraction segment.\n if (\n decimalPlaces > 0 &&\n (ke.key === '.' || ke.key === ',') &&\n ke.key !== decimalSeparator &&\n pos > 0 &&\n target.value[pos - 1] === ke.key\n ) {\n target.value = target.value.slice(0, pos - 1) + decimalSeparator + target.value.slice(pos)\n }\n\n // Typing a digit into a field whose integer part is still the\n // auto-inserted \"0\" placeholder replaces that zero instead of\n // combining with it (e.g. \"$0.00\" + \"2\" → \"$2.00\", not \"$20.00\").\n const replaced = isDigitKey(ke.key)\n ? applyDecimalMaskReplacingLoneZero(target.value, pos, ke.key, decimalOptions)\n : null\n\n applyResult(target, replaced ?? applyDecimalMask(target.value, pos, decimalOptions))\n })\n }\n\n input.addEventListener('paste', onPaste)\n input.addEventListener(keyEventName, onKey)\n\n return () => {\n input.removeEventListener('paste', onPaste)\n input.removeEventListener(keyEventName, onKey)\n input.removeAttribute(MASKED_ATTR)\n for (const name of attrsSetHere) input.removeAttribute(name)\n }\n}\n"],"mappings":"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"}
@@ -15,25 +15,54 @@ interface MaskResult {
15
15
  readonly value: string;
16
16
  readonly caret: number;
17
17
  }
18
+ /** Options for {@link applyMask} and {@link buildMask}. */
19
+ interface ApplyMaskOptions {
20
+ /**
21
+ * Treat literal separators as hard boundaries between independent fields
22
+ * instead of one continuous digit/character stream. **On by default**: a
23
+ * mask made of independent fields — dates, times, phone area codes — never
24
+ * bleeds digits from one field into a neighboring one when you edit a
25
+ * single field (e.g. the month in "99/99/9999" won't steal a digit from
26
+ * the year).
27
+ *
28
+ * Pass `segmented: false` to opt into the classic flat/reflow behavior
29
+ * instead, where deleting or replacing characters anywhere shifts
30
+ * everything after it to close the gap — useful when a mask really is one
31
+ * continuous number with cosmetic separators (e.g. formatting a running
32
+ * total) rather than independent fields.
33
+ */
34
+ segmented?: boolean;
35
+ }
18
36
  /** Options for {@link bind}. */
19
- interface BindOptions {
37
+ interface BindOptions extends ApplyMaskOptions {
20
38
  /** Fires with the masked value after paste or keyboard-driven changes. */
21
39
  onChange?: (value: string) => void;
22
40
  }
41
+ /** Options for {@link applyDecimalMask}, {@link processDecimal}, {@link unmaskDecimal}, {@link formatDecimalValue}, and {@link bindDecimal}. */
42
+ interface DecimalMaskOptions {
43
+ /** Number of fixed fractional digits. Negative/fractional values are floored to `0`. @default 2 */
44
+ decimalPlaces?: number;
45
+ /** Group the integer part into thousands using `separator`. @default true */
46
+ segmented?: boolean;
47
+ /** Thousands grouping separator, used when `segmented` is `true`. @default ',' */
48
+ separator?: string;
49
+ /** Separator between the integer and fractional parts. @default '.' */
50
+ decimalSeparator?: string;
51
+ /** Fixed text prepended to the formatted number (after the sign, if negative). @default '' */
52
+ prefix?: string;
53
+ /** Fixed text appended to the formatted number. @default '' */
54
+ suffix?: string;
55
+ /** Allow a leading `-` to produce a negative value. @default false */
56
+ allowNegative?: boolean;
57
+ }
58
+ /** Options for {@link bindDecimal}. */
59
+ interface BindDecimalOptions extends DecimalMaskOptions {
60
+ /** Fires with the masked string and its parsed numeric value after paste or keyboard-driven changes. */
61
+ onChange?: (value: string, numericValue: number) => void;
62
+ }
23
63
  //#endregion
24
64
  //#region src/apply-mask.d.ts
25
- /**
26
- * Apply a single mask string to a value, producing the masked output and
27
- * a computed caret position.
28
- *
29
- * **Caret algorithm**: as the mask consumes characters from `value`, every
30
- * time a *matching* input character at a position *before* `inputCaret` is
31
- * written to the output (including any preceding pending literals that were
32
- * just flushed), the output caret is updated to the current output length.
33
- * This correctly handles literal insertion, middle-of-string edits, and
34
- * characters that are skipped because they don't match the current slot.
35
- */
36
- declare function applyMask(value: string, mask: string, inputCaret?: number): MaskResult;
65
+ declare function applyMask(value: string, mask: string, inputCaret?: number, options?: ApplyMaskOptions): MaskResult;
37
66
  //#endregion
38
67
  //#region src/bind.d.ts
39
68
  /**
@@ -52,6 +81,41 @@ declare function applyMask(value: string, mask: string, inputCaret?: number): Ma
52
81
  declare function bind(input: HTMLInputElement | Element, mask: MaskPattern, options?: BindOptions | null): () => void;
53
82
  declare function bind(input: HTMLInputElement | Element, mask: MaskPattern, onChange: ((value: string) => void) | null): () => void;
54
83
  //#endregion
84
+ //#region src/bind-decimal.d.ts
85
+ /**
86
+ * Bind a decimal/currency mask to an input element.
87
+ *
88
+ * Same contract as {@link bind}: idempotent (marked with `data-masked`),
89
+ * returns a dispose function, and reformats on paste and keyboard-driven
90
+ * changes via `requestAnimationFrame`. Unlike the pattern masks, there is no
91
+ * fixed pattern — the integer part grows and shrinks freely; formatting is
92
+ * driven entirely by `options`.
93
+ *
94
+ * @param input - Any `HTMLInputElement` or `Element` that behaves like one.
95
+ * @param options - `DecimalMaskOptions` plus an optional `onChange`, or pass a callback (legacy) directly.
96
+ */
97
+ declare function bindDecimal(input: HTMLInputElement | Element, options?: BindDecimalOptions | null): () => void;
98
+ declare function bindDecimal(input: HTMLInputElement | Element, onChange: ((value: string, numericValue: number) => void) | null): () => void;
99
+ //#endregion
100
+ //#region src/decimal-mask.d.ts
101
+ /**
102
+ * Apply a decimal/currency mask to a value, producing the masked output and
103
+ * a computed caret position. Digits typed before the decimal separator
104
+ * extend the integer part; the fraction only starts once the separator is
105
+ * typed, and is always displayed zero-padded to `decimalPlaces` width.
106
+ */
107
+ declare function applyDecimalMask(value: string, inputCaret?: number, options?: DecimalMaskOptions): MaskResult;
108
+ /** Apply a decimal mask to a raw value and return just the masked string. */
109
+ declare function processDecimal(value: string, options?: DecimalMaskOptions): string;
110
+ /**
111
+ * Parse a raw or already-masked decimal value back into a JS number.
112
+ * Ignores prefix/suffix/thousands separator; returns `0` for an empty or
113
+ * digit-less value.
114
+ */
115
+ declare function unmaskDecimal(value: string, options?: DecimalMaskOptions): number;
116
+ /** Format a plain JS number into its masked display string. */
117
+ declare function formatDecimalValue(value: number, options?: DecimalMaskOptions): string;
118
+ //#endregion
55
119
  //#region src/pattern.d.ts
56
120
  /** Maximum allowed input length for the given mask. */
57
121
  declare function getMaxLength(mask: MaskPattern): number;
@@ -63,14 +127,15 @@ declare class Mask {
63
127
  caret: number;
64
128
  private readonly _value;
65
129
  private readonly _mask;
66
- constructor(value: string, mask: string, caret?: number);
130
+ private readonly _options;
131
+ constructor(value: string, mask: string, caret?: number, options?: ApplyMaskOptions);
67
132
  /** Apply the mask to the value and return the masked string. */
68
133
  process(): string;
69
134
  }
70
135
  /** Build a `Mask` instance, resolving array patterns by value length. */
71
- declare function buildMask(value: string, mask: MaskPattern, caret?: number): Mask;
136
+ declare function buildMask(value: string, mask: MaskPattern, caret?: number, options?: ApplyMaskOptions): Mask;
72
137
  /** Apply a mask pattern to a raw value string and return the masked result. */
73
- declare function process(value: string, mask: MaskPattern): string;
138
+ declare function process(value: string, mask: MaskPattern, options?: ApplyMaskOptions): string;
74
139
  //#endregion
75
- export { type BindOptions, Mask, type MaskPattern, type MaskResult, applyMask, bind, buildMask, getMaxLength, process };
140
+ export { type ApplyMaskOptions, type BindDecimalOptions, type BindOptions, type DecimalMaskOptions, Mask, type MaskPattern, type MaskResult, applyDecimalMask, applyMask, bind, bindDecimal, buildMask, formatDecimalValue, getMaxLength, process, processDecimal, unmaskDecimal };
76
141
  //# sourceMappingURL=mother-mask.d.cts.map
@@ -15,25 +15,54 @@ interface MaskResult {
15
15
  readonly value: string;
16
16
  readonly caret: number;
17
17
  }
18
+ /** Options for {@link applyMask} and {@link buildMask}. */
19
+ interface ApplyMaskOptions {
20
+ /**
21
+ * Treat literal separators as hard boundaries between independent fields
22
+ * instead of one continuous digit/character stream. **On by default**: a
23
+ * mask made of independent fields — dates, times, phone area codes — never
24
+ * bleeds digits from one field into a neighboring one when you edit a
25
+ * single field (e.g. the month in "99/99/9999" won't steal a digit from
26
+ * the year).
27
+ *
28
+ * Pass `segmented: false` to opt into the classic flat/reflow behavior
29
+ * instead, where deleting or replacing characters anywhere shifts
30
+ * everything after it to close the gap — useful when a mask really is one
31
+ * continuous number with cosmetic separators (e.g. formatting a running
32
+ * total) rather than independent fields.
33
+ */
34
+ segmented?: boolean;
35
+ }
18
36
  /** Options for {@link bind}. */
19
- interface BindOptions {
37
+ interface BindOptions extends ApplyMaskOptions {
20
38
  /** Fires with the masked value after paste or keyboard-driven changes. */
21
39
  onChange?: (value: string) => void;
22
40
  }
41
+ /** Options for {@link applyDecimalMask}, {@link processDecimal}, {@link unmaskDecimal}, {@link formatDecimalValue}, and {@link bindDecimal}. */
42
+ interface DecimalMaskOptions {
43
+ /** Number of fixed fractional digits. Negative/fractional values are floored to `0`. @default 2 */
44
+ decimalPlaces?: number;
45
+ /** Group the integer part into thousands using `separator`. @default true */
46
+ segmented?: boolean;
47
+ /** Thousands grouping separator, used when `segmented` is `true`. @default ',' */
48
+ separator?: string;
49
+ /** Separator between the integer and fractional parts. @default '.' */
50
+ decimalSeparator?: string;
51
+ /** Fixed text prepended to the formatted number (after the sign, if negative). @default '' */
52
+ prefix?: string;
53
+ /** Fixed text appended to the formatted number. @default '' */
54
+ suffix?: string;
55
+ /** Allow a leading `-` to produce a negative value. @default false */
56
+ allowNegative?: boolean;
57
+ }
58
+ /** Options for {@link bindDecimal}. */
59
+ interface BindDecimalOptions extends DecimalMaskOptions {
60
+ /** Fires with the masked string and its parsed numeric value after paste or keyboard-driven changes. */
61
+ onChange?: (value: string, numericValue: number) => void;
62
+ }
23
63
  //#endregion
24
64
  //#region src/apply-mask.d.ts
25
- /**
26
- * Apply a single mask string to a value, producing the masked output and
27
- * a computed caret position.
28
- *
29
- * **Caret algorithm**: as the mask consumes characters from `value`, every
30
- * time a *matching* input character at a position *before* `inputCaret` is
31
- * written to the output (including any preceding pending literals that were
32
- * just flushed), the output caret is updated to the current output length.
33
- * This correctly handles literal insertion, middle-of-string edits, and
34
- * characters that are skipped because they don't match the current slot.
35
- */
36
- declare function applyMask(value: string, mask: string, inputCaret?: number): MaskResult;
65
+ declare function applyMask(value: string, mask: string, inputCaret?: number, options?: ApplyMaskOptions): MaskResult;
37
66
  //#endregion
38
67
  //#region src/bind.d.ts
39
68
  /**
@@ -52,6 +81,41 @@ declare function applyMask(value: string, mask: string, inputCaret?: number): Ma
52
81
  declare function bind(input: HTMLInputElement | Element, mask: MaskPattern, options?: BindOptions | null): () => void;
53
82
  declare function bind(input: HTMLInputElement | Element, mask: MaskPattern, onChange: ((value: string) => void) | null): () => void;
54
83
  //#endregion
84
+ //#region src/bind-decimal.d.ts
85
+ /**
86
+ * Bind a decimal/currency mask to an input element.
87
+ *
88
+ * Same contract as {@link bind}: idempotent (marked with `data-masked`),
89
+ * returns a dispose function, and reformats on paste and keyboard-driven
90
+ * changes via `requestAnimationFrame`. Unlike the pattern masks, there is no
91
+ * fixed pattern — the integer part grows and shrinks freely; formatting is
92
+ * driven entirely by `options`.
93
+ *
94
+ * @param input - Any `HTMLInputElement` or `Element` that behaves like one.
95
+ * @param options - `DecimalMaskOptions` plus an optional `onChange`, or pass a callback (legacy) directly.
96
+ */
97
+ declare function bindDecimal(input: HTMLInputElement | Element, options?: BindDecimalOptions | null): () => void;
98
+ declare function bindDecimal(input: HTMLInputElement | Element, onChange: ((value: string, numericValue: number) => void) | null): () => void;
99
+ //#endregion
100
+ //#region src/decimal-mask.d.ts
101
+ /**
102
+ * Apply a decimal/currency mask to a value, producing the masked output and
103
+ * a computed caret position. Digits typed before the decimal separator
104
+ * extend the integer part; the fraction only starts once the separator is
105
+ * typed, and is always displayed zero-padded to `decimalPlaces` width.
106
+ */
107
+ declare function applyDecimalMask(value: string, inputCaret?: number, options?: DecimalMaskOptions): MaskResult;
108
+ /** Apply a decimal mask to a raw value and return just the masked string. */
109
+ declare function processDecimal(value: string, options?: DecimalMaskOptions): string;
110
+ /**
111
+ * Parse a raw or already-masked decimal value back into a JS number.
112
+ * Ignores prefix/suffix/thousands separator; returns `0` for an empty or
113
+ * digit-less value.
114
+ */
115
+ declare function unmaskDecimal(value: string, options?: DecimalMaskOptions): number;
116
+ /** Format a plain JS number into its masked display string. */
117
+ declare function formatDecimalValue(value: number, options?: DecimalMaskOptions): string;
118
+ //#endregion
55
119
  //#region src/pattern.d.ts
56
120
  /** Maximum allowed input length for the given mask. */
57
121
  declare function getMaxLength(mask: MaskPattern): number;
@@ -63,14 +127,15 @@ declare class Mask {
63
127
  caret: number;
64
128
  private readonly _value;
65
129
  private readonly _mask;
66
- constructor(value: string, mask: string, caret?: number);
130
+ private readonly _options;
131
+ constructor(value: string, mask: string, caret?: number, options?: ApplyMaskOptions);
67
132
  /** Apply the mask to the value and return the masked string. */
68
133
  process(): string;
69
134
  }
70
135
  /** Build a `Mask` instance, resolving array patterns by value length. */
71
- declare function buildMask(value: string, mask: MaskPattern, caret?: number): Mask;
136
+ declare function buildMask(value: string, mask: MaskPattern, caret?: number, options?: ApplyMaskOptions): Mask;
72
137
  /** Apply a mask pattern to a raw value string and return the masked result. */
73
- declare function process(value: string, mask: MaskPattern): string;
138
+ declare function process(value: string, mask: MaskPattern, options?: ApplyMaskOptions): string;
74
139
  //#endregion
75
- export { type BindOptions, Mask, type MaskPattern, type MaskResult, applyMask, bind, buildMask, getMaxLength, process };
140
+ export { type ApplyMaskOptions, type BindDecimalOptions, type BindOptions, type DecimalMaskOptions, Mask, type MaskPattern, type MaskResult, applyDecimalMask, applyMask, bind, bindDecimal, buildMask, formatDecimalValue, getMaxLength, process, processDecimal, unmaskDecimal };
76
141
  //# sourceMappingURL=mother-mask.d.mts.map
@@ -1,2 +1,2 @@
1
- function e(e){return e>=`0`&&e<=`9`}function t(e){return e>=`a`&&e<=`z`||e>=`A`&&e<=`Z`}function n(n,r){return r===`9`?e(n):r===`Z`?t(n):e(n)||t(n)}function r(e,t,r=0){if(!e)return{value:``,caret:0};let i=``,a=``,o=0,s=0,c=!1;for(let l=0;l<t.length;l++){let u=t[l];if(u!==`9`&&u!==`Z`&&u!==`A`){a+=u;continue}let d=!1;for(;o<e.length;){let t=e[o++];if(n(t,u)){i+=a+t,a=``,d=!0,c||(o<=r?s=i.length:c=!0);break}}if(!d)break}return c||(s=i.length),{value:i,caret:s}}function i(e){let t=0;for(let n of e)(n>=`0`&&n<=`9`||n>=`a`&&n<=`z`||n>=`A`&&n<=`Z`)&&t++;return t}function a(e){let t=0;for(let n of e)(n===`9`||n===`Z`||n===`A`)&&t++;return t}function o(e,t){if(!Array.isArray(t))return t;let n=i(e),r=0;for(;r<t.length-1&&n>a(t[r]);)r++;return t[r]}function s(e){return Array.isArray(e)?e.length>0?Math.max(...e.map(e=>e.length)):0:e.length}var c=class{caret;_value;_mask;constructor(e,t,n=0){this._value=e,this._mask=t,this.caret=n}process(){let e=r(this._value,this._mask,this.caret);return this.caret=e.caret,e.value}};function l(e,t,n=0){return new c(e,o(e,t),n)}function u(e,t){return l(e,t).process()}let d;function f(){return d===void 0&&(d=typeof navigator<`u`&&/iPad|iPhone|iPod/i.test(navigator.userAgent)),d}const p=`data-masked`;function m(e){return e==null?{}:typeof e==`function`?{onChange:e}:e}function h(e,t,n){if(e.getAttribute(p)!==null)return()=>{};let{onChange:r}=m(n),i=[],a=(t,n)=>{e.hasAttribute(t)||(e.setAttribute(t,n),i.push(t))};e.setAttribute(p,Array.isArray(t)?t.join(`|`):t),a(`autocomplete`,`off`),a(`autocorrect`,`off`),a(`autocapitalize`,`off`),a(`spellcheck`,`false`),a(`maxlength`,String(s(t)));let o=!1,c=f()?`keyup`:`keydown`,u=e=>{let n=e.target;requestAnimationFrame(()=>{n.value=l(n.value,t).process(),r?.(n.value)})},d=e=>{let n=e,i=n.target,a=i.value;if(!n.key){o=!0,requestAnimationFrame(()=>{let e=i.selectionStart??999,n=l(i.value,t,e);i.value=n.process(),i.setSelectionRange(n.caret,n.caret),requestAnimationFrame(()=>{o=!1})});return}if(n.key===`Meta`)return;let c=n.key===`Backspace`,u=n.key===`Delete`,d=n.key.length===1&&!n.ctrlKey&&!n.altKey&&!n.metaKey,p=n.key===`Unidentified`;if(d&&i.selectionStart===i.selectionEnd&&a.length>=s(t)&&!f()){n.preventDefault();return}if(o){n.preventDefault();return}requestAnimationFrame(()=>{let e=i.selectionStart??999,n=l(i.value,t,e);if(i.value=n.process(),p){let t=i.value.length>a.length?n.caret:e;i.setSelectionRange(t,t)}else if(u){let t=a.length===i.value.length?e+1:e;i.setSelectionRange(t,t)}else c?i.setSelectionRange(e,e):d&&i.setSelectionRange(n.caret,n.caret);r?.(i.value)})};return e.addEventListener(`paste`,u),e.addEventListener(c,d),()=>{e.removeEventListener(`paste`,u),e.removeEventListener(c,d),e.removeAttribute(p);for(let t of i)e.removeAttribute(t)}}export{c as Mask,r as applyMask,h as bind,l as buildMask,s as getMaxLength,u as process};
1
+ function e(e){return e>=`0`&&e<=`9`}function t(e){return e>=`a`&&e<=`z`||e>=`A`&&e<=`Z`}function n(e){return e===`9`||e===`Z`||e===`A`}function r(n,r){return r===`9`?e(n):r===`Z`?t(n):e(n)||t(n)}function i(e,t,n){let i=``,a=``,o=0,s=0,c=!1;for(let l=0;l<t.length;l++){let u=t[l];if(u!==`9`&&u!==`Z`&&u!==`A`){a+=u;continue}let d=!1;for(;o<e.length;){let t=e[o++];if(r(t,u)){i+=a+t,a=``,d=!0,c||(o<=n?s=i.length:c=!0);break}}if(!d)break}return c||(s=i.length),{value:i,caret:s}}function a(e){let t=[],r=0;for(;r<e.length;){let i=r,a=n(e[r]);for(;r<e.length&&n(e[r])===a;)r++;let o=e.slice(i,r);t.push(a?{kind:`slots`,chars:o}:{kind:`literal`,text:o})}return t}function o(e,t){let n=0;for(let r=t;r<e.length;r++){let t=e[r];t.kind===`slots`&&(n+=t.chars.length)}return n}function s(n,r){let i=0;for(let a=r;a<n.length;a++){let r=n[a];(e(r)||t(r))&&i++}return i}function c(e,t,n){let i=a(t),c=``,l=``,u=0,d=0,f=!1,p=!1;for(let t=0;t<i.length&&!p;t++){let a=i[t];if(a.kind===`literal`){l+=a.text,e.startsWith(a.text,u)&&(u+=a.text.length);continue}let m=i[t+1],h=o(i,t+1);for(let t=0;t<a.chars.length;t++){let i=a.chars[t],o=!1;for(;u<e.length;){let t=e[u];if(r(t,i)){u++,c+=l+t,l=``,o=!0,f||(u<=n?d=c.length:f=!0);break}if(m?.kind===`literal`&&e.startsWith(m.text,u)&&s(e,u)<=h)break;u++}if(!o){u>=e.length&&(p=!0);break}}}return f||(d=c.length),{value:c,caret:d}}function l(e,t,n=0,r){return e?r?.segmented===!1?i(e,t,n):c(e,t,n):{value:``,caret:0}}function u(e){let t=0;for(let n of e)(n>=`0`&&n<=`9`||n>=`a`&&n<=`z`||n>=`A`&&n<=`Z`)&&t++;return t}function d(e){let t=0;for(let n of e)(n===`9`||n===`Z`||n===`A`)&&t++;return t}function f(e,t){if(!Array.isArray(t))return t;let n=u(e),r=0;for(;r<t.length-1&&n>d(t[r]);)r++;return t[r]}function p(e){return Array.isArray(e)?e.length>0?Math.max(...e.map(e=>e.length)):0:e.length}var m=class{caret;_value;_mask;_options;constructor(e,t,n=0,r){this._value=e,this._mask=t,this.caret=n,this._options=r}process(){let e=l(this._value,this._mask,this.caret,this._options);return this.caret=e.caret,e.value}};function h(e,t,n=0,r){return new m(e,f(e,t),n,r)}function g(e,t,n){return h(e,t,0,n).process()}let _;function v(){return _===void 0&&(_=typeof navigator<`u`&&/iPad|iPhone|iPod/i.test(navigator.userAgent)),_}const y=`data-masked`;function b(e){return e==null?{}:typeof e==`function`?{onChange:e}:e}function x(e,t,n){if(e.getAttribute(y)!==null)return()=>{};let{onChange:r,segmented:i}=b(n),a=[],o=(t,n)=>{e.hasAttribute(t)||(e.setAttribute(t,n),a.push(t))};e.setAttribute(y,Array.isArray(t)?t.join(`|`):t),o(`autocomplete`,`off`),o(`autocorrect`,`off`),o(`autocapitalize`,`off`),o(`spellcheck`,`false`),o(`maxlength`,String(p(t)));let s=!1,c=v()?`keyup`:`keydown`,l=e=>{let n=e.target;requestAnimationFrame(()=>{n.value=h(n.value,t,0,{segmented:i}).process(),r?.(n.value)})},u=e=>{let n=e,a=n.target,o=a.value;if(!n.key){s=!0,requestAnimationFrame(()=>{let e=a.selectionStart??999,n=h(a.value,t,e,{segmented:i});a.value=n.process(),a.setSelectionRange(n.caret,n.caret),requestAnimationFrame(()=>{s=!1})});return}if(n.key===`Meta`)return;let c=n.key===`Backspace`,l=n.key===`Delete`,u=n.key.length===1&&!n.ctrlKey&&!n.altKey&&!n.metaKey,d=n.key===`Unidentified`;if(u&&a.selectionStart===a.selectionEnd&&o.length>=p(t)&&!v()){n.preventDefault();return}if(s){n.preventDefault();return}requestAnimationFrame(()=>{let e=a.selectionStart??999,n=h(a.value,t,e,{segmented:i});if(a.value=n.process(),d){let t=a.value.length>o.length?n.caret:e;a.setSelectionRange(t,t)}else if(l){let t=o.length===a.value.length?e+1:e;a.setSelectionRange(t,t)}else c?a.setSelectionRange(e,e):u&&a.setSelectionRange(n.caret,n.caret);r?.(a.value)})};return e.addEventListener(`paste`,l),e.addEventListener(c,u),()=>{e.removeEventListener(`paste`,l),e.removeEventListener(c,u),e.removeAttribute(y);for(let t of a)e.removeAttribute(t)}}function S(e){return e>=`0`&&e<=`9`}function C(e){let t=e?.decimalPlaces??2;return{decimalPlaces:Number.isFinite(t)?Math.max(0,Math.floor(t)):2,segmented:e?.segmented??!0,separator:e?.separator??`,`,decimalSeparator:e?.decimalSeparator??`.`,prefix:e?.prefix??``,suffix:e?.suffix??``,allowNegative:e?.allowNegative??!1}}function w(e){let t=0;for(;t<e.length-1&&e[t]===`0`;)t++;return e.slice(t)}function T(e,t){if(!t||e.length<=3)return e;let n=[],r=e.length;for(;r>3;)n.unshift(e.slice(r-3,r)),r-=3;return n.unshift(e.slice(0,r)),n.join(t)}function E(e,t){if(t<=0)return 0;let n=0;for(let r=0;r<e.length;r++)if(S(e[r])&&(n++,n===t))return r+1;return e.length}function D(e,t){let n=``,r=``,i=!1,a=!1,o=t.decimalPlaces>0;for(let s of e){if(S(s)){a?r.length<t.decimalPlaces&&(r+=s):n+=s;continue}if(o&&!a&&s===t.decimalSeparator){a=!0;continue}s===`-`&&t.allowNegative&&(i=!0)}return{isNegative:i,intDigits:n,fracDigits:r,hasSeparator:a}}function O(e,t,n){let r=n.decimalPlaces>0,i=!1,a=0;for(let o=0;o<t;o++){let t=e[o];if(S(t)){(!i||a<n.decimalPlaces)&&a++;continue}r&&!i&&t===n.decimalSeparator&&(i=!0,a=0)}return{inFraction:i,digitsBefore:a}}function k(e,t=0,n){let r=C(n);if(!e)return{value:``,caret:0};let{isNegative:i,intDigits:a,fracDigits:o,hasSeparator:s}=D(e,r);if(a===``&&o===``&&!s)return{value:``,caret:0};let c=w(a||`0`),l=r.segmented?T(c,r.separator):c,u=r.decimalPlaces>0?o.padEnd(r.decimalPlaces,`0`):``,d=l+(r.decimalPlaces>0?r.decimalSeparator+u:``),f=i?`-`:``,p=f+r.prefix+d+r.suffix,{inFraction:m,digitsBefore:h}=O(e,Math.max(0,Math.min(t,e.length)),r),g=f.length+r.prefix.length;return{value:p,caret:m?g+l.length+r.decimalSeparator.length+h:g+E(l,h)}}function A(e,t){return k(e,e.length,t).value}function j(e,t){let{isNegative:n,intDigits:r,fracDigits:i}=D(e,C(t)),a=Number(i?`${r||`0`}.${i}`:r||`0`);return n?-a:a}function M(e,t){let n=C(t);if(n.decimalPlaces<=0)return null;let{isNegative:r,intDigits:i,hasSeparator:a}=D(e,n);if(a||i.length<n.decimalPlaces+1)return null;let o=i.length-n.decimalPlaces,s=i.slice(o),c=i.slice(0,o-1),l=r?`-`:``;return k(l+c+n.decimalSeparator+s,l.length+c.length,n)}function N(e,t,n,r){let i=C(r),a=t-1;if(a<0||e[a]!==n)return null;let{isNegative:o,intDigits:s,fracDigits:c,hasSeparator:l}=D(e.slice(0,a)+e.slice(t),i);if(s!==`0`)return null;let u=+!!o+i.prefix.length;if(a<u||a>u+1)return null;let d=o?`-`:``;return k(d+n+(l?i.decimalSeparator+c:``),d.length+1,i)}function P(e,t){let n=C(t);if(!Number.isFinite(e))return``;let r=n.allowNegative&&e<0,i=Math.abs(e).toFixed(n.decimalPlaces),a=i.indexOf(`.`),o=a===-1?i:i.slice(0,a),s=a===-1?``:i.slice(a+1),c=w(o||`0`),l=(n.segmented?T(c,n.separator):c)+(n.decimalPlaces>0?n.decimalSeparator+s:``);return(r?`-`:``)+n.prefix+l+n.suffix}const F=`data-masked`;function I(e){return e==null?{}:typeof e==`function`?{onChange:e}:e}function L(e){return e.length===1&&e>=`0`&&e<=`9`}function R(e,t){if(e.getAttribute(F)!==null)return()=>{};let{onChange:n,...r}=I(t),i=r,{decimalSeparator:a,decimalPlaces:o}=C(i),s=[],c=(t,n)=>{e.hasAttribute(t)||(e.setAttribute(t,n),s.push(t))};e.setAttribute(F,`decimal`),c(`autocomplete`,`off`),c(`autocorrect`,`off`),c(`autocapitalize`,`off`),c(`spellcheck`,`false`);let l=!1,u=v()?`keyup`:`keydown`,d=(e,t)=>{e.value=t.value,e.setSelectionRange(t.caret,t.caret),n?.(t.value,j(t.value,i))},f=e=>{let t=e.target;requestAnimationFrame(()=>{d(t,k(t.value,t.value.length,i))})},p=e=>{let t=e,n=t.target;if(!t.key){l=!0,requestAnimationFrame(()=>{let e=n.selectionStart??n.value.length;d(n,k(n.value,e,i)),requestAnimationFrame(()=>{l=!1})});return}if(t.key===`Meta`)return;if(l){t.preventDefault();return}let r=t.key===`Backspace`,s=t.key===`Delete`,c=t.key.length===1&&!t.ctrlKey&&!t.altKey&&!t.metaKey;!r&&!s&&!c||requestAnimationFrame(()=>{let e=n.selectionStart??n.value.length;if(r){let e=M(n.value,i);if(e){d(n,e);return}}o>0&&(t.key===`.`||t.key===`,`)&&t.key!==a&&e>0&&n.value[e-1]===t.key&&(n.value=n.value.slice(0,e-1)+a+n.value.slice(e)),d(n,(L(t.key)?N(n.value,e,t.key,i):null)??k(n.value,e,i))})};return e.addEventListener(`paste`,f),e.addEventListener(u,p),()=>{e.removeEventListener(`paste`,f),e.removeEventListener(u,p),e.removeAttribute(F);for(let t of s)e.removeAttribute(t)}}export{m as Mask,k as applyDecimalMask,l as applyMask,x as bind,R as bindDecimal,h as buildMask,P as formatDecimalValue,p as getMaxLength,g as process,A as processDecimal,j as unmaskDecimal};
2
2
  //# sourceMappingURL=mother-mask.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"mother-mask.mjs","names":[],"sources":["../src/apply-mask.ts","../src/pattern.ts","../src/mask.ts","../src/platform.ts","../src/bind.ts"],"sourcesContent":["import type { MaskResult } from './types'\n\n// ---------------------------------------------------------------------------\n// Character classification (no regex — avoids the empty-string pitfall)\n// ---------------------------------------------------------------------------\n\nfunction isDigitChar(ch: string): boolean {\n return ch >= '0' && ch <= '9'\n}\n\nfunction isLetterChar(ch: string): boolean {\n return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')\n}\n\nfunction matchesSlot(ch: string, slot: string): boolean {\n if (slot === '9') return isDigitChar(ch)\n if (slot === 'Z') return isLetterChar(ch)\n // slot === 'A' → alphanumeric\n return isDigitChar(ch) || isLetterChar(ch)\n}\n\n// ---------------------------------------------------------------------------\n// Core masking — pure function\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a single mask string to a value, producing the masked output and\n * a computed caret position.\n *\n * **Caret algorithm**: as the mask consumes characters from `value`, every\n * time a *matching* input character at a position *before* `inputCaret` is\n * written to the output (including any preceding pending literals that were\n * just flushed), the output caret is updated to the current output length.\n * This correctly handles literal insertion, middle-of-string edits, and\n * characters that are skipped because they don't match the current slot.\n */\nexport function applyMask(value: string, mask: string, inputCaret = 0): MaskResult {\n if (!value) return { value: '', caret: 0 }\n\n let output = ''\n let pending = ''\n let valueIdx = 0\n let outputCaret = 0\n let caretResolved = false\n\n for (let maskIdx = 0; maskIdx < mask.length; maskIdx++) {\n const maskCh = mask[maskIdx]\n\n if (maskCh !== '9' && maskCh !== 'Z' && maskCh !== 'A') {\n pending += maskCh\n continue\n }\n\n // Find next value character that matches this slot\n let found = false\n while (valueIdx < value.length) {\n const ch = value[valueIdx++]\n\n if (matchesSlot(ch, maskCh)) {\n // Flush pending literals then write the matched char\n output += pending + ch\n pending = ''\n found = true\n\n // Caret tracking: if this consumed char was before the input caret,\n // the output caret is (at least) at the current output length.\n if (!caretResolved) {\n if (valueIdx <= inputCaret) {\n outputCaret = output.length\n } else {\n caretResolved = true\n }\n }\n break\n }\n // Non-matching chars are silently skipped (iterative, no recursion).\n }\n\n if (!found) break\n }\n\n // If every matched char was before the input caret (or no chars matched at\n // all past the caret), place the output caret at the end of the output.\n if (!caretResolved) outputCaret = output.length\n\n return { value: output, caret: outputCaret }\n}\n","import type { MaskPattern } from './types'\n\n/**\n * Count alphanumeric characters in a value.\n *\n * Used by `resolveMask` so that both raw input (e.g. \"11999887766\") and\n * already-masked values (e.g. \"(11) 99988-7766\") produce the same data-char\n * count, enabling correct mask selection even when all keystrokes arrive\n * before the first rAF fires (fast typing).\n */\nfunction countDataChars(value: string): number {\n let n = 0\n for (const ch of value) {\n if (\n (ch >= '0' && ch <= '9') ||\n (ch >= 'a' && ch <= 'z') ||\n (ch >= 'A' && ch <= 'Z')\n )\n n++\n }\n return n\n}\n\n/** Count input slots (9, Z, A) in a mask string. */\nfunction countMaskSlots(mask: string): number {\n let n = 0\n for (const ch of mask) {\n if (ch === '9' || ch === 'Z' || ch === 'A') n++\n }\n return n\n}\n\n/**\n * Select the right mask string for the current value.\n *\n * Compares the number of alphanumeric data characters in `value` to the\n * slot capacity of each candidate mask. This correctly handles both\n * progressively-masked values (normal typing) and raw accumulated characters\n * (fast typing where multiple keystrokes arrive before any rAF fires).\n */\nexport function resolveMask(value: string, mask: MaskPattern): string {\n if (!Array.isArray(mask)) return mask\n const dataCount = countDataChars(value)\n let i = 0\n while (i < mask.length - 1 && dataCount > countMaskSlots(mask[i])) i++\n return mask[i]\n}\n\n/** Maximum allowed input length for the given mask. */\nexport function getMaxLength(mask: MaskPattern): number {\n if (Array.isArray(mask)) {\n return mask.length > 0 ? Math.max(...mask.map((m) => m.length)) : 0\n }\n return mask.length\n}\n","import { applyMask } from './apply-mask'\nimport { resolveMask } from './pattern'\nimport type { MaskPattern } from './types'\n\nexport { getMaxLength } from './pattern'\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** Low-level mask processor. Tracks caret position after masking. */\nexport class Mask {\n /** Caret position after `process()` runs. */\n caret: number\n\n private readonly _value: string\n private readonly _mask: string\n\n constructor(value: string, mask: string, caret = 0) {\n this._value = value\n this._mask = mask\n this.caret = caret\n }\n\n /** Apply the mask to the value and return the masked string. */\n process(): string {\n const result = applyMask(this._value, this._mask, this.caret)\n this.caret = result.caret\n return result.value\n }\n}\n\n/** Build a `Mask` instance, resolving array patterns by value length. */\nexport function buildMask(value: string, mask: MaskPattern, caret = 0): Mask {\n return new Mask(value, resolveMask(value, mask), caret)\n}\n\n/** Apply a mask pattern to a raw value string and return the masked result. */\nexport function process(value: string, mask: MaskPattern): string {\n return buildMask(value, mask).process()\n}\n","let cachedIsIos: boolean | undefined\n\n/** True when the runtime looks like iOS Safari / WebKit (affects key event choice in {@link bind}). */\nexport function isIos(): boolean {\n if (cachedIsIos !== undefined) return cachedIsIos\n cachedIsIos =\n typeof navigator !== 'undefined' && /iPad|iPhone|iPod/i.test(navigator.userAgent)\n return cachedIsIos\n}\n","import { buildMask, getMaxLength } from './mask'\nimport { isIos } from './platform'\nimport type { BindOptions, MaskPattern } from './types'\n\nconst MASKED_ATTR = 'data-masked'\n\nfunction toBindOptions(\n third: BindOptions | ((value: string) => void) | null | undefined,\n): BindOptions {\n if (third == null) return {}\n if (typeof third === 'function') return { onChange: third }\n return third\n}\n\n/**\n * Bind a mask pattern to an input element.\n *\n * Idempotent — calling `bind()` on an already-bound element has no effect.\n * The element receives a `data-masked` attribute marking it as bound.\n *\n * Returns a function that removes listeners and clears `data-masked` so the\n * element can be bound again later.\n *\n * @param input - Any `HTMLInputElement` or `Element` that behaves like one.\n * @param mask - A single pattern string or an ordered array (shortest → longest).\n * @param options - Optional `{ onChange }`, or pass a callback (legacy) as the third argument.\n */\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n options?: BindOptions | null,\n): () => void\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n onChange: ((value: string) => void) | null,\n): () => void\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n third?: BindOptions | ((value: string) => void) | null,\n): () => void {\n if (input.getAttribute(MASKED_ATTR) !== null) return () => {}\n\n const { onChange } = toBindOptions(third)\n\n /** Attribute names set by this bind call; removed on dispose so a later `bind()` can re-apply. */\n const attrsSetHere: string[] = []\n const setIfMissing = (name: string, value: string): void => {\n if (!input.hasAttribute(name)) {\n input.setAttribute(name, value)\n attrsSetHere.push(name)\n }\n }\n\n input.setAttribute(MASKED_ATTR, Array.isArray(mask) ? mask.join('|') : mask)\n setIfMissing('autocomplete', 'off')\n setIfMissing('autocorrect', 'off')\n setIfMissing('autocapitalize', 'off')\n setIfMissing('spellcheck', 'false')\n setIfMissing('maxlength', String(getMaxLength(mask)))\n\n let lockInput = false\n\n const keyEventName = isIos() ? 'keyup' : 'keydown'\n\n const onPaste = (e: Event): void => {\n const target = e.target as HTMLInputElement\n requestAnimationFrame(() => {\n const m = buildMask(target.value, mask)\n target.value = m.process()\n onChange?.(target.value)\n })\n }\n\n const onKey = (e: Event): void => {\n const ke = e as KeyboardEvent\n const target = ke.target as HTMLInputElement\n const oldValue = target.value\n\n // Older Android WebViews may fire key events without a `key` value.\n if (!(ke as { key?: string }).key) {\n lockInput = true\n requestAnimationFrame(() => {\n const pos = target.selectionStart ?? 999\n const m = buildMask(target.value, mask, pos)\n target.value = m.process()\n target.setSelectionRange(m.caret, m.caret)\n requestAnimationFrame(() => {\n lockInput = false\n })\n })\n return\n }\n\n if (ke.key === 'Meta') return\n\n const isBackspace = ke.key === 'Backspace'\n const isDelete = ke.key === 'Delete'\n const isCharInsert = ke.key.length === 1 && !ke.ctrlKey && !ke.altKey && !ke.metaKey\n const isUnidentified = ke.key === 'Unidentified'\n\n // Block inserting when mask is full (desktop only — iOS handles this natively)\n if (isCharInsert && target.selectionStart === target.selectionEnd) {\n if (oldValue.length >= getMaxLength(mask) && !isIos()) {\n ke.preventDefault()\n return\n }\n }\n\n // `lockInput` is only set by the Android WebView path above; block normal\n // key events while that asynchronous path is in flight.\n if (lockInput) {\n ke.preventDefault()\n return\n }\n\n requestAnimationFrame(() => {\n const pos = target.selectionStart ?? 999\n const m = buildMask(target.value, mask, pos)\n target.value = m.process()\n\n if (isUnidentified) {\n const newPos = target.value.length > oldValue.length ? m.caret : pos\n target.setSelectionRange(newPos, newPos)\n } else if (isDelete) {\n const newPos = oldValue.length === target.value.length ? pos + 1 : pos\n target.setSelectionRange(newPos, newPos)\n } else if (isBackspace) {\n target.setSelectionRange(pos, pos)\n } else if (isCharInsert) {\n target.setSelectionRange(m.caret, m.caret)\n }\n\n onChange?.(target.value)\n })\n }\n\n input.addEventListener('paste', onPaste)\n input.addEventListener(keyEventName, onKey)\n\n return () => {\n input.removeEventListener('paste', onPaste)\n input.removeEventListener(keyEventName, onKey)\n input.removeAttribute(MASKED_ATTR)\n for (const name of attrsSetHere) input.removeAttribute(name)\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mother-mask.mjs","names":["isDigitChar","MASKED_ATTR"],"sources":["../src/apply-mask.ts","../src/pattern.ts","../src/mask.ts","../src/platform.ts","../src/bind.ts","../src/decimal-mask.ts","../src/bind-decimal.ts"],"sourcesContent":["import type { ApplyMaskOptions, MaskResult } from './types'\n\n// ---------------------------------------------------------------------------\n// Character classification (no regex — avoids the empty-string pitfall)\n// ---------------------------------------------------------------------------\n\nfunction isDigitChar(ch: string): boolean {\n return ch >= '0' && ch <= '9'\n}\n\nfunction isLetterChar(ch: string): boolean {\n return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')\n}\n\nfunction isSlotChar(ch: string): boolean {\n return ch === '9' || ch === 'Z' || ch === 'A'\n}\n\nfunction matchesSlot(ch: string, slot: string): boolean {\n if (slot === '9') return isDigitChar(ch)\n if (slot === 'Z') return isLetterChar(ch)\n // slot === 'A' → alphanumeric\n return isDigitChar(ch) || isLetterChar(ch)\n}\n\n// ---------------------------------------------------------------------------\n// Flat masking (default) — treats the mask as one continuous character\n// stream. Best for continuous identifiers (phone numbers, CPF/CNPJ, credit\n// cards) where deleting/inserting a digit anywhere is expected to reflow\n// every digit after it — this is the classic mother-mask behavior and is\n// relied on by the majority of the test suite (paste, backspace, mid-string\n// insert, etc).\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a single mask string to a value, producing the masked output and\n * a computed caret position.\n *\n * **Caret algorithm**: as the mask consumes characters from `value`, every\n * time a *matching* input character at a position *before* `inputCaret` is\n * written to the output (including any preceding pending literals that were\n * just flushed), the output caret is updated to the current output length.\n * This correctly handles literal insertion, middle-of-string edits, and\n * characters that are skipped because they don't match the current slot.\n */\nfunction applyFlatMask(value: string, mask: string, inputCaret: number): MaskResult {\n let output = ''\n let pending = ''\n let valueIdx = 0\n let outputCaret = 0\n let caretResolved = false\n\n for (let maskIdx = 0; maskIdx < mask.length; maskIdx++) {\n const maskCh = mask[maskIdx]\n\n if (maskCh !== '9' && maskCh !== 'Z' && maskCh !== 'A') {\n pending += maskCh\n continue\n }\n\n // Find next value character that matches this slot\n let found = false\n while (valueIdx < value.length) {\n const ch = value[valueIdx++]\n\n if (matchesSlot(ch, maskCh)) {\n // Flush pending literals then write the matched char\n output += pending + ch\n pending = ''\n found = true\n\n // Caret tracking: if this consumed char was before the input caret,\n // the output caret is (at least) at the current output length.\n if (!caretResolved) {\n if (valueIdx <= inputCaret) {\n outputCaret = output.length\n } else {\n caretResolved = true\n }\n }\n break\n }\n // Non-matching chars are silently skipped (iterative, no recursion).\n }\n\n if (!found) break\n }\n\n // If every matched char was before the input caret (or no chars matched at\n // all past the caret), place the output caret at the end of the output.\n if (!caretResolved) outputCaret = output.length\n\n return { value: output, caret: outputCaret }\n}\n\n// ---------------------------------------------------------------------------\n// Segmented masking (opt-in) — treats literal separators as hard boundaries\n// between independent fields (e.g. day/month/year in \"99/99/9999\"). Editing\n// one segment never bleeds characters into a neighboring one, so replacing\n// the \"12\" in \"25/12/2025\" with a shorter or longer value keeps the year\n// exactly where it is instead of shifting digits across the \"/\".\n// ---------------------------------------------------------------------------\n\ntype MaskToken = { kind: 'literal'; text: string } | { kind: 'slots'; chars: string }\n\n/**\n * Split a mask into alternating literal and slot-run tokens (e.g. \"99/99/9999\"\n * → slots\"99\", literal\"/\", slots\"99\", literal\"/\", slots\"9999\"), so the masking\n * pass can reason about segment boundaries instead of a flat character stream.\n */\nfunction tokenizeMask(mask: string): MaskToken[] {\n const tokens: MaskToken[] = []\n let i = 0\n while (i < mask.length) {\n const start = i\n const wantSlots = isSlotChar(mask[i])\n while (i < mask.length && isSlotChar(mask[i]) === wantSlots) i++\n const text = mask.slice(start, i)\n tokens.push(wantSlots ? { kind: 'slots', chars: text } : { kind: 'literal', text })\n }\n return tokens\n}\n\n/** Total slot capacity from token index `from` (inclusive) to the end of `tokens`. */\nfunction slotCapacityFrom(tokens: MaskToken[], from: number): number {\n let capacity = 0\n for (let i = from; i < tokens.length; i++) {\n const token = tokens[i]\n if (token.kind === 'slots') capacity += token.chars.length\n }\n return capacity\n}\n\n/** Count of remaining slot-matchable (digit/letter) characters in `value` from `fromIdx` onward. */\nfunction remainingDataChars(value: string, fromIdx: number): number {\n let count = 0\n for (let i = fromIdx; i < value.length; i++) {\n const ch = value[i]\n if (isDigitChar(ch) || isLetterChar(ch)) count++\n }\n return count\n}\n\n/**\n * Same contract as {@link applyFlatMask}, but walks the mask one *segment* at\n * a time (a run of slots, or a literal) rather than one character at a time.\n * The rule that keeps an edit inside a segment from crossing into its\n * neighbor is an **early stop**: if the value hits the literal that ends the\n * current slot run before all of that run's slots are filled (e.g. only one\n * digit typed into a two-digit month slot), the run is left partially filled\n * instead of skipping past the separator to steal a digit from the next\n * segment.\n *\n * That stop is only taken when it's actually safe — i.e. every character\n * still to come in `value` fits in the slot capacity that remains *after*\n * this segment. If stopping here would strand more data than the rest of\n * the mask can hold (e.g. pasting into a later segment while an earlier one\n * still sits under-filled from before), the \"separator\" is treated as stray\n * noise instead and skipped, letting this segment take the extra slot it\n * needs so nothing at the end gets silently dropped. This is also what lets\n * an array mask grow or shrink its pattern (moving every literal after the\n * change point) reflow correctly instead of losing a digit at the boundary.\n */\nfunction applySegmentedMask(value: string, mask: string, inputCaret: number): MaskResult {\n const tokens = tokenizeMask(mask)\n\n let output = ''\n let pending = ''\n let valueIdx = 0\n let outputCaret = 0\n let caretResolved = false\n let exhausted = false\n\n for (let t = 0; t < tokens.length && !exhausted; t++) {\n const token = tokens[t]\n\n if (token.kind === 'literal') {\n pending += token.text\n if (value.startsWith(token.text, valueIdx)) valueIdx += token.text.length\n continue\n }\n\n const nextToken = tokens[t + 1]\n const capacityAfter = slotCapacityFrom(tokens, t + 1)\n\n for (let s = 0; s < token.chars.length; s++) {\n const slotCh = token.chars[s]\n let found = false\n\n while (valueIdx < value.length) {\n const ch = value[valueIdx]\n\n if (matchesSlot(ch, slotCh)) {\n valueIdx++\n output += pending + ch\n pending = ''\n found = true\n\n if (!caretResolved) {\n if (valueIdx <= inputCaret) {\n outputCaret = output.length\n } else {\n caretResolved = true\n }\n }\n break\n }\n\n // This segment's ending separator showed up before the run filled.\n // Stopping here is only safe if the rest of the value still fits in\n // whatever slot capacity remains after this segment — otherwise\n // stopping would strand data, so fall through and skip this char as\n // noise instead, letting the segment take the slot it needs.\n if (\n nextToken?.kind === 'literal' &&\n value.startsWith(nextToken.text, valueIdx) &&\n remainingDataChars(value, valueIdx) <= capacityAfter\n ) {\n break\n }\n\n valueIdx++ // stray/noise char — skip it\n }\n\n if (!found) {\n if (valueIdx >= value.length) exhausted = true\n break\n }\n }\n }\n\n if (!caretResolved) outputCaret = output.length\n\n return { value: output, caret: outputCaret }\n}\n\n// ---------------------------------------------------------------------------\n// Public entry point\n// ---------------------------------------------------------------------------\n\nexport function applyMask(\n value: string,\n mask: string,\n inputCaret = 0,\n options?: ApplyMaskOptions,\n): MaskResult {\n if (!value) return { value: '', caret: 0 }\n return options?.segmented === false\n ? applyFlatMask(value, mask, inputCaret)\n : applySegmentedMask(value, mask, inputCaret)\n}\n","import type { MaskPattern } from './types'\n\n/**\n * Count alphanumeric characters in a value.\n *\n * Used by `resolveMask` so that both raw input (e.g. \"11999887766\") and\n * already-masked values (e.g. \"(11) 99988-7766\") produce the same data-char\n * count, enabling correct mask selection even when all keystrokes arrive\n * before the first rAF fires (fast typing).\n */\nfunction countDataChars(value: string): number {\n let n = 0\n for (const ch of value) {\n if (\n (ch >= '0' && ch <= '9') ||\n (ch >= 'a' && ch <= 'z') ||\n (ch >= 'A' && ch <= 'Z')\n )\n n++\n }\n return n\n}\n\n/** Count input slots (9, Z, A) in a mask string. */\nfunction countMaskSlots(mask: string): number {\n let n = 0\n for (const ch of mask) {\n if (ch === '9' || ch === 'Z' || ch === 'A') n++\n }\n return n\n}\n\n/**\n * Select the right mask string for the current value.\n *\n * Compares the number of alphanumeric data characters in `value` to the\n * slot capacity of each candidate mask. This correctly handles both\n * progressively-masked values (normal typing) and raw accumulated characters\n * (fast typing where multiple keystrokes arrive before any rAF fires).\n */\nexport function resolveMask(value: string, mask: MaskPattern): string {\n if (!Array.isArray(mask)) return mask\n const dataCount = countDataChars(value)\n let i = 0\n while (i < mask.length - 1 && dataCount > countMaskSlots(mask[i])) i++\n return mask[i]\n}\n\n/** Maximum allowed input length for the given mask. */\nexport function getMaxLength(mask: MaskPattern): number {\n if (Array.isArray(mask)) {\n return mask.length > 0 ? Math.max(...mask.map((m) => m.length)) : 0\n }\n return mask.length\n}\n","import { applyMask } from './apply-mask'\nimport { resolveMask } from './pattern'\nimport type { ApplyMaskOptions, MaskPattern } from './types'\n\nexport { getMaxLength } from './pattern'\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** Low-level mask processor. Tracks caret position after masking. */\nexport class Mask {\n /** Caret position after `process()` runs. */\n caret: number\n\n private readonly _value: string\n private readonly _mask: string\n private readonly _options: ApplyMaskOptions | undefined\n\n constructor(value: string, mask: string, caret = 0, options?: ApplyMaskOptions) {\n this._value = value\n this._mask = mask\n this.caret = caret\n this._options = options\n }\n\n /** Apply the mask to the value and return the masked string. */\n process(): string {\n const result = applyMask(this._value, this._mask, this.caret, this._options)\n this.caret = result.caret\n return result.value\n }\n}\n\n/** Build a `Mask` instance, resolving array patterns by value length. */\nexport function buildMask(\n value: string,\n mask: MaskPattern,\n caret = 0,\n options?: ApplyMaskOptions,\n): Mask {\n return new Mask(value, resolveMask(value, mask), caret, options)\n}\n\n/** Apply a mask pattern to a raw value string and return the masked result. */\nexport function process(value: string, mask: MaskPattern, options?: ApplyMaskOptions): string {\n return buildMask(value, mask, 0, options).process()\n}\n","let cachedIsIos: boolean | undefined\n\n/** True when the runtime looks like iOS Safari / WebKit (affects key event choice in {@link bind}). */\nexport function isIos(): boolean {\n if (cachedIsIos !== undefined) return cachedIsIos\n cachedIsIos =\n typeof navigator !== 'undefined' && /iPad|iPhone|iPod/i.test(navigator.userAgent)\n return cachedIsIos\n}\n","import { buildMask, getMaxLength } from './mask'\nimport { isIos } from './platform'\nimport type { BindOptions, MaskPattern } from './types'\n\nconst MASKED_ATTR = 'data-masked'\n\nfunction toBindOptions(\n third: BindOptions | ((value: string) => void) | null | undefined,\n): BindOptions {\n if (third == null) return {}\n if (typeof third === 'function') return { onChange: third }\n return third\n}\n\n/**\n * Bind a mask pattern to an input element.\n *\n * Idempotent — calling `bind()` on an already-bound element has no effect.\n * The element receives a `data-masked` attribute marking it as bound.\n *\n * Returns a function that removes listeners and clears `data-masked` so the\n * element can be bound again later.\n *\n * @param input - Any `HTMLInputElement` or `Element` that behaves like one.\n * @param mask - A single pattern string or an ordered array (shortest → longest).\n * @param options - Optional `{ onChange }`, or pass a callback (legacy) as the third argument.\n */\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n options?: BindOptions | null,\n): () => void\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n onChange: ((value: string) => void) | null,\n): () => void\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n third?: BindOptions | ((value: string) => void) | null,\n): () => void {\n if (input.getAttribute(MASKED_ATTR) !== null) return () => {}\n\n const { onChange, segmented } = toBindOptions(third)\n\n /** Attribute names set by this bind call; removed on dispose so a later `bind()` can re-apply. */\n const attrsSetHere: string[] = []\n const setIfMissing = (name: string, value: string): void => {\n if (!input.hasAttribute(name)) {\n input.setAttribute(name, value)\n attrsSetHere.push(name)\n }\n }\n\n input.setAttribute(MASKED_ATTR, Array.isArray(mask) ? mask.join('|') : mask)\n setIfMissing('autocomplete', 'off')\n setIfMissing('autocorrect', 'off')\n setIfMissing('autocapitalize', 'off')\n setIfMissing('spellcheck', 'false')\n setIfMissing('maxlength', String(getMaxLength(mask)))\n\n let lockInput = false\n\n const keyEventName = isIos() ? 'keyup' : 'keydown'\n\n const onPaste = (e: Event): void => {\n const target = e.target as HTMLInputElement\n requestAnimationFrame(() => {\n const m = buildMask(target.value, mask, 0, { segmented })\n target.value = m.process()\n onChange?.(target.value)\n })\n }\n\n const onKey = (e: Event): void => {\n const ke = e as KeyboardEvent\n const target = ke.target as HTMLInputElement\n const oldValue = target.value\n\n // Older Android WebViews may fire key events without a `key` value.\n if (!(ke as { key?: string }).key) {\n lockInput = true\n requestAnimationFrame(() => {\n const pos = target.selectionStart ?? 999\n const m = buildMask(target.value, mask, pos, { segmented })\n target.value = m.process()\n target.setSelectionRange(m.caret, m.caret)\n requestAnimationFrame(() => {\n lockInput = false\n })\n })\n return\n }\n\n if (ke.key === 'Meta') return\n\n const isBackspace = ke.key === 'Backspace'\n const isDelete = ke.key === 'Delete'\n const isCharInsert = ke.key.length === 1 && !ke.ctrlKey && !ke.altKey && !ke.metaKey\n const isUnidentified = ke.key === 'Unidentified'\n\n // Block inserting when mask is full (desktop only — iOS handles this natively)\n if (isCharInsert && target.selectionStart === target.selectionEnd) {\n if (oldValue.length >= getMaxLength(mask) && !isIos()) {\n ke.preventDefault()\n return\n }\n }\n\n // `lockInput` is only set by the Android WebView path above; block normal\n // key events while that asynchronous path is in flight.\n if (lockInput) {\n ke.preventDefault()\n return\n }\n\n requestAnimationFrame(() => {\n const pos = target.selectionStart ?? 999\n const m = buildMask(target.value, mask, pos, { segmented })\n target.value = m.process()\n\n if (isUnidentified) {\n const newPos = target.value.length > oldValue.length ? m.caret : pos\n target.setSelectionRange(newPos, newPos)\n } else if (isDelete) {\n const newPos = oldValue.length === target.value.length ? pos + 1 : pos\n target.setSelectionRange(newPos, newPos)\n } else if (isBackspace) {\n target.setSelectionRange(pos, pos)\n } else if (isCharInsert) {\n target.setSelectionRange(m.caret, m.caret)\n }\n\n onChange?.(target.value)\n })\n }\n\n input.addEventListener('paste', onPaste)\n input.addEventListener(keyEventName, onKey)\n\n return () => {\n input.removeEventListener('paste', onPaste)\n input.removeEventListener(keyEventName, onKey)\n input.removeAttribute(MASKED_ATTR)\n for (const name of attrsSetHere) input.removeAttribute(name)\n }\n}\n","import type { DecimalMaskOptions, MaskResult } from './types'\n\n// ---------------------------------------------------------------------------\n// Character classification (no regex — mirrors apply-mask.ts)\n// ---------------------------------------------------------------------------\n\nfunction isDigitChar(ch: string): boolean {\n return ch >= '0' && ch <= '9'\n}\n\n// ---------------------------------------------------------------------------\n// Option resolution\n// ---------------------------------------------------------------------------\n\ninterface ResolvedDecimalOptions {\n decimalPlaces: number\n segmented: boolean\n separator: string\n decimalSeparator: string\n prefix: string\n suffix: string\n allowNegative: boolean\n}\n\n/** @internal exported for {@link bindDecimal}'s \".\" / \",\" key normalization */\nexport function resolveDecimalOptions(options?: DecimalMaskOptions): ResolvedDecimalOptions {\n const rawPlaces = options?.decimalPlaces ?? 2\n const decimalPlaces = Number.isFinite(rawPlaces) ? Math.max(0, Math.floor(rawPlaces)) : 2\n return {\n decimalPlaces,\n segmented: options?.segmented ?? true,\n separator: options?.separator ?? ',',\n decimalSeparator: options?.decimalSeparator ?? '.',\n prefix: options?.prefix ?? '',\n suffix: options?.suffix ?? '',\n allowNegative: options?.allowNegative ?? false,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Digit-stream helpers\n// ---------------------------------------------------------------------------\n\n/** Strip leading zeros from a digit string, always keeping at least one digit. */\nfunction stripLeadingZeros(s: string): string {\n let i = 0\n while (i < s.length - 1 && s[i] === '0') i++\n return s.slice(i)\n}\n\n/** Insert `sep` every 3 digits from the right (e.g. \"1234567\" → \"1,234,567\"). */\nfunction groupThousands(s: string, sep: string): string {\n if (!sep || s.length <= 3) return s\n const parts: string[] = []\n let i = s.length\n while (i > 3) {\n parts.unshift(s.slice(i - 3, i))\n i -= 3\n }\n parts.unshift(s.slice(0, i))\n return parts.join(sep)\n}\n\n/**\n * Find the position in `s` that leaves exactly `digitsBefore` digit\n * characters preceding it — the position immediately after that digit and\n * before any subsequent literal (grouping separator, ...), so the caret\n * stays glued to the last digit the user placed there.\n */\nfunction caretForDigitsBefore(s: string, digitsBefore: number): number {\n if (digitsBefore <= 0) return 0\n let count = 0\n for (let i = 0; i < s.length; i++) {\n if (isDigitChar(s[i])) {\n count++\n if (count === digitsBefore) return i + 1\n }\n }\n return s.length\n}\n\n// ---------------------------------------------------------------------------\n// Segmented parsing — integer digits before the decimal separator, fraction\n// digits after. Unlike a slot-pattern mask, the integer segment has no fixed\n// length; only the fraction is fixed-width (`decimalPlaces`), zero-padded on\n// the right so a shorter fraction reads as its low-order (trailing) digits\n// being zero rather than reflowing/shifting — e.g. editing \"423,42\" down to\n// \"423,4\" produces \"423,40\", not \"42,34\".\n//\n// The decimal separator only has meaning as the *first* occurrence of\n// `opts.decimalSeparator`; every other non-digit character (thousands\n// separator, prefix/suffix text, a second stray separator, ...) is noise and\n// is dropped. This keeps re-parsing an already-masked value idempotent.\n// ---------------------------------------------------------------------------\n\ninterface DecimalParts {\n isNegative: boolean\n intDigits: string\n fracDigits: string\n hasSeparator: boolean\n}\n\nfunction computeDecimalParts(raw: string, opts: ResolvedDecimalOptions): DecimalParts {\n let intDigits = ''\n let fracDigits = ''\n let isNegative = false\n let inFraction = false\n const canHaveFraction = opts.decimalPlaces > 0\n\n for (const ch of raw) {\n if (isDigitChar(ch)) {\n if (inFraction) {\n if (fracDigits.length < opts.decimalPlaces) fracDigits += ch\n } else {\n intDigits += ch\n }\n continue\n }\n if (canHaveFraction && !inFraction && ch === opts.decimalSeparator) {\n inFraction = true\n continue\n }\n if (ch === '-' && opts.allowNegative) isNegative = true\n // Anything else — thousands separator, prefix/suffix text, a repeated\n // separator, stray letters — is noise and is dropped.\n }\n\n return { isNegative, intDigits, fracDigits, hasSeparator: inFraction }\n}\n\n/**\n * Walk `raw[0:caret]` to find which segment the caret sits in (integer or\n * fraction) and how many digits of that segment precede it, so the same\n * position can be re-derived in the freshly formatted output.\n */\nfunction locateCaretSegment(\n raw: string,\n caret: number,\n opts: ResolvedDecimalOptions,\n): { inFraction: boolean; digitsBefore: number } {\n const canHaveFraction = opts.decimalPlaces > 0\n let inFraction = false\n let digitsBefore = 0\n\n for (let i = 0; i < caret; i++) {\n const ch = raw[i]\n if (isDigitChar(ch)) {\n if (!inFraction || digitsBefore < opts.decimalPlaces) digitsBefore++\n continue\n }\n if (canHaveFraction && !inFraction && ch === opts.decimalSeparator) {\n inFraction = true\n digitsBefore = 0\n }\n }\n\n return { inFraction, digitsBefore }\n}\n\n// ---------------------------------------------------------------------------\n// Public entry points\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a decimal/currency mask to a value, producing the masked output and\n * a computed caret position. Digits typed before the decimal separator\n * extend the integer part; the fraction only starts once the separator is\n * typed, and is always displayed zero-padded to `decimalPlaces` width.\n */\nexport function applyDecimalMask(\n value: string,\n inputCaret = 0,\n options?: DecimalMaskOptions,\n): MaskResult {\n const opts = resolveDecimalOptions(options)\n if (!value) return { value: '', caret: 0 }\n\n const { isNegative, intDigits, fracDigits, hasSeparator } = computeDecimalParts(value, opts)\n if (intDigits === '' && fracDigits === '' && !hasSeparator) return { value: '', caret: 0 }\n\n const intPart = stripLeadingZeros(intDigits || '0')\n const groupedInt = opts.segmented ? groupThousands(intPart, opts.separator) : intPart\n const fracPadded = opts.decimalPlaces > 0 ? fracDigits.padEnd(opts.decimalPlaces, '0') : ''\n const numberStr = groupedInt + (opts.decimalPlaces > 0 ? opts.decimalSeparator + fracPadded : '')\n const signStr = isNegative ? '-' : ''\n const output = signStr + opts.prefix + numberStr + opts.suffix\n\n const clampedCaret = Math.max(0, Math.min(inputCaret, value.length))\n const { inFraction, digitsBefore } = locateCaretSegment(value, clampedCaret, opts)\n const prefixLen = signStr.length + opts.prefix.length\n const caret = inFraction\n ? prefixLen + groupedInt.length + opts.decimalSeparator.length + digitsBefore\n : prefixLen + caretForDigitsBefore(groupedInt, digitsBefore)\n\n return { value: output, caret }\n}\n\n/** Apply a decimal mask to a raw value and return just the masked string. */\nexport function processDecimal(value: string, options?: DecimalMaskOptions): string {\n return applyDecimalMask(value, value.length, options).value\n}\n\n/**\n * Parse a raw or already-masked decimal value back into a JS number.\n * Ignores prefix/suffix/thousands separator; returns `0` for an empty or\n * digit-less value.\n */\nexport function unmaskDecimal(value: string, options?: DecimalMaskOptions): number {\n const opts = resolveDecimalOptions(options)\n const { isNegative, intDigits, fracDigits } = computeDecimalParts(value, opts)\n const n = Number(fracDigits ? `${intDigits || '0'}.${fracDigits}` : intDigits || '0')\n return isNegative ? -n : n\n}\n\n/**\n * After a Backspace removes the decimal separator itself, the integer and\n * fraction digit runs collapse into one continuous stream (e.g. \"25.00\"\n * with the caret right after \".\" → Backspace deletes the \".\" → \"2500\").\n * Left alone, that reads as one big integer (\"$2,500.00\"). This restores\n * the segment boundary instead: the trailing `decimalPlaces` digits are\n * still the fraction, and the digit right before them — the one that used\n * to sit at the end of the integer part — is the one Backspace actually\n * removed, so it's dropped (not kept) — \"$25.00\" → \"$2.00\".\n *\n * Every reformat re-appends `decimalSeparator` whenever `decimalPlaces > 0`,\n * so its absence from `value` is an unambiguous signal that this exact\n * keystroke just deleted it — no \"value before this keystroke\" snapshot is\n * needed. Returns `null` when there's nothing to restore (the separator is\n * still present, `decimalPlaces` is `0`, or too few digits remain), so the\n * caller falls through to a plain {@link applyDecimalMask} call.\n */\nexport function applyDecimalMaskUnmergingSeparator(\n value: string,\n options?: DecimalMaskOptions,\n): MaskResult | null {\n const opts = resolveDecimalOptions(options)\n if (opts.decimalPlaces <= 0) return null\n\n const { isNegative, intDigits, hasSeparator } = computeDecimalParts(value, opts)\n if (hasSeparator || intDigits.length < opts.decimalPlaces + 1) return null\n\n const preMergeIntLength = intDigits.length - opts.decimalPlaces\n const fracDigits = intDigits.slice(preMergeIntLength)\n const remainingInt = intDigits.slice(0, preMergeIntLength - 1)\n\n const signPart = isNegative ? '-' : ''\n const raw = signPart + remainingInt + opts.decimalSeparator + fracDigits\n return applyDecimalMask(raw, signPart.length + remainingInt.length, opts)\n}\n\n/**\n * Special-cases typing a single digit into a field whose integer part is\n * exactly the auto-inserted \"0\" placeholder: the new digit replaces that\n * zero instead of combining with it — e.g. \"$0.00\" with the caret anywhere\n * against that lone \"0\" and typing \"2\" gives \"$2.00\", not \"$20.00\"/\"$02.00\".\n * Any already-typed fraction is preserved.\n *\n * `value`/`caret` must be the state *after* the browser has already\n * inserted `digit` at `caret - 1` (the same post-insertion snapshot\n * `applyDecimalMask` itself expects from `bindDecimal`). Returns `null`\n * when the pattern doesn't apply — either the integer part has real digits\n * already, or the caret wasn't against that lone zero — so the caller falls\n * through to a plain {@link applyDecimalMask} call.\n */\nexport function applyDecimalMaskReplacingLoneZero(\n value: string,\n caret: number,\n digit: string,\n options?: DecimalMaskOptions,\n): MaskResult | null {\n const opts = resolveDecimalOptions(options)\n const insertIdx = caret - 1\n if (insertIdx < 0 || value[insertIdx] !== digit) return null\n\n const withoutDigit = value.slice(0, insertIdx) + value.slice(caret)\n const { isNegative, intDigits, fracDigits, hasSeparator } = computeDecimalParts(withoutDigit, opts)\n if (intDigits !== '0') return null\n\n const prefixLen = (isNegative ? 1 : 0) + opts.prefix.length\n if (insertIdx < prefixLen || insertIdx > prefixLen + 1) return null\n\n const signPart = isNegative ? '-' : ''\n const raw = signPart + digit + (hasSeparator ? opts.decimalSeparator + fracDigits : '')\n return applyDecimalMask(raw, signPart.length + 1, opts)\n}\n\n/** Format a plain JS number into its masked display string. */\nexport function formatDecimalValue(value: number, options?: DecimalMaskOptions): string {\n const opts = resolveDecimalOptions(options)\n if (!Number.isFinite(value)) return ''\n\n const isNegative = opts.allowNegative && value < 0\n const fixed = Math.abs(value).toFixed(opts.decimalPlaces)\n const dotIdx = fixed.indexOf('.')\n const intRaw = dotIdx === -1 ? fixed : fixed.slice(0, dotIdx)\n const fracPart = dotIdx === -1 ? '' : fixed.slice(dotIdx + 1)\n const intPart = stripLeadingZeros(intRaw || '0')\n\n const groupedInt = opts.segmented ? groupThousands(intPart, opts.separator) : intPart\n const numberStr = groupedInt + (opts.decimalPlaces > 0 ? opts.decimalSeparator + fracPart : '')\n\n return (isNegative ? '-' : '') + opts.prefix + numberStr + opts.suffix\n}\n","import {\n applyDecimalMask,\n applyDecimalMaskReplacingLoneZero,\n applyDecimalMaskUnmergingSeparator,\n resolveDecimalOptions,\n unmaskDecimal,\n} from './decimal-mask'\nimport { isIos } from './platform'\nimport type { BindDecimalOptions, DecimalMaskOptions, MaskResult } from './types'\n\nconst MASKED_ATTR = 'data-masked'\n\nfunction toBindDecimalOptions(\n second:\n | BindDecimalOptions\n | ((value: string, numericValue: number) => void)\n | null\n | undefined,\n): BindDecimalOptions {\n if (second == null) return {}\n if (typeof second === 'function') return { onChange: second }\n return second\n}\n\nfunction isDigitKey(key: string): boolean {\n return key.length === 1 && key >= '0' && key <= '9'\n}\n\n/**\n * Bind a decimal/currency mask to an input element.\n *\n * Same contract as {@link bind}: idempotent (marked with `data-masked`),\n * returns a dispose function, and reformats on paste and keyboard-driven\n * changes via `requestAnimationFrame`. Unlike the pattern masks, there is no\n * fixed pattern — the integer part grows and shrinks freely; formatting is\n * driven entirely by `options`.\n *\n * @param input - Any `HTMLInputElement` or `Element` that behaves like one.\n * @param options - `DecimalMaskOptions` plus an optional `onChange`, or pass a callback (legacy) directly.\n */\nexport function bindDecimal(\n input: HTMLInputElement | Element,\n options?: BindDecimalOptions | null,\n): () => void\nexport function bindDecimal(\n input: HTMLInputElement | Element,\n onChange: ((value: string, numericValue: number) => void) | null,\n): () => void\nexport function bindDecimal(\n input: HTMLInputElement | Element,\n second?: BindDecimalOptions | ((value: string, numericValue: number) => void) | null,\n): () => void {\n if (input.getAttribute(MASKED_ATTR) !== null) return () => {}\n\n const { onChange, ...maskOptions } = toBindDecimalOptions(second)\n const decimalOptions: DecimalMaskOptions = maskOptions\n const { decimalSeparator, decimalPlaces } = resolveDecimalOptions(decimalOptions)\n\n const attrsSetHere: string[] = []\n const setIfMissing = (name: string, value: string): void => {\n if (!input.hasAttribute(name)) {\n input.setAttribute(name, value)\n attrsSetHere.push(name)\n }\n }\n\n input.setAttribute(MASKED_ATTR, 'decimal')\n setIfMissing('autocomplete', 'off')\n setIfMissing('autocorrect', 'off')\n setIfMissing('autocapitalize', 'off')\n setIfMissing('spellcheck', 'false')\n\n let lockInput = false\n const keyEventName = isIos() ? 'keyup' : 'keydown'\n\n const applyResult = (target: HTMLInputElement, m: MaskResult): void => {\n target.value = m.value\n target.setSelectionRange(m.caret, m.caret)\n onChange?.(m.value, unmaskDecimal(m.value, decimalOptions))\n }\n\n const onPaste = (e: Event): void => {\n const target = e.target as HTMLInputElement\n requestAnimationFrame(() => {\n applyResult(target, applyDecimalMask(target.value, target.value.length, decimalOptions))\n })\n }\n\n const onKey = (e: Event): void => {\n const ke = e as KeyboardEvent\n const target = ke.target as HTMLInputElement\n\n // Older Android WebViews may fire key events without a `key` value.\n if (!(ke as { key?: string }).key) {\n lockInput = true\n requestAnimationFrame(() => {\n const pos = target.selectionStart ?? target.value.length\n applyResult(target, applyDecimalMask(target.value, pos, decimalOptions))\n requestAnimationFrame(() => {\n lockInput = false\n })\n })\n return\n }\n\n if (ke.key === 'Meta') return\n\n // `lockInput` is only set by the Android WebView path above; block normal\n // key events while that asynchronous path is in flight.\n if (lockInput) {\n ke.preventDefault()\n return\n }\n\n const isBackspace = ke.key === 'Backspace'\n const isDelete = ke.key === 'Delete'\n const isCharInsert = ke.key.length === 1 && !ke.ctrlKey && !ke.altKey && !ke.metaKey\n\n // Navigation (arrows, Home/End, Tab, ...), selection, and shortcut keys\n // (Ctrl/Cmd+A, Ctrl/Cmd+C, ...) don't change the text — leave the\n // browser's native caret/selection handling alone instead of recomputing\n // and overwriting it on every keystroke.\n if (!isBackspace && !isDelete && !isCharInsert) return\n\n // Everything below reads `target.value`/`selectionStart` inside the rAF\n // callback rather than synchronously here, since the browser's native\n // character insertion for this keystroke isn't guaranteed to have landed\n // yet at the point a keydown listener runs — only by the next frame.\n requestAnimationFrame(() => {\n const pos = target.selectionStart ?? target.value.length\n\n // Backspace that just deleted the decimal separator merges the\n // integer and fraction digit runs into one continuous stream —\n // restore the boundary instead of treating that as one big integer.\n if (isBackspace) {\n const unmerged = applyDecimalMaskUnmergingSeparator(target.value, decimalOptions)\n if (unmerged) {\n applyResult(target, unmerged)\n return\n }\n }\n\n // A numeric keypad (or a locale mismatch) may only offer \".\" or \",\".\n // Normalize whichever one the user just typed to the configured\n // `decimalSeparator` so it reliably opens the fraction segment.\n if (\n decimalPlaces > 0 &&\n (ke.key === '.' || ke.key === ',') &&\n ke.key !== decimalSeparator &&\n pos > 0 &&\n target.value[pos - 1] === ke.key\n ) {\n target.value = target.value.slice(0, pos - 1) + decimalSeparator + target.value.slice(pos)\n }\n\n // Typing a digit into a field whose integer part is still the\n // auto-inserted \"0\" placeholder replaces that zero instead of\n // combining with it (e.g. \"$0.00\" + \"2\" → \"$2.00\", not \"$20.00\").\n const replaced = isDigitKey(ke.key)\n ? applyDecimalMaskReplacingLoneZero(target.value, pos, ke.key, decimalOptions)\n : null\n\n applyResult(target, replaced ?? applyDecimalMask(target.value, pos, decimalOptions))\n })\n }\n\n input.addEventListener('paste', onPaste)\n input.addEventListener(keyEventName, onKey)\n\n return () => {\n input.removeEventListener('paste', onPaste)\n input.removeEventListener(keyEventName, onKey)\n input.removeAttribute(MASKED_ATTR)\n for (const name of attrsSetHere) input.removeAttribute(name)\n }\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.MotherMask={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function t(e){return e>=`0`&&e<=`9`}function n(e){return e>=`a`&&e<=`z`||e>=`A`&&e<=`Z`}function r(e,r){return r===`9`?t(e):r===`Z`?n(e):t(e)||n(e)}function i(e,t,n=0){if(!e)return{value:``,caret:0};let i=``,a=``,o=0,s=0,c=!1;for(let l=0;l<t.length;l++){let u=t[l];if(u!==`9`&&u!==`Z`&&u!==`A`){a+=u;continue}let d=!1;for(;o<e.length;){let t=e[o++];if(r(t,u)){i+=a+t,a=``,d=!0,c||(o<=n?s=i.length:c=!0);break}}if(!d)break}return c||(s=i.length),{value:i,caret:s}}function a(e){let t=0;for(let n of e)(n>=`0`&&n<=`9`||n>=`a`&&n<=`z`||n>=`A`&&n<=`Z`)&&t++;return t}function o(e){let t=0;for(let n of e)(n===`9`||n===`Z`||n===`A`)&&t++;return t}function s(e,t){if(!Array.isArray(t))return t;let n=a(e),r=0;for(;r<t.length-1&&n>o(t[r]);)r++;return t[r]}function c(e){return Array.isArray(e)?e.length>0?Math.max(...e.map(e=>e.length)):0:e.length}var l=class{caret;_value;_mask;constructor(e,t,n=0){this._value=e,this._mask=t,this.caret=n}process(){let e=i(this._value,this._mask,this.caret);return this.caret=e.caret,e.value}};function u(e,t,n=0){return new l(e,s(e,t),n)}function d(e,t){return u(e,t).process()}let f;function p(){return f===void 0&&(f=typeof navigator<`u`&&/iPad|iPhone|iPod/i.test(navigator.userAgent)),f}let m=`data-masked`;function h(e){return e==null?{}:typeof e==`function`?{onChange:e}:e}function g(e,t,n){if(e.getAttribute(m)!==null)return()=>{};let{onChange:r}=h(n),i=[],a=(t,n)=>{e.hasAttribute(t)||(e.setAttribute(t,n),i.push(t))};e.setAttribute(m,Array.isArray(t)?t.join(`|`):t),a(`autocomplete`,`off`),a(`autocorrect`,`off`),a(`autocapitalize`,`off`),a(`spellcheck`,`false`),a(`maxlength`,String(c(t)));let o=!1,s=p()?`keyup`:`keydown`,l=e=>{let n=e.target;requestAnimationFrame(()=>{n.value=u(n.value,t).process(),r?.(n.value)})},d=e=>{let n=e,i=n.target,a=i.value;if(!n.key){o=!0,requestAnimationFrame(()=>{let e=i.selectionStart??999,n=u(i.value,t,e);i.value=n.process(),i.setSelectionRange(n.caret,n.caret),requestAnimationFrame(()=>{o=!1})});return}if(n.key===`Meta`)return;let s=n.key===`Backspace`,l=n.key===`Delete`,d=n.key.length===1&&!n.ctrlKey&&!n.altKey&&!n.metaKey,f=n.key===`Unidentified`;if(d&&i.selectionStart===i.selectionEnd&&a.length>=c(t)&&!p()){n.preventDefault();return}if(o){n.preventDefault();return}requestAnimationFrame(()=>{let e=i.selectionStart??999,n=u(i.value,t,e);if(i.value=n.process(),f){let t=i.value.length>a.length?n.caret:e;i.setSelectionRange(t,t)}else if(l){let t=a.length===i.value.length?e+1:e;i.setSelectionRange(t,t)}else s?i.setSelectionRange(e,e):d&&i.setSelectionRange(n.caret,n.caret);r?.(i.value)})};return e.addEventListener(`paste`,l),e.addEventListener(s,d),()=>{e.removeEventListener(`paste`,l),e.removeEventListener(s,d),e.removeAttribute(m);for(let t of i)e.removeAttribute(t)}}e.Mask=l,e.applyMask=i,e.bind=g,e.buildMask=u,e.getMaxLength=c,e.process=d});
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.MotherMask={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function t(e){return e>=`0`&&e<=`9`}function n(e){return e>=`a`&&e<=`z`||e>=`A`&&e<=`Z`}function r(e){return e===`9`||e===`Z`||e===`A`}function i(e,r){return r===`9`?t(e):r===`Z`?n(e):t(e)||n(e)}function a(e,t,n){let r=``,a=``,o=0,s=0,c=!1;for(let l=0;l<t.length;l++){let u=t[l];if(u!==`9`&&u!==`Z`&&u!==`A`){a+=u;continue}let d=!1;for(;o<e.length;){let t=e[o++];if(i(t,u)){r+=a+t,a=``,d=!0,c||(o<=n?s=r.length:c=!0);break}}if(!d)break}return c||(s=r.length),{value:r,caret:s}}function o(e){let t=[],n=0;for(;n<e.length;){let i=n,a=r(e[n]);for(;n<e.length&&r(e[n])===a;)n++;let o=e.slice(i,n);t.push(a?{kind:`slots`,chars:o}:{kind:`literal`,text:o})}return t}function s(e,t){let n=0;for(let r=t;r<e.length;r++){let t=e[r];t.kind===`slots`&&(n+=t.chars.length)}return n}function c(e,r){let i=0;for(let a=r;a<e.length;a++){let r=e[a];(t(r)||n(r))&&i++}return i}function l(e,t,n){let r=o(t),a=``,l=``,u=0,d=0,f=!1,p=!1;for(let t=0;t<r.length&&!p;t++){let o=r[t];if(o.kind===`literal`){l+=o.text,e.startsWith(o.text,u)&&(u+=o.text.length);continue}let m=r[t+1],h=s(r,t+1);for(let t=0;t<o.chars.length;t++){let r=o.chars[t],s=!1;for(;u<e.length;){let t=e[u];if(i(t,r)){u++,a+=l+t,l=``,s=!0,f||(u<=n?d=a.length:f=!0);break}if(m?.kind===`literal`&&e.startsWith(m.text,u)&&c(e,u)<=h)break;u++}if(!s){u>=e.length&&(p=!0);break}}}return f||(d=a.length),{value:a,caret:d}}function u(e,t,n=0,r){return e?r?.segmented===!1?a(e,t,n):l(e,t,n):{value:``,caret:0}}function d(e){let t=0;for(let n of e)(n>=`0`&&n<=`9`||n>=`a`&&n<=`z`||n>=`A`&&n<=`Z`)&&t++;return t}function f(e){let t=0;for(let n of e)(n===`9`||n===`Z`||n===`A`)&&t++;return t}function p(e,t){if(!Array.isArray(t))return t;let n=d(e),r=0;for(;r<t.length-1&&n>f(t[r]);)r++;return t[r]}function m(e){return Array.isArray(e)?e.length>0?Math.max(...e.map(e=>e.length)):0:e.length}var h=class{caret;_value;_mask;_options;constructor(e,t,n=0,r){this._value=e,this._mask=t,this.caret=n,this._options=r}process(){let e=u(this._value,this._mask,this.caret,this._options);return this.caret=e.caret,e.value}};function g(e,t,n=0,r){return new h(e,p(e,t),n,r)}function _(e,t,n){return g(e,t,0,n).process()}let v;function y(){return v===void 0&&(v=typeof navigator<`u`&&/iPad|iPhone|iPod/i.test(navigator.userAgent)),v}let b=`data-masked`;function x(e){return e==null?{}:typeof e==`function`?{onChange:e}:e}function S(e,t,n){if(e.getAttribute(b)!==null)return()=>{};let{onChange:r,segmented:i}=x(n),a=[],o=(t,n)=>{e.hasAttribute(t)||(e.setAttribute(t,n),a.push(t))};e.setAttribute(b,Array.isArray(t)?t.join(`|`):t),o(`autocomplete`,`off`),o(`autocorrect`,`off`),o(`autocapitalize`,`off`),o(`spellcheck`,`false`),o(`maxlength`,String(m(t)));let s=!1,c=y()?`keyup`:`keydown`,l=e=>{let n=e.target;requestAnimationFrame(()=>{n.value=g(n.value,t,0,{segmented:i}).process(),r?.(n.value)})},u=e=>{let n=e,a=n.target,o=a.value;if(!n.key){s=!0,requestAnimationFrame(()=>{let e=a.selectionStart??999,n=g(a.value,t,e,{segmented:i});a.value=n.process(),a.setSelectionRange(n.caret,n.caret),requestAnimationFrame(()=>{s=!1})});return}if(n.key===`Meta`)return;let c=n.key===`Backspace`,l=n.key===`Delete`,u=n.key.length===1&&!n.ctrlKey&&!n.altKey&&!n.metaKey,d=n.key===`Unidentified`;if(u&&a.selectionStart===a.selectionEnd&&o.length>=m(t)&&!y()){n.preventDefault();return}if(s){n.preventDefault();return}requestAnimationFrame(()=>{let e=a.selectionStart??999,n=g(a.value,t,e,{segmented:i});if(a.value=n.process(),d){let t=a.value.length>o.length?n.caret:e;a.setSelectionRange(t,t)}else if(l){let t=o.length===a.value.length?e+1:e;a.setSelectionRange(t,t)}else c?a.setSelectionRange(e,e):u&&a.setSelectionRange(n.caret,n.caret);r?.(a.value)})};return e.addEventListener(`paste`,l),e.addEventListener(c,u),()=>{e.removeEventListener(`paste`,l),e.removeEventListener(c,u),e.removeAttribute(b);for(let t of a)e.removeAttribute(t)}}function C(e){return e>=`0`&&e<=`9`}function w(e){let t=e?.decimalPlaces??2;return{decimalPlaces:Number.isFinite(t)?Math.max(0,Math.floor(t)):2,segmented:e?.segmented??!0,separator:e?.separator??`,`,decimalSeparator:e?.decimalSeparator??`.`,prefix:e?.prefix??``,suffix:e?.suffix??``,allowNegative:e?.allowNegative??!1}}function T(e){let t=0;for(;t<e.length-1&&e[t]===`0`;)t++;return e.slice(t)}function E(e,t){if(!t||e.length<=3)return e;let n=[],r=e.length;for(;r>3;)n.unshift(e.slice(r-3,r)),r-=3;return n.unshift(e.slice(0,r)),n.join(t)}function D(e,t){if(t<=0)return 0;let n=0;for(let r=0;r<e.length;r++)if(C(e[r])&&(n++,n===t))return r+1;return e.length}function O(e,t){let n=``,r=``,i=!1,a=!1,o=t.decimalPlaces>0;for(let s of e){if(C(s)){a?r.length<t.decimalPlaces&&(r+=s):n+=s;continue}if(o&&!a&&s===t.decimalSeparator){a=!0;continue}s===`-`&&t.allowNegative&&(i=!0)}return{isNegative:i,intDigits:n,fracDigits:r,hasSeparator:a}}function k(e,t,n){let r=n.decimalPlaces>0,i=!1,a=0;for(let o=0;o<t;o++){let t=e[o];if(C(t)){(!i||a<n.decimalPlaces)&&a++;continue}r&&!i&&t===n.decimalSeparator&&(i=!0,a=0)}return{inFraction:i,digitsBefore:a}}function A(e,t=0,n){let r=w(n);if(!e)return{value:``,caret:0};let{isNegative:i,intDigits:a,fracDigits:o,hasSeparator:s}=O(e,r);if(a===``&&o===``&&!s)return{value:``,caret:0};let c=T(a||`0`),l=r.segmented?E(c,r.separator):c,u=r.decimalPlaces>0?o.padEnd(r.decimalPlaces,`0`):``,d=l+(r.decimalPlaces>0?r.decimalSeparator+u:``),f=i?`-`:``,p=f+r.prefix+d+r.suffix,{inFraction:m,digitsBefore:h}=k(e,Math.max(0,Math.min(t,e.length)),r),g=f.length+r.prefix.length;return{value:p,caret:m?g+l.length+r.decimalSeparator.length+h:g+D(l,h)}}function j(e,t){return A(e,e.length,t).value}function M(e,t){let{isNegative:n,intDigits:r,fracDigits:i}=O(e,w(t)),a=Number(i?`${r||`0`}.${i}`:r||`0`);return n?-a:a}function N(e,t){let n=w(t);if(n.decimalPlaces<=0)return null;let{isNegative:r,intDigits:i,hasSeparator:a}=O(e,n);if(a||i.length<n.decimalPlaces+1)return null;let o=i.length-n.decimalPlaces,s=i.slice(o),c=i.slice(0,o-1),l=r?`-`:``;return A(l+c+n.decimalSeparator+s,l.length+c.length,n)}function P(e,t,n,r){let i=w(r),a=t-1;if(a<0||e[a]!==n)return null;let{isNegative:o,intDigits:s,fracDigits:c,hasSeparator:l}=O(e.slice(0,a)+e.slice(t),i);if(s!==`0`)return null;let u=+!!o+i.prefix.length;if(a<u||a>u+1)return null;let d=o?`-`:``;return A(d+n+(l?i.decimalSeparator+c:``),d.length+1,i)}function F(e,t){let n=w(t);if(!Number.isFinite(e))return``;let r=n.allowNegative&&e<0,i=Math.abs(e).toFixed(n.decimalPlaces),a=i.indexOf(`.`),o=a===-1?i:i.slice(0,a),s=a===-1?``:i.slice(a+1),c=T(o||`0`),l=(n.segmented?E(c,n.separator):c)+(n.decimalPlaces>0?n.decimalSeparator+s:``);return(r?`-`:``)+n.prefix+l+n.suffix}let I=`data-masked`;function L(e){return e==null?{}:typeof e==`function`?{onChange:e}:e}function R(e){return e.length===1&&e>=`0`&&e<=`9`}function z(e,t){if(e.getAttribute(I)!==null)return()=>{};let{onChange:n,...r}=L(t),i=r,{decimalSeparator:a,decimalPlaces:o}=w(i),s=[],c=(t,n)=>{e.hasAttribute(t)||(e.setAttribute(t,n),s.push(t))};e.setAttribute(I,`decimal`),c(`autocomplete`,`off`),c(`autocorrect`,`off`),c(`autocapitalize`,`off`),c(`spellcheck`,`false`);let l=!1,u=y()?`keyup`:`keydown`,d=(e,t)=>{e.value=t.value,e.setSelectionRange(t.caret,t.caret),n?.(t.value,M(t.value,i))},f=e=>{let t=e.target;requestAnimationFrame(()=>{d(t,A(t.value,t.value.length,i))})},p=e=>{let t=e,n=t.target;if(!t.key){l=!0,requestAnimationFrame(()=>{let e=n.selectionStart??n.value.length;d(n,A(n.value,e,i)),requestAnimationFrame(()=>{l=!1})});return}if(t.key===`Meta`)return;if(l){t.preventDefault();return}let r=t.key===`Backspace`,s=t.key===`Delete`,c=t.key.length===1&&!t.ctrlKey&&!t.altKey&&!t.metaKey;!r&&!s&&!c||requestAnimationFrame(()=>{let e=n.selectionStart??n.value.length;if(r){let e=N(n.value,i);if(e){d(n,e);return}}o>0&&(t.key===`.`||t.key===`,`)&&t.key!==a&&e>0&&n.value[e-1]===t.key&&(n.value=n.value.slice(0,e-1)+a+n.value.slice(e)),d(n,(R(t.key)?P(n.value,e,t.key,i):null)??A(n.value,e,i))})};return e.addEventListener(`paste`,f),e.addEventListener(u,p),()=>{e.removeEventListener(`paste`,f),e.removeEventListener(u,p),e.removeAttribute(I);for(let t of s)e.removeAttribute(t)}}e.Mask=h,e.applyDecimalMask=A,e.applyMask=u,e.bind=S,e.bindDecimal=z,e.buildMask=g,e.formatDecimalValue=F,e.getMaxLength=m,e.process=_,e.processDecimal=j,e.unmaskDecimal=M});
2
2
  //# sourceMappingURL=mother-mask.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mother-mask.umd.js","names":[],"sources":["../src/apply-mask.ts","../src/pattern.ts","../src/mask.ts","../src/platform.ts","../src/bind.ts"],"sourcesContent":["import type { MaskResult } from './types'\n\n// ---------------------------------------------------------------------------\n// Character classification (no regex — avoids the empty-string pitfall)\n// ---------------------------------------------------------------------------\n\nfunction isDigitChar(ch: string): boolean {\n return ch >= '0' && ch <= '9'\n}\n\nfunction isLetterChar(ch: string): boolean {\n return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')\n}\n\nfunction matchesSlot(ch: string, slot: string): boolean {\n if (slot === '9') return isDigitChar(ch)\n if (slot === 'Z') return isLetterChar(ch)\n // slot === 'A' → alphanumeric\n return isDigitChar(ch) || isLetterChar(ch)\n}\n\n// ---------------------------------------------------------------------------\n// Core masking — pure function\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a single mask string to a value, producing the masked output and\n * a computed caret position.\n *\n * **Caret algorithm**: as the mask consumes characters from `value`, every\n * time a *matching* input character at a position *before* `inputCaret` is\n * written to the output (including any preceding pending literals that were\n * just flushed), the output caret is updated to the current output length.\n * This correctly handles literal insertion, middle-of-string edits, and\n * characters that are skipped because they don't match the current slot.\n */\nexport function applyMask(value: string, mask: string, inputCaret = 0): MaskResult {\n if (!value) return { value: '', caret: 0 }\n\n let output = ''\n let pending = ''\n let valueIdx = 0\n let outputCaret = 0\n let caretResolved = false\n\n for (let maskIdx = 0; maskIdx < mask.length; maskIdx++) {\n const maskCh = mask[maskIdx]\n\n if (maskCh !== '9' && maskCh !== 'Z' && maskCh !== 'A') {\n pending += maskCh\n continue\n }\n\n // Find next value character that matches this slot\n let found = false\n while (valueIdx < value.length) {\n const ch = value[valueIdx++]\n\n if (matchesSlot(ch, maskCh)) {\n // Flush pending literals then write the matched char\n output += pending + ch\n pending = ''\n found = true\n\n // Caret tracking: if this consumed char was before the input caret,\n // the output caret is (at least) at the current output length.\n if (!caretResolved) {\n if (valueIdx <= inputCaret) {\n outputCaret = output.length\n } else {\n caretResolved = true\n }\n }\n break\n }\n // Non-matching chars are silently skipped (iterative, no recursion).\n }\n\n if (!found) break\n }\n\n // If every matched char was before the input caret (or no chars matched at\n // all past the caret), place the output caret at the end of the output.\n if (!caretResolved) outputCaret = output.length\n\n return { value: output, caret: outputCaret }\n}\n","import type { MaskPattern } from './types'\n\n/**\n * Count alphanumeric characters in a value.\n *\n * Used by `resolveMask` so that both raw input (e.g. \"11999887766\") and\n * already-masked values (e.g. \"(11) 99988-7766\") produce the same data-char\n * count, enabling correct mask selection even when all keystrokes arrive\n * before the first rAF fires (fast typing).\n */\nfunction countDataChars(value: string): number {\n let n = 0\n for (const ch of value) {\n if (\n (ch >= '0' && ch <= '9') ||\n (ch >= 'a' && ch <= 'z') ||\n (ch >= 'A' && ch <= 'Z')\n )\n n++\n }\n return n\n}\n\n/** Count input slots (9, Z, A) in a mask string. */\nfunction countMaskSlots(mask: string): number {\n let n = 0\n for (const ch of mask) {\n if (ch === '9' || ch === 'Z' || ch === 'A') n++\n }\n return n\n}\n\n/**\n * Select the right mask string for the current value.\n *\n * Compares the number of alphanumeric data characters in `value` to the\n * slot capacity of each candidate mask. This correctly handles both\n * progressively-masked values (normal typing) and raw accumulated characters\n * (fast typing where multiple keystrokes arrive before any rAF fires).\n */\nexport function resolveMask(value: string, mask: MaskPattern): string {\n if (!Array.isArray(mask)) return mask\n const dataCount = countDataChars(value)\n let i = 0\n while (i < mask.length - 1 && dataCount > countMaskSlots(mask[i])) i++\n return mask[i]\n}\n\n/** Maximum allowed input length for the given mask. */\nexport function getMaxLength(mask: MaskPattern): number {\n if (Array.isArray(mask)) {\n return mask.length > 0 ? Math.max(...mask.map((m) => m.length)) : 0\n }\n return mask.length\n}\n","import { applyMask } from './apply-mask'\nimport { resolveMask } from './pattern'\nimport type { MaskPattern } from './types'\n\nexport { getMaxLength } from './pattern'\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** Low-level mask processor. Tracks caret position after masking. */\nexport class Mask {\n /** Caret position after `process()` runs. */\n caret: number\n\n private readonly _value: string\n private readonly _mask: string\n\n constructor(value: string, mask: string, caret = 0) {\n this._value = value\n this._mask = mask\n this.caret = caret\n }\n\n /** Apply the mask to the value and return the masked string. */\n process(): string {\n const result = applyMask(this._value, this._mask, this.caret)\n this.caret = result.caret\n return result.value\n }\n}\n\n/** Build a `Mask` instance, resolving array patterns by value length. */\nexport function buildMask(value: string, mask: MaskPattern, caret = 0): Mask {\n return new Mask(value, resolveMask(value, mask), caret)\n}\n\n/** Apply a mask pattern to a raw value string and return the masked result. */\nexport function process(value: string, mask: MaskPattern): string {\n return buildMask(value, mask).process()\n}\n","let cachedIsIos: boolean | undefined\n\n/** True when the runtime looks like iOS Safari / WebKit (affects key event choice in {@link bind}). */\nexport function isIos(): boolean {\n if (cachedIsIos !== undefined) return cachedIsIos\n cachedIsIos =\n typeof navigator !== 'undefined' && /iPad|iPhone|iPod/i.test(navigator.userAgent)\n return cachedIsIos\n}\n","import { buildMask, getMaxLength } from './mask'\nimport { isIos } from './platform'\nimport type { BindOptions, MaskPattern } from './types'\n\nconst MASKED_ATTR = 'data-masked'\n\nfunction toBindOptions(\n third: BindOptions | ((value: string) => void) | null | undefined,\n): BindOptions {\n if (third == null) return {}\n if (typeof third === 'function') return { onChange: third }\n return third\n}\n\n/**\n * Bind a mask pattern to an input element.\n *\n * Idempotent — calling `bind()` on an already-bound element has no effect.\n * The element receives a `data-masked` attribute marking it as bound.\n *\n * Returns a function that removes listeners and clears `data-masked` so the\n * element can be bound again later.\n *\n * @param input - Any `HTMLInputElement` or `Element` that behaves like one.\n * @param mask - A single pattern string or an ordered array (shortest → longest).\n * @param options - Optional `{ onChange }`, or pass a callback (legacy) as the third argument.\n */\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n options?: BindOptions | null,\n): () => void\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n onChange: ((value: string) => void) | null,\n): () => void\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n third?: BindOptions | ((value: string) => void) | null,\n): () => void {\n if (input.getAttribute(MASKED_ATTR) !== null) return () => {}\n\n const { onChange } = toBindOptions(third)\n\n /** Attribute names set by this bind call; removed on dispose so a later `bind()` can re-apply. */\n const attrsSetHere: string[] = []\n const setIfMissing = (name: string, value: string): void => {\n if (!input.hasAttribute(name)) {\n input.setAttribute(name, value)\n attrsSetHere.push(name)\n }\n }\n\n input.setAttribute(MASKED_ATTR, Array.isArray(mask) ? mask.join('|') : mask)\n setIfMissing('autocomplete', 'off')\n setIfMissing('autocorrect', 'off')\n setIfMissing('autocapitalize', 'off')\n setIfMissing('spellcheck', 'false')\n setIfMissing('maxlength', String(getMaxLength(mask)))\n\n let lockInput = false\n\n const keyEventName = isIos() ? 'keyup' : 'keydown'\n\n const onPaste = (e: Event): void => {\n const target = e.target as HTMLInputElement\n requestAnimationFrame(() => {\n const m = buildMask(target.value, mask)\n target.value = m.process()\n onChange?.(target.value)\n })\n }\n\n const onKey = (e: Event): void => {\n const ke = e as KeyboardEvent\n const target = ke.target as HTMLInputElement\n const oldValue = target.value\n\n // Older Android WebViews may fire key events without a `key` value.\n if (!(ke as { key?: string }).key) {\n lockInput = true\n requestAnimationFrame(() => {\n const pos = target.selectionStart ?? 999\n const m = buildMask(target.value, mask, pos)\n target.value = m.process()\n target.setSelectionRange(m.caret, m.caret)\n requestAnimationFrame(() => {\n lockInput = false\n })\n })\n return\n }\n\n if (ke.key === 'Meta') return\n\n const isBackspace = ke.key === 'Backspace'\n const isDelete = ke.key === 'Delete'\n const isCharInsert = ke.key.length === 1 && !ke.ctrlKey && !ke.altKey && !ke.metaKey\n const isUnidentified = ke.key === 'Unidentified'\n\n // Block inserting when mask is full (desktop only — iOS handles this natively)\n if (isCharInsert && target.selectionStart === target.selectionEnd) {\n if (oldValue.length >= getMaxLength(mask) && !isIos()) {\n ke.preventDefault()\n return\n }\n }\n\n // `lockInput` is only set by the Android WebView path above; block normal\n // key events while that asynchronous path is in flight.\n if (lockInput) {\n ke.preventDefault()\n return\n }\n\n requestAnimationFrame(() => {\n const pos = target.selectionStart ?? 999\n const m = buildMask(target.value, mask, pos)\n target.value = m.process()\n\n if (isUnidentified) {\n const newPos = target.value.length > oldValue.length ? m.caret : pos\n target.setSelectionRange(newPos, newPos)\n } else if (isDelete) {\n const newPos = oldValue.length === target.value.length ? pos + 1 : pos\n target.setSelectionRange(newPos, newPos)\n } else if (isBackspace) {\n target.setSelectionRange(pos, pos)\n } else if (isCharInsert) {\n target.setSelectionRange(m.caret, m.caret)\n }\n\n onChange?.(target.value)\n })\n }\n\n input.addEventListener('paste', onPaste)\n input.addEventListener(keyEventName, onKey)\n\n return () => {\n input.removeEventListener('paste', onPaste)\n input.removeEventListener(keyEventName, onKey)\n input.removeAttribute(MASKED_ATTR)\n for (const name of attrsSetHere) input.removeAttribute(name)\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mother-mask.umd.js","names":["isDigitChar","MASKED_ATTR"],"sources":["../src/apply-mask.ts","../src/pattern.ts","../src/mask.ts","../src/platform.ts","../src/bind.ts","../src/decimal-mask.ts","../src/bind-decimal.ts"],"sourcesContent":["import type { ApplyMaskOptions, MaskResult } from './types'\n\n// ---------------------------------------------------------------------------\n// Character classification (no regex — avoids the empty-string pitfall)\n// ---------------------------------------------------------------------------\n\nfunction isDigitChar(ch: string): boolean {\n return ch >= '0' && ch <= '9'\n}\n\nfunction isLetterChar(ch: string): boolean {\n return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')\n}\n\nfunction isSlotChar(ch: string): boolean {\n return ch === '9' || ch === 'Z' || ch === 'A'\n}\n\nfunction matchesSlot(ch: string, slot: string): boolean {\n if (slot === '9') return isDigitChar(ch)\n if (slot === 'Z') return isLetterChar(ch)\n // slot === 'A' → alphanumeric\n return isDigitChar(ch) || isLetterChar(ch)\n}\n\n// ---------------------------------------------------------------------------\n// Flat masking (default) — treats the mask as one continuous character\n// stream. Best for continuous identifiers (phone numbers, CPF/CNPJ, credit\n// cards) where deleting/inserting a digit anywhere is expected to reflow\n// every digit after it — this is the classic mother-mask behavior and is\n// relied on by the majority of the test suite (paste, backspace, mid-string\n// insert, etc).\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a single mask string to a value, producing the masked output and\n * a computed caret position.\n *\n * **Caret algorithm**: as the mask consumes characters from `value`, every\n * time a *matching* input character at a position *before* `inputCaret` is\n * written to the output (including any preceding pending literals that were\n * just flushed), the output caret is updated to the current output length.\n * This correctly handles literal insertion, middle-of-string edits, and\n * characters that are skipped because they don't match the current slot.\n */\nfunction applyFlatMask(value: string, mask: string, inputCaret: number): MaskResult {\n let output = ''\n let pending = ''\n let valueIdx = 0\n let outputCaret = 0\n let caretResolved = false\n\n for (let maskIdx = 0; maskIdx < mask.length; maskIdx++) {\n const maskCh = mask[maskIdx]\n\n if (maskCh !== '9' && maskCh !== 'Z' && maskCh !== 'A') {\n pending += maskCh\n continue\n }\n\n // Find next value character that matches this slot\n let found = false\n while (valueIdx < value.length) {\n const ch = value[valueIdx++]\n\n if (matchesSlot(ch, maskCh)) {\n // Flush pending literals then write the matched char\n output += pending + ch\n pending = ''\n found = true\n\n // Caret tracking: if this consumed char was before the input caret,\n // the output caret is (at least) at the current output length.\n if (!caretResolved) {\n if (valueIdx <= inputCaret) {\n outputCaret = output.length\n } else {\n caretResolved = true\n }\n }\n break\n }\n // Non-matching chars are silently skipped (iterative, no recursion).\n }\n\n if (!found) break\n }\n\n // If every matched char was before the input caret (or no chars matched at\n // all past the caret), place the output caret at the end of the output.\n if (!caretResolved) outputCaret = output.length\n\n return { value: output, caret: outputCaret }\n}\n\n// ---------------------------------------------------------------------------\n// Segmented masking (opt-in) — treats literal separators as hard boundaries\n// between independent fields (e.g. day/month/year in \"99/99/9999\"). Editing\n// one segment never bleeds characters into a neighboring one, so replacing\n// the \"12\" in \"25/12/2025\" with a shorter or longer value keeps the year\n// exactly where it is instead of shifting digits across the \"/\".\n// ---------------------------------------------------------------------------\n\ntype MaskToken = { kind: 'literal'; text: string } | { kind: 'slots'; chars: string }\n\n/**\n * Split a mask into alternating literal and slot-run tokens (e.g. \"99/99/9999\"\n * → slots\"99\", literal\"/\", slots\"99\", literal\"/\", slots\"9999\"), so the masking\n * pass can reason about segment boundaries instead of a flat character stream.\n */\nfunction tokenizeMask(mask: string): MaskToken[] {\n const tokens: MaskToken[] = []\n let i = 0\n while (i < mask.length) {\n const start = i\n const wantSlots = isSlotChar(mask[i])\n while (i < mask.length && isSlotChar(mask[i]) === wantSlots) i++\n const text = mask.slice(start, i)\n tokens.push(wantSlots ? { kind: 'slots', chars: text } : { kind: 'literal', text })\n }\n return tokens\n}\n\n/** Total slot capacity from token index `from` (inclusive) to the end of `tokens`. */\nfunction slotCapacityFrom(tokens: MaskToken[], from: number): number {\n let capacity = 0\n for (let i = from; i < tokens.length; i++) {\n const token = tokens[i]\n if (token.kind === 'slots') capacity += token.chars.length\n }\n return capacity\n}\n\n/** Count of remaining slot-matchable (digit/letter) characters in `value` from `fromIdx` onward. */\nfunction remainingDataChars(value: string, fromIdx: number): number {\n let count = 0\n for (let i = fromIdx; i < value.length; i++) {\n const ch = value[i]\n if (isDigitChar(ch) || isLetterChar(ch)) count++\n }\n return count\n}\n\n/**\n * Same contract as {@link applyFlatMask}, but walks the mask one *segment* at\n * a time (a run of slots, or a literal) rather than one character at a time.\n * The rule that keeps an edit inside a segment from crossing into its\n * neighbor is an **early stop**: if the value hits the literal that ends the\n * current slot run before all of that run's slots are filled (e.g. only one\n * digit typed into a two-digit month slot), the run is left partially filled\n * instead of skipping past the separator to steal a digit from the next\n * segment.\n *\n * That stop is only taken when it's actually safe — i.e. every character\n * still to come in `value` fits in the slot capacity that remains *after*\n * this segment. If stopping here would strand more data than the rest of\n * the mask can hold (e.g. pasting into a later segment while an earlier one\n * still sits under-filled from before), the \"separator\" is treated as stray\n * noise instead and skipped, letting this segment take the extra slot it\n * needs so nothing at the end gets silently dropped. This is also what lets\n * an array mask grow or shrink its pattern (moving every literal after the\n * change point) reflow correctly instead of losing a digit at the boundary.\n */\nfunction applySegmentedMask(value: string, mask: string, inputCaret: number): MaskResult {\n const tokens = tokenizeMask(mask)\n\n let output = ''\n let pending = ''\n let valueIdx = 0\n let outputCaret = 0\n let caretResolved = false\n let exhausted = false\n\n for (let t = 0; t < tokens.length && !exhausted; t++) {\n const token = tokens[t]\n\n if (token.kind === 'literal') {\n pending += token.text\n if (value.startsWith(token.text, valueIdx)) valueIdx += token.text.length\n continue\n }\n\n const nextToken = tokens[t + 1]\n const capacityAfter = slotCapacityFrom(tokens, t + 1)\n\n for (let s = 0; s < token.chars.length; s++) {\n const slotCh = token.chars[s]\n let found = false\n\n while (valueIdx < value.length) {\n const ch = value[valueIdx]\n\n if (matchesSlot(ch, slotCh)) {\n valueIdx++\n output += pending + ch\n pending = ''\n found = true\n\n if (!caretResolved) {\n if (valueIdx <= inputCaret) {\n outputCaret = output.length\n } else {\n caretResolved = true\n }\n }\n break\n }\n\n // This segment's ending separator showed up before the run filled.\n // Stopping here is only safe if the rest of the value still fits in\n // whatever slot capacity remains after this segment — otherwise\n // stopping would strand data, so fall through and skip this char as\n // noise instead, letting the segment take the slot it needs.\n if (\n nextToken?.kind === 'literal' &&\n value.startsWith(nextToken.text, valueIdx) &&\n remainingDataChars(value, valueIdx) <= capacityAfter\n ) {\n break\n }\n\n valueIdx++ // stray/noise char — skip it\n }\n\n if (!found) {\n if (valueIdx >= value.length) exhausted = true\n break\n }\n }\n }\n\n if (!caretResolved) outputCaret = output.length\n\n return { value: output, caret: outputCaret }\n}\n\n// ---------------------------------------------------------------------------\n// Public entry point\n// ---------------------------------------------------------------------------\n\nexport function applyMask(\n value: string,\n mask: string,\n inputCaret = 0,\n options?: ApplyMaskOptions,\n): MaskResult {\n if (!value) return { value: '', caret: 0 }\n return options?.segmented === false\n ? applyFlatMask(value, mask, inputCaret)\n : applySegmentedMask(value, mask, inputCaret)\n}\n","import type { MaskPattern } from './types'\n\n/**\n * Count alphanumeric characters in a value.\n *\n * Used by `resolveMask` so that both raw input (e.g. \"11999887766\") and\n * already-masked values (e.g. \"(11) 99988-7766\") produce the same data-char\n * count, enabling correct mask selection even when all keystrokes arrive\n * before the first rAF fires (fast typing).\n */\nfunction countDataChars(value: string): number {\n let n = 0\n for (const ch of value) {\n if (\n (ch >= '0' && ch <= '9') ||\n (ch >= 'a' && ch <= 'z') ||\n (ch >= 'A' && ch <= 'Z')\n )\n n++\n }\n return n\n}\n\n/** Count input slots (9, Z, A) in a mask string. */\nfunction countMaskSlots(mask: string): number {\n let n = 0\n for (const ch of mask) {\n if (ch === '9' || ch === 'Z' || ch === 'A') n++\n }\n return n\n}\n\n/**\n * Select the right mask string for the current value.\n *\n * Compares the number of alphanumeric data characters in `value` to the\n * slot capacity of each candidate mask. This correctly handles both\n * progressively-masked values (normal typing) and raw accumulated characters\n * (fast typing where multiple keystrokes arrive before any rAF fires).\n */\nexport function resolveMask(value: string, mask: MaskPattern): string {\n if (!Array.isArray(mask)) return mask\n const dataCount = countDataChars(value)\n let i = 0\n while (i < mask.length - 1 && dataCount > countMaskSlots(mask[i])) i++\n return mask[i]\n}\n\n/** Maximum allowed input length for the given mask. */\nexport function getMaxLength(mask: MaskPattern): number {\n if (Array.isArray(mask)) {\n return mask.length > 0 ? Math.max(...mask.map((m) => m.length)) : 0\n }\n return mask.length\n}\n","import { applyMask } from './apply-mask'\nimport { resolveMask } from './pattern'\nimport type { ApplyMaskOptions, MaskPattern } from './types'\n\nexport { getMaxLength } from './pattern'\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** Low-level mask processor. Tracks caret position after masking. */\nexport class Mask {\n /** Caret position after `process()` runs. */\n caret: number\n\n private readonly _value: string\n private readonly _mask: string\n private readonly _options: ApplyMaskOptions | undefined\n\n constructor(value: string, mask: string, caret = 0, options?: ApplyMaskOptions) {\n this._value = value\n this._mask = mask\n this.caret = caret\n this._options = options\n }\n\n /** Apply the mask to the value and return the masked string. */\n process(): string {\n const result = applyMask(this._value, this._mask, this.caret, this._options)\n this.caret = result.caret\n return result.value\n }\n}\n\n/** Build a `Mask` instance, resolving array patterns by value length. */\nexport function buildMask(\n value: string,\n mask: MaskPattern,\n caret = 0,\n options?: ApplyMaskOptions,\n): Mask {\n return new Mask(value, resolveMask(value, mask), caret, options)\n}\n\n/** Apply a mask pattern to a raw value string and return the masked result. */\nexport function process(value: string, mask: MaskPattern, options?: ApplyMaskOptions): string {\n return buildMask(value, mask, 0, options).process()\n}\n","let cachedIsIos: boolean | undefined\n\n/** True when the runtime looks like iOS Safari / WebKit (affects key event choice in {@link bind}). */\nexport function isIos(): boolean {\n if (cachedIsIos !== undefined) return cachedIsIos\n cachedIsIos =\n typeof navigator !== 'undefined' && /iPad|iPhone|iPod/i.test(navigator.userAgent)\n return cachedIsIos\n}\n","import { buildMask, getMaxLength } from './mask'\nimport { isIos } from './platform'\nimport type { BindOptions, MaskPattern } from './types'\n\nconst MASKED_ATTR = 'data-masked'\n\nfunction toBindOptions(\n third: BindOptions | ((value: string) => void) | null | undefined,\n): BindOptions {\n if (third == null) return {}\n if (typeof third === 'function') return { onChange: third }\n return third\n}\n\n/**\n * Bind a mask pattern to an input element.\n *\n * Idempotent — calling `bind()` on an already-bound element has no effect.\n * The element receives a `data-masked` attribute marking it as bound.\n *\n * Returns a function that removes listeners and clears `data-masked` so the\n * element can be bound again later.\n *\n * @param input - Any `HTMLInputElement` or `Element` that behaves like one.\n * @param mask - A single pattern string or an ordered array (shortest → longest).\n * @param options - Optional `{ onChange }`, or pass a callback (legacy) as the third argument.\n */\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n options?: BindOptions | null,\n): () => void\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n onChange: ((value: string) => void) | null,\n): () => void\nexport function bind(\n input: HTMLInputElement | Element,\n mask: MaskPattern,\n third?: BindOptions | ((value: string) => void) | null,\n): () => void {\n if (input.getAttribute(MASKED_ATTR) !== null) return () => {}\n\n const { onChange, segmented } = toBindOptions(third)\n\n /** Attribute names set by this bind call; removed on dispose so a later `bind()` can re-apply. */\n const attrsSetHere: string[] = []\n const setIfMissing = (name: string, value: string): void => {\n if (!input.hasAttribute(name)) {\n input.setAttribute(name, value)\n attrsSetHere.push(name)\n }\n }\n\n input.setAttribute(MASKED_ATTR, Array.isArray(mask) ? mask.join('|') : mask)\n setIfMissing('autocomplete', 'off')\n setIfMissing('autocorrect', 'off')\n setIfMissing('autocapitalize', 'off')\n setIfMissing('spellcheck', 'false')\n setIfMissing('maxlength', String(getMaxLength(mask)))\n\n let lockInput = false\n\n const keyEventName = isIos() ? 'keyup' : 'keydown'\n\n const onPaste = (e: Event): void => {\n const target = e.target as HTMLInputElement\n requestAnimationFrame(() => {\n const m = buildMask(target.value, mask, 0, { segmented })\n target.value = m.process()\n onChange?.(target.value)\n })\n }\n\n const onKey = (e: Event): void => {\n const ke = e as KeyboardEvent\n const target = ke.target as HTMLInputElement\n const oldValue = target.value\n\n // Older Android WebViews may fire key events without a `key` value.\n if (!(ke as { key?: string }).key) {\n lockInput = true\n requestAnimationFrame(() => {\n const pos = target.selectionStart ?? 999\n const m = buildMask(target.value, mask, pos, { segmented })\n target.value = m.process()\n target.setSelectionRange(m.caret, m.caret)\n requestAnimationFrame(() => {\n lockInput = false\n })\n })\n return\n }\n\n if (ke.key === 'Meta') return\n\n const isBackspace = ke.key === 'Backspace'\n const isDelete = ke.key === 'Delete'\n const isCharInsert = ke.key.length === 1 && !ke.ctrlKey && !ke.altKey && !ke.metaKey\n const isUnidentified = ke.key === 'Unidentified'\n\n // Block inserting when mask is full (desktop only — iOS handles this natively)\n if (isCharInsert && target.selectionStart === target.selectionEnd) {\n if (oldValue.length >= getMaxLength(mask) && !isIos()) {\n ke.preventDefault()\n return\n }\n }\n\n // `lockInput` is only set by the Android WebView path above; block normal\n // key events while that asynchronous path is in flight.\n if (lockInput) {\n ke.preventDefault()\n return\n }\n\n requestAnimationFrame(() => {\n const pos = target.selectionStart ?? 999\n const m = buildMask(target.value, mask, pos, { segmented })\n target.value = m.process()\n\n if (isUnidentified) {\n const newPos = target.value.length > oldValue.length ? m.caret : pos\n target.setSelectionRange(newPos, newPos)\n } else if (isDelete) {\n const newPos = oldValue.length === target.value.length ? pos + 1 : pos\n target.setSelectionRange(newPos, newPos)\n } else if (isBackspace) {\n target.setSelectionRange(pos, pos)\n } else if (isCharInsert) {\n target.setSelectionRange(m.caret, m.caret)\n }\n\n onChange?.(target.value)\n })\n }\n\n input.addEventListener('paste', onPaste)\n input.addEventListener(keyEventName, onKey)\n\n return () => {\n input.removeEventListener('paste', onPaste)\n input.removeEventListener(keyEventName, onKey)\n input.removeAttribute(MASKED_ATTR)\n for (const name of attrsSetHere) input.removeAttribute(name)\n }\n}\n","import type { DecimalMaskOptions, MaskResult } from './types'\n\n// ---------------------------------------------------------------------------\n// Character classification (no regex — mirrors apply-mask.ts)\n// ---------------------------------------------------------------------------\n\nfunction isDigitChar(ch: string): boolean {\n return ch >= '0' && ch <= '9'\n}\n\n// ---------------------------------------------------------------------------\n// Option resolution\n// ---------------------------------------------------------------------------\n\ninterface ResolvedDecimalOptions {\n decimalPlaces: number\n segmented: boolean\n separator: string\n decimalSeparator: string\n prefix: string\n suffix: string\n allowNegative: boolean\n}\n\n/** @internal exported for {@link bindDecimal}'s \".\" / \",\" key normalization */\nexport function resolveDecimalOptions(options?: DecimalMaskOptions): ResolvedDecimalOptions {\n const rawPlaces = options?.decimalPlaces ?? 2\n const decimalPlaces = Number.isFinite(rawPlaces) ? Math.max(0, Math.floor(rawPlaces)) : 2\n return {\n decimalPlaces,\n segmented: options?.segmented ?? true,\n separator: options?.separator ?? ',',\n decimalSeparator: options?.decimalSeparator ?? '.',\n prefix: options?.prefix ?? '',\n suffix: options?.suffix ?? '',\n allowNegative: options?.allowNegative ?? false,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Digit-stream helpers\n// ---------------------------------------------------------------------------\n\n/** Strip leading zeros from a digit string, always keeping at least one digit. */\nfunction stripLeadingZeros(s: string): string {\n let i = 0\n while (i < s.length - 1 && s[i] === '0') i++\n return s.slice(i)\n}\n\n/** Insert `sep` every 3 digits from the right (e.g. \"1234567\" → \"1,234,567\"). */\nfunction groupThousands(s: string, sep: string): string {\n if (!sep || s.length <= 3) return s\n const parts: string[] = []\n let i = s.length\n while (i > 3) {\n parts.unshift(s.slice(i - 3, i))\n i -= 3\n }\n parts.unshift(s.slice(0, i))\n return parts.join(sep)\n}\n\n/**\n * Find the position in `s` that leaves exactly `digitsBefore` digit\n * characters preceding it — the position immediately after that digit and\n * before any subsequent literal (grouping separator, ...), so the caret\n * stays glued to the last digit the user placed there.\n */\nfunction caretForDigitsBefore(s: string, digitsBefore: number): number {\n if (digitsBefore <= 0) return 0\n let count = 0\n for (let i = 0; i < s.length; i++) {\n if (isDigitChar(s[i])) {\n count++\n if (count === digitsBefore) return i + 1\n }\n }\n return s.length\n}\n\n// ---------------------------------------------------------------------------\n// Segmented parsing — integer digits before the decimal separator, fraction\n// digits after. Unlike a slot-pattern mask, the integer segment has no fixed\n// length; only the fraction is fixed-width (`decimalPlaces`), zero-padded on\n// the right so a shorter fraction reads as its low-order (trailing) digits\n// being zero rather than reflowing/shifting — e.g. editing \"423,42\" down to\n// \"423,4\" produces \"423,40\", not \"42,34\".\n//\n// The decimal separator only has meaning as the *first* occurrence of\n// `opts.decimalSeparator`; every other non-digit character (thousands\n// separator, prefix/suffix text, a second stray separator, ...) is noise and\n// is dropped. This keeps re-parsing an already-masked value idempotent.\n// ---------------------------------------------------------------------------\n\ninterface DecimalParts {\n isNegative: boolean\n intDigits: string\n fracDigits: string\n hasSeparator: boolean\n}\n\nfunction computeDecimalParts(raw: string, opts: ResolvedDecimalOptions): DecimalParts {\n let intDigits = ''\n let fracDigits = ''\n let isNegative = false\n let inFraction = false\n const canHaveFraction = opts.decimalPlaces > 0\n\n for (const ch of raw) {\n if (isDigitChar(ch)) {\n if (inFraction) {\n if (fracDigits.length < opts.decimalPlaces) fracDigits += ch\n } else {\n intDigits += ch\n }\n continue\n }\n if (canHaveFraction && !inFraction && ch === opts.decimalSeparator) {\n inFraction = true\n continue\n }\n if (ch === '-' && opts.allowNegative) isNegative = true\n // Anything else — thousands separator, prefix/suffix text, a repeated\n // separator, stray letters — is noise and is dropped.\n }\n\n return { isNegative, intDigits, fracDigits, hasSeparator: inFraction }\n}\n\n/**\n * Walk `raw[0:caret]` to find which segment the caret sits in (integer or\n * fraction) and how many digits of that segment precede it, so the same\n * position can be re-derived in the freshly formatted output.\n */\nfunction locateCaretSegment(\n raw: string,\n caret: number,\n opts: ResolvedDecimalOptions,\n): { inFraction: boolean; digitsBefore: number } {\n const canHaveFraction = opts.decimalPlaces > 0\n let inFraction = false\n let digitsBefore = 0\n\n for (let i = 0; i < caret; i++) {\n const ch = raw[i]\n if (isDigitChar(ch)) {\n if (!inFraction || digitsBefore < opts.decimalPlaces) digitsBefore++\n continue\n }\n if (canHaveFraction && !inFraction && ch === opts.decimalSeparator) {\n inFraction = true\n digitsBefore = 0\n }\n }\n\n return { inFraction, digitsBefore }\n}\n\n// ---------------------------------------------------------------------------\n// Public entry points\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a decimal/currency mask to a value, producing the masked output and\n * a computed caret position. Digits typed before the decimal separator\n * extend the integer part; the fraction only starts once the separator is\n * typed, and is always displayed zero-padded to `decimalPlaces` width.\n */\nexport function applyDecimalMask(\n value: string,\n inputCaret = 0,\n options?: DecimalMaskOptions,\n): MaskResult {\n const opts = resolveDecimalOptions(options)\n if (!value) return { value: '', caret: 0 }\n\n const { isNegative, intDigits, fracDigits, hasSeparator } = computeDecimalParts(value, opts)\n if (intDigits === '' && fracDigits === '' && !hasSeparator) return { value: '', caret: 0 }\n\n const intPart = stripLeadingZeros(intDigits || '0')\n const groupedInt = opts.segmented ? groupThousands(intPart, opts.separator) : intPart\n const fracPadded = opts.decimalPlaces > 0 ? fracDigits.padEnd(opts.decimalPlaces, '0') : ''\n const numberStr = groupedInt + (opts.decimalPlaces > 0 ? opts.decimalSeparator + fracPadded : '')\n const signStr = isNegative ? '-' : ''\n const output = signStr + opts.prefix + numberStr + opts.suffix\n\n const clampedCaret = Math.max(0, Math.min(inputCaret, value.length))\n const { inFraction, digitsBefore } = locateCaretSegment(value, clampedCaret, opts)\n const prefixLen = signStr.length + opts.prefix.length\n const caret = inFraction\n ? prefixLen + groupedInt.length + opts.decimalSeparator.length + digitsBefore\n : prefixLen + caretForDigitsBefore(groupedInt, digitsBefore)\n\n return { value: output, caret }\n}\n\n/** Apply a decimal mask to a raw value and return just the masked string. */\nexport function processDecimal(value: string, options?: DecimalMaskOptions): string {\n return applyDecimalMask(value, value.length, options).value\n}\n\n/**\n * Parse a raw or already-masked decimal value back into a JS number.\n * Ignores prefix/suffix/thousands separator; returns `0` for an empty or\n * digit-less value.\n */\nexport function unmaskDecimal(value: string, options?: DecimalMaskOptions): number {\n const opts = resolveDecimalOptions(options)\n const { isNegative, intDigits, fracDigits } = computeDecimalParts(value, opts)\n const n = Number(fracDigits ? `${intDigits || '0'}.${fracDigits}` : intDigits || '0')\n return isNegative ? -n : n\n}\n\n/**\n * After a Backspace removes the decimal separator itself, the integer and\n * fraction digit runs collapse into one continuous stream (e.g. \"25.00\"\n * with the caret right after \".\" → Backspace deletes the \".\" → \"2500\").\n * Left alone, that reads as one big integer (\"$2,500.00\"). This restores\n * the segment boundary instead: the trailing `decimalPlaces` digits are\n * still the fraction, and the digit right before them — the one that used\n * to sit at the end of the integer part — is the one Backspace actually\n * removed, so it's dropped (not kept) — \"$25.00\" → \"$2.00\".\n *\n * Every reformat re-appends `decimalSeparator` whenever `decimalPlaces > 0`,\n * so its absence from `value` is an unambiguous signal that this exact\n * keystroke just deleted it — no \"value before this keystroke\" snapshot is\n * needed. Returns `null` when there's nothing to restore (the separator is\n * still present, `decimalPlaces` is `0`, or too few digits remain), so the\n * caller falls through to a plain {@link applyDecimalMask} call.\n */\nexport function applyDecimalMaskUnmergingSeparator(\n value: string,\n options?: DecimalMaskOptions,\n): MaskResult | null {\n const opts = resolveDecimalOptions(options)\n if (opts.decimalPlaces <= 0) return null\n\n const { isNegative, intDigits, hasSeparator } = computeDecimalParts(value, opts)\n if (hasSeparator || intDigits.length < opts.decimalPlaces + 1) return null\n\n const preMergeIntLength = intDigits.length - opts.decimalPlaces\n const fracDigits = intDigits.slice(preMergeIntLength)\n const remainingInt = intDigits.slice(0, preMergeIntLength - 1)\n\n const signPart = isNegative ? '-' : ''\n const raw = signPart + remainingInt + opts.decimalSeparator + fracDigits\n return applyDecimalMask(raw, signPart.length + remainingInt.length, opts)\n}\n\n/**\n * Special-cases typing a single digit into a field whose integer part is\n * exactly the auto-inserted \"0\" placeholder: the new digit replaces that\n * zero instead of combining with it — e.g. \"$0.00\" with the caret anywhere\n * against that lone \"0\" and typing \"2\" gives \"$2.00\", not \"$20.00\"/\"$02.00\".\n * Any already-typed fraction is preserved.\n *\n * `value`/`caret` must be the state *after* the browser has already\n * inserted `digit` at `caret - 1` (the same post-insertion snapshot\n * `applyDecimalMask` itself expects from `bindDecimal`). Returns `null`\n * when the pattern doesn't apply — either the integer part has real digits\n * already, or the caret wasn't against that lone zero — so the caller falls\n * through to a plain {@link applyDecimalMask} call.\n */\nexport function applyDecimalMaskReplacingLoneZero(\n value: string,\n caret: number,\n digit: string,\n options?: DecimalMaskOptions,\n): MaskResult | null {\n const opts = resolveDecimalOptions(options)\n const insertIdx = caret - 1\n if (insertIdx < 0 || value[insertIdx] !== digit) return null\n\n const withoutDigit = value.slice(0, insertIdx) + value.slice(caret)\n const { isNegative, intDigits, fracDigits, hasSeparator } = computeDecimalParts(withoutDigit, opts)\n if (intDigits !== '0') return null\n\n const prefixLen = (isNegative ? 1 : 0) + opts.prefix.length\n if (insertIdx < prefixLen || insertIdx > prefixLen + 1) return null\n\n const signPart = isNegative ? '-' : ''\n const raw = signPart + digit + (hasSeparator ? opts.decimalSeparator + fracDigits : '')\n return applyDecimalMask(raw, signPart.length + 1, opts)\n}\n\n/** Format a plain JS number into its masked display string. */\nexport function formatDecimalValue(value: number, options?: DecimalMaskOptions): string {\n const opts = resolveDecimalOptions(options)\n if (!Number.isFinite(value)) return ''\n\n const isNegative = opts.allowNegative && value < 0\n const fixed = Math.abs(value).toFixed(opts.decimalPlaces)\n const dotIdx = fixed.indexOf('.')\n const intRaw = dotIdx === -1 ? fixed : fixed.slice(0, dotIdx)\n const fracPart = dotIdx === -1 ? '' : fixed.slice(dotIdx + 1)\n const intPart = stripLeadingZeros(intRaw || '0')\n\n const groupedInt = opts.segmented ? groupThousands(intPart, opts.separator) : intPart\n const numberStr = groupedInt + (opts.decimalPlaces > 0 ? opts.decimalSeparator + fracPart : '')\n\n return (isNegative ? '-' : '') + opts.prefix + numberStr + opts.suffix\n}\n","import {\n applyDecimalMask,\n applyDecimalMaskReplacingLoneZero,\n applyDecimalMaskUnmergingSeparator,\n resolveDecimalOptions,\n unmaskDecimal,\n} from './decimal-mask'\nimport { isIos } from './platform'\nimport type { BindDecimalOptions, DecimalMaskOptions, MaskResult } from './types'\n\nconst MASKED_ATTR = 'data-masked'\n\nfunction toBindDecimalOptions(\n second:\n | BindDecimalOptions\n | ((value: string, numericValue: number) => void)\n | null\n | undefined,\n): BindDecimalOptions {\n if (second == null) return {}\n if (typeof second === 'function') return { onChange: second }\n return second\n}\n\nfunction isDigitKey(key: string): boolean {\n return key.length === 1 && key >= '0' && key <= '9'\n}\n\n/**\n * Bind a decimal/currency mask to an input element.\n *\n * Same contract as {@link bind}: idempotent (marked with `data-masked`),\n * returns a dispose function, and reformats on paste and keyboard-driven\n * changes via `requestAnimationFrame`. Unlike the pattern masks, there is no\n * fixed pattern — the integer part grows and shrinks freely; formatting is\n * driven entirely by `options`.\n *\n * @param input - Any `HTMLInputElement` or `Element` that behaves like one.\n * @param options - `DecimalMaskOptions` plus an optional `onChange`, or pass a callback (legacy) directly.\n */\nexport function bindDecimal(\n input: HTMLInputElement | Element,\n options?: BindDecimalOptions | null,\n): () => void\nexport function bindDecimal(\n input: HTMLInputElement | Element,\n onChange: ((value: string, numericValue: number) => void) | null,\n): () => void\nexport function bindDecimal(\n input: HTMLInputElement | Element,\n second?: BindDecimalOptions | ((value: string, numericValue: number) => void) | null,\n): () => void {\n if (input.getAttribute(MASKED_ATTR) !== null) return () => {}\n\n const { onChange, ...maskOptions } = toBindDecimalOptions(second)\n const decimalOptions: DecimalMaskOptions = maskOptions\n const { decimalSeparator, decimalPlaces } = resolveDecimalOptions(decimalOptions)\n\n const attrsSetHere: string[] = []\n const setIfMissing = (name: string, value: string): void => {\n if (!input.hasAttribute(name)) {\n input.setAttribute(name, value)\n attrsSetHere.push(name)\n }\n }\n\n input.setAttribute(MASKED_ATTR, 'decimal')\n setIfMissing('autocomplete', 'off')\n setIfMissing('autocorrect', 'off')\n setIfMissing('autocapitalize', 'off')\n setIfMissing('spellcheck', 'false')\n\n let lockInput = false\n const keyEventName = isIos() ? 'keyup' : 'keydown'\n\n const applyResult = (target: HTMLInputElement, m: MaskResult): void => {\n target.value = m.value\n target.setSelectionRange(m.caret, m.caret)\n onChange?.(m.value, unmaskDecimal(m.value, decimalOptions))\n }\n\n const onPaste = (e: Event): void => {\n const target = e.target as HTMLInputElement\n requestAnimationFrame(() => {\n applyResult(target, applyDecimalMask(target.value, target.value.length, decimalOptions))\n })\n }\n\n const onKey = (e: Event): void => {\n const ke = e as KeyboardEvent\n const target = ke.target as HTMLInputElement\n\n // Older Android WebViews may fire key events without a `key` value.\n if (!(ke as { key?: string }).key) {\n lockInput = true\n requestAnimationFrame(() => {\n const pos = target.selectionStart ?? target.value.length\n applyResult(target, applyDecimalMask(target.value, pos, decimalOptions))\n requestAnimationFrame(() => {\n lockInput = false\n })\n })\n return\n }\n\n if (ke.key === 'Meta') return\n\n // `lockInput` is only set by the Android WebView path above; block normal\n // key events while that asynchronous path is in flight.\n if (lockInput) {\n ke.preventDefault()\n return\n }\n\n const isBackspace = ke.key === 'Backspace'\n const isDelete = ke.key === 'Delete'\n const isCharInsert = ke.key.length === 1 && !ke.ctrlKey && !ke.altKey && !ke.metaKey\n\n // Navigation (arrows, Home/End, Tab, ...), selection, and shortcut keys\n // (Ctrl/Cmd+A, Ctrl/Cmd+C, ...) don't change the text — leave the\n // browser's native caret/selection handling alone instead of recomputing\n // and overwriting it on every keystroke.\n if (!isBackspace && !isDelete && !isCharInsert) return\n\n // Everything below reads `target.value`/`selectionStart` inside the rAF\n // callback rather than synchronously here, since the browser's native\n // character insertion for this keystroke isn't guaranteed to have landed\n // yet at the point a keydown listener runs — only by the next frame.\n requestAnimationFrame(() => {\n const pos = target.selectionStart ?? target.value.length\n\n // Backspace that just deleted the decimal separator merges the\n // integer and fraction digit runs into one continuous stream —\n // restore the boundary instead of treating that as one big integer.\n if (isBackspace) {\n const unmerged = applyDecimalMaskUnmergingSeparator(target.value, decimalOptions)\n if (unmerged) {\n applyResult(target, unmerged)\n return\n }\n }\n\n // A numeric keypad (or a locale mismatch) may only offer \".\" or \",\".\n // Normalize whichever one the user just typed to the configured\n // `decimalSeparator` so it reliably opens the fraction segment.\n if (\n decimalPlaces > 0 &&\n (ke.key === '.' || ke.key === ',') &&\n ke.key !== decimalSeparator &&\n pos > 0 &&\n target.value[pos - 1] === ke.key\n ) {\n target.value = target.value.slice(0, pos - 1) + decimalSeparator + target.value.slice(pos)\n }\n\n // Typing a digit into a field whose integer part is still the\n // auto-inserted \"0\" placeholder replaces that zero instead of\n // combining with it (e.g. \"$0.00\" + \"2\" → \"$2.00\", not \"$20.00\").\n const replaced = isDigitKey(ke.key)\n ? applyDecimalMaskReplacingLoneZero(target.value, pos, ke.key, decimalOptions)\n : null\n\n applyResult(target, replaced ?? applyDecimalMask(target.value, pos, decimalOptions))\n })\n }\n\n input.addEventListener('paste', onPaste)\n input.addEventListener(keyEventName, onKey)\n\n return () => {\n input.removeEventListener('paste', onPaste)\n input.removeEventListener(keyEventName, onKey)\n input.removeAttribute(MASKED_ATTR)\n for (const name of attrsSetHere) input.removeAttribute(name)\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mother-mask",
3
3
  "private": false,
4
- "version": "2.0.4",
4
+ "version": "3.0.1",
5
5
  "type": "module",
6
6
  "description": "Lightweight input mask library for browsers",
7
7
  "author": "Danilo Celestino de Castro <dan2dev>",
@@ -34,18 +34,18 @@
34
34
  "devDependencies": {
35
35
  "@eslint/js": "^10.0.1",
36
36
  "@eslint/json": "^1.2.0",
37
- "@eslint/markdown": "^8.0.1",
38
- "@types/node": "^25.5.2",
37
+ "@eslint/markdown": "^8.0.3",
38
+ "@types/node": "^25.9.5",
39
39
  "@typescript/native-preview": "7.0.0-dev.20260405.1",
40
- "@vitest/coverage-v8": "^4.1.2",
41
- "bumpp": "^11.0.1",
42
- "eslint": "^10.1.0",
43
- "globals": "^17.4.0",
44
- "jsdom": "^29.0.1",
45
- "tsdown": "^0.21.7",
46
- "typescript": "^6.0.2",
47
- "typescript-eslint": "^8.58.0",
48
- "vitest": "^4.1.2"
40
+ "@vitest/coverage-v8": "^4.1.10",
41
+ "bumpp": "^11.1.0",
42
+ "eslint": "^10.7.0",
43
+ "globals": "^17.7.0",
44
+ "jsdom": "^29.1.1",
45
+ "tsdown": "^0.21.10",
46
+ "typescript": "^6.0.3",
47
+ "typescript-eslint": "^8.65.0",
48
+ "vitest": "^4.1.10"
49
49
  },
50
50
  "repository": {
51
51
  "type": "git",