ngwr 13.0.0 → 14.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -6
- package/color-picker/styles/_index.scss +43 -5
- package/fesm2022/ngwr-alert.mjs +5 -5
- package/fesm2022/ngwr-alert.mjs.map +1 -1
- package/fesm2022/ngwr-back-top.mjs +11 -1
- package/fesm2022/ngwr-back-top.mjs.map +1 -1
- package/fesm2022/ngwr-blur-text.mjs +13 -2
- package/fesm2022/ngwr-blur-text.mjs.map +1 -1
- package/fesm2022/ngwr-border-glow.mjs +12 -3
- package/fesm2022/ngwr-border-glow.mjs.map +1 -1
- package/fesm2022/ngwr-breadcrumbs.mjs +14 -3
- package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
- package/fesm2022/ngwr-button.mjs +4 -4
- package/fesm2022/ngwr-button.mjs.map +1 -1
- package/fesm2022/ngwr-calendar-testing.mjs +15 -0
- package/fesm2022/ngwr-calendar-testing.mjs.map +1 -1
- package/fesm2022/ngwr-calendar.mjs +51 -5
- package/fesm2022/ngwr-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-circular-text.mjs +20 -1
- package/fesm2022/ngwr-circular-text.mjs.map +1 -1
- package/fesm2022/ngwr-collapse.mjs.map +1 -1
- package/fesm2022/ngwr-color-picker-testing.mjs +23 -7
- package/fesm2022/ngwr-color-picker-testing.mjs.map +1 -1
- package/fesm2022/ngwr-color-picker.mjs +89 -10
- package/fesm2022/ngwr-color-picker.mjs.map +1 -1
- package/fesm2022/ngwr-command-palette.mjs +27 -3
- package/fesm2022/ngwr-command-palette.mjs.map +1 -1
- package/fesm2022/ngwr-context-menu.mjs +13 -3
- package/fesm2022/ngwr-context-menu.mjs.map +1 -1
- package/fesm2022/ngwr-date-adapters-fns.mjs +101 -33
- package/fesm2022/ngwr-date-adapters-fns.mjs.map +1 -1
- package/fesm2022/ngwr-date-picker-testing.mjs +10 -2
- package/fesm2022/ngwr-date-picker-testing.mjs.map +1 -1
- package/fesm2022/ngwr-date-picker.mjs +103 -13
- package/fesm2022/ngwr-date-picker.mjs.map +1 -1
- package/fesm2022/ngwr-date.mjs +480 -79
- package/fesm2022/ngwr-date.mjs.map +1 -1
- package/fesm2022/ngwr-dialog.mjs +22 -16
- package/fesm2022/ngwr-dialog.mjs.map +1 -1
- package/fesm2022/ngwr-directives.mjs +5 -2
- package/fesm2022/ngwr-directives.mjs.map +1 -1
- package/fesm2022/ngwr-drag-drop.mjs +30 -2
- package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
- package/fesm2022/ngwr-drawer.mjs +24 -18
- package/fesm2022/ngwr-drawer.mjs.map +1 -1
- package/fesm2022/ngwr-dropdown.mjs +8 -3
- package/fesm2022/ngwr-dropdown.mjs.map +1 -1
- package/fesm2022/ngwr-event-calendar-testing.mjs.map +1 -1
- package/fesm2022/ngwr-event-calendar.mjs +1 -1
- package/fesm2022/ngwr-event-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-file-upload.mjs +52 -12
- package/fesm2022/ngwr-file-upload.mjs.map +1 -1
- package/fesm2022/ngwr-fuzzy-text.mjs +21 -4
- package/fesm2022/ngwr-fuzzy-text.mjs.map +1 -1
- package/fesm2022/ngwr-gradient-text.mjs +9 -2
- package/fesm2022/ngwr-gradient-text.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-en.mjs +63 -1
- package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ru.mjs +28 -1
- package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
- package/fesm2022/ngwr-i18n.mjs +150 -22
- package/fesm2022/ngwr-i18n.mjs.map +1 -1
- package/fesm2022/ngwr-icon-adapters-feather.mjs +11 -0
- package/fesm2022/ngwr-icon-adapters-feather.mjs.map +1 -1
- package/fesm2022/ngwr-icon-adapters-lucide.mjs +22 -2
- package/fesm2022/ngwr-icon-adapters-lucide.mjs.map +1 -1
- package/fesm2022/ngwr-icon.mjs +474 -37
- package/fesm2022/ngwr-icon.mjs.map +1 -1
- package/fesm2022/ngwr-input-number.mjs +3 -3
- package/fesm2022/ngwr-input-number.mjs.map +1 -1
- package/fesm2022/ngwr-input-otp.mjs +21 -3
- package/fesm2022/ngwr-input-otp.mjs.map +1 -1
- package/fesm2022/ngwr-input.mjs +14 -7
- package/fesm2022/ngwr-input.mjs.map +1 -1
- package/fesm2022/ngwr-knob-testing.mjs.map +1 -1
- package/fesm2022/ngwr-knob.mjs +20 -2
- package/fesm2022/ngwr-knob.mjs.map +1 -1
- package/fesm2022/ngwr-loading-bar-router.mjs +73 -0
- package/fesm2022/ngwr-loading-bar-router.mjs.map +1 -0
- package/fesm2022/ngwr-loading-bar.mjs +21 -32
- package/fesm2022/ngwr-loading-bar.mjs.map +1 -1
- package/fesm2022/ngwr-markdown.mjs +63 -11
- package/fesm2022/ngwr-markdown.mjs.map +1 -1
- package/fesm2022/ngwr-mention.mjs +104 -14
- package/fesm2022/ngwr-mention.mjs.map +1 -1
- package/fesm2022/ngwr-overlay.mjs +36 -7
- package/fesm2022/ngwr-overlay.mjs.map +1 -1
- package/fesm2022/ngwr-pagination-testing.mjs +11 -10
- package/fesm2022/ngwr-pagination-testing.mjs.map +1 -1
- package/fesm2022/ngwr-pagination.mjs +65 -30
- package/fesm2022/ngwr-pagination.mjs.map +1 -1
- package/fesm2022/ngwr-popconfirm.mjs +3 -2
- package/fesm2022/ngwr-popconfirm.mjs.map +1 -1
- package/fesm2022/ngwr-popover.mjs +2 -1
- package/fesm2022/ngwr-popover.mjs.map +1 -1
- package/fesm2022/ngwr-qr.mjs +38 -1
- package/fesm2022/ngwr-qr.mjs.map +1 -1
- package/fesm2022/ngwr-select.mjs +93 -34
- package/fesm2022/ngwr-select.mjs.map +1 -1
- package/fesm2022/ngwr-shiny-text.mjs +10 -3
- package/fesm2022/ngwr-shiny-text.mjs.map +1 -1
- package/fesm2022/ngwr-splitter-testing.mjs.map +1 -1
- package/fesm2022/ngwr-squircle.mjs +2 -1
- package/fesm2022/ngwr-squircle.mjs.map +1 -1
- package/fesm2022/ngwr-statistic.mjs +35 -4
- package/fesm2022/ngwr-statistic.mjs.map +1 -1
- package/fesm2022/ngwr-table.mjs +35 -12
- package/fesm2022/ngwr-table.mjs.map +1 -1
- package/fesm2022/ngwr-tabs-router.mjs +158 -0
- package/fesm2022/ngwr-tabs-router.mjs.map +1 -0
- package/fesm2022/ngwr-tabs-testing.mjs +13 -11
- package/fesm2022/ngwr-tabs-testing.mjs.map +1 -1
- package/fesm2022/ngwr-tabs.mjs +104 -12
- package/fesm2022/ngwr-tabs.mjs.map +1 -1
- package/fesm2022/ngwr-textarea.mjs +20 -1
- package/fesm2022/ngwr-textarea.mjs.map +1 -1
- package/fesm2022/ngwr-tour.mjs +1 -1
- package/fesm2022/ngwr-tour.mjs.map +1 -1
- package/fesm2022/ngwr-transfer.mjs +2 -2
- package/fesm2022/ngwr-transfer.mjs.map +1 -1
- package/fesm2022/ngwr-tree.mjs +18 -2
- package/fesm2022/ngwr-tree.mjs.map +1 -1
- package/fesm2022/ngwr-utils.mjs +143 -1
- package/fesm2022/ngwr-utils.mjs.map +1 -1
- package/fesm2022/ngwr-version.mjs +1 -1
- package/fesm2022/ngwr-version.mjs.map +1 -1
- package/fesm2022/ngwr-window-testing.mjs +10 -2
- package/fesm2022/ngwr-window-testing.mjs.map +1 -1
- package/fesm2022/ngwr-window.mjs +4 -4
- package/fesm2022/ngwr-window.mjs.map +1 -1
- package/i18n/en.json +33 -6
- package/i18n/ru.json +33 -6
- package/llms-full.txt +20 -11
- package/llms.txt +47 -16
- package/mcp/catalog.js +3 -3
- package/mcp/providers.js +11 -0
- package/package.json +11 -3
- package/schematics/migrations/v13/index.js +101 -0
- package/schematics/migrations/v14/index.js +422 -0
- package/schematics/migrations.json +10 -0
- package/schematics/page/templates.js +1 -1
- package/schematics/use/symbol-map.json +1 -0
- package/skills/ngwr/SKILL.md +4 -1
- package/skills/ngwr/references/catalog.md +2 -0
- package/skills/ngwr/references/setup.md +4 -0
- package/stepper/styles/_index.scss +19 -1
- package/textarea/styles/_index.scss +14 -6
- package/toast/styles/_index.scss +12 -4
- package/types/ngwr-alert.d.ts +3 -3
- package/types/ngwr-blur-text.d.ts +8 -0
- package/types/ngwr-breadcrumbs.d.ts +1 -0
- package/types/ngwr-button.d.ts +2 -2
- package/types/ngwr-calendar-testing.d.ts +21 -4
- package/types/ngwr-calendar.d.ts +37 -0
- package/types/ngwr-circular-text.d.ts +9 -0
- package/types/ngwr-collapse.d.ts +19 -15
- package/types/ngwr-color-picker-testing.d.ts +10 -4
- package/types/ngwr-color-picker.d.ts +70 -2
- package/types/ngwr-command-palette.d.ts +14 -0
- package/types/ngwr-context-menu.d.ts +1 -1
- package/types/ngwr-date-adapters-fns.d.ts +71 -29
- package/types/ngwr-date-picker-testing.d.ts +206 -185
- package/types/ngwr-date-picker.d.ts +7 -0
- package/types/ngwr-date.d.ts +143 -13
- package/types/ngwr-drag-drop.d.ts +19 -0
- package/types/ngwr-dropdown.d.ts +1 -1
- package/types/ngwr-event-calendar-testing.d.ts +12 -9
- package/types/ngwr-file-upload.d.ts +31 -3
- package/types/ngwr-fuzzy-text.d.ts +5 -0
- package/types/ngwr-i18n.d.ts +89 -7
- package/types/ngwr-icon-adapters-feather.d.ts +11 -0
- package/types/ngwr-icon.d.ts +42 -1
- package/types/ngwr-input-number.d.ts +1 -1
- package/types/ngwr-input-otp.d.ts +3 -0
- package/types/ngwr-input.d.ts +11 -4
- package/types/ngwr-knob-testing.d.ts +8 -4
- package/types/ngwr-knob.d.ts +18 -0
- package/types/ngwr-loading-bar-router.d.ts +35 -0
- package/types/ngwr-loading-bar.d.ts +19 -9
- package/types/ngwr-mention.d.ts +13 -0
- package/types/ngwr-pagination-testing.d.ts +11 -10
- package/types/ngwr-pagination.d.ts +39 -10
- package/types/ngwr-popconfirm.d.ts +1 -1
- package/types/ngwr-popover.d.ts +1 -1
- package/types/ngwr-select.d.ts +65 -10
- package/types/ngwr-shiny-text.d.ts +1 -0
- package/types/ngwr-splitter-testing.d.ts +9 -4
- package/types/ngwr-squircle.d.ts +1 -1
- package/types/ngwr-statistic.d.ts +22 -2
- package/types/ngwr-table.d.ts +24 -5
- package/types/ngwr-tabs-router.d.ts +67 -0
- package/types/ngwr-tabs-testing.d.ts +12 -10
- package/types/ngwr-tabs.d.ts +103 -6
- package/types/ngwr-textarea.d.ts +9 -0
- package/types/ngwr-tree.d.ts +7 -0
- package/types/ngwr-utils.d.ts +79 -1
- package/types/ngwr-version.d.ts +1 -1
- package/types/ngwr-window-testing.d.ts +11 -3
- package/types/ngwr-window.d.ts +11 -4
- package/window/styles/_index.scss +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-icon.mjs","sources":["../../../projects/lib/icon/tokens/icon.token.ts","../../../projects/lib/icon/icon-registry.ts","../../../projects/lib/icon/icon.ts","../../../projects/lib/icon/utils/validate-icon.ts","../../../projects/lib/icon/providers/provide-wr-icons.ts","../../../projects/lib/icon/svg-icon.ts","../../../projects/lib/icon/ngwr-icon.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { InjectionToken } from '@angular/core';\n\nimport type { WrIconDef } from '../interfaces';\n\n/**\n * Multi-provider token for registering icons with the icon registry.\n *\n * Use {@link provideWrIcons} instead of providing this token directly.\n *\n * @internal\n */\nexport const WR_ICONS = new InjectionToken<readonly WrIconDef[][]>('WR_ICONS');\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Injectable, inject } from '@angular/core';\n\nimport type { WrIconDef } from './interfaces';\nimport { WR_ICONS } from './tokens';\n\n/**\n * One link in the icon-registry chain. {@link provideWrIcons} provides an\n * instance alongside its icons, so every injector level that registers icons\n * gets its own link whose parent is the next registering level above it.\n *\n * This exists because Angular does not merge `multi: true` providers across\n * injector levels — an element-level `provideWrIcons()` *replaces* the visible\n * `WR_ICONS` collection rather than adding to it. Reading only the closest\n * contribution silently hid every icon registered further up, which contradicts\n * the \"each call adds to the registry\" contract. Walking the chain restores it\n * for any number of levels.\n *\n * @internal\n */\n@Injectable()\nexport class WrIconRegistry {\n /** The next registering level up, if any. */\n private readonly parent = inject(WrIconRegistry, { skipSelf: true, optional: true });\n\n /** Icons registered at *this* level only. */\n private readonly own = inject(WR_ICONS, { self: true, optional: true }) ?? [];\n\n /**\n * Every icon visible from this level, nearest last so a local registration\n * wins over an identically named one further up.\n */\n resolve(): Map<string, WrIconDef> {\n const map = this.parent?.resolve() ?? new Map<string, WrIconDef>();\n for (const set of this.own) {\n for (const icon of set) map.set(icon.name, icon);\n }\n return map;\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Component, ElementRef, ViewEncapsulation, computed, effect, inject, input, isDevMode } from '@angular/core';\n\nimport { WrIconRegistry } from './icon-registry';\nimport type { WrIconDef, WrIconName } from './interfaces';\n\n/**\n * Renders a registered SVG icon.\n *\n * Icons must be registered via {@link provideWrIcons} before use —\n * either at the application root or on any ancestor component.\n *\n * @example\n * ```html\n * <wr-icon name=\"home\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/icon\n */\n@Component({\n selector: 'wr-icon',\n template: '',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-icon',\n '[attr.data-icon]': 'name()',\n },\n})\nexport class WrIcon {\n readonly name = input.required<WrIconName>();\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n\n /**\n * Every icon visible from this position, walked up the whole chain of\n * registering injector levels.\n *\n * Angular's `inject(WR_ICONS)` returns only the **closest** injector's\n * contribution — `multi` providers are not merged across levels — so a\n * component that registers its own icons used to shadow, not extend, what an\n * ancestor registered. {@link WrIconRegistry} is provided once per\n * registering level and links to its parent, so nesting any number of levels\n * now adds up, with the nearest registration winning a name collision.\n *\n * Optional: with no `provideWrIcons()` anywhere the map is simply empty and\n * every name reports as unregistered.\n */\n private readonly iconRegistry = inject(WrIconRegistry, { optional: true });\n\n private readonly registry = computed<ReadonlyMap<WrIconName, WrIconDef>>(\n () => (this.iconRegistry?.resolve() ?? new Map()) as ReadonlyMap<WrIconName, WrIconDef>\n );\n\n private readonly icon = computed(() => this.registry().get(this.name()));\n\n constructor() {\n effect(() => {\n const icon = this.icon();\n const name = this.name();\n\n if (!icon) {\n if (isDevMode()) {\n // Reported, never thrown. An exception here escapes into\n // `runEffectsInView`, which abandons the remaining effects of the\n // whole view — one unknown name would blank every `<wr-icon>` after\n // it while the console blamed only the first. Error level (not\n // `badgeLog`) so it survives a console filtered to errors.\n // eslint-disable-next-line no-console -- misconfiguration must be visible, but must not throw\n console.error(\n `[NGWR] No icon named \"${name}\" is registered. Did you forget to call provideWrIcons()? ` +\n `If the name IS registered, check the injector level: a component-level provideWrIcons() ` +\n `shadows icons registered on an ancestor component.`\n );\n }\n this.host.nativeElement.innerHTML = '';\n return;\n }\n\n this.host.nativeElement.innerHTML = icon.data;\n\n // Every hand-written inline SVG in the library carries aria-hidden;\n // registered icons went in without it, so a decorative glyph could be\n // announced — usually as a bare \"graphic\" beside the label it decorates.\n // Consumers that need a name put it on the interactive host instead.\n this.host.nativeElement.querySelector('svg')?.setAttribute('aria-hidden', 'true');\n });\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport type { WrIconDef } from '../interfaces';\n\nconst VIEW_BOX_RE = /<svg\\b[^>]*\\bviewBox\\s*=/i;\nconst SVG_ROOT_RE = /<svg\\b/i;\n\n/**\n * Dev-mode sanity check for a registered icon.\n *\n * Warns about issues that prevent correct rendering — most importantly,\n * a missing `viewBox`, which causes the SVG not to scale to the icon\n * host's size.\n *\n * Production builds drop the call entirely via `isDevMode()` tree-shaking.\n *\n * @internal\n */\nexport function validateIcon(icon: WrIconDef): void {\n if (!SVG_ROOT_RE.test(icon.data)) {\n // eslint-disable-next-line no-console -- dev-mode validation\n console.warn(\n `[NGWR] Icon \"${icon.name}\" data does not contain an <svg> root element. ` +\n `Custom icons must be valid SVG markup.`\n );\n return;\n }\n\n if (!VIEW_BOX_RE.test(icon.data)) {\n // eslint-disable-next-line no-console -- dev-mode validation\n console.warn(\n `[NGWR] Icon \"${icon.name}\" has no viewBox attribute — it won't scale ` +\n `to the host size. Add viewBox=\"0 0 W H\" to the <svg> root.`\n );\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { type Provider, isDevMode } from '@angular/core';\n\nimport { WrIconRegistry } from '../icon-registry';\nimport type { WrIconDef } from '../interfaces';\nimport { WR_ICONS } from '../tokens';\nimport { validateIcon } from '../utils';\n\n/**\n * Registers a set of icons for use by `<wr-icon>`.\n *\n * Can be called multiple times — each call adds to the registry.\n * Works at both the application root and at the component level,\n * so a feature module or a single component can bring its own icons\n * without polluting the global config.\n *\n * In dev mode, each icon is validated for common issues (missing\n * `viewBox`, malformed root). Validation is dropped from production\n * builds via `isDevMode()` tree-shaking.\n *\n * @example\n * ```ts\n * // App-level registration\n * import { home, user, cog, provideWrIcons } from 'ngwr/icon';\n *\n * export const appConfig: ApplicationConfig = {\n * providers: [\n * provideWrIcons([home, user, cog]),\n * ],\n * };\n * ```\n *\n * @example\n * ```ts\n * // Component-level registration\n * import { logoGithub, logoNpm, provideWrIcons, WrIconDef } from 'ngwr/icon';\n *\n * @Component({\n * selector: 'ngwr-header',\n * imports: [WrIcon],\n * providers: [provideWrIcons([logoGithub, logoNpm])],\n * template: `<wr-icon name=\"logo-github\" />`,\n * })\n * export class HeaderComponent {}\n * ```\n */\nexport function provideWrIcons(icons: WrIconDef[]): Provider[] {\n if (isDevMode()) {\n for (const icon of icons) validateIcon(icon);\n }\n return [\n { provide: WR_ICONS, useValue: icons, multi: true },\n // A registry link per registering level. Angular does not merge `multi`\n // providers across injectors, so without this chain an element-level\n // registration would shadow everything registered above it instead of\n // adding to it.\n WrIconRegistry,\n ];\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport type { WrIconDef } from './interfaces';\n\n/**\n * Wrap a raw SVG string in the {@link WrIconDef} envelope. Use this when\n * importing icons from sources that ship full `<svg>` files — Tabler,\n * Phosphor, Heroicons, Iconoir, Radix, Bootstrap Icons, your designer.\n *\n * Pair with a bundler's raw-SVG import (Vite's `?raw`, Webpack's\n * `raw-loader`, esbuild's `--loader:.svg=text`) so the SVG file becomes\n * a string at build time.\n *\n * For sets with a non-SVG shape (Lucide's IconNode tuples, Feather's\n * inner-only SVG), use the dedicated adapter under\n * `ngwr/icon/adapters/<set>` instead.\n *\n * @example\n * ```ts\n * import { svgIcon, provideWrIcons } from 'ngwr/icon';\n * // Vite: the `?raw` suffix returns the file as a string.\n * import plusSvg from '@tabler/icons/icons/plus.svg?raw';\n * import phosphorPlusSvg from '@phosphor-icons/core/assets/regular/plus.svg?raw';\n *\n * bootstrapApplication(AppComponent, {\n * providers: [\n * provideWrIcons([\n * svgIcon('plus', plusSvg),\n * svgIcon('plus-phosphor', phosphorPlusSvg),\n * ]),\n * ],\n * });\n * ```\n */\nexport function svgIcon(name: string, svg: string): WrIconDef {\n return { name, data: svg };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-icon.mjs","sources":["../../../projects/lib/icon/tokens/icon.token.ts","../../../projects/lib/icon/icon-registry.ts","../../../projects/lib/icon/utils/sanitize-icon.ts","../../../projects/lib/icon/utils/validate-icon.ts","../../../projects/lib/icon/icon.ts","../../../projects/lib/icon/providers/provide-wr-icons.ts","../../../projects/lib/icon/svg-icon.ts","../../../projects/lib/icon/ngwr-icon.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { InjectionToken } from '@angular/core';\n\nimport type { WrIconDef } from '../interfaces';\n\n/**\n * Multi-provider token for registering icons with the icon registry.\n *\n * Use {@link provideWrIcons} instead of providing this token directly.\n *\n * @internal\n */\nexport const WR_ICONS = new InjectionToken<readonly WrIconDef[][]>('WR_ICONS');\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Injectable, inject } from '@angular/core';\n\nimport type { WrIconDef } from './interfaces';\nimport { WR_ICONS } from './tokens';\n\n/**\n * One link in the icon-registry chain. {@link provideWrIcons} provides an\n * instance alongside its icons, so every injector level that registers icons\n * gets its own link whose parent is the next registering level above it.\n *\n * This exists because Angular does not merge `multi: true` providers across\n * injector levels — an element-level `provideWrIcons()` *replaces* the visible\n * `WR_ICONS` collection rather than adding to it. Reading only the closest\n * contribution silently hid every icon registered further up, which contradicts\n * the \"each call adds to the registry\" contract. Walking the chain restores it\n * for any number of levels.\n *\n * @internal\n */\n@Injectable()\nexport class WrIconRegistry {\n /** The next registering level up, if any. */\n private readonly parent = inject(WrIconRegistry, { skipSelf: true, optional: true });\n\n /** Icons registered at *this* level only. */\n private readonly own = inject(WR_ICONS, { self: true, optional: true }) ?? [];\n\n /**\n * Every icon visible from this level, nearest last so a local registration\n * wins over an identically named one further up.\n */\n resolve(): Map<string, WrIconDef> {\n const map = this.parent?.resolve() ?? new Map<string, WrIconDef>();\n for (const set of this.own) {\n for (const icon of set) map.set(icon.name, icon);\n }\n return map;\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\nconst XLINK_NS = 'http://www.w3.org/1999/xlink';\n\n/**\n * SVG elements an icon may be built from.\n *\n * Derived from the real sets rather than guessed: across the 12 459 SVG files\n * under `icon/_svg` (Lucide, Phosphor, Tabler, Heroicons), the whole of\n * `feather-icons`' `icons.json` and every `lucide` IconNode, only eight\n * elements ever appear — `svg`, `path`, `circle`, `line`, `rect`, `polyline`,\n * `polygon`, `ellipse`. The rest of this list is headroom for the icons a\n * consumer draws themselves: grouping, `<defs>` with gradients, a clip path, a\n * mask, a `<title>`, real `<text>`.\n *\n * What is deliberately absent is the entire scripting surface:\n *\n * - `<script>` — obvious, and the one vector `innerHTML` already refused;\n * - `<foreignObject>` — an XHTML escape hatch, so `<img onerror>` inside an\n * otherwise well-formed icon;\n * - `<image>` — its `onerror` fires on any unreachable `href`, which is the\n * cheapest handler to land, and no real icon set uses it;\n * - `<a>` — a `javascript:` href inside a glyph the user is invited to click;\n * - `<animate>` / `<set>` / `<animateTransform>` / `<animateMotion>` — SMIL can\n * retarget another element's `href` at a `javascript:` URL;\n * - `<style>` — arbitrary CSS in the app's origin, including `url()` requests;\n * - `<filter>` and the `fe*` primitives — `<feImage href>` is another external\n * fetch, and no icon set in the corpus filters.\n *\n * @internal\n */\nconst ELEMENTS: ReadonlySet<string> = new Set([\n 'svg',\n 'g',\n 'defs',\n 'symbol',\n 'use',\n 'title',\n 'desc',\n 'path',\n 'circle',\n 'ellipse',\n 'line',\n 'polyline',\n 'polygon',\n 'rect',\n 'text',\n 'tspan',\n 'textPath',\n 'linearGradient',\n 'radialGradient',\n 'stop',\n 'clipPath',\n 'mask',\n 'pattern',\n]);\n\n/**\n * Attributes an icon may carry, allowed on every element it is allowed on.\n *\n * An allowlist rather than a blocklist, so `onload`, `onerror`, `onclick` and\n * every other handler — including ones no browser has shipped yet — are gone by\n * construction rather than by enumeration.\n *\n * `style` is NOT here, and that is a decision rather than an oversight: not one\n * icon in the corpus uses it, and a `style` attribute is arbitrary CSS in the\n * app's origin. Anything a `style` would have done, a presentation attribute\n * does.\n *\n * @internal\n */\nconst ATTRIBUTES: ReadonlySet<string> = new Set([\n // geometry\n 'viewBox',\n 'preserveAspectRatio',\n 'transform',\n 'transform-origin',\n 'd',\n 'pathLength',\n 'cx',\n 'cy',\n 'r',\n 'rx',\n 'ry',\n 'x',\n 'y',\n 'x1',\n 'y1',\n 'x2',\n 'y2',\n 'dx',\n 'dy',\n 'width',\n 'height',\n 'points',\n // paint servers, clips and masks\n 'gradientUnits',\n 'gradientTransform',\n 'spreadMethod',\n 'offset',\n 'fx',\n 'fy',\n 'fr',\n 'patternUnits',\n 'patternContentUnits',\n 'patternTransform',\n 'maskUnits',\n 'maskContentUnits',\n 'clipPathUnits',\n // presentation\n 'fill',\n 'fill-opacity',\n 'fill-rule',\n 'stroke',\n 'stroke-width',\n 'stroke-linecap',\n 'stroke-linejoin',\n 'stroke-miterlimit',\n 'stroke-dasharray',\n 'stroke-dashoffset',\n 'stroke-opacity',\n 'opacity',\n 'color',\n 'clip-path',\n 'clip-rule',\n 'mask',\n 'paint-order',\n 'vector-effect',\n 'shape-rendering',\n 'stop-color',\n 'stop-opacity',\n 'display',\n 'visibility',\n 'overflow',\n 'isolation',\n 'mix-blend-mode',\n // text\n 'startOffset',\n 'textLength',\n 'lengthAdjust',\n 'text-anchor',\n 'dominant-baseline',\n 'alignment-baseline',\n 'baseline-shift',\n 'font-family',\n 'font-size',\n 'font-stretch',\n 'font-style',\n 'font-variant',\n 'font-weight',\n 'letter-spacing',\n 'word-spacing',\n 'writing-mode',\n 'direction',\n // identity, accessibility and document plumbing\n 'id',\n 'class',\n 'role',\n 'aria-hidden',\n 'aria-label',\n 'aria-labelledby',\n 'aria-describedby',\n 'aria-roledescription',\n 'focusable',\n 'lang',\n 'version',\n 'baseProfile',\n 'xmlns',\n 'xmlns:xlink',\n 'xml:space',\n 'xml:lang',\n // resolved against ONLY_FRAGMENT below, never free-form\n 'href',\n 'xlink:href',\n]);\n\n/**\n * `href` may point at a fragment in the same document and nowhere else — that\n * is the whole legitimate use inside an icon (`<use href=\"#glyph\">`), and it\n * excludes `javascript:`, `data:` and every remote fetch in one rule.\n */\nconst FRAGMENT_ONLY: ReadonlySet<string> = new Set(['href', 'xlink:href']);\n\n/** A same-document fragment reference: `#id`, and nothing that is not one. */\nconst FRAGMENT_RE = /^#[^\\s]+$/;\n\n/**\n * `url(` inside a presentation attribute is a reference to a paint server, a\n * clip path or a mask. Those are fragment references too; a remote one is at\n * best a request the page did not ask for.\n */\nconst URL_FN_RE = /url\\(/i;\nconst URL_FRAGMENT_RE = /^url\\(\\s*(['\"]?)#[^\\s'\")]+\\1\\s*\\)$/i;\n\n/**\n * Icons are shallow — the deepest in the corpus nests three levels. The cap is\n * there so a hostile registration cannot recurse this function into a stack\n * overflow; content below it is dropped, not truncated silently, because\n * {@link WrIconStripReport} names what went.\n */\nconst MAX_DEPTH = 16;\n\n/**\n * What {@link sanitizeIcon} refused, for the dev-mode warning. Collecting is\n * opt-in so the render path does no bookkeeping in production.\n *\n * @internal\n */\ninterface WrIconStripReport {\n readonly elements: Set<string>;\n readonly attributes: Set<string>;\n}\n\n/**\n * Build a live `<svg>` element out of a registered icon's markup, keeping only\n * what an icon legitimately needs.\n *\n * `<wr-icon>` used to assign `icon.data` to `innerHTML`, which executes an\n * `onload` on the root, an `<image onerror>`, an `<img>` smuggled in after a\n * closed `</svg>`, and anything inside a `<foreignObject>` — with the app's own\n * origin, in a production build. The library already refuses exactly this next\n * door: `<wr-markdown>` escapes raw HTML because its input is untrusted by\n * construction and one `<img onerror>` is the whole cost of being wrong. Icon\n * data arrives from `provideWrIcons()`, which a multi-tenant app or a fetched\n * icon pack fills at runtime, so it is untrusted the same way.\n *\n * Three properties do the work, and none of them is a filter over known-bad\n * strings:\n *\n * 1. **Parsed inert, never inserted.** The markup is parsed into a document\n * with no browsing context, so nothing loads and nothing runs even before it\n * is inspected.\n * 2. **Only the first `<svg>` survives.** An icon IS an SVG element, so\n * everything beside the root is dropped — which is what makes\n * `</svg><img src=x onerror=…>` inert regardless of what the payload says,\n * and it is the same shape as the `feather()` shell escape.\n * 3. **Rebuilt, not filtered.** Every element and attribute that lands in the\n * document is one this file created from an allowlist. Nothing is\n * transferred from the parsed tree, so there is no attribute an unfamiliar\n * payload can smuggle past a matcher.\n *\n * A refused element takes its subtree with it rather than having its children\n * hoisted into its parent. Hoisting would preserve more of a hand-drawn icon\n * wrapped in something unsupported, and it is exactly the kind of cleverness\n * that makes \"what does the output contain\" depend on what the parser saw. The\n * blunt rule is the reviewable one, and the dev-mode warning names what went.\n *\n * `DomSanitizer.sanitize(SecurityContext.HTML, …)` is not an option here, which\n * is worth recording so it is not re-proposed: Angular's HTML allowlist\n * (`VOID_ELEMENTS`, `BLOCK_ELEMENTS`, `INLINE_ELEMENTS`) contains no SVG\n * element at all. Run against real Lucide, Feather and Heroicons icons it does\n * not merely drop presentation attributes — it returns the empty string.\n *\n * @returns the rebuilt element, or `null` when the data holds no `<svg>` root.\n * @internal\n */\nfunction sanitizeIcon(doc: Document, data: string, report?: WrIconStripReport): SVGElement | null {\n const source = parseInert(doc, data);\n return source ? copy(doc, source, report, 0) : null;\n}\n\n/**\n * Parse into a document that cannot run anything.\n *\n * `DOMParser` is the path every browser and the unit-test DOM take: it takes a\n * plain string, so it is not a Trusted Types sink, and its result has no\n * browsing context. It does not exist under SSR — `@angular/platform-server`\n * renders into domino — so the server falls back to a detached `<template>`,\n * whose content is a separate inert document for the same reason. That branch\n * is unreachable in a browser, which is what keeps the innerHTML write out of\n * the only place Trusted Types is enforced.\n *\n * Both parsers were checked to agree on the part that matters: the HTML\n * parser's foreign-content adjustment restores the camel case of `viewBox`,\n * `clipPath`, `linearGradient`, `gradientTransform`, `textPath` and puts\n * `xlink:href` in the xlink namespace.\n */\nfunction parseInert(doc: Document, data: string): Element | null {\n const parser = doc.defaultView?.DOMParser;\n if (parser) return firstSvg(new parser().parseFromString(data, 'text/html').body);\n\n const template = doc.createElement('template');\n template.innerHTML = data;\n return firstSvg(template.content);\n}\n\n/**\n * The first `<svg>` in document order, found by walking rather than by\n * `querySelector`.\n *\n * Two reasons, and the second is the one that matters. A control that decides\n * what may execute should not route that decision through a selector engine —\n * `localName === 'svg'` is a comparison anyone can check, and CSS parsing is a\n * layer with its own history. The first reason is merely that it is faster:\n * jsdom's engine costs 4 ms on a document this size, which the unit suite pays\n * on every rendered icon.\n */\nfunction firstSvg(root: Node): Element | null {\n const children = root.childNodes;\n for (let i = 0, n = children.length; i < n; i++) {\n const child = children[i];\n if (child.nodeType !== 1) continue;\n const element = child as Element;\n if (element.localName === 'svg') return element;\n const nested = firstSvg(element);\n if (nested) return nested;\n }\n return null;\n}\n\n/** Recreate one element, then its allowed children, in the live document. */\nfunction copy(doc: Document, source: Element, report: WrIconStripReport | undefined, depth: number): SVGElement {\n const el = doc.createElementNS(SVG_NS, source.localName);\n\n const attributes = source.attributes;\n for (let i = 0, n = attributes.length; i < n; i++) {\n const { name, value } = attributes[i];\n if (!isAllowedAttribute(name, value)) {\n report?.attributes.add(name);\n continue;\n }\n if (name === 'xlink:href') el.setAttributeNS(XLINK_NS, name, value);\n else el.setAttribute(name, value);\n }\n\n if (depth >= MAX_DEPTH) {\n // Named, not silent — the cap is the one rule here that can bite an icon\n // nobody meant as an attack, and a truncated glyph should say so.\n if (source.childNodes.length > 0) report?.elements.add(`… deeper than ${MAX_DEPTH} levels`);\n return el;\n }\n\n const children = source.childNodes;\n for (let i = 0, n = children.length; i < n; i++) {\n const child = children[i];\n // Text, so `<title>`, `<desc>` and `<text>` keep saying what they said.\n // Comments, CDATA and processing instructions are dropped: an icon has no\n // use for them and a comment is a place to hide a payload from a reader.\n if (child.nodeType === 3) {\n el.appendChild(doc.createTextNode(child.nodeValue ?? ''));\n continue;\n }\n if (child.nodeType !== 1) continue;\n\n const element = child as Element;\n if (!ELEMENTS.has(element.localName)) {\n report?.elements.add(element.localName);\n continue;\n }\n el.appendChild(copy(doc, element, report, depth + 1));\n }\n\n return el;\n}\n\nfunction isAllowedAttribute(name: string, value: string): boolean {\n // `data-*` is a styling and testing hook that can never be behavioural.\n if (name.startsWith('data-')) return true;\n if (!ATTRIBUTES.has(name)) return false;\n if (FRAGMENT_ONLY.has(name)) return FRAGMENT_RE.test(value.trim());\n if (URL_FN_RE.test(value)) return URL_FRAGMENT_RE.test(value.trim());\n return true;\n}\n\nexport { sanitizeIcon, type WrIconStripReport };\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport type { WrIconDef } from '../interfaces';\n\nconst VIEW_BOX_RE = /<svg\\b[^>]*\\bviewBox\\s*=/i;\nconst SVG_ROOT_RE = /<svg\\b/i;\n\n/**\n * Dev-mode sanity check for a registered icon — a **rendering** check, and\n * explicitly not a security control.\n *\n * It warns about the two things that make an icon come out wrong: no `<svg>`\n * root, and no `viewBox`, which stops the glyph scaling to the host. It reads\n * the string with two regexes and cannot tell a shape from a script — every\n * payload in the icon corpus passes it, because each one does carry an `<svg>`\n * root with a `viewBox`. It also runs only under `isDevMode()`, so it is absent\n * from the build an attacker would meet.\n *\n * What actually keeps hostile markup out of the DOM is {@link sanitizeIcon},\n * which runs on every render in every build: `<wr-icon>` rebuilds the glyph\n * from an allowlist instead of assigning to `innerHTML`. Anything this function\n * would have to reject is already gone by then, and the component says so in\n * dev mode when it drops something.\n *\n * Production builds drop the call entirely via `isDevMode()` tree-shaking.\n *\n * @internal\n */\nexport function validateIcon(icon: WrIconDef): void {\n if (!SVG_ROOT_RE.test(icon.data)) {\n // eslint-disable-next-line no-console -- dev-mode validation\n console.warn(\n `[NGWR] Icon \"${icon.name}\" data does not contain an <svg> root element. ` +\n `Custom icons must be valid SVG markup.`\n );\n return;\n }\n\n if (!VIEW_BOX_RE.test(icon.data)) {\n // eslint-disable-next-line no-console -- dev-mode validation\n console.warn(\n `[NGWR] Icon \"${icon.name}\" has no viewBox attribute — it won't scale ` +\n `to the host size. Add viewBox=\"0 0 W H\" to the <svg> root.`\n );\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { DOCUMENT } from '@angular/common';\nimport { Component, ElementRef, ViewEncapsulation, computed, effect, inject, input, isDevMode } from '@angular/core';\n\nimport { WrIconRegistry } from './icon-registry';\nimport type { WrIconDef, WrIconName } from './interfaces';\nimport { type WrIconStripReport, sanitizeIcon } from './utils';\n\n/**\n * Renders a registered SVG icon.\n *\n * Icons must be registered via {@link provideWrIcons} before use —\n * either at the application root or on any ancestor component.\n *\n * @example\n * ```html\n * <wr-icon name=\"home\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/icon\n */\n@Component({\n selector: 'wr-icon',\n template: '',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-icon',\n '[attr.data-icon]': 'name()',\n },\n})\nexport class WrIcon {\n readonly name = input.required<WrIconName>();\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly doc = inject(DOCUMENT);\n\n /**\n * Every icon visible from this position, walked up the whole chain of\n * registering injector levels.\n *\n * Angular's `inject(WR_ICONS)` returns only the **closest** injector's\n * contribution — `multi` providers are not merged across levels — so a\n * component that registers its own icons used to shadow, not extend, what an\n * ancestor registered. {@link WrIconRegistry} is provided once per\n * registering level and links to its parent, so nesting any number of levels\n * now adds up, with the nearest registration winning a name collision.\n *\n * Optional: with no `provideWrIcons()` anywhere the map is simply empty and\n * every name reports as unregistered.\n */\n private readonly iconRegistry = inject(WrIconRegistry, { optional: true });\n\n private readonly registry = computed<ReadonlyMap<WrIconName, WrIconDef>>(\n () => (this.iconRegistry?.resolve() ?? new Map()) as ReadonlyMap<WrIconName, WrIconDef>\n );\n\n private readonly icon = computed(() => this.registry().get(this.name()));\n\n constructor() {\n effect(() => {\n const icon = this.icon();\n const name = this.name();\n\n if (!icon) {\n if (isDevMode()) {\n // Reported, never thrown. An exception here escapes into\n // `runEffectsInView`, which abandons the remaining effects of the\n // whole view — one unknown name would blank every `<wr-icon>` after\n // it while the console blamed only the first. Error level (not\n // `badgeLog`) so it survives a console filtered to errors.\n // eslint-disable-next-line no-console -- misconfiguration must be visible, but must not throw\n console.error(\n `[NGWR] No icon named \"${name}\" is registered. Did you forget to call provideWrIcons()? ` +\n `If the name IS registered, check the injector level: a component-level provideWrIcons() ` +\n `shadows icons registered on an ancestor component.`\n );\n }\n this.clear();\n return;\n }\n\n // Rebuilt from an allowlist rather than assigned to `innerHTML`, which\n // ran an `onload` on the root, an `<image onerror>`, an `<img>` smuggled\n // in after a closed `</svg>` and anything inside a `<foreignObject>` —\n // in production builds, with the app's own origin. `provideWrIcons()`\n // data is untrusted the same way `<wr-markdown>`'s input is: a tenant\n // logo or a fetched icon pack is a normal way to fill it. Building nodes\n // also keeps `<wr-icon>` clear of Trusted Types, which refuses the\n // `innerHTML` write outright.\n const report = isDevMode() ? { elements: new Set<string>(), attributes: new Set<string>() } : undefined;\n const svg = sanitizeIcon(this.doc, icon.data, report);\n\n if (!svg) {\n if (isDevMode()) {\n // eslint-disable-next-line no-console -- misconfiguration must be visible, but must not throw\n console.error(\n `[NGWR] Icon \"${name}\" has no <svg> root element and cannot be rendered. ` +\n `Custom icons must be valid SVG markup.`\n );\n }\n this.clear();\n return;\n }\n\n // Every hand-written inline SVG in the library carries aria-hidden;\n // registered icons went in without it, so a decorative glyph could be\n // announced — usually as a bare \"graphic\" beside the label it decorates.\n // Consumers that need a name put it on the interactive host instead.\n svg.setAttribute('aria-hidden', 'true');\n\n this.clear();\n this.host.nativeElement.appendChild(svg);\n\n if (report) this.reportStripped(name, icon, report);\n });\n }\n\n /**\n * `replaceChildren()` would say this in one call, but domino — the DOM\n * `@angular/platform-server` renders into — does not implement it, and\n * `innerHTML = ''` is the write Trusted Types refuses.\n */\n private clear(): void {\n const el = this.host.nativeElement;\n while (el.firstChild) el.removeChild(el.firstChild);\n }\n\n /**\n * Say what the allowlist removed, once per registered icon.\n *\n * A silently stripped icon is the failure mode this rebuild could introduce:\n * a consumer's own glyph renders blank or wrong and nothing anywhere says\n * why. Keyed on the definition object, so a page with fifty instances of the\n * same icon warns once and an icon nobody renders never warns at all.\n */\n private reportStripped(name: string, icon: WrIconDef, report: WrIconStripReport): void {\n if (WrIcon.reported.has(icon)) return;\n if (report.elements.size === 0 && report.attributes.size === 0) return;\n WrIcon.reported.add(icon);\n\n const removed = [\n report.elements.size ? `elements: ${[...report.elements].join(', ')}` : '',\n report.attributes.size ? `attributes: ${[...report.attributes].join(', ')}` : '',\n ].filter(Boolean);\n\n // eslint-disable-next-line no-console -- a stripped icon must not be silent\n console.warn(\n `[NGWR] Icon \"${name}\" was rendered without part of its markup — ${removed.join('; ')}. ` +\n `Registered icon data is treated as untrusted and rebuilt from an allowlist of SVG ` +\n `shape, paint and text elements, so event handlers, <script>, <style>, <image>, ` +\n `<foreignObject>, <a>, SMIL animation and non-fragment hrefs never reach the DOM.`\n );\n }\n\n private static readonly reported = new WeakSet<WrIconDef>();\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { type Provider, isDevMode } from '@angular/core';\n\nimport { WrIconRegistry } from '../icon-registry';\nimport type { WrIconDef } from '../interfaces';\nimport { WR_ICONS } from '../tokens';\nimport { validateIcon } from '../utils';\n\n/**\n * Registers a set of icons for use by `<wr-icon>`.\n *\n * Can be called multiple times — each call adds to the registry.\n * Works at both the application root and at the component level,\n * so a feature module or a single component can bring its own icons\n * without polluting the global config.\n *\n * In dev mode, each icon is validated for common issues (missing\n * `viewBox`, malformed root). Validation is dropped from production\n * builds via `isDevMode()` tree-shaking, and it is a rendering check rather\n * than a security one — see {@link validateIcon}.\n *\n * Icon `data` is treated as **untrusted** at render: `<wr-icon>` rebuilds the\n * glyph from an allowlist instead of writing the string to `innerHTML`, so a\n * set fetched at runtime or supplied by a tenant cannot execute in the app's\n * origin. Dev mode names anything the allowlist removed.\n *\n * @example\n * ```ts\n * // App-level registration\n * import { home, user, cog, provideWrIcons } from 'ngwr/icon';\n *\n * export const appConfig: ApplicationConfig = {\n * providers: [\n * provideWrIcons([home, user, cog]),\n * ],\n * };\n * ```\n *\n * @example\n * ```ts\n * // Component-level registration\n * import { logoGithub, logoNpm, provideWrIcons, WrIconDef } from 'ngwr/icon';\n *\n * @Component({\n * selector: 'ngwr-header',\n * imports: [WrIcon],\n * providers: [provideWrIcons([logoGithub, logoNpm])],\n * template: `<wr-icon name=\"logo-github\" />`,\n * })\n * export class HeaderComponent {}\n * ```\n */\nexport function provideWrIcons(icons: WrIconDef[]): Provider[] {\n if (isDevMode()) {\n for (const icon of icons) validateIcon(icon);\n }\n return [\n { provide: WR_ICONS, useValue: icons, multi: true },\n // A registry link per registering level. Angular does not merge `multi`\n // providers across injectors, so without this chain an element-level\n // registration would shadow everything registered above it instead of\n // adding to it.\n WrIconRegistry,\n ];\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport type { WrIconDef } from './interfaces';\n\n/**\n * Wrap a raw SVG string in the {@link WrIconDef} envelope. Use this when\n * importing icons from sources that ship full `<svg>` files — Tabler,\n * Phosphor, Heroicons, Iconoir, Radix, Bootstrap Icons, your designer.\n *\n * Pair with a bundler's raw-SVG import (Vite's `?raw`, Webpack's\n * `raw-loader`, esbuild's `--loader:.svg=text`) so the SVG file becomes\n * a string at build time.\n *\n * For sets with a non-SVG shape (Lucide's IconNode tuples, Feather's\n * inner-only SVG), use the dedicated adapter under\n * `ngwr/icon/adapters/<set>` instead.\n *\n * @example\n * ```ts\n * import { svgIcon, provideWrIcons } from 'ngwr/icon';\n * // Vite: the `?raw` suffix returns the file as a string.\n * import plusSvg from '@tabler/icons/icons/plus.svg?raw';\n * import phosphorPlusSvg from '@phosphor-icons/core/assets/regular/plus.svg?raw';\n *\n * bootstrapApplication(AppComponent, {\n * providers: [\n * provideWrIcons([\n * svgIcon('plus', plusSvg),\n * svgIcon('plus-phosphor', phosphorPlusSvg),\n * ]),\n * ],\n * });\n * ```\n *\n * **`svg` is treated as untrusted.** It is a string, so it is a natural place\n * for a tenant's uploaded logo or an icon pack fetched from an API to end up,\n * and `<wr-icon>` used to write it to `innerHTML`. It no longer does: the glyph\n * is rebuilt from an allowlist of SVG shape, paint and text elements, so\n * `<script>`, `<style>`, `<image>`, `<foreignObject>`, `<a>`, SMIL animation,\n * every `on*` handler, `style` attributes and non-fragment `href`s are dropped\n * before anything reaches the DOM. In dev mode the component names what it\n * removed, so a legitimate icon that loses something says so out loud.\n */\nexport function svgIcon(name: string, svg: string): WrIconDef {\n return { name, data: svg };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -78,7 +78,7 @@ class WrInputNumber {
|
|
|
78
78
|
// and a config default nothing could ever supply.
|
|
79
79
|
transform: (v) => (v == null ? null : coerceBooleanProperty(v)) });
|
|
80
80
|
/**
|
|
81
|
-
* `size`, then the app config, then `md` — bound to the field's `[
|
|
81
|
+
* `size`, then the app config, then `md` — bound to the field's `[size]`.
|
|
82
82
|
*
|
|
83
83
|
* The chrome this control is built from resolves the same pair for itself, so
|
|
84
84
|
* the config lookup falls through to `input.*` rather than stopping at
|
|
@@ -329,11 +329,11 @@ class WrInputNumber {
|
|
|
329
329
|
return formatter.format(v);
|
|
330
330
|
}
|
|
331
331
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrInputNumber, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
332
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrInputNumber, isStandalone: true, selector: "wr-input-number", inputs: { min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, decimals: { classPropertyName: "decimals", publicName: "decimals", isSignal: true, isRequired: false, transformFunction: null }, showSteppers: { classPropertyName: "showSteppers", publicName: "showSteppers", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, prefix: { classPropertyName: "prefix", publicName: "prefix", isSignal: true, isRequired: false, transformFunction: null }, suffix: { classPropertyName: "suffix", publicName: "suffix", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, incrementLabel: { classPropertyName: "incrementLabel", publicName: "incrementLabel", isSignal: true, isRequired: false, transformFunction: null }, decrementLabel: { classPropertyName: "decrementLabel", publicName: "decrementLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touch: "touch" }, host: { properties: { "class": "classes()" } }, ngImport: i0, template: "<wr-input-group [rounded]=\"resolvedRounded()\">\n @if (prefix(); as p) {\n <span wrInputPrefix>{{ p }}</span>\n }\n <input\n #input\n wrInput\n type=\"text\"\n inputmode=\"decimal\"\n autocomplete=\"off\"\n [
|
|
332
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrInputNumber, isStandalone: true, selector: "wr-input-number", inputs: { min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, decimals: { classPropertyName: "decimals", publicName: "decimals", isSignal: true, isRequired: false, transformFunction: null }, showSteppers: { classPropertyName: "showSteppers", publicName: "showSteppers", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, prefix: { classPropertyName: "prefix", publicName: "prefix", isSignal: true, isRequired: false, transformFunction: null }, suffix: { classPropertyName: "suffix", publicName: "suffix", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, incrementLabel: { classPropertyName: "incrementLabel", publicName: "incrementLabel", isSignal: true, isRequired: false, transformFunction: null }, decrementLabel: { classPropertyName: "decrementLabel", publicName: "decrementLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touch: "touch" }, host: { properties: { "class": "classes()" } }, ngImport: i0, template: "<wr-input-group [rounded]=\"resolvedRounded()\">\n @if (prefix(); as p) {\n <span wrInputPrefix>{{ p }}</span>\n }\n <input\n #input\n wrInput\n type=\"text\"\n inputmode=\"decimal\"\n autocomplete=\"off\"\n [size]=\"resolvedSize()\"\n [rounded]=\"resolvedRounded()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n spellcheck=\"false\"\n [value]=\"text()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\"\n />\n @if (suffix(); as s) {\n <span wrInputSuffix>{{ s }}</span>\n }\n @if (showSteppers()) {\n <div class=\"wr-input-number__steppers\" wrInputSuffix>\n <button\n type=\"button\"\n class=\"wr-input-number__step\"\n tabindex=\"-1\"\n [attr.aria-label]=\"resolvedIncrement()\"\n [disabled]=\"!interactive() || atMax()\"\n (click)=\"stepBy(1)\"\n >\n <svg width=\"10\" height=\"6\" viewBox=\"0 0 8 5\" aria-hidden=\"true\">\n <path d=\"M0 5 L4 0 L8 5 Z\" fill=\"currentColor\" />\n </svg>\n </button>\n <button\n type=\"button\"\n class=\"wr-input-number__step\"\n tabindex=\"-1\"\n [attr.aria-label]=\"resolvedDecrement()\"\n [disabled]=\"!interactive() || atMin()\"\n (click)=\"stepBy(-1)\"\n >\n <svg width=\"10\" height=\"6\" viewBox=\"0 0 8 5\" aria-hidden=\"true\">\n <path d=\"M0 0 L4 5 L8 0 Z\" fill=\"currentColor\" />\n </svg>\n </button>\n </div>\n }\n</wr-input-group>\n", dependencies: [{ kind: "directive", type: WrInput, selector: "input[wrInput], textarea[wrInput]", inputs: ["size", "rounded"] }, { kind: "component", type: WrInputGroup, selector: "wr-input-group", inputs: ["rounded"] }, { kind: "directive", type: WrInputPrefix, selector: "[wrInputPrefix]" }, { kind: "directive", type: WrInputSuffix, selector: "[wrInputSuffix]" }], encapsulation: i0.ViewEncapsulation.None });
|
|
333
333
|
}
|
|
334
334
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrInputNumber, decorators: [{
|
|
335
335
|
type: Component,
|
|
336
|
-
args: [{ selector: 'wr-input-number', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, imports: [WrInput, WrInputGroup, WrInputPrefix, WrInputSuffix], template: "<wr-input-group [rounded]=\"resolvedRounded()\">\n @if (prefix(); as p) {\n <span wrInputPrefix>{{ p }}</span>\n }\n <input\n #input\n wrInput\n type=\"text\"\n inputmode=\"decimal\"\n autocomplete=\"off\"\n [
|
|
336
|
+
args: [{ selector: 'wr-input-number', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, imports: [WrInput, WrInputGroup, WrInputPrefix, WrInputSuffix], template: "<wr-input-group [rounded]=\"resolvedRounded()\">\n @if (prefix(); as p) {\n <span wrInputPrefix>{{ p }}</span>\n }\n <input\n #input\n wrInput\n type=\"text\"\n inputmode=\"decimal\"\n autocomplete=\"off\"\n [size]=\"resolvedSize()\"\n [rounded]=\"resolvedRounded()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n spellcheck=\"false\"\n [value]=\"text()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\"\n />\n @if (suffix(); as s) {\n <span wrInputSuffix>{{ s }}</span>\n }\n @if (showSteppers()) {\n <div class=\"wr-input-number__steppers\" wrInputSuffix>\n <button\n type=\"button\"\n class=\"wr-input-number__step\"\n tabindex=\"-1\"\n [attr.aria-label]=\"resolvedIncrement()\"\n [disabled]=\"!interactive() || atMax()\"\n (click)=\"stepBy(1)\"\n >\n <svg width=\"10\" height=\"6\" viewBox=\"0 0 8 5\" aria-hidden=\"true\">\n <path d=\"M0 5 L4 0 L8 5 Z\" fill=\"currentColor\" />\n </svg>\n </button>\n <button\n type=\"button\"\n class=\"wr-input-number__step\"\n tabindex=\"-1\"\n [attr.aria-label]=\"resolvedDecrement()\"\n [disabled]=\"!interactive() || atMin()\"\n (click)=\"stepBy(-1)\"\n >\n <svg width=\"10\" height=\"6\" viewBox=\"0 0 8 5\" aria-hidden=\"true\">\n <path d=\"M0 0 L4 5 L8 0 Z\" fill=\"currentColor\" />\n </svg>\n </button>\n </div>\n }\n</wr-input-group>\n" }]
|
|
337
337
|
}], ctorParameters: () => [], propDecorators: { min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], decimals: [{ type: i0.Input, args: [{ isSignal: true, alias: "decimals", required: false }] }], showSteppers: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSteppers", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: false }] }], prefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefix", required: false }] }], suffix: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffix", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], incrementLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "incrementLabel", required: false }] }], decrementLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "decrementLabel", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touch: [{ type: i0.Output, args: ["touch"] }] } });
|
|
338
338
|
|
|
339
339
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-input-number.mjs","sources":["../../../projects/lib/input-number/input-number.ts","../../../projects/lib/input-number/input-number.html","../../../projects/lib/input-number/ngwr-input-number.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { type BooleanInput, coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n LOCALE_ID,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n model,\n output,\n signal,\n} from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { useConfigValue } from 'ngwr/config';\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrInput, WrInputGroup, WrInputPrefix, WrInputSuffix, type WrInputSize } from 'ngwr/input';\nimport { clamp, round } from 'ngwr/utils';\n\n/** Round to `decimals` places, avoiding common float artefacts (`1.005 → 1.01`). */\n\n/** Resolve the locale's decimal + grouping separators from `Intl.NumberFormat`. */\nfunction resolveSeparators(locale: string): { decimal: string; group: string } {\n const parts = new Intl.NumberFormat(locale).formatToParts(12345.6);\n const decimal = parts.find(p => p.type === 'decimal')?.value ?? '.';\n const group = parts.find(p => p.type === 'group')?.value ?? '';\n return { decimal, group };\n}\n\n/**\n * Numeric input with locale-aware formatting + ▲▼ stepper buttons.\n *\n * A signal-forms native control: it implements `FormValueControl<number | null>`,\n * so `[formField]` binds straight to its `value` model — no\n * `ControlValueAccessor` in between. `[(value)]` works standalone.\n *\n * Parses leniently while typing (allows partial input like `\"1.\"` or `\"-\"`),\n * re-formats with grouping + fixed decimals on blur.\n *\n * Locale comes from Angular's `LOCALE_ID` — input-number intentionally does\n * NOT depend on `ngwr/date` so it can be used in date-free apps.\n *\n * @example\n * ```html\n * <!-- signal forms -->\n * <wr-input-number [formField]=\"form.qty\" [min]=\"0\" [max]=\"100\" />\n *\n * <!-- standalone two-way binding -->\n * <wr-input-number [(value)]=\"price\" [decimals]=\"2\" suffix=\"USD\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/input-number\n */\n@Component({\n selector: 'wr-input-number',\n templateUrl: './input-number.html',\n encapsulation: ViewEncapsulation.None,\n host: { '[class]': 'classes()' },\n imports: [WrInput, WrInputGroup, WrInputPrefix, WrInputSuffix],\n})\nexport class WrInputNumber implements FormValueControl<number | null> {\n /** Minimum allowed value. @default -Infinity */\n readonly min = input<number | undefined>(Number.NEGATIVE_INFINITY, {\n transform: (v: unknown): number => coerceNumberProperty(v, Number.NEGATIVE_INFINITY),\n });\n\n /** Maximum allowed value. @default Infinity */\n readonly max = input<number | undefined>(Number.POSITIVE_INFINITY, {\n transform: (v: unknown): number => coerceNumberProperty(v, Number.POSITIVE_INFINITY),\n });\n\n private readonly minValue = computed(() => this.min() ?? Number.NEGATIVE_INFINITY);\n private readonly maxValue = computed(() => this.max() ?? Number.POSITIVE_INFINITY);\n\n /** Step used by stepper buttons + arrow keys. @default 1 */\n readonly step = input(1, { transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 1)) });\n\n /** Fixed number of decimals shown on blur. `null` keeps the entered precision. @default null */\n readonly decimals = input<number | null>(null);\n\n /** Render the ▲▼ stepper column. @default true */\n readonly showSteppers = input(true, { transform: coerceBooleanProperty });\n\n /**\n * Control size — forwarded to the field, and shares the `--wr-control-*`\n * contract. Unset falls back to the `inputNumber.size` app default from\n * `provideWrConfig()`, then to the `input.size` one, then to `md`. @default 'md'\n */\n readonly size = input<WrInputSize | null>(null);\n\n /**\n * Pill-shaped corners. Unset falls back to the `inputNumber.rounded` app default\n * from `provideWrConfig()`, then to the `input.rounded` one;\n * `[rounded]=\"false\"` turns a configured `true` back off. @default false\n */\n readonly rounded = input<boolean | null, BooleanInput>(null, {\n // Null-preserving: the plain `coerceBooleanProperty` folds `null` into\n // `false`, which would make \"not set\" and \"set to false\" the same value —\n // and a config default nothing could ever supply.\n transform: (v: BooleanInput): boolean | null => (v == null ? null : coerceBooleanProperty(v)),\n });\n\n /**\n * `size`, then the app config, then `md` — bound to the field's `[wrSize]`.\n *\n * The chrome this control is built from resolves the same pair for itself, so\n * the config lookup falls through to `input.*` rather than stopping at\n * `inputNumber.*`: binding a value down always wins there, and an unchained\n * lookup would therefore SUPPRESS an `input.size` an app had set — the field\n * inside a number input would be the one `[wrInput]` in the app that ignored it.\n * Chained, the specific key wins where it is set and the general one still\n * applies where it is not.\n */\n protected readonly resolvedSize = useConfigValue(this.size, c => c.inputNumber?.size ?? c.input?.size, 'md');\n\n /** `rounded`, then the app config, then `false` — bound to the group AND the field. */\n protected readonly resolvedRounded = useConfigValue(\n this.rounded,\n c => c.inputNumber?.rounded ?? c.input?.rounded,\n false\n );\n\n /** Optional prefix label (e.g. `\"$\"`). */\n readonly prefix = input<string>('');\n\n /** Optional suffix label (e.g. `\"kg\"`). */\n readonly suffix = input<string>('');\n\n /** Placeholder shown when the input is empty. */\n readonly placeholder = input<string>('');\n\n /**\n * Accessible name for the field. Unset, the field is named by whatever labels\n * it — there is deliberately no fallback here.\n *\n * It used to mirror the `placeholder`, and an `aria-label` OUTRANKS a\n * `<label>` in the accname order: inside a `<wr-form-field label=\"Quantity\">`\n * the very input that adopts the field's id announced the placeholder (\"0\")\n * instead, and a placeholder disappears as soon as the user types. Standalone\n * nothing was lost by dropping it — an input with no other name already falls\n * back to its own `placeholder` attribute, which is the name axe's `label`\n * rule accepts too.\n */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = computed(() => {\n // Not `??`: an empty binding is no name at all, and an empty `aria-label` is\n // worse than none — it still wins the accname order, and announces nothing.\n const explicit = this.ariaLabel();\n if (explicit) return explicit;\n return null;\n });\n\n /** Increment button aria-label. Falls back to `inputNumber.increment`. */\n readonly incrementLabel = input<string | null>(null);\n\n /** Decrement button aria-label. Falls back to `inputNumber.decrement`. */\n readonly decrementLabel = input<string | null>(null);\n\n /**\n * The steppers are icon-only buttons, so these ARE their accessible names.\n * They were hard-coded English while the catalog carried the keys for them\n * unread — a Russian app announced \"Increment\".\n */\n protected readonly resolvedIncrement = useI18nText(this.incrementLabel, 'inputNumber.increment', 'Increment');\n protected readonly resolvedDecrement = useI18nText(this.decrementLabel, 'inputNumber.decrement', 'Decrement');\n\n /** Disable interaction. @default false */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /** Read-only — values cannot be changed (steppers + typing disabled). @default false */\n readonly readonly = input(false, { transform: coerceBooleanProperty });\n\n /** Numeric value (`null` when empty). Bound by `[formField]`, or two-way via `[(value)]`. */\n readonly value = model<number | null>(null);\n\n /** Emitted on blur so a bound field can mark itself touched. */\n readonly touch = output<void>();\n\n private readonly locale = inject(LOCALE_ID);\n private readonly separators = resolveSeparators(this.locale);\n\n /** Text currently in the input (may be partial / invalid mid-type). */\n protected readonly text = signal<string>('');\n\n /** Whether the input is focused. While true, the input handlers own `text`. */\n protected readonly focused = signal(false);\n\n protected readonly interactive = computed(() => !this.disabled() && !this.readonly());\n\n protected readonly atMin = computed(() => {\n const v = this.value();\n return v !== null && v <= this.minValue();\n });\n\n protected readonly atMax = computed(() => {\n const v = this.value();\n return v !== null && v >= this.maxValue();\n });\n\n protected readonly classes = computed(() => {\n const parts = ['wr-input-number'];\n if (this.disabled()) parts.push('wr-input-number--disabled');\n return parts.join(' ');\n });\n\n /**\n * Fraction digits the stepper rounds its arithmetic to when `decimals` says\n * nothing. High enough to leave a real value alone, low enough to erase the\n * binary-float tail that repeated stepping accumulates.\n */\n private static readonly STEP_PRECISION = 10;\n\n /**\n * Above this, the anti-drift round is skipped — it would MANUFACTURE drift.\n *\n * `round(v, 10)` is `Math.round((v + EPSILON) * 1e10) / 1e10`, and the scale\n * factor is only exact while `v * 1e10` stays inside 2^53, i.e. |v| under\n * about 900,720. In practice the round-trip still recovers up to ~9.2e8 and\n * then breaks: `round(1000000001, 10)` is `1000000000.9999999`, and since\n * `format` prints up to 20 fraction digits that tail reaches the screen —\n * and compounds, because the next press starts from it. Integer stepping at\n * that magnitude is exact in IEEE754 and needed no help; the only thing this\n * rounding exists to defend is `0.1 + 0.1 + 0.1`.\n */\n private static readonly SAFE_ROUND_MAGNITUDE = 1e6;\n\n /** Steps finer than this would be rounded away by {@link STEP_PRECISION}. */\n private static readonly SAFE_ROUND_STEP = 1e-9;\n\n constructor() {\n // Keep the display `text` in sync with external writes to `value` (via\n // `[formField]` / `[(value)]`). While the user is editing, the input\n // handlers own `text`, so skip re-formatting to avoid clobbering partial\n // input (e.g. mid-typed grouping or a value that is still being clamped).\n effect(() => {\n const v = this.value();\n if (this.focused()) return;\n if (v === null || Number.isNaN(v)) {\n this.text.set('');\n return;\n }\n // SHOW WHAT THE MODEL HOLDS. It used to format `constrain(v)`, so a\n // `[max]=\"10\"` field handed 500 drew \"10\" over a model that still said\n // 500 — the display was the thing lying, not the model.\n //\n // The tempting repair is to write the clamp back so the two agree, and\n // that is worse: under `[formField]` the bounds arrive ONLY through the\n // schema's `min()` / `max()` rules (binding `[min]` next to `[formField]`\n // is an NG8022 compile error), so overwriting the value ERASES the very\n // error those rules exist to raise — and the write marks a pristine form\n // dirty, tripping unsaved-changes guards and opening every other\n // validator's message on first paint.\n //\n // So: `min` / `max` bound what this component's own edits may produce,\n // and a value written from outside is displayed as it is and left for\n // validation to judge.\n this.text.set(this.format(v));\n });\n }\n\n // Template handlers\n\n protected onInput(event: Event): void {\n const raw = (event.target as HTMLInputElement).value;\n this.text.set(raw);\n if (!raw.trim()) {\n this.value.set(null);\n return;\n }\n const parsed = this.parse(raw);\n if (parsed === null) return; // partial input — wait for more\n const next = this.constrain(parsed);\n this.value.set(next);\n }\n\n protected onFocus(): void {\n this.focused.set(true);\n }\n\n protected onBlur(): void {\n // Flipping `focused` false lets the sync effect re-format `text` from the\n // committed `value` (e.g. `\"1.\"` → `\"1\"`, or grouping the final number).\n this.focused.set(false);\n this.touch.emit();\n }\n\n protected onKeydown(event: KeyboardEvent): void {\n if (!this.interactive()) return;\n if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') return;\n event.preventDefault();\n const direction = event.key === 'ArrowUp' ? 1 : -1;\n const multiplier = event.shiftKey ? 10 : 1;\n this.bump(direction * multiplier);\n }\n\n protected stepBy(direction: 1 | -1): void {\n if (!this.interactive()) return;\n this.bump(direction);\n }\n\n // Internals\n\n private bump(units: number): void {\n const stepSize = this.step() || 1;\n const base = this.value() ?? this.fallbackBase();\n // `round` before `constrain`, and only on the stepped ARITHMETIC. Binary\n // floats do not add: three presses of a `step=\"0.1\"` stepper produced\n // 0.30000000000000004, `constrain` only rounds when `decimals` is set, and\n // `format` prints up to 20 fraction digits — so the drift went into the\n // model AND onto the screen. Ten digits erases IEEE noise without\n // quantising a value the user typed (0.15000000000000002 stays 0.15, it\n // does not snap to the step grid).\n //\n // Guarded on BOTH ends, because outside them the correction is the thing\n // that corrupts: see SAFE_ROUND_MAGNITUDE, and a step finer than 1e-9 would\n // simply be rounded away to nothing.\n const raw = base + units * stepSize;\n const correctable =\n Math.abs(raw) < WrInputNumber.SAFE_ROUND_MAGNITUDE && Math.abs(stepSize) >= WrInputNumber.SAFE_ROUND_STEP;\n const next = this.constrain(correctable ? round(raw, WrInputNumber.STEP_PRECISION) : raw);\n this.value.set(next);\n this.text.set(this.format(next));\n }\n\n /** Choose a sensible starting point when bumping from `null`. */\n private fallbackBase(): number {\n if (Number.isFinite(this.minValue()) && this.minValue() > 0) return this.minValue();\n if (Number.isFinite(this.maxValue()) && this.maxValue() < 0) return this.maxValue();\n return 0;\n }\n\n private constrain(v: number): number {\n let next = clamp(v, this.minValue(), this.maxValue());\n const d = this.decimals();\n if (d !== null) next = round(next, d);\n return next;\n }\n\n /** Parse a locale-formatted string. Returns `null` for partial / not-yet-numeric input. */\n private parse(raw: string): number | null {\n const trimmed = raw.trim();\n if (!trimmed) return null;\n // Strip group separators, normalise the decimal separator to `.`.\n let normalised = trimmed;\n if (this.separators.group) {\n normalised = normalised.split(this.separators.group).join('');\n }\n if (this.separators.decimal !== '.') {\n normalised = normalised.split(this.separators.decimal).join('.');\n }\n // Partial inputs we don't want to clobber while the user is still typing:\n // `-`, `-.`, `.`, `1.`, `-1.` etc.\n if (/^-?\\.?$/.test(normalised) || normalised.endsWith('.')) return null;\n const n = Number(normalised);\n return Number.isFinite(n) ? n : null;\n }\n\n /** Format a value using `Intl.NumberFormat` for the active locale. */\n private format(v: number): string {\n const d = this.decimals();\n const formatter = new Intl.NumberFormat(this.locale, {\n minimumFractionDigits: d ?? 0,\n maximumFractionDigits: d ?? 20,\n useGrouping: true,\n });\n return formatter.format(v);\n }\n}\n","<wr-input-group [rounded]=\"resolvedRounded()\">\n @if (prefix(); as p) {\n <span wrInputPrefix>{{ p }}</span>\n }\n <input\n #input\n wrInput\n type=\"text\"\n inputmode=\"decimal\"\n autocomplete=\"off\"\n [wrSize]=\"resolvedSize()\"\n [rounded]=\"resolvedRounded()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n spellcheck=\"false\"\n [value]=\"text()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\"\n />\n @if (suffix(); as s) {\n <span wrInputSuffix>{{ s }}</span>\n }\n @if (showSteppers()) {\n <div class=\"wr-input-number__steppers\" wrInputSuffix>\n <button\n type=\"button\"\n class=\"wr-input-number__step\"\n tabindex=\"-1\"\n [attr.aria-label]=\"resolvedIncrement()\"\n [disabled]=\"!interactive() || atMax()\"\n (click)=\"stepBy(1)\"\n >\n <svg width=\"10\" height=\"6\" viewBox=\"0 0 8 5\" aria-hidden=\"true\">\n <path d=\"M0 5 L4 0 L8 5 Z\" fill=\"currentColor\" />\n </svg>\n </button>\n <button\n type=\"button\"\n class=\"wr-input-number__step\"\n tabindex=\"-1\"\n [attr.aria-label]=\"resolvedDecrement()\"\n [disabled]=\"!interactive() || atMin()\"\n (click)=\"stepBy(-1)\"\n >\n <svg width=\"10\" height=\"6\" viewBox=\"0 0 8 5\" aria-hidden=\"true\">\n <path d=\"M0 0 L4 5 L8 0 Z\" fill=\"currentColor\" />\n </svg>\n </button>\n </div>\n }\n</wr-input-group>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-input-number.mjs","sources":["../../../projects/lib/input-number/input-number.ts","../../../projects/lib/input-number/input-number.html","../../../projects/lib/input-number/ngwr-input-number.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { type BooleanInput, coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n LOCALE_ID,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n model,\n output,\n signal,\n} from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { useConfigValue } from 'ngwr/config';\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrInput, WrInputGroup, WrInputPrefix, WrInputSuffix, type WrInputSize } from 'ngwr/input';\nimport { clamp, round } from 'ngwr/utils';\n\n/** Round to `decimals` places, avoiding common float artefacts (`1.005 → 1.01`). */\n\n/** Resolve the locale's decimal + grouping separators from `Intl.NumberFormat`. */\nfunction resolveSeparators(locale: string): { decimal: string; group: string } {\n const parts = new Intl.NumberFormat(locale).formatToParts(12345.6);\n const decimal = parts.find(p => p.type === 'decimal')?.value ?? '.';\n const group = parts.find(p => p.type === 'group')?.value ?? '';\n return { decimal, group };\n}\n\n/**\n * Numeric input with locale-aware formatting + ▲▼ stepper buttons.\n *\n * A signal-forms native control: it implements `FormValueControl<number | null>`,\n * so `[formField]` binds straight to its `value` model — no\n * `ControlValueAccessor` in between. `[(value)]` works standalone.\n *\n * Parses leniently while typing (allows partial input like `\"1.\"` or `\"-\"`),\n * re-formats with grouping + fixed decimals on blur.\n *\n * Locale comes from Angular's `LOCALE_ID` — input-number intentionally does\n * NOT depend on `ngwr/date` so it can be used in date-free apps.\n *\n * @example\n * ```html\n * <!-- signal forms -->\n * <wr-input-number [formField]=\"form.qty\" [min]=\"0\" [max]=\"100\" />\n *\n * <!-- standalone two-way binding -->\n * <wr-input-number [(value)]=\"price\" [decimals]=\"2\" suffix=\"USD\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/input-number\n */\n@Component({\n selector: 'wr-input-number',\n templateUrl: './input-number.html',\n encapsulation: ViewEncapsulation.None,\n host: { '[class]': 'classes()' },\n imports: [WrInput, WrInputGroup, WrInputPrefix, WrInputSuffix],\n})\nexport class WrInputNumber implements FormValueControl<number | null> {\n /** Minimum allowed value. @default -Infinity */\n readonly min = input<number | undefined>(Number.NEGATIVE_INFINITY, {\n transform: (v: unknown): number => coerceNumberProperty(v, Number.NEGATIVE_INFINITY),\n });\n\n /** Maximum allowed value. @default Infinity */\n readonly max = input<number | undefined>(Number.POSITIVE_INFINITY, {\n transform: (v: unknown): number => coerceNumberProperty(v, Number.POSITIVE_INFINITY),\n });\n\n private readonly minValue = computed(() => this.min() ?? Number.NEGATIVE_INFINITY);\n private readonly maxValue = computed(() => this.max() ?? Number.POSITIVE_INFINITY);\n\n /** Step used by stepper buttons + arrow keys. @default 1 */\n readonly step = input(1, { transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 1)) });\n\n /** Fixed number of decimals shown on blur. `null` keeps the entered precision. @default null */\n readonly decimals = input<number | null>(null);\n\n /** Render the ▲▼ stepper column. @default true */\n readonly showSteppers = input(true, { transform: coerceBooleanProperty });\n\n /**\n * Control size — forwarded to the field, and shares the `--wr-control-*`\n * contract. Unset falls back to the `inputNumber.size` app default from\n * `provideWrConfig()`, then to the `input.size` one, then to `md`. @default 'md'\n */\n readonly size = input<WrInputSize | null>(null);\n\n /**\n * Pill-shaped corners. Unset falls back to the `inputNumber.rounded` app default\n * from `provideWrConfig()`, then to the `input.rounded` one;\n * `[rounded]=\"false\"` turns a configured `true` back off. @default false\n */\n readonly rounded = input<boolean | null, BooleanInput>(null, {\n // Null-preserving: the plain `coerceBooleanProperty` folds `null` into\n // `false`, which would make \"not set\" and \"set to false\" the same value —\n // and a config default nothing could ever supply.\n transform: (v: BooleanInput): boolean | null => (v == null ? null : coerceBooleanProperty(v)),\n });\n\n /**\n * `size`, then the app config, then `md` — bound to the field's `[size]`.\n *\n * The chrome this control is built from resolves the same pair for itself, so\n * the config lookup falls through to `input.*` rather than stopping at\n * `inputNumber.*`: binding a value down always wins there, and an unchained\n * lookup would therefore SUPPRESS an `input.size` an app had set — the field\n * inside a number input would be the one `[wrInput]` in the app that ignored it.\n * Chained, the specific key wins where it is set and the general one still\n * applies where it is not.\n */\n protected readonly resolvedSize = useConfigValue(this.size, c => c.inputNumber?.size ?? c.input?.size, 'md');\n\n /** `rounded`, then the app config, then `false` — bound to the group AND the field. */\n protected readonly resolvedRounded = useConfigValue(\n this.rounded,\n c => c.inputNumber?.rounded ?? c.input?.rounded,\n false\n );\n\n /** Optional prefix label (e.g. `\"$\"`). */\n readonly prefix = input<string>('');\n\n /** Optional suffix label (e.g. `\"kg\"`). */\n readonly suffix = input<string>('');\n\n /** Placeholder shown when the input is empty. */\n readonly placeholder = input<string>('');\n\n /**\n * Accessible name for the field. Unset, the field is named by whatever labels\n * it — there is deliberately no fallback here.\n *\n * It used to mirror the `placeholder`, and an `aria-label` OUTRANKS a\n * `<label>` in the accname order: inside a `<wr-form-field label=\"Quantity\">`\n * the very input that adopts the field's id announced the placeholder (\"0\")\n * instead, and a placeholder disappears as soon as the user types. Standalone\n * nothing was lost by dropping it — an input with no other name already falls\n * back to its own `placeholder` attribute, which is the name axe's `label`\n * rule accepts too.\n */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = computed(() => {\n // Not `??`: an empty binding is no name at all, and an empty `aria-label` is\n // worse than none — it still wins the accname order, and announces nothing.\n const explicit = this.ariaLabel();\n if (explicit) return explicit;\n return null;\n });\n\n /** Increment button aria-label. Falls back to `inputNumber.increment`. */\n readonly incrementLabel = input<string | null>(null);\n\n /** Decrement button aria-label. Falls back to `inputNumber.decrement`. */\n readonly decrementLabel = input<string | null>(null);\n\n /**\n * The steppers are icon-only buttons, so these ARE their accessible names.\n * They were hard-coded English while the catalog carried the keys for them\n * unread — a Russian app announced \"Increment\".\n */\n protected readonly resolvedIncrement = useI18nText(this.incrementLabel, 'inputNumber.increment', 'Increment');\n protected readonly resolvedDecrement = useI18nText(this.decrementLabel, 'inputNumber.decrement', 'Decrement');\n\n /** Disable interaction. @default false */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /** Read-only — values cannot be changed (steppers + typing disabled). @default false */\n readonly readonly = input(false, { transform: coerceBooleanProperty });\n\n /** Numeric value (`null` when empty). Bound by `[formField]`, or two-way via `[(value)]`. */\n readonly value = model<number | null>(null);\n\n /** Emitted on blur so a bound field can mark itself touched. */\n readonly touch = output<void>();\n\n private readonly locale = inject(LOCALE_ID);\n private readonly separators = resolveSeparators(this.locale);\n\n /** Text currently in the input (may be partial / invalid mid-type). */\n protected readonly text = signal<string>('');\n\n /** Whether the input is focused. While true, the input handlers own `text`. */\n protected readonly focused = signal(false);\n\n protected readonly interactive = computed(() => !this.disabled() && !this.readonly());\n\n protected readonly atMin = computed(() => {\n const v = this.value();\n return v !== null && v <= this.minValue();\n });\n\n protected readonly atMax = computed(() => {\n const v = this.value();\n return v !== null && v >= this.maxValue();\n });\n\n protected readonly classes = computed(() => {\n const parts = ['wr-input-number'];\n if (this.disabled()) parts.push('wr-input-number--disabled');\n return parts.join(' ');\n });\n\n /**\n * Fraction digits the stepper rounds its arithmetic to when `decimals` says\n * nothing. High enough to leave a real value alone, low enough to erase the\n * binary-float tail that repeated stepping accumulates.\n */\n private static readonly STEP_PRECISION = 10;\n\n /**\n * Above this, the anti-drift round is skipped — it would MANUFACTURE drift.\n *\n * `round(v, 10)` is `Math.round((v + EPSILON) * 1e10) / 1e10`, and the scale\n * factor is only exact while `v * 1e10` stays inside 2^53, i.e. |v| under\n * about 900,720. In practice the round-trip still recovers up to ~9.2e8 and\n * then breaks: `round(1000000001, 10)` is `1000000000.9999999`, and since\n * `format` prints up to 20 fraction digits that tail reaches the screen —\n * and compounds, because the next press starts from it. Integer stepping at\n * that magnitude is exact in IEEE754 and needed no help; the only thing this\n * rounding exists to defend is `0.1 + 0.1 + 0.1`.\n */\n private static readonly SAFE_ROUND_MAGNITUDE = 1e6;\n\n /** Steps finer than this would be rounded away by {@link STEP_PRECISION}. */\n private static readonly SAFE_ROUND_STEP = 1e-9;\n\n constructor() {\n // Keep the display `text` in sync with external writes to `value` (via\n // `[formField]` / `[(value)]`). While the user is editing, the input\n // handlers own `text`, so skip re-formatting to avoid clobbering partial\n // input (e.g. mid-typed grouping or a value that is still being clamped).\n effect(() => {\n const v = this.value();\n if (this.focused()) return;\n if (v === null || Number.isNaN(v)) {\n this.text.set('');\n return;\n }\n // SHOW WHAT THE MODEL HOLDS. It used to format `constrain(v)`, so a\n // `[max]=\"10\"` field handed 500 drew \"10\" over a model that still said\n // 500 — the display was the thing lying, not the model.\n //\n // The tempting repair is to write the clamp back so the two agree, and\n // that is worse: under `[formField]` the bounds arrive ONLY through the\n // schema's `min()` / `max()` rules (binding `[min]` next to `[formField]`\n // is an NG8022 compile error), so overwriting the value ERASES the very\n // error those rules exist to raise — and the write marks a pristine form\n // dirty, tripping unsaved-changes guards and opening every other\n // validator's message on first paint.\n //\n // So: `min` / `max` bound what this component's own edits may produce,\n // and a value written from outside is displayed as it is and left for\n // validation to judge.\n this.text.set(this.format(v));\n });\n }\n\n // Template handlers\n\n protected onInput(event: Event): void {\n const raw = (event.target as HTMLInputElement).value;\n this.text.set(raw);\n if (!raw.trim()) {\n this.value.set(null);\n return;\n }\n const parsed = this.parse(raw);\n if (parsed === null) return; // partial input — wait for more\n const next = this.constrain(parsed);\n this.value.set(next);\n }\n\n protected onFocus(): void {\n this.focused.set(true);\n }\n\n protected onBlur(): void {\n // Flipping `focused` false lets the sync effect re-format `text` from the\n // committed `value` (e.g. `\"1.\"` → `\"1\"`, or grouping the final number).\n this.focused.set(false);\n this.touch.emit();\n }\n\n protected onKeydown(event: KeyboardEvent): void {\n if (!this.interactive()) return;\n if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') return;\n event.preventDefault();\n const direction = event.key === 'ArrowUp' ? 1 : -1;\n const multiplier = event.shiftKey ? 10 : 1;\n this.bump(direction * multiplier);\n }\n\n protected stepBy(direction: 1 | -1): void {\n if (!this.interactive()) return;\n this.bump(direction);\n }\n\n // Internals\n\n private bump(units: number): void {\n const stepSize = this.step() || 1;\n const base = this.value() ?? this.fallbackBase();\n // `round` before `constrain`, and only on the stepped ARITHMETIC. Binary\n // floats do not add: three presses of a `step=\"0.1\"` stepper produced\n // 0.30000000000000004, `constrain` only rounds when `decimals` is set, and\n // `format` prints up to 20 fraction digits — so the drift went into the\n // model AND onto the screen. Ten digits erases IEEE noise without\n // quantising a value the user typed (0.15000000000000002 stays 0.15, it\n // does not snap to the step grid).\n //\n // Guarded on BOTH ends, because outside them the correction is the thing\n // that corrupts: see SAFE_ROUND_MAGNITUDE, and a step finer than 1e-9 would\n // simply be rounded away to nothing.\n const raw = base + units * stepSize;\n const correctable =\n Math.abs(raw) < WrInputNumber.SAFE_ROUND_MAGNITUDE && Math.abs(stepSize) >= WrInputNumber.SAFE_ROUND_STEP;\n const next = this.constrain(correctable ? round(raw, WrInputNumber.STEP_PRECISION) : raw);\n this.value.set(next);\n this.text.set(this.format(next));\n }\n\n /** Choose a sensible starting point when bumping from `null`. */\n private fallbackBase(): number {\n if (Number.isFinite(this.minValue()) && this.minValue() > 0) return this.minValue();\n if (Number.isFinite(this.maxValue()) && this.maxValue() < 0) return this.maxValue();\n return 0;\n }\n\n private constrain(v: number): number {\n let next = clamp(v, this.minValue(), this.maxValue());\n const d = this.decimals();\n if (d !== null) next = round(next, d);\n return next;\n }\n\n /** Parse a locale-formatted string. Returns `null` for partial / not-yet-numeric input. */\n private parse(raw: string): number | null {\n const trimmed = raw.trim();\n if (!trimmed) return null;\n // Strip group separators, normalise the decimal separator to `.`.\n let normalised = trimmed;\n if (this.separators.group) {\n normalised = normalised.split(this.separators.group).join('');\n }\n if (this.separators.decimal !== '.') {\n normalised = normalised.split(this.separators.decimal).join('.');\n }\n // Partial inputs we don't want to clobber while the user is still typing:\n // `-`, `-.`, `.`, `1.`, `-1.` etc.\n if (/^-?\\.?$/.test(normalised) || normalised.endsWith('.')) return null;\n const n = Number(normalised);\n return Number.isFinite(n) ? n : null;\n }\n\n /** Format a value using `Intl.NumberFormat` for the active locale. */\n private format(v: number): string {\n const d = this.decimals();\n const formatter = new Intl.NumberFormat(this.locale, {\n minimumFractionDigits: d ?? 0,\n maximumFractionDigits: d ?? 20,\n useGrouping: true,\n });\n return formatter.format(v);\n }\n}\n","<wr-input-group [rounded]=\"resolvedRounded()\">\n @if (prefix(); as p) {\n <span wrInputPrefix>{{ p }}</span>\n }\n <input\n #input\n wrInput\n type=\"text\"\n inputmode=\"decimal\"\n autocomplete=\"off\"\n [size]=\"resolvedSize()\"\n [rounded]=\"resolvedRounded()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n spellcheck=\"false\"\n [value]=\"text()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\"\n />\n @if (suffix(); as s) {\n <span wrInputSuffix>{{ s }}</span>\n }\n @if (showSteppers()) {\n <div class=\"wr-input-number__steppers\" wrInputSuffix>\n <button\n type=\"button\"\n class=\"wr-input-number__step\"\n tabindex=\"-1\"\n [attr.aria-label]=\"resolvedIncrement()\"\n [disabled]=\"!interactive() || atMax()\"\n (click)=\"stepBy(1)\"\n >\n <svg width=\"10\" height=\"6\" viewBox=\"0 0 8 5\" aria-hidden=\"true\">\n <path d=\"M0 5 L4 0 L8 5 Z\" fill=\"currentColor\" />\n </svg>\n </button>\n <button\n type=\"button\"\n class=\"wr-input-number__step\"\n tabindex=\"-1\"\n [attr.aria-label]=\"resolvedDecrement()\"\n [disabled]=\"!interactive() || atMin()\"\n (click)=\"stepBy(-1)\"\n >\n <svg width=\"10\" height=\"6\" viewBox=\"0 0 8 5\" aria-hidden=\"true\">\n <path d=\"M0 0 L4 5 L8 0 Z\" fill=\"currentColor\" />\n </svg>\n </button>\n </div>\n }\n</wr-input-group>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,7 @@ import { inject, input, model, output, signal, computed, viewChildren, effect, u
|
|
|
5
5
|
import { useConfigValue } from 'ngwr/config';
|
|
6
6
|
import { useFormFieldAria } from 'ngwr/form';
|
|
7
7
|
import { useI18nText, useI18nFormatter } from 'ngwr/i18n';
|
|
8
|
+
import { isComposing } from 'ngwr/utils';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* @license
|
|
@@ -150,7 +151,19 @@ class WrInputOtp {
|
|
|
150
151
|
}
|
|
151
152
|
// Template handlers
|
|
152
153
|
onInput(event, index) {
|
|
153
|
-
|
|
154
|
+
// An input method leaves its half-built reading in the box and only replaces
|
|
155
|
+
// it on `compositionend`. Sanitising that intermediate text writes `''` back
|
|
156
|
+
// into the field the IME is still composing in, which aborts the conversion
|
|
157
|
+
// outright — so the box is left alone until the text is real.
|
|
158
|
+
if (event.isComposing)
|
|
159
|
+
return;
|
|
160
|
+
this.commitCell(event.target, index);
|
|
161
|
+
}
|
|
162
|
+
/** @see onInput — this is where an IME's text finally arrives. */
|
|
163
|
+
onCompositionEnd(event, index) {
|
|
164
|
+
this.commitCell(event.target, index);
|
|
165
|
+
}
|
|
166
|
+
commitCell(target, index) {
|
|
154
167
|
if (this.readonly()) {
|
|
155
168
|
// A read-only input refuses typed characters on its own; this covers the
|
|
156
169
|
// synthetic writes a script (or a spec) can still make.
|
|
@@ -164,6 +177,11 @@ class WrInputOtp {
|
|
|
164
177
|
this.focusCell(index + 1);
|
|
165
178
|
}
|
|
166
179
|
onKeyDown(event, index) {
|
|
180
|
+
// Backspace and the arrows move the caret between boxes; while a conversion
|
|
181
|
+
// is open they belong to the candidate window, and moving focus out of the
|
|
182
|
+
// composing box discards it.
|
|
183
|
+
if (isComposing(event))
|
|
184
|
+
return;
|
|
167
185
|
const target = event.target;
|
|
168
186
|
switch (this.inlineKey(event.key)) {
|
|
169
187
|
case 'Backspace':
|
|
@@ -289,11 +307,11 @@ class WrInputOtp {
|
|
|
289
307
|
return key;
|
|
290
308
|
}
|
|
291
309
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrInputOtp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
292
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrInputOtp, isStandalone: true, selector: "wr-input-otp", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, length: { classPropertyName: "length", publicName: "length", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, mask: { classPropertyName: "mask", publicName: "mask", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", completed: "completed", touch: "touch" }, host: { attributes: { "role": "group" }, properties: { "class": "classes()", "attr.aria-label": "resolvedAriaLabel()" } }, viewQueries: [{ propertyName: "cellRefs", predicate: ["cell"], descendants: true, isSignal: true }], ngImport: i0, template: "@for (cell of cells(); track trackIndex($index)) {\n <input\n #cell\n class=\"wr-input-otp__cell\"\n [type]=\"cellType()\"\n [attr.inputmode]=\"cellInputMode()\"\n maxlength=\"1\"\n autocomplete=\"one-time-code\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n [value]=\"cell\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [placeholder]=\"placeholder()\"\n [attr.aria-label]=\"cellLabel($index)\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n (input)=\"onInput($event, $index)\"\n (keydown)=\"onKeyDown($event, $index)\"\n (paste)=\"onPaste($event)\"\n (blur)=\"onBlur()\"\n />\n}\n", encapsulation: i0.ViewEncapsulation.None });
|
|
310
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrInputOtp, isStandalone: true, selector: "wr-input-otp", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, length: { classPropertyName: "length", publicName: "length", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, mask: { classPropertyName: "mask", publicName: "mask", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", completed: "completed", touch: "touch" }, host: { attributes: { "role": "group" }, properties: { "class": "classes()", "attr.aria-label": "resolvedAriaLabel()" } }, viewQueries: [{ propertyName: "cellRefs", predicate: ["cell"], descendants: true, isSignal: true }], ngImport: i0, template: "@for (cell of cells(); track trackIndex($index)) {\n <input\n #cell\n class=\"wr-input-otp__cell\"\n [type]=\"cellType()\"\n [attr.inputmode]=\"cellInputMode()\"\n maxlength=\"1\"\n autocomplete=\"one-time-code\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n [value]=\"cell\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [placeholder]=\"placeholder()\"\n [attr.aria-label]=\"cellLabel($index)\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n (input)=\"onInput($event, $index)\"\n (keydown)=\"onKeyDown($event, $index)\"\n (compositionend)=\"onCompositionEnd($event, $index)\"\n (paste)=\"onPaste($event)\"\n (blur)=\"onBlur()\"\n />\n}\n", encapsulation: i0.ViewEncapsulation.None });
|
|
293
311
|
}
|
|
294
312
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrInputOtp, decorators: [{
|
|
295
313
|
type: Component,
|
|
296
|
-
args: [{ selector: 'wr-input-otp', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()', role: 'group', '[attr.aria-label]': 'resolvedAriaLabel()' }, template: "@for (cell of cells(); track trackIndex($index)) {\n <input\n #cell\n class=\"wr-input-otp__cell\"\n [type]=\"cellType()\"\n [attr.inputmode]=\"cellInputMode()\"\n maxlength=\"1\"\n autocomplete=\"one-time-code\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n [value]=\"cell\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [placeholder]=\"placeholder()\"\n [attr.aria-label]=\"cellLabel($index)\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n (input)=\"onInput($event, $index)\"\n (keydown)=\"onKeyDown($event, $index)\"\n (paste)=\"onPaste($event)\"\n (blur)=\"onBlur()\"\n />\n}\n" }]
|
|
314
|
+
args: [{ selector: 'wr-input-otp', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()', role: 'group', '[attr.aria-label]': 'resolvedAriaLabel()' }, template: "@for (cell of cells(); track trackIndex($index)) {\n <input\n #cell\n class=\"wr-input-otp__cell\"\n [type]=\"cellType()\"\n [attr.inputmode]=\"cellInputMode()\"\n maxlength=\"1\"\n autocomplete=\"one-time-code\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n [value]=\"cell\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [placeholder]=\"placeholder()\"\n [attr.aria-label]=\"cellLabel($index)\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n (input)=\"onInput($event, $index)\"\n (keydown)=\"onKeyDown($event, $index)\"\n (compositionend)=\"onCompositionEnd($event, $index)\"\n (paste)=\"onPaste($event)\"\n (blur)=\"onBlur()\"\n />\n}\n" }]
|
|
297
315
|
}], ctorParameters: () => [], propDecorators: { ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], length: [{ type: i0.Input, args: [{ isSignal: true, alias: "length", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], mask: [{ type: i0.Input, args: [{ isSignal: true, alias: "mask", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], completed: [{ type: i0.Output, args: ["completed"] }], touch: [{ type: i0.Output, args: ["touch"] }], cellRefs: [{ type: i0.ViewChildren, args: ['cell', { isSignal: true }] }] } });
|
|
298
316
|
|
|
299
317
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-input-otp.mjs","sources":["../../../projects/lib/input-otp/input-otp.ts","../../../projects/lib/input-otp/input-otp.html","../../../projects/lib/input-otp/ngwr-input-otp.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Directionality } from '@angular/cdk/bidi';\nimport { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n type ElementRef,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n model,\n output,\n signal,\n untracked,\n viewChildren,\n} from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { useConfigValue } from 'ngwr/config';\nimport { useFormFieldAria } from 'ngwr/form';\nimport { useI18nFormatter, useI18nText } from 'ngwr/i18n';\n\nimport type { WrInputOtpMode, WrInputOtpSize } from './interfaces';\n\n/**\n * Fixed-length one-time-code input. Renders one `<input>` per character,\n * auto-advances focus on typing, handles paste of a full code, and supports\n * masking like a password field.\n *\n * A signal-forms native control: it implements `FormValueControl<string>`, so\n * `[formField]` binds straight to its `value` model — no\n * `ControlValueAccessor` in between. `[(value)]` works standalone. Emits\n * `completed` once all cells are filled (useful for auto-submission).\n *\n * @example\n * ```html\n * <!-- signal forms -->\n * <wr-input-otp [formField]=\"form.code\" length=\"6\" (completed)=\"verify($event)\" />\n *\n * <!-- standalone two-way binding -->\n * <wr-input-otp [(value)]=\"code\" length=\"6\" (completed)=\"verify($event)\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/input-otp\n */\n@Component({\n selector: 'wr-input-otp',\n templateUrl: './input-otp.html',\n encapsulation: ViewEncapsulation.None,\n host: { '[class]': 'classes()', role: 'group', '[attr.aria-label]': 'resolvedAriaLabel()' },\n})\nexport class WrInputOtp implements FormValueControl<string> {\n private readonly dir = inject(Directionality, { optional: true });\n\n /** Accessible name of the whole strip. Falls back to `inputOtp.label`. */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = useI18nText(this.ariaLabel, 'inputOtp.label', 'Verification code');\n\n /** Number of cells to render. Clamped to `[1, 20]`. @default 6 */\n readonly length = input(6, {\n transform: (v: unknown): number => Math.max(1, Math.min(20, coerceNumberProperty(v, 6))),\n });\n\n /** Character set per cell. @default 'numeric' */\n readonly mode = input<WrInputOtpMode>('numeric');\n\n /**\n * Control size — shares the `--wr-control-*` contract. Unset falls back to the\n * `inputOtp.size` app default from `provideWrConfig()`. @default 'md'\n */\n readonly size = input<WrInputOtpSize | null>(null);\n\n /**\n * `size`, then the app config, then `md`.\n *\n * Read ONCE, here: a box carries no size of its own — the host modifier sets\n * `--wr-input-otp-size` / `-font` / `-radius` / `-gap` and every box reads them\n * from the cascade — so one resolution sizes the whole strip however many boxes\n * `length` renders.\n */\n protected readonly resolvedSize = useConfigValue(this.size, c => c.inputOtp?.size, 'md');\n\n /** Mask the typed characters like a password. @default false */\n readonly mask = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Disable interaction. Bound automatically from the field's disabled state\n * when used with `[formField]`.\n *\n * @default false\n */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Refuse edits while every box stays focusable and the code still submits.\n * Bound automatically from the field's readonly state when used with\n * `[formField]`.\n *\n * Each box is a real text input, so this is the native `readonly` attribute —\n * arrow keys, Home / End and selection keep working, which is the whole\n * difference from `disabled`. Paste is cancelled too, since a `paste` still\n * reaches a read-only input even though typing does not.\n *\n * @default false\n */\n readonly readonly = input(false, { transform: coerceBooleanProperty });\n\n /**\n * The surrounding `<wr-form-field>`'s error state — bound per BOX rather than\n * on the `role=\"group\"` host, because the box is what takes focus and a reader\n * queries the focused element.\n *\n * @internal\n */\n protected readonly fieldAria = useFormFieldAria();\n\n /** Character shown in empty cells. @default '•' */\n readonly placeholder = input<string>('•');\n\n /** The entered code. Bound by `[formField]`, or two-way via `[(value)]`. */\n readonly value = model<string>('');\n\n /** Fires once when every cell holds a character. */\n readonly completed = output<string>();\n\n /** Emitted on blur so a bound field can mark itself touched. */\n readonly touch = output<void>();\n\n protected readonly cells = signal<readonly string[]>(Array.from({ length: 6 }, () => ''));\n\n protected readonly classes = computed(() => {\n const parts = ['wr-input-otp'];\n const size = this.resolvedSize();\n if (size !== 'md') parts.push(`wr-input-otp--${size}`);\n if (this.disabled()) parts.push('wr-input-otp--disabled');\n else if (this.readonly()) parts.push('wr-input-otp--readonly');\n return parts.join(' ');\n });\n\n protected readonly cellType = computed<'password' | 'text'>(() => (this.mask() ? 'password' : 'text'));\n protected readonly cellInputMode = computed(() => (this.mode() === 'numeric' ? 'numeric' : 'text'));\n\n private readonly digitLabel = useI18nFormatter('inputOtp.digit', 'Digit {{index}}');\n private readonly characterLabel = useI18nFormatter('inputOtp.character', 'Character {{index}}');\n\n protected readonly cellRefs = viewChildren<ElementRef<HTMLInputElement>>('cell');\n\n constructor() {\n // Resize the cell array whenever `length` changes — preserves existing values.\n effect(() => {\n const len = this.length();\n const current = this.cells();\n if (current.length === len) return;\n const next = Array.from({ length: len }, (_, i) => current[i] ?? '');\n this.cells.set(next);\n });\n\n // Split an external `value` write back into cells (mirrors the old\n // writeValue). Skipped when the value is merely the echo of an in-cell\n // edit, so re-splitting can never shift or clobber what the user typed.\n effect(() => {\n // Coerce null/undefined to '' — a classic-forms binding can write null,\n // which the old `writeValue(v: string | null)` accepted too.\n const v = this.value() ?? '';\n untracked(() => {\n if (this.cells().join('') === v) return;\n const len = this.length();\n const raw = v.slice(0, len);\n const next = Array.from({ length: len }, (_, i) => this.sanitiseChar(raw[i] ?? ''));\n this.cells.set(next);\n });\n });\n }\n\n // Template handlers\n\n protected onInput(event: Event, index: number): void {\n const target = event.target as HTMLInputElement;\n if (this.readonly()) {\n // A read-only input refuses typed characters on its own; this covers the\n // synthetic writes a script (or a spec) can still make.\n target.value = this.cells()[index] ?? '';\n return;\n }\n const char = this.sanitiseChar(target.value.slice(-1));\n target.value = char;\n this.update(index, char);\n if (char) this.focusCell(index + 1);\n }\n\n protected onKeyDown(event: KeyboardEvent, index: number): void {\n const target = event.target as HTMLInputElement;\n switch (this.inlineKey(event.key)) {\n case 'Backspace':\n if (this.readonly()) {\n // Backspace on an empty box is an EDIT (it clears the previous one),\n // so it goes; the pure navigation keys below stay.\n event.preventDefault();\n break;\n }\n if (target.value === '') {\n event.preventDefault();\n this.update(index - 1, '');\n this.focusCell(index - 1);\n }\n break;\n case 'ArrowLeft':\n event.preventDefault();\n this.focusCell(index - 1);\n break;\n case 'ArrowRight':\n event.preventDefault();\n this.focusCell(index + 1);\n break;\n case 'Home':\n event.preventDefault();\n this.focusCell(0);\n break;\n case 'End':\n event.preventDefault();\n this.focusCell(this.length() - 1);\n break;\n }\n }\n\n protected onPaste(event: ClipboardEvent): void {\n event.preventDefault();\n if (this.readonly()) return;\n const text = event.clipboardData?.getData('text') ?? '';\n const chars = text\n .split('')\n .map(c => this.sanitiseChar(c))\n .filter(c => c.length > 0);\n if (chars.length === 0) return;\n const trimmed = chars.slice(0, this.length());\n const next = Array.from({ length: this.length() }, (_, i) => trimmed[i] ?? '');\n this.cells.set(next);\n this.emitChange();\n this.focusCell(Math.min(trimmed.length, this.length() - 1));\n }\n\n protected onBlur(): void {\n this.touch.emit();\n }\n\n protected trackIndex(index: number): number {\n return index;\n }\n\n /**\n * A box's accessible name. It projects no text and carries no label of its own,\n * so this string IS the name a screen reader reads for it.\n *\n * Keyed off `mode`, not fixed: `sanitiseChar` passes any letter through in\n * `alphanumeric` and `text`, so the old literal announced \"Digit 3\" over a box\n * holding `A` — wrong in a way translating it would not have fixed.\n */\n protected cellLabel(index: number): string {\n const params = { index: index + 1 };\n return this.mode() === 'numeric' ? this.digitLabel(params) : this.characterLabel(params);\n }\n\n // Internals\n\n private update(index: number, char: string): void {\n if (index < 0 || index >= this.length()) return;\n const next = [...this.cells()];\n next[index] = char;\n this.cells.set(next);\n this.emitChange();\n }\n\n private emitChange(): void {\n const code = this.cells().join('');\n this.value.set(code);\n if (code.length === this.length() && code.split('').every(c => c.length === 1)) {\n this.completed.emit(code);\n }\n }\n\n private focusCell(i: number): void {\n if (i < 0 || i >= this.length()) return;\n const el = this.cellRefs()[i]?.nativeElement;\n if (!el) return;\n el.focus();\n el.select();\n }\n\n /** Drop a character that doesn't match the configured mode. */\n private sanitiseChar(c: string): string {\n if (!c) return '';\n const one = c.slice(0, 1);\n switch (this.mode()) {\n case 'numeric':\n return /\\d/.test(one) ? one : '';\n case 'alphanumeric':\n return /[a-zA-Z0-9]/.test(one) ? one : '';\n case 'text':\n default:\n return one;\n }\n }\n\n /**\n * The key as the strip SEES it.\n *\n * The boxes run along the inline axis, so under `dir=\"rtl\"` the box to the\n * visual right of the caret is the PREVIOUS one. Home/End stay semantic (first\n * and last box), and Backspace is not a direction at all.\n */\n private inlineKey(key: string): string {\n if (this.dir?.value !== 'rtl') return key;\n if (key === 'ArrowRight') return 'ArrowLeft';\n if (key === 'ArrowLeft') return 'ArrowRight';\n return key;\n }\n}\n","@for (cell of cells(); track trackIndex($index)) {\n <input\n #cell\n class=\"wr-input-otp__cell\"\n [type]=\"cellType()\"\n [attr.inputmode]=\"cellInputMode()\"\n maxlength=\"1\"\n autocomplete=\"one-time-code\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n [value]=\"cell\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [placeholder]=\"placeholder()\"\n [attr.aria-label]=\"cellLabel($index)\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n (input)=\"onInput($event, $index)\"\n (keydown)=\"onKeyDown($event, $index)\"\n (paste)=\"onPaste($event)\"\n (blur)=\"onBlur()\"\n />\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;;;AAAA;;;;;AAKG;AA0BH;;;;;;;;;;;;;;;;;;;;AAoBG;MAOU,UAAU,CAAA;IACJ,GAAG,GAAG,MAAM,CAAC,cAAc,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;IAGxD,SAAS,GAAG,KAAK,CAAgB,IAAI;kFAAC;IAE5B,iBAAiB,GAAG,WAAW,CAAC,IAAI,CAAC,SAAS,EAAE,gBAAgB,EAAE,mBAAmB,CAAC;;AAGhG,IAAA,MAAM,GAAG,KAAK,CAAC,CAAC,8EACvB,SAAS,EAAE,CAAC,CAAU,KAAa,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GACxF;;IAGO,IAAI,GAAG,KAAK,CAAiB,SAAS;6EAAC;AAEhD;;;AAGG;IACM,IAAI,GAAG,KAAK,CAAwB,IAAI;6EAAC;AAElD;;;;;;;AAOG;AACgB,IAAA,YAAY,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC;;IAG/E,IAAI,GAAG,KAAK,CAAC,KAAK,4EAAI,SAAS,EAAE,qBAAqB,EAAA,CAAG;AAElE;;;;;AAKG;IACM,QAAQ,GAAG,KAAK,CAAC,KAAK,gFAAI,SAAS,EAAE,qBAAqB,EAAA,CAAG;AAEtE;;;;;;;;;;;AAWG;IACM,QAAQ,GAAG,KAAK,CAAC,KAAK,gFAAI,SAAS,EAAE,qBAAqB,EAAA,CAAG;AAEtE;;;;;;AAMG;IACgB,SAAS,GAAG,gBAAgB,EAAE;;IAGxC,WAAW,GAAG,KAAK,CAAS,GAAG;oFAAC;;IAGhC,KAAK,GAAG,KAAK,CAAS,EAAE;8EAAC;;IAGzB,SAAS,GAAG,MAAM,EAAU;;IAG5B,KAAK,GAAG,MAAM,EAAQ;AAEZ,IAAA,KAAK,GAAG,MAAM,CAAoB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC;8EAAC;AAEtE,IAAA,OAAO,GAAG,QAAQ,CAAC,MAAK;AACzC,QAAA,MAAM,KAAK,GAAG,CAAC,cAAc,CAAC;AAC9B,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE;QAChC,IAAI,IAAI,KAAK,IAAI;AAAE,YAAA,KAAK,CAAC,IAAI,CAAC,iBAAiB,IAAI,CAAA,CAAE,CAAC;QACtD,IAAI,IAAI,CAAC,QAAQ,EAAE;AAAE,YAAA,KAAK,CAAC,IAAI,CAAC,wBAAwB,CAAC;aACpD,IAAI,IAAI,CAAC,QAAQ,EAAE;AAAE,YAAA,KAAK,CAAC,IAAI,CAAC,wBAAwB,CAAC;AAC9D,QAAA,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;IACxB,CAAC;gFAAC;AAEiB,IAAA,QAAQ,GAAG,QAAQ,CAAsB,OAAO,IAAI,CAAC,IAAI,EAAE,GAAG,UAAU,GAAG,MAAM,CAAC;iFAAC;IACnF,aAAa,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,IAAI,EAAE,KAAK,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;sFAAC;AAElF,IAAA,UAAU,GAAG,gBAAgB,CAAC,gBAAgB,EAAE,iBAAiB,CAAC;AAClE,IAAA,cAAc,GAAG,gBAAgB,CAAC,oBAAoB,EAAE,qBAAqB,CAAC;IAE5E,QAAQ,GAAG,YAAY,CAA+B,MAAM;iFAAC;AAEhF,IAAA,WAAA,GAAA;;QAEE,MAAM,CAAC,MAAK;AACV,YAAA,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE;AACzB,YAAA,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,EAAE;AAC5B,YAAA,IAAI,OAAO,CAAC,MAAM,KAAK,GAAG;gBAAE;YAC5B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACpE,YAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;AACtB,QAAA,CAAC,CAAC;;;;QAKF,MAAM,CAAC,MAAK;;;YAGV,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE;YAC5B,SAAS,CAAC,MAAK;gBACb,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC;oBAAE;AACjC,gBAAA,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE;gBACzB,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;AAC3B,gBAAA,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AACnF,gBAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;AACtB,YAAA,CAAC,CAAC;AACJ,QAAA,CAAC,CAAC;IACJ;;IAIU,OAAO,CAAC,KAAY,EAAE,KAAa,EAAA;AAC3C,QAAA,MAAM,MAAM,GAAG,KAAK,CAAC,MAA0B;AAC/C,QAAA,IAAI,IAAI,CAAC,QAAQ,EAAE,EAAE;;;AAGnB,YAAA,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE;YACxC;QACF;AACA,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACtD,QAAA,MAAM,CAAC,KAAK,GAAG,IAAI;AACnB,QAAA,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC;AACxB,QAAA,IAAI,IAAI;AAAE,YAAA,IAAI,CAAC,SAAS,CAAC,KAAK,GAAG,CAAC,CAAC;IACrC;IAEU,SAAS,CAAC,KAAoB,EAAE,KAAa,EAAA;AACrD,QAAA,MAAM,MAAM,GAAG,KAAK,CAAC,MAA0B;QAC/C,QAAQ,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC;AAC/B,YAAA,KAAK,WAAW;AACd,gBAAA,IAAI,IAAI,CAAC,QAAQ,EAAE,EAAE;;;oBAGnB,KAAK,CAAC,cAAc,EAAE;oBACtB;gBACF;AACA,gBAAA,IAAI,MAAM,CAAC,KAAK,KAAK,EAAE,EAAE;oBACvB,KAAK,CAAC,cAAc,EAAE;oBACtB,IAAI,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,EAAE,EAAE,CAAC;AAC1B,oBAAA,IAAI,CAAC,SAAS,CAAC,KAAK,GAAG,CAAC,CAAC;gBAC3B;gBACA;AACF,YAAA,KAAK,WAAW;gBACd,KAAK,CAAC,cAAc,EAAE;AACtB,gBAAA,IAAI,CAAC,SAAS,CAAC,KAAK,GAAG,CAAC,CAAC;gBACzB;AACF,YAAA,KAAK,YAAY;gBACf,KAAK,CAAC,cAAc,EAAE;AACtB,gBAAA,IAAI,CAAC,SAAS,CAAC,KAAK,GAAG,CAAC,CAAC;gBACzB;AACF,YAAA,KAAK,MAAM;gBACT,KAAK,CAAC,cAAc,EAAE;AACtB,gBAAA,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;gBACjB;AACF,YAAA,KAAK,KAAK;gBACR,KAAK,CAAC,cAAc,EAAE;gBACtB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;gBACjC;;IAEN;AAEU,IAAA,OAAO,CAAC,KAAqB,EAAA;QACrC,KAAK,CAAC,cAAc,EAAE;QACtB,IAAI,IAAI,CAAC,QAAQ,EAAE;YAAE;AACrB,QAAA,MAAM,IAAI,GAAG,KAAK,CAAC,aAAa,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE;QACvD,MAAM,KAAK,GAAG;aACX,KAAK,CAAC,EAAE;aACR,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;aAC7B,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;AAC5B,QAAA,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE;AACxB,QAAA,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;AAC7C,QAAA,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAC9E,QAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;QACpB,IAAI,CAAC,UAAU,EAAE;AACjB,QAAA,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IAC7D;IAEU,MAAM,GAAA;AACd,QAAA,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;IACnB;AAEU,IAAA,UAAU,CAAC,KAAa,EAAA;AAChC,QAAA,OAAO,KAAK;IACd;AAEA;;;;;;;AAOG;AACO,IAAA,SAAS,CAAC,KAAa,EAAA;QAC/B,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,EAAE;QACnC,OAAO,IAAI,CAAC,IAAI,EAAE,KAAK,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC;IAC1F;;IAIQ,MAAM,CAAC,KAAa,EAAE,IAAY,EAAA;QACxC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE;YAAE;QACzC,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;AAC9B,QAAA,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI;AAClB,QAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;QACpB,IAAI,CAAC,UAAU,EAAE;IACnB;IAEQ,UAAU,GAAA;QAChB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;AAClC,QAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;AACpB,QAAA,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE;AAC9E,YAAA,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;QAC3B;IACF;AAEQ,IAAA,SAAS,CAAC,CAAS,EAAA;QACzB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE;YAAE;QACjC,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa;AAC5C,QAAA,IAAI,CAAC,EAAE;YAAE;QACT,EAAE,CAAC,KAAK,EAAE;QACV,EAAE,CAAC,MAAM,EAAE;IACb;;AAGQ,IAAA,YAAY,CAAC,CAAS,EAAA;AAC5B,QAAA,IAAI,CAAC,CAAC;AAAE,YAAA,OAAO,EAAE;QACjB,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;AACzB,QAAA,QAAQ,IAAI,CAAC,IAAI,EAAE;AACjB,YAAA,KAAK,SAAS;AACZ,gBAAA,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,EAAE;AAClC,YAAA,KAAK,cAAc;AACjB,gBAAA,OAAO,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,EAAE;AAC3C,YAAA,KAAK,MAAM;AACX,YAAA;AACE,gBAAA,OAAO,GAAG;;IAEhB;AAEA;;;;;;AAMG;AACK,IAAA,SAAS,CAAC,GAAW,EAAA;AAC3B,QAAA,IAAI,IAAI,CAAC,GAAG,EAAE,KAAK,KAAK,KAAK;AAAE,YAAA,OAAO,GAAG;QACzC,IAAI,GAAG,KAAK,YAAY;AAAE,YAAA,OAAO,WAAW;QAC5C,IAAI,GAAG,KAAK,WAAW;AAAE,YAAA,OAAO,YAAY;AAC5C,QAAA,OAAO,GAAG;IACZ;uGAzQW,UAAU,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAV,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAU,i+CC1DvB,8sBAuBA,EAAA,aAAA,EAAA,EAAA,CAAA,iBAAA,CAAA,IAAA,EAAA,CAAA;;2FDmCa,UAAU,EAAA,UAAA,EAAA,CAAA;kBANtB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,cAAc,iBAET,iBAAiB,CAAC,IAAI,EAAA,IAAA,EAC/B,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,EAAA,QAAA,EAAA,8sBAAA,EAAA;2jCAiGlB,MAAM,EAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,EAAA,EAAA,CAAA;;AEzJjF;;AAEG;;;;"}
|
|
1
|
+
{"version":3,"file":"ngwr-input-otp.mjs","sources":["../../../projects/lib/input-otp/input-otp.ts","../../../projects/lib/input-otp/input-otp.html","../../../projects/lib/input-otp/ngwr-input-otp.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Directionality } from '@angular/cdk/bidi';\nimport { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n type ElementRef,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n model,\n output,\n signal,\n untracked,\n viewChildren,\n} from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { useConfigValue } from 'ngwr/config';\nimport { useFormFieldAria } from 'ngwr/form';\nimport { useI18nFormatter, useI18nText } from 'ngwr/i18n';\nimport { isComposing } from 'ngwr/utils';\n\nimport type { WrInputOtpMode, WrInputOtpSize } from './interfaces';\n\n/**\n * Fixed-length one-time-code input. Renders one `<input>` per character,\n * auto-advances focus on typing, handles paste of a full code, and supports\n * masking like a password field.\n *\n * A signal-forms native control: it implements `FormValueControl<string>`, so\n * `[formField]` binds straight to its `value` model — no\n * `ControlValueAccessor` in between. `[(value)]` works standalone. Emits\n * `completed` once all cells are filled (useful for auto-submission).\n *\n * @example\n * ```html\n * <!-- signal forms -->\n * <wr-input-otp [formField]=\"form.code\" length=\"6\" (completed)=\"verify($event)\" />\n *\n * <!-- standalone two-way binding -->\n * <wr-input-otp [(value)]=\"code\" length=\"6\" (completed)=\"verify($event)\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/input-otp\n */\n@Component({\n selector: 'wr-input-otp',\n templateUrl: './input-otp.html',\n encapsulation: ViewEncapsulation.None,\n host: { '[class]': 'classes()', role: 'group', '[attr.aria-label]': 'resolvedAriaLabel()' },\n})\nexport class WrInputOtp implements FormValueControl<string> {\n private readonly dir = inject(Directionality, { optional: true });\n\n /** Accessible name of the whole strip. Falls back to `inputOtp.label`. */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = useI18nText(this.ariaLabel, 'inputOtp.label', 'Verification code');\n\n /** Number of cells to render. Clamped to `[1, 20]`. @default 6 */\n readonly length = input(6, {\n transform: (v: unknown): number => Math.max(1, Math.min(20, coerceNumberProperty(v, 6))),\n });\n\n /** Character set per cell. @default 'numeric' */\n readonly mode = input<WrInputOtpMode>('numeric');\n\n /**\n * Control size — shares the `--wr-control-*` contract. Unset falls back to the\n * `inputOtp.size` app default from `provideWrConfig()`. @default 'md'\n */\n readonly size = input<WrInputOtpSize | null>(null);\n\n /**\n * `size`, then the app config, then `md`.\n *\n * Read ONCE, here: a box carries no size of its own — the host modifier sets\n * `--wr-input-otp-size` / `-font` / `-radius` / `-gap` and every box reads them\n * from the cascade — so one resolution sizes the whole strip however many boxes\n * `length` renders.\n */\n protected readonly resolvedSize = useConfigValue(this.size, c => c.inputOtp?.size, 'md');\n\n /** Mask the typed characters like a password. @default false */\n readonly mask = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Disable interaction. Bound automatically from the field's disabled state\n * when used with `[formField]`.\n *\n * @default false\n */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Refuse edits while every box stays focusable and the code still submits.\n * Bound automatically from the field's readonly state when used with\n * `[formField]`.\n *\n * Each box is a real text input, so this is the native `readonly` attribute —\n * arrow keys, Home / End and selection keep working, which is the whole\n * difference from `disabled`. Paste is cancelled too, since a `paste` still\n * reaches a read-only input even though typing does not.\n *\n * @default false\n */\n readonly readonly = input(false, { transform: coerceBooleanProperty });\n\n /**\n * The surrounding `<wr-form-field>`'s error state — bound per BOX rather than\n * on the `role=\"group\"` host, because the box is what takes focus and a reader\n * queries the focused element.\n *\n * @internal\n */\n protected readonly fieldAria = useFormFieldAria();\n\n /** Character shown in empty cells. @default '•' */\n readonly placeholder = input<string>('•');\n\n /** The entered code. Bound by `[formField]`, or two-way via `[(value)]`. */\n readonly value = model<string>('');\n\n /** Fires once when every cell holds a character. */\n readonly completed = output<string>();\n\n /** Emitted on blur so a bound field can mark itself touched. */\n readonly touch = output<void>();\n\n protected readonly cells = signal<readonly string[]>(Array.from({ length: 6 }, () => ''));\n\n protected readonly classes = computed(() => {\n const parts = ['wr-input-otp'];\n const size = this.resolvedSize();\n if (size !== 'md') parts.push(`wr-input-otp--${size}`);\n if (this.disabled()) parts.push('wr-input-otp--disabled');\n else if (this.readonly()) parts.push('wr-input-otp--readonly');\n return parts.join(' ');\n });\n\n protected readonly cellType = computed<'password' | 'text'>(() => (this.mask() ? 'password' : 'text'));\n protected readonly cellInputMode = computed(() => (this.mode() === 'numeric' ? 'numeric' : 'text'));\n\n private readonly digitLabel = useI18nFormatter('inputOtp.digit', 'Digit {{index}}');\n private readonly characterLabel = useI18nFormatter('inputOtp.character', 'Character {{index}}');\n\n protected readonly cellRefs = viewChildren<ElementRef<HTMLInputElement>>('cell');\n\n constructor() {\n // Resize the cell array whenever `length` changes — preserves existing values.\n effect(() => {\n const len = this.length();\n const current = this.cells();\n if (current.length === len) return;\n const next = Array.from({ length: len }, (_, i) => current[i] ?? '');\n this.cells.set(next);\n });\n\n // Split an external `value` write back into cells (mirrors the old\n // writeValue). Skipped when the value is merely the echo of an in-cell\n // edit, so re-splitting can never shift or clobber what the user typed.\n effect(() => {\n // Coerce null/undefined to '' — a classic-forms binding can write null,\n // which the old `writeValue(v: string | null)` accepted too.\n const v = this.value() ?? '';\n untracked(() => {\n if (this.cells().join('') === v) return;\n const len = this.length();\n const raw = v.slice(0, len);\n const next = Array.from({ length: len }, (_, i) => this.sanitiseChar(raw[i] ?? ''));\n this.cells.set(next);\n });\n });\n }\n\n // Template handlers\n\n protected onInput(event: Event, index: number): void {\n // An input method leaves its half-built reading in the box and only replaces\n // it on `compositionend`. Sanitising that intermediate text writes `''` back\n // into the field the IME is still composing in, which aborts the conversion\n // outright — so the box is left alone until the text is real.\n if ((event as InputEvent).isComposing) return;\n this.commitCell(event.target as HTMLInputElement, index);\n }\n\n /** @see onInput — this is where an IME's text finally arrives. */\n protected onCompositionEnd(event: CompositionEvent, index: number): void {\n this.commitCell(event.target as HTMLInputElement, index);\n }\n\n private commitCell(target: HTMLInputElement, index: number): void {\n if (this.readonly()) {\n // A read-only input refuses typed characters on its own; this covers the\n // synthetic writes a script (or a spec) can still make.\n target.value = this.cells()[index] ?? '';\n return;\n }\n const char = this.sanitiseChar(target.value.slice(-1));\n target.value = char;\n this.update(index, char);\n if (char) this.focusCell(index + 1);\n }\n\n protected onKeyDown(event: KeyboardEvent, index: number): void {\n // Backspace and the arrows move the caret between boxes; while a conversion\n // is open they belong to the candidate window, and moving focus out of the\n // composing box discards it.\n if (isComposing(event)) return;\n\n const target = event.target as HTMLInputElement;\n switch (this.inlineKey(event.key)) {\n case 'Backspace':\n if (this.readonly()) {\n // Backspace on an empty box is an EDIT (it clears the previous one),\n // so it goes; the pure navigation keys below stay.\n event.preventDefault();\n break;\n }\n if (target.value === '') {\n event.preventDefault();\n this.update(index - 1, '');\n this.focusCell(index - 1);\n }\n break;\n case 'ArrowLeft':\n event.preventDefault();\n this.focusCell(index - 1);\n break;\n case 'ArrowRight':\n event.preventDefault();\n this.focusCell(index + 1);\n break;\n case 'Home':\n event.preventDefault();\n this.focusCell(0);\n break;\n case 'End':\n event.preventDefault();\n this.focusCell(this.length() - 1);\n break;\n }\n }\n\n protected onPaste(event: ClipboardEvent): void {\n event.preventDefault();\n if (this.readonly()) return;\n const text = event.clipboardData?.getData('text') ?? '';\n const chars = text\n .split('')\n .map(c => this.sanitiseChar(c))\n .filter(c => c.length > 0);\n if (chars.length === 0) return;\n const trimmed = chars.slice(0, this.length());\n const next = Array.from({ length: this.length() }, (_, i) => trimmed[i] ?? '');\n this.cells.set(next);\n this.emitChange();\n this.focusCell(Math.min(trimmed.length, this.length() - 1));\n }\n\n protected onBlur(): void {\n this.touch.emit();\n }\n\n protected trackIndex(index: number): number {\n return index;\n }\n\n /**\n * A box's accessible name. It projects no text and carries no label of its own,\n * so this string IS the name a screen reader reads for it.\n *\n * Keyed off `mode`, not fixed: `sanitiseChar` passes any letter through in\n * `alphanumeric` and `text`, so the old literal announced \"Digit 3\" over a box\n * holding `A` — wrong in a way translating it would not have fixed.\n */\n protected cellLabel(index: number): string {\n const params = { index: index + 1 };\n return this.mode() === 'numeric' ? this.digitLabel(params) : this.characterLabel(params);\n }\n\n // Internals\n\n private update(index: number, char: string): void {\n if (index < 0 || index >= this.length()) return;\n const next = [...this.cells()];\n next[index] = char;\n this.cells.set(next);\n this.emitChange();\n }\n\n private emitChange(): void {\n const code = this.cells().join('');\n this.value.set(code);\n if (code.length === this.length() && code.split('').every(c => c.length === 1)) {\n this.completed.emit(code);\n }\n }\n\n private focusCell(i: number): void {\n if (i < 0 || i >= this.length()) return;\n const el = this.cellRefs()[i]?.nativeElement;\n if (!el) return;\n el.focus();\n el.select();\n }\n\n /** Drop a character that doesn't match the configured mode. */\n private sanitiseChar(c: string): string {\n if (!c) return '';\n const one = c.slice(0, 1);\n switch (this.mode()) {\n case 'numeric':\n return /\\d/.test(one) ? one : '';\n case 'alphanumeric':\n return /[a-zA-Z0-9]/.test(one) ? one : '';\n case 'text':\n default:\n return one;\n }\n }\n\n /**\n * The key as the strip SEES it.\n *\n * The boxes run along the inline axis, so under `dir=\"rtl\"` the box to the\n * visual right of the caret is the PREVIOUS one. Home/End stay semantic (first\n * and last box), and Backspace is not a direction at all.\n */\n private inlineKey(key: string): string {\n if (this.dir?.value !== 'rtl') return key;\n if (key === 'ArrowRight') return 'ArrowLeft';\n if (key === 'ArrowLeft') return 'ArrowRight';\n return key;\n }\n}\n","@for (cell of cells(); track trackIndex($index)) {\n <input\n #cell\n class=\"wr-input-otp__cell\"\n [type]=\"cellType()\"\n [attr.inputmode]=\"cellInputMode()\"\n maxlength=\"1\"\n autocomplete=\"one-time-code\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n [value]=\"cell\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [placeholder]=\"placeholder()\"\n [attr.aria-label]=\"cellLabel($index)\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n (input)=\"onInput($event, $index)\"\n (keydown)=\"onKeyDown($event, $index)\"\n (compositionend)=\"onCompositionEnd($event, $index)\"\n (paste)=\"onPaste($event)\"\n (blur)=\"onBlur()\"\n />\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/fesm2022/ngwr-input.mjs
CHANGED
|
@@ -68,12 +68,19 @@ class WrInput {
|
|
|
68
68
|
describedBy = computed(() => this.field?.describedBy() ?? null, /* @ts-ignore */
|
|
69
69
|
...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
|
|
70
70
|
/**
|
|
71
|
-
* Control size.
|
|
72
|
-
* native `<input size>` attribute. Unset, it falls back to
|
|
71
|
+
* Control size. Unset, it falls back to
|
|
73
72
|
* `provideWrConfig({ input: { size } })`. @default 'md'
|
|
73
|
+
*
|
|
74
|
+
* Called `wrSize` until v14, to dodge the native `<input size>` attribute —
|
|
75
|
+
* which measures the field in characters and which `.wr-input`'s `width: 100%`
|
|
76
|
+
* has always overridden, so the clash it guarded against was never observable.
|
|
77
|
+
* It made this the one size input in the catalog spelled differently from the
|
|
78
|
+
* other twenty-three. The cost of the change is that a consumer who wanted the
|
|
79
|
+
* NATIVE `size` on an ngwr input can no longer have it — but `width: 100%`
|
|
80
|
+
* meant they never did.
|
|
74
81
|
*/
|
|
75
|
-
|
|
76
|
-
...(ngDevMode ? [{ debugName: "
|
|
82
|
+
size = input(null, /* @ts-ignore */
|
|
83
|
+
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
77
84
|
/**
|
|
78
85
|
* Pill-shaped corners. Unset, it falls back to
|
|
79
86
|
* `provideWrConfig({ input: { rounded } })`; `[rounded]="false"` turns a
|
|
@@ -89,7 +96,7 @@ class WrInput {
|
|
|
89
96
|
// there, "the template said nothing" and "the template said md" are the same value,
|
|
90
97
|
// so no app-wide default could ever apply. The effective default is the last
|
|
91
98
|
// argument below, which is where the old inline one moved to.
|
|
92
|
-
resolvedSize = useConfigValue(this.
|
|
99
|
+
resolvedSize = useConfigValue(this.size, c => c.input?.size, 'md');
|
|
93
100
|
resolvedRounded = useConfigValue(this.rounded, c => c.input?.rounded, false);
|
|
94
101
|
classes = computed(() => {
|
|
95
102
|
const parts = ['wr-input'];
|
|
@@ -102,7 +109,7 @@ class WrInput {
|
|
|
102
109
|
}, /* @ts-ignore */
|
|
103
110
|
...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
104
111
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrInput, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
105
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.4", type: WrInput, isStandalone: true, selector: "input[wrInput], textarea[wrInput]", inputs: {
|
|
112
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.4", type: WrInput, isStandalone: true, selector: "input[wrInput], textarea[wrInput]", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()", "attr.id": "resolvedId()", "attr.aria-invalid": "ariaInvalid()", "attr.aria-describedby": "describedBy()" } }, ngImport: i0 });
|
|
106
113
|
}
|
|
107
114
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrInput, decorators: [{
|
|
108
115
|
type: Directive,
|
|
@@ -115,7 +122,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
115
122
|
'[attr.aria-describedby]': 'describedBy()',
|
|
116
123
|
},
|
|
117
124
|
}]
|
|
118
|
-
}], ctorParameters: () => [], propDecorators: {
|
|
125
|
+
}], ctorParameters: () => [], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: false }] }] } });
|
|
119
126
|
|
|
120
127
|
/**
|
|
121
128
|
* @license
|