mother-mask 3.41.0 → 3.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +1159 -0
  2. package/dist/alpine.cjs +2 -0
  3. package/dist/alpine.cjs.map +1 -0
  4. package/dist/alpine.d.cts +44 -0
  5. package/dist/alpine.d.mts +44 -0
  6. package/dist/alpine.mjs +2 -0
  7. package/dist/alpine.mjs.map +1 -0
  8. package/dist/angular.cjs +2 -0
  9. package/dist/angular.cjs.map +1 -0
  10. package/dist/angular.d.cts +98 -0
  11. package/dist/angular.d.mts +98 -0
  12. package/dist/angular.mjs +2 -0
  13. package/dist/angular.mjs.map +1 -0
  14. package/dist/ember.cjs +2 -0
  15. package/dist/ember.cjs.map +1 -0
  16. package/dist/ember.d.cts +103 -0
  17. package/dist/ember.d.mts +103 -0
  18. package/dist/ember.mjs +2 -0
  19. package/dist/ember.mjs.map +1 -0
  20. package/dist/inferno.cjs +2 -0
  21. package/dist/inferno.cjs.map +1 -0
  22. package/dist/inferno.d.cts +58 -0
  23. package/dist/inferno.d.mts +58 -0
  24. package/dist/inferno.mjs +2 -0
  25. package/dist/inferno.mjs.map +1 -0
  26. package/dist/knockout.cjs +2 -0
  27. package/dist/knockout.cjs.map +1 -0
  28. package/dist/knockout.d.cts +44 -0
  29. package/dist/knockout.d.mts +44 -0
  30. package/dist/knockout.mjs +2 -0
  31. package/dist/knockout.mjs.map +1 -0
  32. package/dist/lit.cjs +21 -0
  33. package/dist/lit.cjs.map +1 -0
  34. package/dist/lit.d.cts +99 -0
  35. package/dist/lit.d.mts +99 -0
  36. package/dist/lit.mjs +21 -0
  37. package/dist/lit.mjs.map +1 -0
  38. package/dist/mithril.cjs +2 -0
  39. package/dist/mithril.cjs.map +1 -0
  40. package/dist/mithril.d.cts +48 -0
  41. package/dist/mithril.d.mts +48 -0
  42. package/dist/mithril.mjs +2 -0
  43. package/dist/mithril.mjs.map +1 -0
  44. package/dist/mother-mask.cjs +1 -1
  45. package/dist/mother-mask.cjs.map +1 -1
  46. package/dist/mother-mask.mjs +1 -1
  47. package/dist/mother-mask.mjs.map +1 -1
  48. package/dist/mother-mask.umd.js +1 -1
  49. package/dist/mother-mask.umd.js.map +1 -1
  50. package/dist/preact.cjs +2 -0
  51. package/dist/preact.cjs.map +1 -0
  52. package/dist/preact.d.cts +43 -0
  53. package/dist/preact.d.mts +43 -0
  54. package/dist/preact.mjs +2 -0
  55. package/dist/preact.mjs.map +1 -0
  56. package/dist/qwik/InputDecimal.d.ts +9 -0
  57. package/dist/qwik/InputMask.d.ts +22 -0
  58. package/dist/qwik/index.d.ts +5 -0
  59. package/dist/qwik/index.qwik.mjs +75 -0
  60. package/dist/riot.cjs +2 -0
  61. package/dist/riot.cjs.map +1 -0
  62. package/dist/riot.d.cts +76 -0
  63. package/dist/riot.d.mts +76 -0
  64. package/dist/riot.mjs +2 -0
  65. package/dist/riot.mjs.map +1 -0
  66. package/dist/solid.cjs +2 -0
  67. package/dist/solid.cjs.map +1 -0
  68. package/dist/solid.d.cts +54 -0
  69. package/dist/solid.d.mts +54 -0
  70. package/dist/solid.mjs +2 -0
  71. package/dist/solid.mjs.map +1 -0
  72. package/dist/stencil/index.d.ts +22 -0
  73. package/dist/stencil/index.js +1 -0
  74. package/dist/stencil/p-BOEnjTpG.js +1 -0
  75. package/dist/stencil/stencil-mask-decimal.d.ts +20 -0
  76. package/dist/stencil/stencil-mask-decimal.js +1 -0
  77. package/dist/stencil/stencil-mask-input.d.ts +27 -0
  78. package/dist/stencil/stencil-mask-input.js +1 -0
  79. package/dist/svelte.cjs +2 -0
  80. package/dist/svelte.cjs.map +1 -0
  81. package/dist/svelte.d.cts +44 -0
  82. package/dist/svelte.d.mts +44 -0
  83. package/dist/svelte.mjs +2 -0
  84. package/dist/svelte.mjs.map +1 -0
  85. package/dist/vue.cjs +2 -0
  86. package/dist/vue.cjs.map +1 -0
  87. package/dist/vue.d.cts +57 -0
  88. package/dist/vue.d.mts +57 -0
  89. package/dist/vue.mjs +2 -0
  90. package/dist/vue.mjs.map +1 -0
  91. package/dist/web-components.cjs +2 -0
  92. package/dist/web-components.cjs.map +1 -0
  93. package/dist/web-components.d.cts +93 -0
  94. package/dist/web-components.d.mts +93 -0
  95. package/dist/web-components.mjs +2 -0
  96. package/dist/web-components.mjs.map +1 -0
  97. package/package.json +235 -7
  98. package/src/octane/InputDecimal.tsx +85 -0
  99. package/src/octane/InputMask.tsx +116 -0
  100. package/src/octane/index.ts +9 -0
