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