package/README.md CHANGED
@@ -169,6 +169,1165 @@ server-only code can continue importing pure helpers from `mother-mask`.
169
169
  See the [React example](https://github.com/dan2dev/mother-mask/tree/main/examples/react-simple)
170
170
  for a runnable app and browser lifecycle/memory tests.
171
171
 
172
+ ## Vue
173
+
174
+ Import the custom directives from the separate `mother-mask/vue` entry:
175
+
176
+ ```vue
177
+ <script setup lang="ts">
178
+ import { ref } from 'vue'
179
+ import { vMotherMask, vMotherMaskDecimal, formatDecimalValue } from 'mother-mask/vue'
180
+
181
+ // Keep the options object stable across renders.
182
+ const currency = { decimalPlaces: 2, prefix: '$', allowNegative: true }
183
+
184
+ const phone = ref('')
185
+ const amount = ref('')
186
+ </script>
187
+
188
+ <template>
189
+ <label for="phone">Phone</label>
190
+ <input
191
+ id="phone"
192
+ name="phone"
193
+ inputmode="tel"
194
+ v-mother-mask="{ mask: '(99) 99999-9999', value: phone, onValueChange: (v) => (phone = v) }"
195
+ />
196
+
197
+ <label for="amount">Amount</label>
198
+ <input
199
+ id="amount"
200
+ name="amount"
201
+ v-mother-mask-decimal="{ options: currency, value: amount, onValueChange: (v) => (amount = v) }"
202
+ />
203
+
204
+ <button type="button" @click="amount = formatDecimalValue(1234.5, currency)">
205
+ Set amount
206
+ </button>
207
+ </template>
208
+ ```
209
+
210
+ `mother-mask/vue` re-exports every core function, class, and type as well as
211
+ the `vMotherMask` and `vMotherMaskDecimal` directives. Its core exports are
212
+ aliases to `mother-mask`, sharing the same implementation and caches. The
213
+ core ESM, CommonJS, and UMD builds remain independent of Vue. Vue is an
214
+ optional peer dependency required only when importing `mother-mask/vue`; the
215
+ supported version range is `^3.5.0`. Vue is not bundled.
216
+
217
+ - `vMotherMask` takes `{ mask, options?, value?, onValueChange? }`;
218
+ `vMotherMaskDecimal` takes `{ options?, value?, onValueChange? }` and calls
219
+ `onValueChange(value, numericValue)`. `vMotherMaskDecimal` always sets
220
+ `inputmode="decimal"` on mount.
221
+ - Both are exported pre-named `v` + PascalCase — Vue's own convention for a
222
+ local directive in `<script setup>` — so `import { vMotherMask } from
223
+ 'mother-mask/vue'` alone makes `v-mother-mask="..."` available in the
224
+ template with no import rename. Register it globally instead with
225
+ `app.directive('mother-mask', vMotherMask)` to make `v-mother-mask`
226
+ available in every component.
227
+ - `mounted` runs only once Vue mounts the element to the live DOM — Vue
228
+ never invokes directive hooks during server-side rendering, so this is
229
+ SSR-safe with no `typeof window` guard needed. `updated` runs on every
230
+ re-render of the owning component (not only when the bound value actually
231
+ changes), so it rebinds only if the mask, options, or an externally-set
232
+ value actually changed — an echo of the directive's own `onValueChange` is
233
+ ignored. `beforeUnmount` guarantees `dispose()` runs exactly once.
234
+ - A `declare module 'vue' { interface GlobalDirectives { ... } }`
235
+ augmentation ships alongside each directive, so a globally-registered
236
+ `v-mother-mask`/`v-mother-mask-decimal` also type-checks in templates
237
+ without extra setup.
238
+
239
+ See the [Vue example](https://github.com/dan2dev/mother-mask/tree/main/examples/vue-simple)
240
+ for a runnable app.
241
+
242
+ ## Angular
243
+
244
+ Import the standalone directives from the separate `mother-mask/angular` entry:
245
+
246
+ ```ts
247
+ import { Component } from '@angular/core'
248
+ import { MotherMaskDecimalDirective, MotherMaskDirective, formatDecimalValue } from 'mother-mask/angular'
249
+
250
+ // Keep option objects stable across change detection cycles.
251
+ const currency = { decimalPlaces: 2, prefix: '$', allowNegative: true }
252
+
253
+ @Component({
254
+ standalone: true,
255
+ imports: [MotherMaskDirective, MotherMaskDecimalDirective],
256
+ template: `
257
+ <label for="phone">Phone</label>
258
+ <input id="phone" name="phone" motherMask="(99) 99999-9999" inputmode="tel" [(value)]="phone" />
259
+
260
+ <label for="amount">Amount</label>
261
+ <input id="amount" name="amount" motherMaskDecimal [motherMaskDecimalOptions]="currency" [(value)]="amount" />
262
+
263
+ <button type="button" (click)="amount = formatDecimalValue(1234.5, currency)">Set amount</button>
264
+ `,
265
+ })
266
+ export class FormComponent {
267
+ readonly currency = currency
268
+ // Plain fields, not signals: the directives below bind `value` through
269
+ // classic @Input/@Output (see why in the notes under this example), so
270
+ // `[(value)]` assigns to this property directly on each change.
271
+ phone = ''
272
+ amount = ''
273
+ }
274
+ ```
275
+
276
+ `mother-mask/angular` re-exports every core function, class, and type as well
277
+ as `MotherMaskDirective` and `MotherMaskDecimalDirective`. Its core exports
278
+ are aliases to `mother-mask`, sharing the same implementation and caches. The
279
+ core ESM, CommonJS, and UMD builds remain independent of Angular. Angular is
280
+ an optional peer dependency required only when importing
281
+ `mother-mask/angular`; the supported version range is `^19.0.0 || ^20.0.0`.
282
+ Angular is not bundled.
283
+
284
+ - Both directives are attribute selectors (`input[motherMask]` and
285
+ `input[motherMaskDecimal]`) and support `[(value)]` two-way binding.
286
+ `MotherMaskDirective` accepts `motherMask` (a `MaskPattern`) and optional
287
+ `[motherMaskOptions]`; `MotherMaskDecimalDirective` accepts optional
288
+ `[motherMaskDecimalOptions]` and additionally emits `(numericValueChange)`.
289
+ - The binding lifecycle is Signals-driven: `ngOnChanges` pushes the current
290
+ mask/options/value into an internal `signal`, and `afterRenderEffect` —
291
+ which Angular runs only on the browser, never during server-side
292
+ rendering, so no manual `typeof window` check is needed — reactively
293
+ (re)binds whenever that signal changes. `ngOnDestroy` disposes the
294
+ binding, and it is disposed and recreated whenever the mask/options change
295
+ or `value` is updated externally.
296
+ - `value`/`valueChange` (and `numericValueChange`) use classic
297
+ `@Input`/`@Output` rather than the functional `input()`/`model()` API,
298
+ since Angular only wires signal-based inputs to template bindings through
299
+ the ngtsc compiler's static analysis — unavailable without the Angular CLI
300
+ build toolchain. Both forms compile to the same `[(value)]` template
301
+ syntax for consumers.
302
+ - `MotherMaskDecimalDirective` always sets `inputmode="decimal"` via a host
303
+ binding.
304
+
305
+ See the [Angular example](https://github.com/dan2dev/mother-mask/tree/main/examples/angular-simple)
306
+ for a runnable app.
307
+
308
+ ## Svelte
309
+
310
+ Import the Svelte 5 actions from the separate `mother-mask/svelte` entry:
311
+
312
+ ```svelte
313
+ <script lang="ts">
314
+ import { motherMask, motherMaskDecimal, formatDecimalValue } from 'mother-mask/svelte'
315
+
316
+ // Keep the options object stable across renders.
317
+ const currency = { decimalPlaces: 2, prefix: '$', allowNegative: true }
318
+
319
+ let phone = $state('')
320
+ let amount = $state('')
321
+ </script>
322
+
323
+ <label for="phone">Phone</label>
324
+ <input
325
+ id="phone"
326
+ name="phone"
327
+ inputmode="tel"
328
+ use:motherMask={{ mask: '(99) 99999-9999', value: phone, onValueChange: (v) => (phone = v) }}
329
+ />
330
+
331
+ <label for="amount">Amount</label>
332
+ <input
333
+ id="amount"
334
+ name="amount"
335
+ use:motherMaskDecimal={{ options: currency, value: amount, onValueChange: (v) => (amount = v) }}
336
+ />
337
+
338
+ <button type="button" onclick={() => (amount = formatDecimalValue(1234.5, currency))}>
339
+ Set amount
340
+ </button>
341
+ ```
342
+
343
+ `mother-mask/svelte` re-exports every core function, class, and type as well
344
+ as the `motherMask` and `motherMaskDecimal` actions. Its core exports are
345
+ aliases to `mother-mask`, sharing the same implementation and caches. The
346
+ core ESM, CommonJS, and UMD builds remain independent of Svelte, and this
347
+ entry has no runtime dependency on the `svelte` package at all — it's plain
348
+ TypeScript, usable from any Svelte 5 (or 4) component via `use:`.
349
+
350
+ - Actions run only once Svelte mounts the element to the live DOM, and are
351
+ torn down when it leaves — Svelte never invokes `use:` directives during
352
+ server-side rendering, so this is SSR-safe with no `typeof window` guard.
353
+ `destroy()` guarantees `dispose()` runs exactly once.
354
+ - `update(params)` is Svelte's own reactivity hook: whenever a `$state`
355
+ value read inside the `use:motherMask={...}` expression changes — `mask`,
356
+ `options`, or `value` — Svelte re-invokes it with the new params, and the
357
+ action rebinds only if the mask/options changed or `value` was set
358
+ externally (an echo of the action's own `onValueChange` is ignored).
359
+ - `motherMask` takes `{ mask, options?, value?, onValueChange? }`;
360
+ `motherMaskDecimal` takes `{ options?, value?, onValueChange? }` and calls
361
+ `onValueChange(value, numericValue)`. `motherMaskDecimal` always sets
362
+ `inputmode="decimal"` on mount.
363
+
364
+ See the [Svelte example](https://github.com/dan2dev/mother-mask/tree/main/examples/svelte-simple)
365
+ for a runnable app.
366
+
367
+ ## SolidJS
368
+
369
+ Import the custom directives from the separate `mother-mask/solid` entry:
370
+
371
+ ```tsx
372
+ import { createSignal } from 'solid-js'
373
+ import { motherMask, motherMaskDecimal, formatDecimalValue } from 'mother-mask/solid'
374
+
375
+ // Keep the options object stable across renders.
376
+ const currency = { decimalPlaces: 2, prefix: '$', allowNegative: true }
377
+
378
+ export function Form() {
379
+ const [phone, setPhone] = createSignal('')
380
+ const [amount, setAmount] = createSignal('')
381
+
382
+ return (
383
+ <>
384
+ <label for="phone">Phone</label>
385
+ <input
386
+ id="phone"
387
+ name="phone"
388
+ inputmode="tel"
389
+ use:motherMask={{ mask: '(99) 99999-9999', value: phone(), onValueChange: setPhone }}
390
+ />
391
+
392
+ <label for="amount">Amount</label>
393
+ <input
394
+ id="amount"
395
+ name="amount"
396
+ use:motherMaskDecimal={{ options: currency, value: amount(), onValueChange: setAmount }}
397
+ />
398
+
399
+ <button type="button" onClick={() => setAmount(formatDecimalValue(1234.5, currency))}>
400
+ Set amount
401
+ </button>
402
+ </>
403
+ )
404
+ }
405
+ ```
406
+
407
+ Directive props are typed via `declare module 'solid-js' { namespace JSX { interface Directives { ... } } }`,
408
+ so `use:motherMask`/`use:motherMaskDecimal` type-check without extra setup once
409
+ `mother-mask/solid` is imported anywhere in your project.
410
+
411
+ `mother-mask/solid` re-exports every core function, class, and type as well
412
+ as the `motherMask` and `motherMaskDecimal` directives. Its core exports are
413
+ aliases to `mother-mask`, sharing the same implementation and caches. The
414
+ core ESM, CommonJS, and UMD builds remain independent of Solid. Solid is an
415
+ optional peer dependency required only when importing `mother-mask/solid`;
416
+ the supported version range is `^1.9.0`. Solid is not bundled.
417
+
418
+ - Directives run when Solid mounts the element to the live DOM. Solid's
419
+ server-rendering path never instantiates real elements or invokes `use:`
420
+ callbacks, so this is SSR-safe with no `typeof window` guard needed.
421
+ - `createEffect` gives fine-grained reactivity: it tracks only the signals
422
+ actually read while building the params object passed to `use:motherMask`
423
+ (e.g. `value: phone()`), and reruns exactly when those change — rebinding
424
+ only if the mask, options, or an externally-set value actually changed
425
+ (an echo of the directive's own `onValueChange` is ignored). `onCleanup`
426
+ guarantees `dispose()` runs exactly once, both before every rebind and
427
+ when the element unmounts.
428
+ - `motherMask` takes `{ mask, options?, value?, onValueChange? }`;
429
+ `motherMaskDecimal` takes `{ options?, value?, onValueChange? }` and calls
430
+ `onValueChange(value, numericValue)`, and always sets `inputmode="decimal"`.
431
+
432
+ See the [SolidJS example](https://github.com/dan2dev/mother-mask/tree/main/examples/solid-simple)
433
+ for a runnable app.
434
+
435
+ ## Preact
436
+
437
+ Import the Preact components from the separate `mother-mask/preact` entry:
438
+
439
+ ```tsx
440
+ import { useState } from 'preact/hooks'
441
+ import { InputMask, InputDecimal, formatDecimalValue } from 'mother-mask/preact'
442
+
443
+ // Keep option objects and mask arrays stable across renders.
444
+ const currency = { decimalPlaces: 2, prefix: '$', allowNegative: true }
445
+
446
+ export function Form() {
447
+ const [phone, setPhone] = useState('')
448
+ const [amount, setAmount] = useState('')
449
+
450
+ return (
451
+ <>
452
+ <label htmlFor="phone">Phone</label>
453
+ <InputMask
454
+ id="phone"
455
+ name="phone"
456
+ mask="(99) 99999-9999"
457
+ inputMode="tel"
458
+ value={phone}
459
+ onValueChange={setPhone}
460
+ />
461
+ <label htmlFor="amount">Amount</label>
462
+ <InputDecimal
463
+ id="amount"
464
+ name="amount"
465
+ options={currency}
466
+ value={amount}
467
+ onValueChange={setAmount}
468
+ />
469
+ <button type="button" onClick={() => setAmount(formatDecimalValue(1234.5, currency))}>
470
+ Set amount
471
+ </button>
472
+ </>
473
+ )
474
+ }
475
+ ```
476
+
477
+ `mother-mask/preact` re-exports every core function, class, and type as well
478
+ as `InputMask`, `InputDecimal`, `InputMaskProps`, and `InputDecimalProps`.
479
+ Its core exports are aliases to `mother-mask`, sharing the same
480
+ implementation and caches. The core ESM, CommonJS, and UMD builds remain
481
+ independent of Preact. Preact is an optional peer dependency required only
482
+ when importing `mother-mask/preact`; the supported version range is
483
+ `^10.0.0`. Preact is not bundled, and this entry never imports
484
+ `preact/compat` — it's built directly on `preact` + `preact/hooks` to keep
485
+ Preact's footprint advantage intact.
486
+
487
+ - Both components accept controlled string `value` or uncontrolled string
488
+ `defaultValue`, same as the React version. `InputMask` accepts `mask` and
489
+ optional `BindOptions`; `InputDecimal` accepts optional
490
+ `BindDecimalOptions` and reports `(value, numericValue)`.
491
+ - Access to the underlying `<input>` is via an `inputRef` prop, not `ref`:
492
+ Preact passes `ref` on a function component straight through to the
493
+ built-in element/instance ref rather than forwarding it as a normal prop
494
+ (unlike React 19), so reusing `ref` here would require `preact/compat`'s
495
+ `forwardRef` — extra weight this entry deliberately avoids.
496
+ - This is a leaner port than the React version: it drops the IME
497
+ composition and native-`reset` edge-case handling built for React-DOM's
498
+ synthetic event layer (which doesn't exist in Preact), keeping to
499
+ `useLayoutEffect`/`useRef`/`useState` from `preact/hooks`. The dispose
500
+ guarantee is identical — every rebind and unmount releases the previous
501
+ binding first.
502
+
503
+ See the [Preact example](https://github.com/dan2dev/mother-mask/tree/main/examples/preact-simple)
504
+ for a runnable app.
505
+
506
+ ## Lit
507
+
508
+ Import the `<lit-mask-input>` and `<lit-mask-decimal>` custom elements from
509
+ the separate `mother-mask/lit` entry — importing the module registers both
510
+ elements as a side effect:
511
+
512
+ ```html
513
+ <script type="module">
514
+ import 'mother-mask/lit'
515
+ </script>
516
+
517
+ <label for="phone">Phone</label>
518
+ <lit-mask-input id="phone" name="phone" mask="(99) 99999-9999" input-mode="tel"></lit-mask-input>
519
+
520
+ <label for="amount">Amount</label>
521
+ <lit-mask-decimal id="amount" name="amount"></lit-mask-decimal>
522
+
523
+ <script type="module">
524
+ const phone = document.getElementById('phone')
525
+ phone.addEventListener('value-change', (e) => console.log(e.detail))
526
+
527
+ const amount = document.getElementById('amount')
528
+ amount.options = { decimalPlaces: 2, prefix: '$', allowNegative: true }
529
+ amount.addEventListener('numeric-value-change', (e) => console.log(e.detail))
530
+ </script>
531
+ ```
532
+
533
+ `mother-mask/lit` re-exports every core function, class, and type as well as
534
+ the `LitMaskInput` and `LitMaskDecimal` classes (for `instanceof` checks or
535
+ subclassing). Its core exports are aliases to `mother-mask`, sharing the
536
+ same implementation and caches. The core ESM, CommonJS, and UMD builds
537
+ remain independent of Lit. Lit is an optional peer dependency required only
538
+ when importing `mother-mask/lit`; the supported version range is `^3.0.0`.
539
+ Lit is not bundled.
540
+
541
+ - Both elements render into **light DOM** (`createRenderRoot()` returns
542
+ `this`, not a shadow root), so a page's own `<label for>` and global CSS
543
+ reach the native `<input>` directly, the same as any other text field.
544
+ - `<lit-mask-input>` accepts `mask` (a plain string works as an HTML
545
+ attribute; assign an array/function `MaskPattern` as a JS property) and an
546
+ `options` object (JS property only — objects can't be expressed as plain
547
+ attributes). It emits `value-change` (`detail: string`).
548
+ `<lit-mask-decimal>` accepts an `options` object and emits both
549
+ `value-change` and `numeric-value-change` (`detail: number`), and always
550
+ sets `inputmode="decimal"`.
551
+ - Binding happens in `firstUpdated` (Lit's DOM-is-ready hook) and disposes
552
+ in `disconnectedCallback` — both fire only on the browser, so there's no
553
+ `typeof window` guard to write. `updated` reactively rebinds whenever
554
+ `mask`/`options` change or `value` is set externally; an echo of the
555
+ element's own `value-change` event is ignored.
556
+ - A curated set of native attributes forward to the inner `<input>`: `name`,
557
+ `placeholder`, `disabled`, `readonly`, `required`, `input-mode`, and
558
+ `autocomplete`. `id` is a special case: `<lit-mask-input id="phone">`
559
+ writes `id="phone"` onto the custom element itself, not the `<input>` it
560
+ renders — a `<label for="phone">` would then resolve to the (unfocusable)
561
+ host instead of the actual control. After first render, the element moves
562
+ its own `id` onto the rendered `<input>` and removes it from itself, so
563
+ `<label for="phone">` keeps working exactly as if `<lit-mask-input>`
564
+ weren't there.
565
+
566
+ ## Stencil
567
+
568
+ Import the `<stencil-mask-input>` and `<stencil-mask-decimal>` custom
569
+ elements from their own `mother-mask/stencil/mask-input` and
570
+ `mother-mask/stencil/mask-decimal` entries — each registers its element as
571
+ an import side effect:
572
+
573
+ ```html
574
+ <script type="module">
575
+ import 'mother-mask/stencil/mask-input'
576
+ import 'mother-mask/stencil/mask-decimal'
577
+ </script>
578
+
579
+ <label for="phone">Phone</label>
580
+ <stencil-mask-input id="phone" name="phone" mask="(99) 99999-9999" input-mode="tel"></stencil-mask-input>
581
+
582
+ <label for="amount">Amount</label>
583
+ <stencil-mask-decimal id="amount" name="amount"></stencil-mask-decimal>
584
+
585
+ <script type="module">
586
+ const phone = document.getElementById('phone')
587
+ phone.addEventListener('value-change', (e) => console.log(e.detail))
588
+
589
+ const amount = document.getElementById('amount')
590
+ amount.options = { decimalPlaces: 2, prefix: '$', allowNegative: true }
591
+ amount.addEventListener('numeric-value-change', (e) => console.log(e.detail))
592
+ </script>
593
+ ```
594
+
595
+ Stencil components are fundamentally a compiled artifact — `@Component`,
596
+ `@Prop`, `@Event`, and friends are erased by the Stencil compiler at build
597
+ time and have no meaningful runtime implementation on their own, so (unlike
598
+ every other integration in this README) `mother-mask/stencil` is **not**
599
+ built by the same tsdown pipeline as the rest of this package; it's a
600
+ separate Stencil project ([`stencil-src/`](../../packages/mother-mask/stencil-src))
601
+ compiled with the Stencil CLI's `dist-custom-elements` output target and
602
+ copied into `dist/stencil` as part of `bun run build`. Two consequences
603
+ follow from that:
604
+
605
+ - **No `mother-mask/stencil` barrel re-exporting core helpers or a
606
+ `@stencil/core` peer dependency.** The Stencil compiler bundles
607
+ `mother-mask`'s core logic directly into each component (there's no
608
+ shared module identity/cache with a page's own `mother-mask` import, the
609
+ way the other framework entries alias `export * from 'mother-mask'`), and
610
+ bundles its own tiny runtime too (`externalRuntime: false`) — so
611
+ `<stencil-mask-input>`/`<stencil-mask-decimal>` have **zero runtime
612
+ dependencies** at all, usable on any page. Import core helpers like
613
+ `formatDecimalValue` from `mother-mask` directly.
614
+ - Each component is its own tree-shakeable module (`mask-input`,
615
+ `mask-decimal`) rather than one combined entry, matching how Stencil's
616
+ own `dist-custom-elements` output is meant to be consumed.
617
+
618
+ - `<stencil-mask-input>` accepts `mask`, `options` (JS property only), and
619
+ a curated set of forwarded attributes: `name`, `placeholder`, `disabled`,
620
+ `readonly`, `required`, and `input-mode` (exposed as the `inputModeAttr`
621
+ JS property — not `inputMode`, since this class is a real `HTMLElement`
622
+ at runtime and already declares its own incompatible `inputMode`). It
623
+ emits `value-change` (`detail: string`). `<stencil-mask-decimal>` accepts
624
+ `options` and emits both `value-change` and `numeric-value-change`
625
+ (`detail: number`), and always sets `inputmode="decimal"`.
626
+ - The ref to the native `<input>` is grabbed via `render()`'s `ref`
627
+ callback — the only point Stencil hands back a real DOM node. Binding
628
+ itself happens in `connectedCallback` when that ref is already available
629
+ (true on every *re*-connection — the element was moved or re-appended —
630
+ since the ref from the first render still exists) and otherwise in
631
+ `componentDidLoad`, which fires exactly once, right after the very first
632
+ render actually creates that ref. Either way, both only ever run in the
633
+ browser — Stencil's SSR/hydration path never calls them, so there's no
634
+ `typeof window` guard to write. `@Watch('mask')`/`@Watch('options')`/
635
+ `@Watch('value')` reactively rebind; `disconnectedCallback` always
636
+ disposes.
637
+ - Same `id`-transfer behavior as Lit's elements (both render into light
638
+ DOM): after first render, a host `id` moves onto the rendered `<input>`
639
+ so `<label for>` keeps resolving to the actual control.
640
+ - Because raw `.tsx` Stencil source can't run outside the Stencil compiler,
641
+ [`tests/stencil.test.ts`](../../packages/mother-mask/tests/stencil.test.ts)
642
+ exercises the *built* `dist/stencil` output directly rather than the
643
+ source — run `bun run build` before `bun run test` if you're working on
644
+ this entry locally.
645
+
646
+ ## Alpine.js
647
+
648
+ Register the `x-mask` directive from the separate `mother-mask/alpine`
649
+ entry with `Alpine.plugin(...)`:
650
+
651
+ ```html
652
+ <script type="module">
653
+ import Alpine from 'alpinejs'
654
+ import motherMaskPlugin from 'mother-mask/alpine'
655
+
656
+ Alpine.plugin(motherMaskPlugin)
657
+ Alpine.start()
658
+ </script>
659
+
660
+ <div x-data="{ phone: '', amount: '' }">
661
+ <label for="phone">Phone</label>
662
+ <input
663
+ id="phone"
664
+ name="phone"
665
+ inputmode="tel"
666
+ x-mask="{ mask: '(99) 99999-9999', value: phone }"
667
+ x-on:mask-change="phone = $event.detail"
668
+ />
669
+
670
+ <label for="amount">Amount</label>
671
+ <input
672
+ id="amount"
673
+ name="amount"
674
+ x-mask.decimal="{ options: { decimalPlaces: 2, prefix: '$', allowNegative: true }, value: amount }"
675
+ x-on:mask-change="amount = $event.detail"
676
+ />
677
+ </div>
678
+ ```
679
+
680
+ `mother-mask/alpine` re-exports every core function, class, and type as well
681
+ as `motherMaskPlugin` (both a named export and the module's default export,
682
+ matching Alpine's own documented plugin convention — `export default
683
+ function (Alpine) { ... }`). Its core exports are aliases to `mother-mask`,
684
+ sharing the same implementation and caches. The core ESM, CommonJS, and UMD
685
+ builds remain independent of Alpine. Alpine is an optional peer dependency
686
+ required only when importing `mother-mask/alpine`; the supported version
687
+ range is `^3.0.0`. Alpine is not bundled.
688
+
689
+ - `x-mask` takes an expression evaluating to `{ mask, options?, value? }`;
690
+ `x-mask.decimal` takes `{ options?, value? }`. Reading a reactive value
691
+ inside that expression (e.g. `value: phone`) is what makes it reactive —
692
+ Alpine's own `effect()` re-runs the directive whenever a dependency read
693
+ while evaluating the expression changes, rebinding only if the mask,
694
+ options, or an externally-set value actually changed.
695
+ - **Two-way binding goes through a DOM event, not `x-model`.** The
696
+ directive dispatches `mask-change` (`detail: string`, and for
697
+ `.decimal`, also `mask-numeric-change` with `detail: number`) — bind
698
+ those with `x-on:mask-change`, as shown above. Combining `x-mask` with
699
+ `x-model` on the same element isn't supported: both would independently
700
+ react to the native `input` event with no defined ordering between them,
701
+ and could fight over the field's live value on every keystroke.
702
+ - The directive's callback runs when Alpine walks the live DOM tree —
703
+ Alpine has no server-rendering step, so there's no `typeof window` guard
704
+ to write. `cleanup()` guarantees `dispose()` runs exactly once, both
705
+ before every rebind and when the element is removed from the DOM.
706
+
707
+ ## Native Web Components
708
+
709
+ Import the `<mm-mask-input>` and `<mm-mask-decimal>` custom elements from
710
+ the separate `mother-mask/web-components` entry — no framework at all,
711
+ plain `customElements`. Importing the module registers both elements as a
712
+ side effect:
713
+
714
+ ```html
715
+ <script type="module">
716
+ import 'mother-mask/web-components'
717
+ </script>
718
+
719
+ <label for="phone">Phone</label>
720
+ <mm-mask-input id="phone" name="phone" mask="(99) 99999-9999" input-mode="tel"></mm-mask-input>
721
+
722
+ <label for="amount">Amount</label>
723
+ <mm-mask-decimal id="amount" name="amount"></mm-mask-decimal>
724
+
725
+ <script type="module">
726
+ const phone = document.getElementById('phone')
727
+ phone.addEventListener('value-change', (e) => console.log(e.detail))
728
+
729
+ const amount = document.getElementById('amount')
730
+ amount.options = { decimalPlaces: 2, prefix: '$', allowNegative: true }
731
+ amount.addEventListener('numeric-value-change', (e) => console.log(e.detail))
732
+ </script>
733
+ ```
734
+
735
+ `mother-mask/web-components` re-exports every core function, class, and
736
+ type as well as the `MotherMaskInputElement` and `MotherMaskDecimalElement`
737
+ classes (for `instanceof` checks or subclassing). Its core exports are
738
+ aliases to `mother-mask`, sharing the same implementation and caches. The
739
+ core ESM, CommonJS, and UMD builds remain independent of this entry, and it
740
+ has **zero runtime dependencies** of its own — it's the one integration in
741
+ this README usable with no framework, no build step, and no peer
742
+ dependency at all; drop the `<script type="module">` tag on any page.
743
+
744
+ - Both elements render into **light DOM** (a single `<input>` appended as a
745
+ real child, no shadow root), so a page's own `<label for>` and global CSS
746
+ reach it directly, the same as Lit's and Stencil's entries.
747
+ - `<mm-mask-input>` accepts `mask` and `options` as either a plain
748
+ attribute (`mask`, string patterns only) or a JS property (any
749
+ `MaskPattern`, including arrays/functions, plus `options` objects, which
750
+ can't be expressed as attributes at all). A curated set of other
751
+ attributes forward to the inner `<input>`: `name`, `placeholder`,
752
+ `disabled`, `readonly`, `required`, `input-mode`, and `autocomplete`. It
753
+ emits `value-change` (`detail: string`). `<mm-mask-decimal>` accepts
754
+ `options` (JS property only) and emits both `value-change` and
755
+ `numeric-value-change` (`detail: number`), and always sets
756
+ `inputmode="decimal"`.
757
+ - Binding happens in `connectedCallback` and disposes in
758
+ `disconnectedCallback` — both fire only when a real document actually
759
+ connects/disconnects the element, so there's no `typeof window` guard to
760
+ write. `attributeChangedCallback` and the `mask`/`options`/`value`
761
+ property setters reactively rebind; an echo of the element's own
762
+ `value-change` event is ignored.
763
+ - Same `id`-transfer behavior as Lit's and Stencil's elements: after
764
+ connecting, a host `id` moves onto the rendered `<input>` so
765
+ `<label for>` keeps resolving to the actual control.
766
+
767
+ ## Qwik
768
+
769
+ Import the `InputMask` and `InputDecimal` components from the separate
770
+ `mother-mask/qwik` entry:
771
+
772
+ ```tsx
773
+ import { component$, useSignal } from '@builder.io/qwik'
774
+ import { InputMask, InputDecimal, formatDecimalValue } from 'mother-mask/qwik'
775
+
776
+ // Keep option objects outside the component so they stay stable across renders.
777
+ const currency = { decimalPlaces: 2, prefix: '$', allowNegative: true }
778
+
779
+ export default component$(() => {
780
+ const phone = useSignal('')
781
+ const amount = useSignal('')
782
+
783
+ return (
784
+ <>
785
+ <label for="phone">Phone</label>
786
+ <InputMask
787
+ id="phone"
788
+ name="phone"
789
+ mask="(99) 99999-9999"
790
+ inputMode="tel"
791
+ value={phone.value}
792
+ onValueChange$={(v) => (phone.value = v)}
793
+ />
794
+
795
+ <label for="amount">Amount</label>
796
+ <InputDecimal
797
+ id="amount"
798
+ name="amount"
799
+ options={currency}
800
+ value={amount.value}
801
+ onValueChange$={(v) => (amount.value = v)}
802
+ />
803
+
804
+ <button type="button" onClick$={() => (amount.value = formatDecimalValue(1234.5, currency))}>
805
+ Set amount
806
+ </button>
807
+ </>
808
+ )
809
+ })
810
+ ```
811
+
812
+ `mother-mask/qwik` re-exports every core function, class, and type as well
813
+ as `InputMask`, `InputDecimal`, `InputMaskProps`, and `InputDecimalProps`.
814
+ The core ESM, CommonJS, and UMD builds remain independent of Qwik. Qwik is
815
+ an optional peer dependency required only when importing `mother-mask/qwik`;
816
+ the supported version range is `^1.0.0`. Qwik's own components are not
817
+ bundled here — see below for why this entry, alone among the ones in this
818
+ README, ships close to raw source.
819
+
820
+ - Both components accept `value` and `onValueChange$` (a QRL — inline
821
+ arrow functions work; the Qwik Optimizer converts them for you). `mask`
822
+ and `options` work the same as the React version.
823
+ - Binding happens in `useVisibleTask$`, a hook Qwik only ever runs on the
824
+ client once the element is visible — Qwik's SSR renderer never executes
825
+ it, so this is safe to render on the server with no `typeof window`
826
+ guard. `track()` makes it reactive: it reruns whenever `mask`, `options`,
827
+ or `value` change, rebinding only if the mask, options, or an
828
+ externally-set value actually changed (an echo of the component's own
829
+ `onValueChange$` is ignored). `cleanup()` — called both before every
830
+ re-run and when the component unmounts — guarantees `dispose()` runs
831
+ exactly once per binding.
832
+ - `InputDecimal` defaults to `inputMode="decimal"` and reports
833
+ `onValueChange$(value, numericValue)`.
834
+
835
+ **Why this entry isn't built like the others.** `component$`, `useVisibleTask$`,
836
+ and every other `$`-suffixed Qwik API throw at runtime ("Optimizer should
837
+ replace all usages of $() ...") unless the Qwik Optimizer has already
838
+ processed them — there's no plain-JS fallback the way Lit's or Solid's
839
+ reactivity primitives have. So `mother-mask/qwik` is built with Vite's
840
+ documented ["library" mode](https://qwik.dev/docs/advanced/library/)
841
+ (`qwikVite()` + `build.lib`, see `qwik.vite.config.mjs`) rather than
842
+ tsdown, and the output (`dist/qwik/index.qwik.mjs`) deliberately keeps
843
+ `component$`/`useVisibleTask$` **unprocessed** — exactly like Qwik's own
844
+ documented component-library recipe. A consuming Qwik app's own build
845
+ (which always runs through `qwikVite` too) is what finishes optimizing
846
+ these calls into lazy-loaded segments, scoped to that app. This also means
847
+ `mother-mask/qwik` has no shared module identity/cache with a page's own
848
+ `mother-mask` import, unlike every other framework entry's
849
+ `export * from 'mother-mask'` aliasing.
850
+ - Tests for this entry run under a separate config
851
+ ([`vitest.qwik.config.ts`](../../packages/mother-mask/vitest.qwik.config.ts),
852
+ `bun run test:qwik`) — Qwik's own `@builder.io/qwik/testing` needs the
853
+ `node` environment, not `jsdom` (the two DOM implementations collide),
854
+ and `qwikVite` as a plugin isn't scoped to its own `srcDir`, so adding it
855
+ to the main config broke every other framework's unrelated JSX.
856
+
857
+ ## Inferno
858
+
859
+ Import the `InputMask` and `InputDecimal` class components from the
860
+ separate `mother-mask/inferno` entry:
861
+
862
+ ```js
863
+ import { Component, render } from 'inferno'
864
+ import { InputMask, InputDecimal, formatDecimalValue } from 'mother-mask/inferno'
865
+
866
+ // Keep option objects outside the component so they stay stable across renders.
867
+ const currency = { decimalPlaces: 2, prefix: '$', allowNegative: true }
868
+
869
+ class App extends Component {
870
+ state = { phone: '', amount: '' }
871
+
872
+ render() {
873
+ return (
874
+ <>
875
+ <label for="phone">Phone</label>
876
+ <InputMask
877
+ id="phone"
878
+ name="phone"
879
+ mask="(99) 99999-9999"
880
+ inputMode="tel"
881
+ value={this.state.phone}
882
+ onValueChange={(phone) => this.setState({ phone })}
883
+ />
884
+
885
+ <label for="amount">Amount</label>
886
+ <InputDecimal
887
+ id="amount"
888
+ name="amount"
889
+ options={currency}
890
+ value={this.state.amount}
891
+ onValueChange={(amount) => this.setState({ amount })}
892
+ />
893
+
894
+ <button type="button" onClick={() => this.setState({ amount: formatDecimalValue(1234.5, currency) })}>
895
+ Set amount
896
+ </button>
897
+ </>
898
+ )
899
+ }
900
+ }
901
+
902
+ render(<App />, document.getElementById('root'))
903
+ ```
904
+
905
+ `mother-mask/inferno` re-exports every core function, class, and type as
906
+ well as `InputMask`, `InputDecimal`, `InputMaskProps`, and
907
+ `InputDecimalProps`. Its core exports are aliases to `mother-mask`, sharing
908
+ the same implementation and caches. The core ESM, CommonJS, and UMD builds
909
+ remain independent of this entry. Inferno is an optional peer dependency
910
+ required only when importing `mother-mask/inferno`; the supported version
911
+ range is `^9.0.0`.
912
+
913
+ - Both components accept `value` and `onValueChange`. `mask` and `options`
914
+ work the same as the React version; other props are typed against
915
+ Inferno's own `InputHTMLAttributes<HTMLInputElement>`, so `name`,
916
+ `placeholder`, `inputMode`, and friends all get real autocomplete and
917
+ type-checking. `InputDecimal` always sets `inputmode="decimal"`
918
+ regardless (its own `inputMode` prop is omitted from its type, since
919
+ setting it wouldn't do anything).
920
+ - Binding happens in `componentDidMount` and disposes in
921
+ `componentWillUnmount` — Inferno's server-rendering path
922
+ (`inferno-server`) never calls either, so this is safe to render on the
923
+ server with no `typeof window` guard. `componentDidUpdate` reactively
924
+ rebinds whenever `mask`/`options` change or `value` is set externally; an
925
+ echo of a component's own `onValueChange` is ignored.
926
+ - `InputDecimal` reports `onValueChange(value, numericValue)`.
927
+ - This entry ships without JSX: Inferno's own JSX support needs
928
+ `ts-plugin-inferno`/`babel-plugin-inferno` for correct compile-time vnode
929
+ flags, which isn't worth the extra toolchain for wrapping one leaf
930
+ `<input>` — internally each component calls Inferno's `createVNode`
931
+ directly through a small `h()` helper. This only affects the library's
932
+ own source; consuming apps keep using their own JSX/Babel/TypeScript
933
+ setup exactly as shown above.
934
+
935
+ ## Octane
936
+
937
+ Import the `InputMask` and `InputDecimal` function components from the
938
+ separate `mother-mask/octane` entry:
939
+
940
+ ```tsx
941
+ /** @jsxImportSource octane */
942
+ import { useState } from 'octane'
943
+ import { InputMask, InputDecimal, formatDecimalValue } from 'mother-mask/octane'
944
+
945
+ // Keep option objects outside the component so they stay stable across renders.
946
+ const currency = { decimalPlaces: 2, prefix: '$', allowNegative: true }
947
+
948
+ export function Checkout() {
949
+ const [phone, setPhone] = useState('')
950
+ const [amount, setAmount] = useState('')
951
+
952
+ return (
953
+ <>
954
+ <label for="phone">Phone</label>
955
+ <InputMask
956
+ id="phone"
957
+ name="phone"
958
+ mask="(99) 99999-9999"
959
+ inputMode="tel"
960
+ value={phone}
961
+ onValueChange={setPhone}
962
+ />
963
+
964
+ <label for="amount">Amount</label>
965
+ <InputDecimal id="amount" name="amount" options={currency} value={amount} onValueChange={setAmount} />
966
+
967
+ <button onClick={() => setAmount(formatDecimalValue(1234.5, currency))}>Set amount</button>
968
+ </>
969
+ )
970
+ }
971
+ ```
972
+
973
+ `mother-mask/octane` re-exports every core function, class, and type as
974
+ well as `InputMask`, `InputDecimal`, `InputMaskProps`, and
975
+ `InputDecimalProps`. Its core exports are aliases to `mother-mask`, sharing
976
+ the same implementation and caches. Octane is an optional peer dependency
977
+ required only when importing `mother-mask/octane`; the supported version
978
+ range is `^0.2.0`.
979
+
980
+ - Both components accept `value` and `onValueChange`. `mask` and `options`
981
+ work the same as the React version. Access to the underlying `<input>` is
982
+ through `inputRef` rather than `ref` — see **why this entry ships raw**
983
+ below for why a plain `ref` would collide.
984
+ - Binding happens in `useLayoutEffect`, a hook Octane's server renderer
985
+ never executes — this is safe to render on the server with no
986
+ `typeof window` guard. It reconciles on every commit, rebinding only if
987
+ the mask, options, or an externally-set value actually changed (an echo
988
+ of this component's own `onValueChange` is ignored). The registered
989
+ cleanup guarantees `dispose()` runs exactly once per binding, including
990
+ on unmount.
991
+ - `InputDecimal` defaults to `inputMode="decimal"` and reports
992
+ `onValueChange(value, numericValue)`.
993
+
994
+ **Why this entry ships raw, unlike every other one in this README.**
995
+ Octane compiles function components down to direct DOM-update code keyed to
996
+ compiler-assigned hook slots — there's no virtual DOM diff at runtime, so
997
+ `useState`/`useRef`/`useLayoutEffect` only work correctly once the Octane
998
+ compiler has processed the file. Octane's own publishing guidance for
999
+ component libraries is therefore to distribute source, not a pre-built
1000
+ bundle, and let the *consuming app's* own Octane toolchain (Vite, Rspack,
1001
+ or Rsbuild, all via an official Octane plugin) compile it — that plugin
1002
+ already knows to look inside `node_modules` for Octane-owned files, so no
1003
+ extra bundler configuration is needed on the consumer's side. Accordingly,
1004
+ `"./octane"` in this package's `exports` map points straight at
1005
+ `src/octane/index.ts` (and `src/octane/*.tsx`) rather than at anything in
1006
+ `dist/`, and this is the one entry in this README with no `tsdown` build
1007
+ step of its own. The `/** @jsxImportSource octane */` pragma at the top of
1008
+ each `.tsx` file is what opts a plain-TSX file into Octane's compiler (its
1009
+ Vite/Rspack plugins otherwise only claim `.tsrx` files by default).
1010
+ Tests for this entry run under a separate config
1011
+ ([`vitest.octane.config.ts`](../../packages/mother-mask/vitest.octane.config.ts),
1012
+ `bun run test:octane`) with the Octane Vite plugin scoped to just that one
1013
+ test file, mirroring how Qwik's tests are isolated.
1014
+
1015
+ ## Mithril.js
1016
+
1017
+ Import the `InputMask` and `InputDecimal` closure components from the
1018
+ separate `mother-mask/mithril` entry:
1019
+
1020
+ ```js
1021
+ import m from 'mithril'
1022
+ import { InputMask, InputDecimal, formatDecimalValue } from 'mother-mask/mithril'
1023
+
1024
+ // Keep option objects outside the view so they stay stable across redraws.
1025
+ const currency = { decimalPlaces: 2, prefix: '$', allowNegative: true }
1026
+
1027
+ let phone = ''
1028
+ let amount = ''
1029
+
1030
+ const Checkout = {
1031
+ view: () =>
1032
+ m('div', [
1033
+ m('label', { for: 'phone' }, 'Phone'),
1034
+ m(InputMask, {
1035
+ id: 'phone',
1036
+ name: 'phone',
1037
+ mask: '(99) 99999-9999',
1038
+ inputmode: 'tel',
1039
+ value: phone,
1040
+ onValueChange: (v) => { phone = v },
1041
+ }),
1042
+
1043
+ m('label', { for: 'amount' }, 'Amount'),
1044
+ m(InputDecimal, {
1045
+ id: 'amount',
1046
+ name: 'amount',
1047
+ options: currency,
1048
+ value: amount,
1049
+ onValueChange: (v) => { amount = v },
1050
+ }),
1051
+
1052
+ m('button', { onclick: () => { amount = formatDecimalValue(1234.5, currency) } }, 'Set amount'),
1053
+ ]),
1054
+ }
1055
+
1056
+ m.mount(document.body, Checkout)
1057
+ ```
1058
+
1059
+ `mother-mask/mithril` re-exports every core function, class, and type as
1060
+ well as `InputMask`, `InputDecimal`, `InputMaskAttrs`, and
1061
+ `InputDecimalAttrs`. Its core exports are aliases to `mother-mask`, sharing
1062
+ the same implementation and caches. The core ESM, CommonJS, and UMD builds
1063
+ remain independent of this entry. Mithril is an optional peer dependency
1064
+ required only when importing `mother-mask/mithril`; the supported version
1065
+ range is `^2.0.0`.
1066
+
1067
+ - Both components accept `value` and `onValueChange`; `mask` and `options`
1068
+ work the same as the React version.
1069
+ - Binding happens in `oncreate` and disposes in `onremove` — Mithril's
1070
+ server-side rendering never calls either, so this is safe to render on
1071
+ the server with no `typeof window` guard. `onupdate` reactively rebinds
1072
+ whenever `mask`/`options` change or `value` is set externally, ignoring
1073
+ an echo of this component's own `onValueChange`. Elements with any of
1074
+ these three hooks are exempt from Mithril's DOM-node recycling, so the
1075
+ same `<input>` is reused across redraws for as long as the vnode stays
1076
+ mounted.
1077
+ - `value` is deliberately never forwarded as an `m('input', ...)`
1078
+ attribute: Mithril's vdom diff would otherwise reassign `.value` from
1079
+ `vnode.attrs` on every redraw, fighting the mask's own intermediate
1080
+ edits — the wrapper controls the DOM value directly instead, exactly
1081
+ like every other framework entry in this package.
1082
+ - `InputDecimal` always sets `inputmode="decimal"` and reports
1083
+ `onValueChange(value, numericValue)`.
1084
+
1085
+ ## Ember.js
1086
+
1087
+ Import the `maskInput` and `maskDecimal` modifiers from the separate
1088
+ `mother-mask/ember` entry:
1089
+
1090
+ ```hbs
1091
+ {{! app/components/checkout.gjs / .gts, or a classic template }}
1092
+ import { maskInput, maskDecimal } from 'mother-mask/ember';
1093
+ import { formatDecimalValue } from 'mother-mask';
1094
+
1095
+ <template>
1096
+ <label for="phone">Phone</label>
1097
+ <input
1098
+ id="phone"
1099
+ name="phone"
1100
+ {{maskInput "(99) 99999-9999" value=this.phone onValueChange=this.setPhone}}
1101
+ />
1102
+
1103
+ <label for="amount">Amount</label>
1104
+ <input
1105
+ id="amount"
1106
+ name="amount"
1107
+ {{maskDecimal options=this.currency value=this.amount onValueChange=this.setAmount}}
1108
+ />
1109
+
1110
+ <button type="button" {{on "click" this.setPreset}}>Set amount</button>
1111
+ </template>
1112
+ ```
1113
+
1114
+ ```js
1115
+ import Component from '@glimmer/component';
1116
+ import { tracked } from '@glimmer/tracking';
1117
+ import { action } from '@ember/object';
1118
+
1119
+ export default class Checkout extends Component {
1120
+ // Keep option objects outside the class body's reactive graph so they
1121
+ // stay stable across renders — a fresh object literal here would read as
1122
+ // a changed `options` argument on every re-render.
1123
+ currency = { decimalPlaces: 2, prefix: '$', allowNegative: true };
1124
+
1125
+ @tracked phone = '';
1126
+ @tracked amount = '';
1127
+
1128
+ @action setPhone(value) { this.phone = value; }
1129
+ @action setAmount(value) { this.amount = value; }
1130
+ @action setPreset() { this.amount = formatDecimalValue(1234.5, this.currency); }
1131
+ }
1132
+ ```
1133
+
1134
+ `mother-mask/ember` re-exports every core function, class, and type as well
1135
+ as `maskInput`, `maskDecimal`, `maskInputModifier`, `maskDecimalModifier`,
1136
+ `MaskInputNamedArgs`, and `MaskDecimalNamedArgs`. Its core exports are
1137
+ aliases to `mother-mask`, sharing the same implementation and caches. The
1138
+ core ESM, CommonJS, and UMD builds remain independent of this entry.
1139
+ `ember-modifier` is an optional peer dependency required only when
1140
+ importing `mother-mask/ember`; the supported version range is `^4.0.0`
1141
+ (ships in every app generated by current `ember-cli`).
1142
+
1143
+ - `maskInput` takes the mask as its one positional argument
1144
+ (`{{maskInput "999-999" ...}}`); `maskDecimal` takes none. Both accept
1145
+ `value` and `onValueChange` as named arguments, plus `options`.
1146
+ - Binding happens the first time the element the modifier is attached to is
1147
+ inserted into the document, and only on the client: Ember's server-side
1148
+ rendering (FastBoot) renders to a string and never attaches real elements
1149
+ or runs modifiers, so there is no `typeof window` guard to write.
1150
+ `ember-modifier`'s own auto-tracking reruns the modifier — tearing down
1151
+ the previous binding first — whenever an argument it reads changes,
1152
+ including the mask, `options`, or an externally-set `value`.
1153
+ - Unlike every other framework entry in this README, that teardown-then-
1154
+ rerun happens unconditionally on **every** value change, including an
1155
+ echo of the modifier's own `onValueChange` — there's no hook to skip it
1156
+ from inside the modifier, since Ember has already torn down the previous
1157
+ binding by the time it runs again. This stays visually seamless anyway:
1158
+ formatting an already-formatted value is a no-op (the element's `.value`
1159
+ is never reassigned, so the caret never moves), and removing/re-adding
1160
+ the underlying event listeners doesn't touch focus or selection.
1161
+ - `maskDecimal` always sets `inputmode="decimal"` and reports
1162
+ `onValueChange(value, numericValue)`.
1163
+ - The masking logic lives in `maskInputModifier`/`maskDecimalModifier` —
1164
+ plain functions with the exact `(element, positional, named)` shape
1165
+ `ember-modifier` calls a function-based modifier with, exported
1166
+ separately from the `modifier()`-wrapped `maskInput`/`maskDecimal` so
1167
+ this package's own test suite can exercise them directly against a plain
1168
+ `HTMLInputElement`. That split exists because `ember-modifier` imports
1169
+ Ember framework internals (`@ember/application`, `@ember/modifier`,
1170
+ `@ember/destroyable`) that only resolve inside a real Ember app's build —
1171
+ they aren't separately installable npm packages — so the wrapped
1172
+ `modifier()` exports are verified at the type level (`tsc --noEmit`
1173
+ against `ember-modifier`'s published types) rather than by a runtime
1174
+ test in this repository.
1175
+
1176
+ ## Knockout.js
1177
+
1178
+ Import the separate `mother-mask/knockout` entry once, for its side effect
1179
+ of registering the `mask` and `maskDecimal` binding handlers on
1180
+ `ko.bindingHandlers`:
1181
+
1182
+ ```js
1183
+ import ko from 'knockout'
1184
+ import 'mother-mask/knockout'
1185
+ import { formatDecimalValue } from 'mother-mask'
1186
+
1187
+ // Keep option objects outside the view model's observables so they stay
1188
+ // stable across recomputes.
1189
+ const currency = { decimalPlaces: 2, prefix: '$', allowNegative: true }
1190
+
1191
+ function CheckoutViewModel() {
1192
+ this.phone = ko.observable('')
1193
+ this.amount = ko.observable('')
1194
+ this.setPreset = () => this.amount(formatDecimalValue(1234.5, currency))
1195
+ }
1196
+
1197
+ ko.applyBindings(new CheckoutViewModel(), document.getElementById('app'))
1198
+ ```
1199
+
1200
+ ```html
1201
+ <div id="app">
1202
+ <label for="phone">Phone</label>
1203
+ <input id="phone" name="phone"
1204
+ data-bind="mask: { mask: '(99) 99999-9999', value: phone, onValueChange: phone }">
1205
+
1206
+ <label for="amount">Amount</label>
1207
+ <input id="amount" name="amount"
1208
+ data-bind="maskDecimal: { options: { decimalPlaces: 2, prefix: '$', allowNegative: true }, value: amount, onValueChange: amount }">
1209
+
1210
+ <button data-bind="click: setPreset">Set amount</button>
1211
+ </div>
1212
+ ```
1213
+
1214
+ `mother-mask/knockout` re-exports every core function, class, and type as
1215
+ well as `maskBindingHandler`, `maskDecimalBindingHandler`,
1216
+ `MaskBindingConfig`, and `MaskDecimalBindingConfig`. Its core exports are
1217
+ aliases to `mother-mask`, sharing the same implementation and caches. The
1218
+ core ESM, CommonJS, and UMD builds remain independent of this entry.
1219
+ Knockout is an optional peer dependency required only when importing
1220
+ `mother-mask/knockout`; the supported version range is `^3.5.0`.
1221
+
1222
+ - Both bindings take a single object: `value` (a plain string, a Knockout
1223
+ observable/computed, or any accessor `ko.unwrap` understands) and
1224
+ `onValueChange`. `mask` and `options` work the same as the React version
1225
+ — `onValueChange` in the example above is passed directly as the
1226
+ observable itself (`phone`/`amount`), since calling an observable with an
1227
+ argument is how Knockout writes to it.
1228
+ - `init` binds once the element is live; `update` re-runs whenever an
1229
+ observable read while evaluating the binding's object literal changes —
1230
+ Knockout's own dependency tracking — rebinding only if the mask, options,
1231
+ or an externally-set value actually changed. An echo of the binding's own
1232
+ `onValueChange` is ignored.
1233
+ - `ko.utils.domNodeDisposal.addDisposeCallback` guarantees `dispose()` runs
1234
+ exactly once, both before every rebind and when Knockout removes the
1235
+ element (`ko.removeNode`, `ko.cleanNode`, or an `if`/`foreach`/template
1236
+ removing it). Knockout never touches real DOM nodes during any
1237
+ server-side step, since it has none — `applyBindings` only ever runs
1238
+ against a live document, so there is no `typeof window` guard to write.
1239
+ - `maskDecimal` always sets `inputmode="decimal"` and reports
1240
+ `onValueChange(value, numericValue)`.
1241
+ - Binding state (the live `dispose()`, the last-emitted value, the last
1242
+ mask/options) lives in a `WeakMap` keyed by the element, not in the
1243
+ handler's own closure: `ko.bindingHandlers.mask` is one object shared
1244
+ across every element that uses the binding, unlike a fresh-per-instance
1245
+ factory (a React hook call, Vue's `setup()`, …) elsewhere in this package.
1246
+
1247
+ ## Riot.js
1248
+
1249
+ Import the `maskInput` and `maskDecimal` pure-component factories from the
1250
+ separate `mother-mask/riot` entry:
1251
+
1252
+ ```js
1253
+ import { pure } from 'riot'
1254
+ import { maskInput, maskDecimal, formatDecimalValue } from 'mother-mask/riot'
1255
+
1256
+ // Keep option objects module-level so they stay stable across updates.
1257
+ const currency = { decimalPlaces: 2, prefix: '$', allowNegative: true }
1258
+
1259
+ let phone = ''
1260
+ const phoneField = pure(maskInput)({
1261
+ props: {
1262
+ mask: '(99) 99999-9999',
1263
+ name: 'phone',
1264
+ inputMode: 'tel',
1265
+ autocomplete: 'tel',
1266
+ value: phone,
1267
+ onValueChange: (v) => (phone = v),
1268
+ },
1269
+ })
1270
+ phoneField.mount(document.getElementById('phone'))
1271
+
1272
+ let amount = ''
1273
+ const amountField = pure(maskDecimal)({
1274
+ props: { options: currency, name: 'amount', value: amount, onValueChange: (v) => (amount = v) },
1275
+ })
1276
+ amountField.mount(document.getElementById('amount'))
1277
+
1278
+ // Later, e.g. from a parent Riot component's onUpdated:
1279
+ amountField.update({ options: currency, value: formatDecimalValue(1234.5, currency) })
1280
+ ```
1281
+
1282
+ ```html
1283
+ <label for="phone">Phone</label>
1284
+ <span id="phone"></span>
1285
+
1286
+ <label for="amount">Amount</label>
1287
+ <span id="amount"></span>
1288
+ ```
1289
+
1290
+ `mother-mask/riot` re-exports every core function, class, and type as well
1291
+ as `maskInput`, `maskDecimal`, `MaskInputProps`, and `MaskDecimalProps`. Its
1292
+ core exports are aliases to `mother-mask`, sharing the same implementation
1293
+ and caches. The core ESM, CommonJS, and UMD builds remain independent of
1294
+ this entry. Riot is an optional peer dependency required only when
1295
+ importing `mother-mask/riot`; the supported version range is `^10.0.0`.
1296
+
1297
+ - Both factories accept `value` and `onValueChange`; `mask` (on `maskInput`
1298
+ only) and `options` work the same as the React version. A curated set of
1299
+ other props forward as plain attributes: `name`, `placeholder`,
1300
+ `inputMode` (rendered as `inputmode`, `maskInput` only — `maskDecimal`
1301
+ always sets it to `"decimal"`), `autocomplete`, `disabled`, `readonly`,
1302
+ and `required`.
1303
+ - Renders into light DOM: the `<input>` is appended as a real child of the
1304
+ host element the component mounts onto, so a page's own `<label for>` and
1305
+ global CSS reach it directly, the same as this package's Lit/Stencil/Web
1306
+ Components entries. A host `id` is moved onto the `<input>` once, right
1307
+ after mounting, for the same reason those entries do it.
1308
+ - `maskDecimal` always sets `inputmode="decimal"` and reports
1309
+ `onValueChange(value, numericValue)`.
1310
+
1311
+ **Why this entry uses Riot's `pure()` instead of a compiled `.riot`
1312
+ component.** `pure()` is Riot's own documented escape hatch for
1313
+ mounting non-Riot-templated content — third-party libraries, plain DOM —
1314
+ as a node in a Riot component tree, bypassing the `.riot`-file/compiler
1315
+ pipeline entirely; that's a direct fit here, since this wrapper's only job
1316
+ is to own one plain `<input>` imperatively, exactly like every other
1317
+ framework entry in this package. `riot.pure`'s own lifecycle names —
1318
+ `mount`/`update`/`unmount` — serve the same purpose as a full `.riot`
1319
+ component's `onMounted`/`onUnmounted`, with one difference worth calling
1320
+ out: `update()` isn't automatically re-invoked by reactive tracking the
1321
+ way `onUpdated` is on a full component. A parent must call this
1322
+ component's own `update(props)` explicitly (typically from its own
1323
+ `onUpdated`, or right after changing props) to reformat for a new
1324
+ `mask`/`options`/`value` — `sync()` still rebinds only if the mask,
1325
+ options, or an externally-set value actually changed, and still ignores an
1326
+ echo of this component's own `onValueChange`, exactly like every other
1327
+ framework entry. `mount`/`update`/`unmount` only ever run on the client:
1328
+ Riot has no built-in server-side renderer of its own, so there is no
1329
+ `typeof window` guard to write.
1330
+
172
1331
  ## Decimal Inputs
173
1332
 
174
1333
  Use `bindDecimal` for numbers, currency fields, and values where the integer part should grow freely.