@ngneers/controls 0.0.1-next.1 → 0.0.1-next.2
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/fesm2022/ngneers-controls-api-ng.mjs +11 -18
- package/fesm2022/ngneers-controls-api-ng.mjs.map +1 -1
- package/fesm2022/ngneers-controls-badge.mjs +160 -0
- package/fesm2022/ngneers-controls-badge.mjs.map +1 -0
- package/fesm2022/ngneers-controls-base.mjs +85 -8
- package/fesm2022/ngneers-controls-base.mjs.map +1 -1
- package/fesm2022/{ngneers-controls-calendar-time-CJT5AFlA.mjs → ngneers-controls-calendar-time-paM6WVpV.mjs} +2 -2
- package/fesm2022/{ngneers-controls-calendar-time-CJT5AFlA.mjs.map → ngneers-controls-calendar-time-paM6WVpV.mjs.map} +1 -1
- package/fesm2022/ngneers-controls-calendar.mjs +13 -6
- package/fesm2022/ngneers-controls-calendar.mjs.map +1 -1
- package/fesm2022/ngneers-controls-checkbox.mjs +3 -3
- package/fesm2022/ngneers-controls-checkbox.mjs.map +1 -1
- package/fesm2022/ngneers-controls-color-picker.mjs +336 -0
- package/fesm2022/ngneers-controls-color-picker.mjs.map +1 -0
- package/fesm2022/ngneers-controls-default-icons.mjs +5 -0
- package/fesm2022/ngneers-controls-default-icons.mjs.map +1 -1
- package/fesm2022/ngneers-controls-directives.mjs +64 -7
- package/fesm2022/ngneers-controls-directives.mjs.map +1 -1
- package/fesm2022/ngneers-controls-edit-inplace.mjs +15 -5
- package/fesm2022/ngneers-controls-edit-inplace.mjs.map +1 -1
- package/fesm2022/ngneers-controls-errors.mjs +203 -148
- package/fesm2022/ngneers-controls-errors.mjs.map +1 -1
- package/fesm2022/ngneers-controls-filter.mjs +2 -2
- package/fesm2022/ngneers-controls-filter.mjs.map +1 -1
- package/fesm2022/ngneers-controls-hint.mjs +18 -1
- package/fesm2022/ngneers-controls-hint.mjs.map +1 -1
- package/fesm2022/ngneers-controls-i18n-translations-de.mjs +14 -0
- package/fesm2022/ngneers-controls-i18n-translations-de.mjs.map +1 -1
- package/fesm2022/ngneers-controls-i18n-translations-en.mjs +16 -0
- package/fesm2022/ngneers-controls-i18n-translations-en.mjs.map +1 -1
- package/fesm2022/ngneers-controls-icon.mjs +23 -6
- package/fesm2022/ngneers-controls-icon.mjs.map +1 -1
- package/fesm2022/ngneers-controls-input-field.mjs +18 -2
- package/fesm2022/ngneers-controls-input-field.mjs.map +1 -1
- package/fesm2022/ngneers-controls-input.mjs +49 -9
- package/fesm2022/ngneers-controls-input.mjs.map +1 -1
- package/fesm2022/ngneers-controls-list-box.mjs +2 -2
- package/fesm2022/ngneers-controls-list-box.mjs.map +1 -1
- package/fesm2022/ngneers-controls-mask-input.mjs +4 -1
- package/fesm2022/ngneers-controls-mask-input.mjs.map +1 -1
- package/fesm2022/ngneers-controls-number-input.mjs +4 -4
- package/fesm2022/ngneers-controls-number-input.mjs.map +1 -1
- package/fesm2022/ngneers-controls-otp.mjs +16 -4
- package/fesm2022/ngneers-controls-otp.mjs.map +1 -1
- package/fesm2022/ngneers-controls-paginator.mjs +36 -9
- package/fesm2022/ngneers-controls-paginator.mjs.map +1 -1
- package/fesm2022/ngneers-controls-radio.mjs +4 -4
- package/fesm2022/ngneers-controls-radio.mjs.map +1 -1
- package/fesm2022/ngneers-controls-rating.mjs +180 -0
- package/fesm2022/ngneers-controls-rating.mjs.map +1 -0
- package/fesm2022/ngneers-controls-scroll-shadow.mjs +33 -7
- package/fesm2022/ngneers-controls-scroll-shadow.mjs.map +1 -1
- package/fesm2022/ngneers-controls-scroller.mjs +4 -2
- package/fesm2022/ngneers-controls-scroller.mjs.map +1 -1
- package/fesm2022/ngneers-controls-select-button.mjs +2 -1
- package/fesm2022/ngneers-controls-select-button.mjs.map +1 -1
- package/fesm2022/ngneers-controls-select.mjs +24 -4
- package/fesm2022/ngneers-controls-select.mjs.map +1 -1
- package/fesm2022/ngneers-controls-slider.mjs +3 -3
- package/fesm2022/ngneers-controls-slider.mjs.map +1 -1
- package/fesm2022/ngneers-controls-stepper.mjs +142 -0
- package/fesm2022/ngneers-controls-stepper.mjs.map +1 -0
- package/fesm2022/ngneers-controls-switch.mjs +3 -3
- package/fesm2022/ngneers-controls-switch.mjs.map +1 -1
- package/fesm2022/ngneers-controls-table.mjs +282 -15
- package/fesm2022/ngneers-controls-table.mjs.map +1 -1
- package/fesm2022/ngneers-controls-tabs.mjs +2 -2
- package/fesm2022/ngneers-controls-tabs.mjs.map +1 -1
- package/fesm2022/ngneers-controls-toggle-button.mjs +3 -3
- package/fesm2022/ngneers-controls-toggle-button.mjs.map +1 -1
- package/fesm2022/ngneers-controls-tooltip.mjs +16 -28
- package/fesm2022/ngneers-controls-tooltip.mjs.map +1 -1
- package/fesm2022/ngneers-controls-tree.mjs +2 -2
- package/fesm2022/ngneers-controls-tree.mjs.map +1 -1
- package/fesm2022/ngneers-controls-utils.mjs +138 -1
- package/fesm2022/ngneers-controls-utils.mjs.map +1 -1
- package/package.json +18 -2
- package/types/ngneers-controls-api-ng.d.ts +12 -9
- package/types/ngneers-controls-badge.d.ts +67 -0
- package/types/ngneers-controls-base.d.ts +61 -4
- package/types/ngneers-controls-breadcrumb.d.ts +14 -0
- package/types/ngneers-controls-calendar.d.ts +21 -6
- package/types/ngneers-controls-chip.d.ts +14 -0
- package/types/ngneers-controls-color-picker.d.ts +119 -0
- package/types/ngneers-controls-dialog.d.ts +14 -0
- package/types/ngneers-controls-directives.d.ts +31 -2
- package/types/ngneers-controls-drawer.d.ts +14 -0
- package/types/ngneers-controls-edit-inplace.d.ts +20 -0
- package/types/ngneers-controls-errors.d.ts +105 -16
- package/types/ngneers-controls-filter.d.ts +27 -13
- package/types/ngneers-controls-hint.d.ts +8 -1
- package/types/ngneers-controls-i18n-translations-de.d.ts +14 -0
- package/types/ngneers-controls-i18n-translations-en.d.ts +14 -0
- package/types/ngneers-controls-i18n.d.ts +14 -0
- package/types/ngneers-controls-icon.d.ts +2 -2
- package/types/ngneers-controls-input-field.d.ts +25 -2
- package/types/ngneers-controls-input.d.ts +35 -3
- package/types/ngneers-controls-list-box.d.ts +14 -0
- package/types/ngneers-controls-mask-input.d.ts +14 -0
- package/types/ngneers-controls-otp.d.ts +19 -0
- package/types/ngneers-controls-paginator.d.ts +31 -3
- package/types/ngneers-controls-rating.d.ts +69 -0
- package/types/ngneers-controls-scroll-shadow.d.ts +11 -2
- package/types/ngneers-controls-scroller.d.ts +3 -1
- package/types/ngneers-controls-select.d.ts +27 -3
- package/types/ngneers-controls-snackbar.d.ts +14 -0
- package/types/ngneers-controls-splitter.d.ts +14 -0
- package/types/ngneers-controls-state.d.ts +2 -2
- package/types/ngneers-controls-stepper.d.ts +68 -0
- package/types/ngneers-controls-table.d.ts +273 -169
- package/types/ngneers-controls-toast.d.ts +14 -0
- package/types/ngneers-controls-tooltip.d.ts +0 -1
- package/types/ngneers-controls-tree.d.ts +14 -0
- package/types/ngneers-controls-upload.d.ts +14 -0
- package/types/ngneers-controls-utils.d.ts +27 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngneers-controls-number-input.mjs","sources":["../../src/number-input/helper.ts","../../src/number-input/number-input.ts","../../src/number-input/ngneers-controls-number-input.ts"],"sourcesContent":["/** Locale-specific separators derived from `Intl.NumberFormat`. */\nexport type LocaleNumberInfo = {\n /** The decimal separator, e.g. `.` (en) or `,` (de). */\n decimal: string;\n /** The grouping (thousands) separator, e.g. `,` (en) or `.` (de). */\n group: string;\n /**\n * The locale's digit glyphs indexed by value (`digits[0]` … `digits[9]`).\n * Non-Latin numbering systems (e.g. `ar-EG` → `٠…٩`) are mapped back to\n * ASCII before parsing. `null` when the locale already uses ASCII digits.\n */\n digits: readonly string[] | null;\n};\n\n/** Derives the decimal/grouping separators and digit glyphs for a locale. */\nexport function localeNumberInfo(locale: string): LocaleNumberInfo {\n const parts = new Intl.NumberFormat(locale).formatToParts(12345.6);\n const plain = new Intl.NumberFormat(locale, { useGrouping: false });\n const digits = Array.from({ length: 10 }, (_, i) => plain.format(i));\n const isAscii = digits.every((glyph, i) => glyph === String(i));\n return {\n decimal: parts.find(p => p.type === 'decimal')?.value ?? '.',\n group: parts.find(p => p.type === 'group')?.value ?? ',',\n digits: isAscii ? null : digits,\n };\n}\n\nexport type ParsedNumber =\n | { kind: 'empty' }\n | { kind: 'invalid' }\n | { kind: 'value'; value: number };\n\n/**\n * Parses user-typed text into a number using the locale's separators:\n * grouping separators (and non-breaking spaces used as such) are stripped,\n * the locale decimal separator is normalized to `.`. Exponent notation is\n * accepted; anything else non-numeric is `invalid`.\n */\nexport function parseLocaleNumber(text: string, info: LocaleNumberInfo): ParsedNumber {\n const trimmed = text.trim();\n if (!trimmed) {\n return { kind: 'empty' };\n }\n let normalized = trimmed;\n // Map non-Latin digit glyphs (e.g. ar-EG ٠…٩) back to ASCII before validating.\n if (info.digits) {\n for (let i = 0; i < 10; i++) {\n normalized = normalized.split(info.digits[i]!).join(String(i));\n }\n }\n normalized = normalized.split(info.group).join('');\n // Locales that group with spaces render non-breaking variants; users type\n // plain spaces — \\s covers both, so strip all whitespace flavors.\n normalized = normalized.replace(/\\s/g, '');\n if (info.decimal !== '.') {\n normalized = normalized.split(info.decimal).join('.');\n }\n if (!/^[+-]?(\\d+(\\.\\d*)?|\\.\\d+)(e[+-]?\\d+)?$/i.test(normalized)) {\n return { kind: 'invalid' };\n }\n const value = Number(normalized);\n if (!Number.isFinite(value)) {\n return { kind: 'invalid' };\n }\n return { kind: 'value', value };\n}\n\n/** Number of decimal places of `value` (bounded to 20, exponent-safe). */\nexport function decimalPlaces(value: number): number {\n if (!Number.isFinite(value)) {\n return 0;\n }\n let factor = 1;\n let places = 0;\n while (Math.round(value * factor) / factor !== value && places < 20) {\n factor *= 10;\n places++;\n }\n return places;\n}\n\n/** Rounds `value` to `decimals` decimal places (float-drift cleanup). */\nexport function roundToPrecision(value: number, decimals: number): number {\n const factor = Math.pow(10, decimals);\n return Math.round(value * factor) / factor;\n}\n\n/** Clamps `value` into `[min, max]`; open bounds when `null`/`undefined`. */\nexport function clampValue(value: number, min?: number | null, max?: number | null): number {\n if (min != null && value < min) {\n return min;\n }\n if (max != null && value > max) {\n return max;\n }\n return value;\n}\n\n/**\n * Steps `current` by `amount` in `direction`, clamped into `[min, max]` (no\n * wrapping). The result is rounded to the combined precision of the operands\n * so float drift (`0.1 + 0.2`) never surfaces. Stepping from `null` seeds at\n * `min` (increment) or `max` (decrement), falling back to `0` on open bounds.\n */\nexport function stepNumberValue(\n current: number | null,\n direction: 1 | -1,\n amount: number,\n min?: number | null,\n max?: number | null\n): number {\n if (current === null) {\n const seed = direction === 1 ? (min ?? 0) : (max ?? 0);\n return clampValue(seed, min, max);\n }\n const decimals = Math.max(decimalPlaces(current), decimalPlaces(amount));\n const next = roundToPrecision(current + direction * amount, decimals);\n return clampValue(next, min, max);\n}\n","import {\n computed,\n Directive,\n effect,\n inject,\n input,\n LOCALE_ID,\n numberAttribute,\n signal,\n untracked,\n} from '@angular/core';\nimport { domEventHandler } from '@ngneers/controls/api/ng';\nimport { provideSelf, ValueControlBase } from '@ngneers/controls/base';\nimport { inputControlTemplate } from '@ngneers/controls-themes/templates/input';\n\nimport { clampValue, localeNumberInfo, parseLocaleNumber, stepNumberValue } from './helper';\n\n/** `numberAttribute` that keeps `undefined`/`null`/`''` as \"no bound\" instead of `NaN`. */\nfunction optionalNumberAttribute(value: unknown): number | undefined {\n return value == null || value === '' ? undefined : numberAttribute(value);\n}\n\n/** A finite, strictly-positive step, or `undefined` for an invalid input. */\nfunction positiveStep(value: number | undefined): number | undefined {\n return value != null && Number.isFinite(value) && value > 0 ? value : undefined;\n}\n\n/**\n * Turns a native `<input>` into a locale-aware number input with a\n * `number | null` value model:\n *\n * - Free typing while focused; the value is committed on blur/Enter\n * (unparseable text reverts, out-of-range values are clamped — no wrapping).\n * - Display is formatted via `Intl.NumberFormat` while blurred; while focused\n * the text is the raw, ungrouped edit form.\n * - `ArrowUp`/`ArrowDown` step by `step`, with `Shift` by `bigStep`.\n * Results are rounded to the operands' precision so float drift never shows.\n * - Exposes the {@link NgnBase.stepValue}/{@link NgnBase.canStepValue} hooks,\n * so `ngn-spin-buttons` (or a surrounding field) can step the value.\n *\n * @category control\n */\n@Directive({\n selector: 'input[ngnNumberInput]',\n providers: [provideSelf(NgnNumberInput)],\n exportAs: 'ngnNumberInput',\n host: {\n type: 'text',\n role: 'spinbutton',\n inputmode: 'decimal',\n autocomplete: 'off',\n '[attr.aria-valuenow]': 'value() ?? null',\n '[attr.aria-valuemin]': 'min() ?? null',\n '[attr.aria-valuemax]': 'max() ?? null',\n '[attr.aria-label]': 'label()',\n '[attr.aria-invalid]': 'invalid() ? \"true\" : null',\n '[disabled]': 'disabled()',\n '[readOnly]': 'readonly()',\n },\n})\nexport class NgnNumberInput extends ValueControlBase<'numberInput', number | null> {\n public override readonly isFieldControl = true;\n protected readonly theme = this.injectThemeTemplate(inputControlTemplate, {\n root: true,\n invalid: () => this.invalid(),\n empty: () => this.empty(),\n });\n\n /**\n * The minimum allowed value (inclusive). Typed values below are clamped on\n * commit; stepping stops here.\n */\n public readonly min = input(undefined, { transform: optionalNumberAttribute });\n /**\n * The maximum allowed value (inclusive). Typed values above are clamped on\n * commit; stepping stops here.\n */\n public readonly max = input(undefined, { transform: optionalNumberAttribute });\n /**\n * The step amount applied by the spin buttons and `ArrowUp`/`ArrowDown`.\n * Non-finite or non-positive values fall back to `1`.\n * @default 1\n */\n public readonly step = input(1, { transform: numberAttribute });\n /**\n * The step amount applied with `Shift+ArrowUp`/`Shift+ArrowDown`.\n * Non-finite or non-positive values fall back to `appliedStep * 10`.\n * @default step * 10\n */\n public readonly bigStep = input(undefined, { transform: optionalNumberAttribute });\n /**\n * The locale used for formatting and parsing.\n * @default LOCALE_ID\n */\n public readonly locale = input<string | undefined>(undefined);\n /**\n * Additional `Intl.NumberFormat` options for the blurred display format only\n * (e.g. `{ minimumFractionDigits: 2 }`). While focused the input shows the\n * plain, ungrouped edit form, so display options never round or reshape the\n * value the user is typing.\n */\n public readonly formatOptions = input<Intl.NumberFormatOptions | undefined>(undefined);\n\n private readonly _input = this.element.nativeElement as HTMLInputElement;\n private readonly _localeId = inject(LOCALE_ID);\n\n protected readonly appliedLocale = computed(() => this.locale() ?? this._localeId);\n /** Sanitized step — a finite positive number, defaulting to `1`. */\n protected readonly appliedStep = computed(() => positiveStep(this.step()) ?? 1);\n protected readonly appliedBigStep = computed(\n () => positiveStep(this.bigStep()) ?? this.appliedStep() * 10\n );\n\n private readonly _localeInfo = computed(() => localeNumberInfo(this.appliedLocale()));\n private readonly _displayFormat = computed(\n () =>\n new Intl.NumberFormat(this.appliedLocale(), {\n maximumFractionDigits: 20,\n ...this.formatOptions(),\n })\n );\n // The edit formatter is deliberately plain — it does NOT apply formatOptions.\n // Display options (fraction digits, currency, percent) would round the\n // in-progress value or make the editable text unparsable on commit.\n private readonly _editFormat = computed(\n () =>\n new Intl.NumberFormat(this.appliedLocale(), {\n maximumFractionDigits: 20,\n useGrouping: false,\n })\n );\n\n /** Whether the input currently holds focus (raw edit format while focused). */\n protected readonly focused = signal(false);\n\n /** Mirror of the input element's current text (drives the `empty` class). */\n private readonly _text = signal(this._input.value ?? '');\n\n /**\n * The last value applied by an internal mutation (step/commit/clear) that\n * already set the edit text itself. Lets the value→text effect ignore those\n * changes while focused, so a deferred flush can't clobber text the user\n * typed right after (`undefined` = nothing applied internally yet).\n */\n private readonly _appliedValue = signal<number | null | undefined>(undefined);\n\n /** Whether the input holds no text at all. */\n public readonly empty = computed(() => this._text() === '');\n\n constructor() {\n super();\n\n // External value → text. While focused only overwrite when the parsed text\n // semantically differs (programmatic set wins, typing is never clobbered).\n effect(() => {\n const value = this.value() ?? null;\n const text = untracked(() => this._text());\n if (!this.focused()) {\n const next = value === null ? '' : this._displayFormat().format(value);\n if (next !== text) {\n this._setText(next);\n }\n // Record the reconciled value so a later focus (with no intervening\n // external change) short-circuits below instead of re-parsing the\n // edit text — otherwise focusing then typing can race this effect,\n // which would clobber the freshly typed text back to `value`.\n this._appliedValue.set(value);\n return;\n }\n // Only reflect EXTERNAL value changes into the edit text. Internal\n // mutations (step/commit/clear) set the text themselves and record\n // `_appliedValue`; skipping them here prevents a deferred effect flush\n // from clobbering text the user typed right after such a mutation.\n if (value === untracked(() => this._appliedValue())) {\n return;\n }\n const parsed = parseLocaleNumber(\n text,\n untracked(() => this._localeInfo())\n );\n const current = parsed.kind === 'value' ? parsed.value : null;\n if (current !== value) {\n this._setText(value === null ? '' : this._editFormat().format(value));\n }\n this._appliedValue.set(value);\n });\n\n domEventHandler(this.element, 'input', () => {\n this._text.set(this._input.value);\n });\n\n domEventHandler(this.element, 'focus', () => {\n this.focused.set(true);\n const value = this.value() ?? null;\n if (value !== null) {\n this._setText(this._editFormat().format(value));\n }\n });\n\n domEventHandler(this.element, 'blur', () => {\n this._commit();\n this.focused.set(false);\n this.touched.set(true);\n });\n\n domEventHandler(this.element, 'keydown', event => {\n if (this.disabled() || this.readonly()) {\n return;\n }\n if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {\n if (event.altKey || event.ctrlKey || event.metaKey) {\n return;\n }\n event.preventDefault();\n this.stepValue(event.key === 'ArrowUp' ? 1 : -1, event.shiftKey);\n } else if (event.key === 'Enter') {\n // Commit before a potential form submit sees the value.\n this._commit();\n }\n });\n }\n\n /**\n * Steps the value by `step` (or `bigStep`) without wrapping. Uncommitted\n * typed text is parsed and used as the base; an empty value seeds at `min`\n * (increment) or `max` (decrement), falling back to `0` on open bounds.\n */\n public override stepValue(direction: 1 | -1, big = false): boolean {\n if (this.disabled() || this.readonly()) {\n return false;\n }\n const parsed = parseLocaleNumber(this._input.value, this._localeInfo());\n const base =\n parsed.kind === 'value'\n ? clampValue(parsed.value, this.min(), this.max())\n : (this.value() ?? null);\n const amount = big ? this.appliedBigStep() : this.appliedStep();\n const next = stepNumberValue(base, direction, amount, this.min(), this.max());\n this._appliedValue.set(next);\n this.value.set(next);\n this._setText((this.focused() ? this._editFormat() : this._displayFormat()).format(next));\n return true;\n }\n\n /** Whether stepping in `direction` is currently possible (bound not reached). */\n public override canStepValue(direction: 1 | -1): boolean {\n if (this.disabled() || this.readonly()) {\n return false;\n }\n const value = this.value() ?? null;\n if (value === null) {\n return true;\n }\n const bound = direction === 1 ? this.max() : this.min();\n if (bound == null) {\n return true;\n }\n return direction === 1 ? value < bound : value > bound;\n }\n\n /** Clears value and text (invoked by a surrounding field's clear button). */\n public override clearValue(): boolean {\n if (this.disabled() || this.readonly()) {\n return false;\n }\n this._appliedValue.set(null);\n this.value.set(null);\n this._setText('');\n return true;\n }\n\n /**\n * Parses the current text and commits it to the value model: empty → `null`,\n * out-of-range → clamped, unparseable → reverted to the last committed value.\n */\n private _commit(): void {\n const format = this.focused() ? this._editFormat() : this._displayFormat();\n const parsed = parseLocaleNumber(this._input.value, this._localeInfo());\n if (parsed.kind === 'invalid') {\n const value = this.value() ?? null;\n this._appliedValue.set(value);\n this._setText(value === null ? '' : format.format(value));\n return;\n }\n const next = parsed.kind === 'empty' ? null : clampValue(parsed.value, this.min(), this.max());\n this._appliedValue.set(next);\n this.value.set(next);\n this._setText(next === null ? '' : format.format(next));\n }\n\n private _setText(text: string): void {\n this._input.value = text;\n this._text.set(text);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngneers-controls-number-input.mjs","sources":["../../src/number-input/helper.ts","../../src/number-input/number-input.ts","../../src/number-input/ngneers-controls-number-input.ts"],"sourcesContent":["/** Locale-specific separators derived from `Intl.NumberFormat`. */\nexport type LocaleNumberInfo = {\n /** The decimal separator, e.g. `.` (en) or `,` (de). */\n decimal: string;\n /** The grouping (thousands) separator, e.g. `,` (en) or `.` (de). */\n group: string;\n /**\n * The locale's digit glyphs indexed by value (`digits[0]` … `digits[9]`).\n * Non-Latin numbering systems (e.g. `ar-EG` → `٠…٩`) are mapped back to\n * ASCII before parsing. `null` when the locale already uses ASCII digits.\n */\n digits: readonly string[] | null;\n};\n\n/** Derives the decimal/grouping separators and digit glyphs for a locale. */\nexport function localeNumberInfo(locale: string): LocaleNumberInfo {\n const parts = new Intl.NumberFormat(locale).formatToParts(12345.6);\n const plain = new Intl.NumberFormat(locale, { useGrouping: false });\n const digits = Array.from({ length: 10 }, (_, i) => plain.format(i));\n const isAscii = digits.every((glyph, i) => glyph === String(i));\n return {\n decimal: parts.find(p => p.type === 'decimal')?.value ?? '.',\n group: parts.find(p => p.type === 'group')?.value ?? ',',\n digits: isAscii ? null : digits,\n };\n}\n\nexport type ParsedNumber =\n | { kind: 'empty' }\n | { kind: 'invalid' }\n | { kind: 'value'; value: number };\n\n/**\n * Parses user-typed text into a number using the locale's separators:\n * grouping separators (and non-breaking spaces used as such) are stripped,\n * the locale decimal separator is normalized to `.`. Exponent notation is\n * accepted; anything else non-numeric is `invalid`.\n */\nexport function parseLocaleNumber(text: string, info: LocaleNumberInfo): ParsedNumber {\n const trimmed = text.trim();\n if (!trimmed) {\n return { kind: 'empty' };\n }\n let normalized = trimmed;\n // Map non-Latin digit glyphs (e.g. ar-EG ٠…٩) back to ASCII before validating.\n if (info.digits) {\n for (let i = 0; i < 10; i++) {\n normalized = normalized.split(info.digits[i]!).join(String(i));\n }\n }\n normalized = normalized.split(info.group).join('');\n // Locales that group with spaces render non-breaking variants; users type\n // plain spaces — \\s covers both, so strip all whitespace flavors.\n normalized = normalized.replace(/\\s/g, '');\n if (info.decimal !== '.') {\n normalized = normalized.split(info.decimal).join('.');\n }\n if (!/^[+-]?(\\d+(\\.\\d*)?|\\.\\d+)(e[+-]?\\d+)?$/i.test(normalized)) {\n return { kind: 'invalid' };\n }\n const value = Number(normalized);\n if (!Number.isFinite(value)) {\n return { kind: 'invalid' };\n }\n return { kind: 'value', value };\n}\n\n/** Number of decimal places of `value` (bounded to 20, exponent-safe). */\nexport function decimalPlaces(value: number): number {\n if (!Number.isFinite(value)) {\n return 0;\n }\n let factor = 1;\n let places = 0;\n while (Math.round(value * factor) / factor !== value && places < 20) {\n factor *= 10;\n places++;\n }\n return places;\n}\n\n/** Rounds `value` to `decimals` decimal places (float-drift cleanup). */\nexport function roundToPrecision(value: number, decimals: number): number {\n const factor = Math.pow(10, decimals);\n return Math.round(value * factor) / factor;\n}\n\n/** Clamps `value` into `[min, max]`; open bounds when `null`/`undefined`. */\nexport function clampValue(value: number, min?: number | null, max?: number | null): number {\n if (min != null && value < min) {\n return min;\n }\n if (max != null && value > max) {\n return max;\n }\n return value;\n}\n\n/**\n * Steps `current` by `amount` in `direction`, clamped into `[min, max]` (no\n * wrapping). The result is rounded to the combined precision of the operands\n * so float drift (`0.1 + 0.2`) never surfaces. Stepping from `null` seeds at\n * `min` (increment) or `max` (decrement), falling back to `0` on open bounds.\n */\nexport function stepNumberValue(\n current: number | null,\n direction: 1 | -1,\n amount: number,\n min?: number | null,\n max?: number | null\n): number {\n if (current === null) {\n const seed = direction === 1 ? (min ?? 0) : (max ?? 0);\n return clampValue(seed, min, max);\n }\n const decimals = Math.max(decimalPlaces(current), decimalPlaces(amount));\n const next = roundToPrecision(current + direction * amount, decimals);\n return clampValue(next, min, max);\n}\n","import {\n computed,\n Directive,\n effect,\n inject,\n input,\n LOCALE_ID,\n numberAttribute,\n signal,\n untracked,\n} from '@angular/core';\nimport { domEventHandler } from '@ngneers/controls/api/ng';\nimport { provideSelf, ValueControlBase } from '@ngneers/controls/base';\nimport { inputControlTemplate } from '@ngneers/controls-themes/templates/input';\n\nimport { clampValue, localeNumberInfo, parseLocaleNumber, stepNumberValue } from './helper';\n\n/** `numberAttribute` that keeps `undefined`/`null`/`''` as \"no bound\" instead of `NaN`. */\nfunction optionalNumberAttribute(value: unknown): number | undefined {\n return value == null || value === '' ? undefined : numberAttribute(value);\n}\n\n/** A finite, strictly-positive step, or `undefined` for an invalid input. */\nfunction positiveStep(value: number | undefined): number | undefined {\n return value != null && Number.isFinite(value) && value > 0 ? value : undefined;\n}\n\n/**\n * Turns a native `<input>` into a locale-aware number input with a\n * `number | null` value model:\n *\n * - Free typing while focused; the value is committed on blur/Enter\n * (unparseable text reverts, out-of-range values are clamped — no wrapping).\n * - Display is formatted via `Intl.NumberFormat` while blurred; while focused\n * the text is the raw, ungrouped edit form.\n * - `ArrowUp`/`ArrowDown` step by `step`, with `Shift` by `bigStep`.\n * Results are rounded to the operands' precision so float drift never shows.\n * - Exposes the {@link NgnBase.stepValue}/{@link NgnBase.canStepValue} hooks,\n * so `ngn-spin-buttons` (or a surrounding field) can step the value.\n *\n * @category control\n */\n@Directive({\n selector: 'input[ngnNumberInput]',\n providers: [provideSelf(NgnNumberInput)],\n exportAs: 'ngnNumberInput',\n host: {\n type: 'text',\n role: 'spinbutton',\n inputmode: 'decimal',\n autocomplete: 'off',\n '[attr.aria-valuenow]': 'value() ?? null',\n '[attr.aria-valuemin]': 'min() ?? null',\n '[attr.aria-valuemax]': 'max() ?? null',\n '[attr.aria-label]': 'label()',\n '[attr.aria-invalid]': 'invalidState() ? \"true\" : null',\n '[disabled]': 'disabled()',\n '[readOnly]': 'readonly()',\n },\n})\nexport class NgnNumberInput extends ValueControlBase<'numberInput', number | null> {\n public override readonly isFieldControl = true;\n protected readonly theme = this.injectThemeTemplate(inputControlTemplate, {\n root: true,\n invalid: () => this.invalidState(),\n empty: () => this.empty(),\n });\n\n /**\n * The minimum allowed value (inclusive). Typed values below are clamped on\n * commit; stepping stops here.\n */\n public readonly min = input(undefined, { transform: optionalNumberAttribute });\n /**\n * The maximum allowed value (inclusive). Typed values above are clamped on\n * commit; stepping stops here.\n */\n public readonly max = input(undefined, { transform: optionalNumberAttribute });\n /**\n * The step amount applied by the spin buttons and `ArrowUp`/`ArrowDown`.\n * Non-finite or non-positive values fall back to `1`.\n * @default 1\n */\n public readonly step = input(1, { transform: numberAttribute });\n /**\n * The step amount applied with `Shift+ArrowUp`/`Shift+ArrowDown`.\n * Non-finite or non-positive values fall back to `appliedStep * 10`.\n * @default step * 10\n */\n public readonly bigStep = input(undefined, { transform: optionalNumberAttribute });\n /**\n * The locale used for formatting and parsing.\n * @default LOCALE_ID\n */\n public readonly locale = input<string | undefined>(undefined);\n /**\n * Additional `Intl.NumberFormat` options for the blurred display format only\n * (e.g. `{ minimumFractionDigits: 2 }`). While focused the input shows the\n * plain, ungrouped edit form, so display options never round or reshape the\n * value the user is typing.\n */\n public readonly formatOptions = input<Intl.NumberFormatOptions | undefined>(undefined);\n\n private readonly _input = this.element.nativeElement as HTMLInputElement;\n private readonly _localeId = inject(LOCALE_ID);\n\n protected readonly appliedLocale = computed(() => this.locale() ?? this._localeId);\n /** Sanitized step — a finite positive number, defaulting to `1`. */\n protected readonly appliedStep = computed(() => positiveStep(this.step()) ?? 1);\n protected readonly appliedBigStep = computed(\n () => positiveStep(this.bigStep()) ?? this.appliedStep() * 10\n );\n\n private readonly _localeInfo = computed(() => localeNumberInfo(this.appliedLocale()));\n private readonly _displayFormat = computed(\n () =>\n new Intl.NumberFormat(this.appliedLocale(), {\n maximumFractionDigits: 20,\n ...this.formatOptions(),\n })\n );\n // The edit formatter is deliberately plain — it does NOT apply formatOptions.\n // Display options (fraction digits, currency, percent) would round the\n // in-progress value or make the editable text unparsable on commit.\n private readonly _editFormat = computed(\n () =>\n new Intl.NumberFormat(this.appliedLocale(), {\n maximumFractionDigits: 20,\n useGrouping: false,\n })\n );\n\n /** Whether the input currently holds focus (raw edit format while focused). */\n protected readonly focused = signal(false);\n\n /** Mirror of the input element's current text (drives the `empty` class). */\n private readonly _text = signal(this._input.value ?? '');\n\n /**\n * The last value applied by an internal mutation (step/commit/clear) that\n * already set the edit text itself. Lets the value→text effect ignore those\n * changes while focused, so a deferred flush can't clobber text the user\n * typed right after (`undefined` = nothing applied internally yet).\n */\n private readonly _appliedValue = signal<number | null | undefined>(undefined);\n\n /** Whether the input holds no text at all. */\n public override readonly empty = computed(() => this._text() === '');\n\n constructor() {\n super();\n\n // External value → text. While focused only overwrite when the parsed text\n // semantically differs (programmatic set wins, typing is never clobbered).\n effect(() => {\n const value = this.value() ?? null;\n const text = untracked(() => this._text());\n if (!this.focused()) {\n const next = value === null ? '' : this._displayFormat().format(value);\n if (next !== text) {\n this._setText(next);\n }\n // Record the reconciled value so a later focus (with no intervening\n // external change) short-circuits below instead of re-parsing the\n // edit text — otherwise focusing then typing can race this effect,\n // which would clobber the freshly typed text back to `value`.\n this._appliedValue.set(value);\n return;\n }\n // Only reflect EXTERNAL value changes into the edit text. Internal\n // mutations (step/commit/clear) set the text themselves and record\n // `_appliedValue`; skipping them here prevents a deferred effect flush\n // from clobbering text the user typed right after such a mutation.\n if (value === untracked(() => this._appliedValue())) {\n return;\n }\n const parsed = parseLocaleNumber(\n text,\n untracked(() => this._localeInfo())\n );\n const current = parsed.kind === 'value' ? parsed.value : null;\n if (current !== value) {\n this._setText(value === null ? '' : this._editFormat().format(value));\n }\n this._appliedValue.set(value);\n });\n\n domEventHandler(this.element, 'input', () => {\n this._text.set(this._input.value);\n });\n\n domEventHandler(this.element, 'focus', () => {\n this.focused.set(true);\n const value = this.value() ?? null;\n if (value !== null) {\n this._setText(this._editFormat().format(value));\n }\n });\n\n domEventHandler(this.element, 'blur', () => {\n this._commit();\n this.focused.set(false);\n this.markTouched();\n });\n\n domEventHandler(this.element, 'keydown', event => {\n if (this.disabled() || this.readonly()) {\n return;\n }\n if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {\n if (event.altKey || event.ctrlKey || event.metaKey) {\n return;\n }\n event.preventDefault();\n this.stepValue(event.key === 'ArrowUp' ? 1 : -1, event.shiftKey);\n } else if (event.key === 'Enter') {\n // Commit before a potential form submit sees the value.\n this._commit();\n }\n });\n }\n\n /**\n * Steps the value by `step` (or `bigStep`) without wrapping. Uncommitted\n * typed text is parsed and used as the base; an empty value seeds at `min`\n * (increment) or `max` (decrement), falling back to `0` on open bounds.\n */\n public override stepValue(direction: 1 | -1, big = false): boolean {\n if (this.disabled() || this.readonly()) {\n return false;\n }\n const parsed = parseLocaleNumber(this._input.value, this._localeInfo());\n const base =\n parsed.kind === 'value'\n ? clampValue(parsed.value, this.min(), this.max())\n : (this.value() ?? null);\n const amount = big ? this.appliedBigStep() : this.appliedStep();\n const next = stepNumberValue(base, direction, amount, this.min(), this.max());\n this._appliedValue.set(next);\n this.value.set(next);\n this._setText((this.focused() ? this._editFormat() : this._displayFormat()).format(next));\n return true;\n }\n\n /** Whether stepping in `direction` is currently possible (bound not reached). */\n public override canStepValue(direction: 1 | -1): boolean {\n if (this.disabled() || this.readonly()) {\n return false;\n }\n const value = this.value() ?? null;\n if (value === null) {\n return true;\n }\n const bound = direction === 1 ? this.max() : this.min();\n if (bound == null) {\n return true;\n }\n return direction === 1 ? value < bound : value > bound;\n }\n\n /** Clears value and text (invoked by a surrounding field's clear button). */\n public override clearValue(): boolean {\n if (this.disabled() || this.readonly()) {\n return false;\n }\n this._appliedValue.set(null);\n this.value.set(null);\n this._setText('');\n return true;\n }\n\n /**\n * Parses the current text and commits it to the value model: empty → `null`,\n * out-of-range → clamped, unparseable → reverted to the last committed value.\n */\n private _commit(): void {\n const format = this.focused() ? this._editFormat() : this._displayFormat();\n const parsed = parseLocaleNumber(this._input.value, this._localeInfo());\n if (parsed.kind === 'invalid') {\n const value = this.value() ?? null;\n this._appliedValue.set(value);\n this._setText(value === null ? '' : format.format(value));\n return;\n }\n const next = parsed.kind === 'empty' ? null : clampValue(parsed.value, this.min(), this.max());\n this._appliedValue.set(next);\n this.value.set(next);\n this._setText(next === null ? '' : format.format(next));\n }\n\n private _setText(text: string): void {\n this._input.value = text;\n this._text.set(text);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -33,7 +33,7 @@ function normalizeLength(value) {
|
|
|
33
33
|
class NgnOtp extends ValueControlBase {
|
|
34
34
|
theme = this.injectThemeTemplate(otpControlTemplate, {
|
|
35
35
|
root: true,
|
|
36
|
-
invalid: () => this.
|
|
36
|
+
invalid: () => this.invalidState(),
|
|
37
37
|
});
|
|
38
38
|
i18n = inject(I18n).translations;
|
|
39
39
|
/**
|
|
@@ -199,6 +199,17 @@ class NgnOtp extends ValueControlBase {
|
|
|
199
199
|
this.cells.set(Array.from({ length: len }, (_, i) => chars[i] ?? ''));
|
|
200
200
|
this._focusCell(chars.length >= len ? len - 1 : chars.length);
|
|
201
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* Mark touched once focus leaves the whole group — moving between cells keeps
|
|
204
|
+
* the host focused, so those intra-cell hops don't count as a blur.
|
|
205
|
+
*/
|
|
206
|
+
onFocusOut(event) {
|
|
207
|
+
const next = event.relatedTarget;
|
|
208
|
+
if (next instanceof Node && this.element.nativeElement.contains(next)) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
this.markTouched();
|
|
212
|
+
}
|
|
202
213
|
/** Clears every cell (also serves a surrounding field's clear button). */
|
|
203
214
|
clearValue() {
|
|
204
215
|
if (this.disabled() || this.readonly())
|
|
@@ -231,7 +242,7 @@ class NgnOtp extends ValueControlBase {
|
|
|
231
242
|
this._boxes()[index]?.nativeElement.focus();
|
|
232
243
|
}
|
|
233
244
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnOtp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
234
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: NgnOtp, isStandalone: true, selector: "ngn-otp", inputs: { length: { classPropertyName: "length", publicName: "length", isSignal: true, isRequired: false, transformFunction: null }, mask: { classPropertyName: "mask", publicName: "mask", isSignal: true, isRequired: false, transformFunction: null }, integerOnly: { classPropertyName: "integerOnly", publicName: "integerOnly", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { completed: "completed" }, host: { attributes: { "role": "group" }, properties: { "attr.aria-label": "label()", "attr.aria-labelledby": "labelledBy()", "attr.aria-invalid": "
|
|
245
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: NgnOtp, isStandalone: true, selector: "ngn-otp", inputs: { length: { classPropertyName: "length", publicName: "length", isSignal: true, isRequired: false, transformFunction: null }, mask: { classPropertyName: "mask", publicName: "mask", isSignal: true, isRequired: false, transformFunction: null }, integerOnly: { classPropertyName: "integerOnly", publicName: "integerOnly", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { completed: "completed" }, host: { attributes: { "role": "group" }, listeners: { "focusout": "onFocusOut($event)" }, properties: { "attr.aria-label": "label()", "attr.aria-labelledby": "labelledBy()", "attr.aria-invalid": "invalidState() ? \"true\" : null" } }, providers: [provideSelf(NgnOtp)], viewQueries: [{ propertyName: "_boxes", predicate: ["box"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@for (i of indices(); track i) {\n <input\n #box\n [ptInt]=\"this\"\n [ptClass]=\"'box'\"\n [type]=\"boxType()\"\n [attr.inputmode]=\"boxInputMode()\"\n [value]=\"cells()[i] ?? ''\"\n [disabled]=\"disabled()\"\n [attr.readonly]=\"readonly() || null\"\n [attr.aria-invalid]=\"invalidState() ? 'true' : null\"\n [attr.aria-label]=\"cellLabel(i)\"\n [attr.autocomplete]=\"i === 0 ? 'one-time-code' : 'off'\"\n maxlength=\"1\"\n autocapitalize=\"off\"\n autocorrect=\"off\"\n spellcheck=\"false\"\n (beforeinput)=\"onBeforeInput(i, $event)\"\n (keydown)=\"onKeyDown(i, $event)\"\n (paste)=\"onPaste($event)\"\n />\n}\n", dependencies: [{ kind: "directive", type: NgnPt, selector: "[ptInt]", inputs: ["ptInt", "ptClass", "ptDep"] }] });
|
|
235
246
|
}
|
|
236
247
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnOtp, decorators: [{
|
|
237
248
|
type: Component,
|
|
@@ -239,8 +250,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
|
|
|
239
250
|
role: 'group',
|
|
240
251
|
'[attr.aria-label]': 'label()',
|
|
241
252
|
'[attr.aria-labelledby]': 'labelledBy()',
|
|
242
|
-
'[attr.aria-invalid]': '
|
|
243
|
-
|
|
253
|
+
'[attr.aria-invalid]': 'invalidState() ? "true" : null',
|
|
254
|
+
'(focusout)': 'onFocusOut($event)',
|
|
255
|
+
}, template: "@for (i of indices(); track i) {\n <input\n #box\n [ptInt]=\"this\"\n [ptClass]=\"'box'\"\n [type]=\"boxType()\"\n [attr.inputmode]=\"boxInputMode()\"\n [value]=\"cells()[i] ?? ''\"\n [disabled]=\"disabled()\"\n [attr.readonly]=\"readonly() || null\"\n [attr.aria-invalid]=\"invalidState() ? 'true' : null\"\n [attr.aria-label]=\"cellLabel(i)\"\n [attr.autocomplete]=\"i === 0 ? 'one-time-code' : 'off'\"\n maxlength=\"1\"\n autocapitalize=\"off\"\n autocorrect=\"off\"\n spellcheck=\"false\"\n (beforeinput)=\"onBeforeInput(i, $event)\"\n (keydown)=\"onKeyDown(i, $event)\"\n (paste)=\"onPaste($event)\"\n />\n}\n" }]
|
|
244
256
|
}], ctorParameters: () => [], propDecorators: { length: [{ type: i0.Input, args: [{ isSignal: true, alias: "length", required: false }] }], mask: [{ type: i0.Input, args: [{ isSignal: true, alias: "mask", required: false }] }], integerOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "integerOnly", required: false }] }], completed: [{ type: i0.Output, args: ["completed"] }], _boxes: [{ type: i0.ViewChildren, args: ['box', { isSignal: true }] }] } });
|
|
245
257
|
|
|
246
258
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngneers-controls-otp.mjs","sources":["../../src/otp/otp.ts","../../src/otp/otp.html","../../src/otp/ngneers-controls-otp.ts"],"sourcesContent":["import {\n booleanAttribute,\n Component,\n computed,\n effect,\n ElementRef,\n inject,\n input,\n numberAttribute,\n output,\n signal,\n untracked,\n viewChildren,\n} from '@angular/core';\nimport { NgnPt, provideSelf, ValueControlBase } from '@ngneers/controls/base';\nimport { I18n } from '@ngneers/controls/i18n';\nimport { otpControlTemplate } from '@ngneers/controls-themes/templates/otp';\n\n/**\n * Coerce a requested length to a sane cell count (at least one). Coerces\n * strings too, so a value set without the `numberAttribute` transform (e.g. via\n * a dynamic property write) still yields a number instead of collapsing to 1.\n */\nfunction normalizeLength(value: number): number {\n const n = Number(value);\n return Number.isFinite(n) && n > 0 ? Math.floor(n) : 1;\n}\n\n/**\n * A one-time-password / verification-code input that splits a short code across\n * a row of single-character cells (like the PrimeNG `InputOtp`).\n *\n * - Typing a character fills the active cell and advances focus to the next.\n * - **Backspace** clears the active cell, or steps back and clears the previous\n * one when the active cell is already empty.\n * - **←/→** move between cells, **Home/End** jump to the first/last.\n * - Pasting a code distributes its characters across the cells.\n *\n * It is a self-contained value control — bind `[value]`/`(valueChange)` (or\n * `[(value)]`) directly on `<ngn-otp>`. The value is the composed string\n * (`null` while every cell is empty); {@link completed} fires once the whole\n * code is filled.\n *\n * @category control\n */\n@Component({\n selector: 'ngn-otp',\n templateUrl: './otp.html',\n imports: [NgnPt],\n providers: [provideSelf(NgnOtp)],\n host: {\n role: 'group',\n '[attr.aria-label]': 'label()',\n '[attr.aria-labelledby]': 'labelledBy()',\n '[attr.aria-invalid]': 'invalid() ? \"true\" : null',\n },\n})\nexport class NgnOtp extends ValueControlBase<'otp', string | null> {\n protected readonly theme = this.injectThemeTemplate(otpControlTemplate, {\n root: true,\n invalid: () => this.invalid(),\n });\n protected readonly i18n = inject(I18n).translations;\n\n /**\n * The number of character cells the code is split across.\n * @default 6\n */\n public readonly length = input(6, { transform: numberAttribute });\n /**\n * Render each entered character as a masked dot instead of the character\n * itself (like a password field).\n * @default false\n */\n public readonly mask = input(false, { transform: booleanAttribute });\n /**\n * Restrict entry to digits (`0`–`9`) only. Also switches the on-screen\n * keyboard to numeric on touch devices.\n * @default false\n */\n public readonly integerOnly = input(false, { transform: booleanAttribute });\n\n /** Emits the fully composed code once every cell holds a character. */\n public readonly completed = output<string>();\n\n /** Per-cell characters — the source of truth the composed value derives from. */\n protected readonly cells = signal<string[]>([]);\n\n /** Effective, sanitized cell count. */\n protected readonly appliedLength = computed(() => normalizeLength(this.length()));\n\n /** Cell indices for the template `@for` loop. */\n protected readonly indices = computed(() =>\n Array.from({ length: this.appliedLength() }, (_, i) => i)\n );\n\n protected readonly boxType = computed(() => (this.mask() ? 'password' : 'text'));\n protected readonly boxInputMode = computed(() => (this.integerOnly() ? 'numeric' : 'text'));\n\n private readonly _boxes = viewChildren<ElementRef<HTMLInputElement>>('box');\n\n constructor() {\n super();\n\n // Keep the cell array sized to `length`, preserving already-typed chars so\n // shrinking/growing the field does not wipe what the user entered.\n effect(() => {\n const len = this.appliedLength();\n const cur = untracked(() => this.cells());\n if (cur.length !== len) {\n this.cells.set(Array.from({ length: len }, (_, i) => cur[i] ?? ''));\n }\n });\n\n // External value → cells. Guard the write-loop by comparing to the current\n // composed value so internal typing never re-triggers a reset.\n effect(() => {\n const len = this.appliedLength();\n const ext = this.value() ?? '';\n const current = untracked(() => this.cells().join(''));\n if (ext !== current) {\n const chars = ext.split('').slice(0, len);\n this.cells.set(Array.from({ length: len }, (_, i) => chars[i] ?? ''));\n }\n });\n\n // Cells → external value + completion. `null` while the field is empty.\n effect(() => {\n const cells = this.cells();\n const composed = cells.join('');\n this.value.set(composed === '' ? null : composed);\n if (cells.length > 0 && cells.every(c => c !== '')) {\n this.completed.emit(composed);\n }\n });\n }\n\n /** Accessible name for a cell — its 1-based position within the code. */\n protected cellLabel(index: number): string {\n return this.i18n['otp_cellLabel']({ index: index + 1, total: this.appliedLength() });\n }\n\n /**\n * The control fully owns each cell's value: every text mutation is cancelled\n * and the character is written to the {@link cells} signal instead, which the\n * `[value]` binding reflects back. This makes overtype work from any caret\n * position (no `maxlength`/selection tricks) and lets a single multi-character\n * insert — e.g. SMS one-time-code autofill — spread across the cells.\n */\n protected onBeforeInput(index: number, event: InputEvent): void {\n if (this.disabled() || this.readonly()) return;\n const type = event.inputType;\n\n // Deletions: desktop goes through keydown, but mobile soft keyboards emit\n // these directly, so handle them here too.\n if (type.startsWith('delete')) {\n event.preventDefault();\n if (this.cells()[index]) {\n this._setCell(index, '');\n } else if (index > 0) {\n this._setCell(index - 1, '');\n this._focusCell(index - 1);\n }\n return;\n }\n\n // Paste/drop are handled by the dedicated (paste) handler.\n if (type === 'insertFromPaste' || type === 'insertFromDrop') return;\n if (!type.startsWith('insert')) return;\n\n event.preventDefault();\n let data = event.data ?? '';\n if (this.integerOnly()) data = data.replace(/\\D/g, '');\n if (!data) return;\n this._fillFrom(index, data);\n }\n\n protected onKeyDown(index: number, event: KeyboardEvent): void {\n if (this.disabled() || this.readonly()) return;\n if (event.ctrlKey || event.metaKey || event.altKey) return;\n\n switch (event.key) {\n case 'Backspace': {\n event.preventDefault();\n const cells = this.cells();\n if (cells[index]) {\n this._setCell(index, '');\n } else if (index > 0) {\n this._setCell(index - 1, '');\n this._focusCell(index - 1);\n }\n break;\n }\n case 'Delete':\n event.preventDefault();\n this._setCell(index, '');\n break;\n case 'ArrowLeft':\n event.preventDefault();\n if (index > 0) this._focusCell(index - 1);\n break;\n case 'ArrowRight':\n event.preventDefault();\n if (index < this.appliedLength() - 1) 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.appliedLength() - 1);\n break;\n }\n }\n\n protected onPaste(event: ClipboardEvent): void {\n if (this.disabled() || this.readonly()) return;\n event.preventDefault();\n let text = event.clipboardData?.getData('text') ?? '';\n text = this.integerOnly() ? text.replace(/\\D/g, '') : text.replace(/\\s/g, '');\n if (!text) return;\n // A paste replaces the whole code from the first cell, clearing the rest.\n const len = this.appliedLength();\n const chars = text.split('').slice(0, len);\n this.cells.set(Array.from({ length: len }, (_, i) => chars[i] ?? ''));\n this._focusCell(chars.length >= len ? len - 1 : chars.length);\n }\n\n /** Clears every cell (also serves a surrounding field's clear button). */\n public override clearValue(): boolean {\n if (this.disabled() || this.readonly()) return false;\n this.cells.set(Array.from({ length: this.appliedLength() }, () => ''));\n this._focusCell(0);\n return true;\n }\n\n private _setCell(index: number, value: string): void {\n const next = [...this.cells()];\n next[index] = value;\n this.cells.set(next);\n }\n\n /** Writes `text` into consecutive cells starting at `start`, then focuses the\n * next empty cell (or the last one). Used by typing and autofill. */\n private _fillFrom(start: number, text: string): void {\n const len = this.appliedLength();\n const next = [...this.cells()];\n let i = start;\n for (const ch of text.split('')) {\n if (i >= len) break;\n next[i] = ch;\n i++;\n }\n this.cells.set(next);\n this._focusCell(Math.min(i, len - 1));\n }\n\n private _focusCell(index: number): void {\n this._boxes()[index]?.nativeElement.focus();\n }\n}\n","@for (i of indices(); track i) {\n <input\n #box\n [ptInt]=\"this\"\n [ptClass]=\"'box'\"\n [type]=\"boxType()\"\n [attr.inputmode]=\"boxInputMode()\"\n [value]=\"cells()[i] ?? ''\"\n [disabled]=\"disabled()\"\n [attr.readonly]=\"readonly() || null\"\n [attr.aria-invalid]=\"invalid() ? 'true' : null\"\n [attr.aria-label]=\"cellLabel(i)\"\n [attr.autocomplete]=\"i === 0 ? 'one-time-code' : 'off'\"\n maxlength=\"1\"\n autocapitalize=\"off\"\n autocorrect=\"off\"\n spellcheck=\"false\"\n (beforeinput)=\"onBeforeInput(i, $event)\"\n (keydown)=\"onKeyDown(i, $event)\"\n (paste)=\"onPaste($event)\"\n />\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngneers-controls-otp.mjs","sources":["../../src/otp/otp.ts","../../src/otp/otp.html","../../src/otp/ngneers-controls-otp.ts"],"sourcesContent":["import {\n booleanAttribute,\n Component,\n computed,\n effect,\n ElementRef,\n inject,\n input,\n numberAttribute,\n output,\n signal,\n untracked,\n viewChildren,\n} from '@angular/core';\nimport { NgnPt, provideSelf, ValueControlBase } from '@ngneers/controls/base';\nimport { I18n } from '@ngneers/controls/i18n';\nimport { otpControlTemplate } from '@ngneers/controls-themes/templates/otp';\n\n/**\n * Coerce a requested length to a sane cell count (at least one). Coerces\n * strings too, so a value set without the `numberAttribute` transform (e.g. via\n * a dynamic property write) still yields a number instead of collapsing to 1.\n */\nfunction normalizeLength(value: number): number {\n const n = Number(value);\n return Number.isFinite(n) && n > 0 ? Math.floor(n) : 1;\n}\n\n/**\n * A one-time-password / verification-code input that splits a short code across\n * a row of single-character cells (like the PrimeNG `InputOtp`).\n *\n * - Typing a character fills the active cell and advances focus to the next.\n * - **Backspace** clears the active cell, or steps back and clears the previous\n * one when the active cell is already empty.\n * - **←/→** move between cells, **Home/End** jump to the first/last.\n * - Pasting a code distributes its characters across the cells.\n *\n * It is a self-contained value control — bind `[value]`/`(valueChange)` (or\n * `[(value)]`) directly on `<ngn-otp>`. The value is the composed string\n * (`null` while every cell is empty); {@link completed} fires once the whole\n * code is filled.\n *\n * @category control\n */\n@Component({\n selector: 'ngn-otp',\n templateUrl: './otp.html',\n imports: [NgnPt],\n providers: [provideSelf(NgnOtp)],\n host: {\n role: 'group',\n '[attr.aria-label]': 'label()',\n '[attr.aria-labelledby]': 'labelledBy()',\n '[attr.aria-invalid]': 'invalidState() ? \"true\" : null',\n '(focusout)': 'onFocusOut($event)',\n },\n})\nexport class NgnOtp extends ValueControlBase<'otp', string | null> {\n protected readonly theme = this.injectThemeTemplate(otpControlTemplate, {\n root: true,\n invalid: () => this.invalidState(),\n });\n protected readonly i18n = inject(I18n).translations;\n\n /**\n * The number of character cells the code is split across.\n * @default 6\n */\n public readonly length = input(6, { transform: numberAttribute });\n /**\n * Render each entered character as a masked dot instead of the character\n * itself (like a password field).\n * @default false\n */\n public readonly mask = input(false, { transform: booleanAttribute });\n /**\n * Restrict entry to digits (`0`–`9`) only. Also switches the on-screen\n * keyboard to numeric on touch devices.\n * @default false\n */\n public readonly integerOnly = input(false, { transform: booleanAttribute });\n\n /** Emits the fully composed code once every cell holds a character. */\n public readonly completed = output<string>();\n\n /** Per-cell characters — the source of truth the composed value derives from. */\n protected readonly cells = signal<string[]>([]);\n\n /** Effective, sanitized cell count. */\n protected readonly appliedLength = computed(() => normalizeLength(this.length()));\n\n /** Cell indices for the template `@for` loop. */\n protected readonly indices = computed(() =>\n Array.from({ length: this.appliedLength() }, (_, i) => i)\n );\n\n protected readonly boxType = computed(() => (this.mask() ? 'password' : 'text'));\n protected readonly boxInputMode = computed(() => (this.integerOnly() ? 'numeric' : 'text'));\n\n private readonly _boxes = viewChildren<ElementRef<HTMLInputElement>>('box');\n\n constructor() {\n super();\n\n // Keep the cell array sized to `length`, preserving already-typed chars so\n // shrinking/growing the field does not wipe what the user entered.\n effect(() => {\n const len = this.appliedLength();\n const cur = untracked(() => this.cells());\n if (cur.length !== len) {\n this.cells.set(Array.from({ length: len }, (_, i) => cur[i] ?? ''));\n }\n });\n\n // External value → cells. Guard the write-loop by comparing to the current\n // composed value so internal typing never re-triggers a reset.\n effect(() => {\n const len = this.appliedLength();\n const ext = this.value() ?? '';\n const current = untracked(() => this.cells().join(''));\n if (ext !== current) {\n const chars = ext.split('').slice(0, len);\n this.cells.set(Array.from({ length: len }, (_, i) => chars[i] ?? ''));\n }\n });\n\n // Cells → external value + completion. `null` while the field is empty.\n effect(() => {\n const cells = this.cells();\n const composed = cells.join('');\n this.value.set(composed === '' ? null : composed);\n if (cells.length > 0 && cells.every(c => c !== '')) {\n this.completed.emit(composed);\n }\n });\n }\n\n /** Accessible name for a cell — its 1-based position within the code. */\n protected cellLabel(index: number): string {\n return this.i18n['otp_cellLabel']({ index: index + 1, total: this.appliedLength() });\n }\n\n /**\n * The control fully owns each cell's value: every text mutation is cancelled\n * and the character is written to the {@link cells} signal instead, which the\n * `[value]` binding reflects back. This makes overtype work from any caret\n * position (no `maxlength`/selection tricks) and lets a single multi-character\n * insert — e.g. SMS one-time-code autofill — spread across the cells.\n */\n protected onBeforeInput(index: number, event: InputEvent): void {\n if (this.disabled() || this.readonly()) return;\n const type = event.inputType;\n\n // Deletions: desktop goes through keydown, but mobile soft keyboards emit\n // these directly, so handle them here too.\n if (type.startsWith('delete')) {\n event.preventDefault();\n if (this.cells()[index]) {\n this._setCell(index, '');\n } else if (index > 0) {\n this._setCell(index - 1, '');\n this._focusCell(index - 1);\n }\n return;\n }\n\n // Paste/drop are handled by the dedicated (paste) handler.\n if (type === 'insertFromPaste' || type === 'insertFromDrop') return;\n if (!type.startsWith('insert')) return;\n\n event.preventDefault();\n let data = event.data ?? '';\n if (this.integerOnly()) data = data.replace(/\\D/g, '');\n if (!data) return;\n this._fillFrom(index, data);\n }\n\n protected onKeyDown(index: number, event: KeyboardEvent): void {\n if (this.disabled() || this.readonly()) return;\n if (event.ctrlKey || event.metaKey || event.altKey) return;\n\n switch (event.key) {\n case 'Backspace': {\n event.preventDefault();\n const cells = this.cells();\n if (cells[index]) {\n this._setCell(index, '');\n } else if (index > 0) {\n this._setCell(index - 1, '');\n this._focusCell(index - 1);\n }\n break;\n }\n case 'Delete':\n event.preventDefault();\n this._setCell(index, '');\n break;\n case 'ArrowLeft':\n event.preventDefault();\n if (index > 0) this._focusCell(index - 1);\n break;\n case 'ArrowRight':\n event.preventDefault();\n if (index < this.appliedLength() - 1) 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.appliedLength() - 1);\n break;\n }\n }\n\n protected onPaste(event: ClipboardEvent): void {\n if (this.disabled() || this.readonly()) return;\n event.preventDefault();\n let text = event.clipboardData?.getData('text') ?? '';\n text = this.integerOnly() ? text.replace(/\\D/g, '') : text.replace(/\\s/g, '');\n if (!text) return;\n // A paste replaces the whole code from the first cell, clearing the rest.\n const len = this.appliedLength();\n const chars = text.split('').slice(0, len);\n this.cells.set(Array.from({ length: len }, (_, i) => chars[i] ?? ''));\n this._focusCell(chars.length >= len ? len - 1 : chars.length);\n }\n\n /**\n * Mark touched once focus leaves the whole group — moving between cells keeps\n * the host focused, so those intra-cell hops don't count as a blur.\n */\n protected onFocusOut(event: FocusEvent): void {\n const next = event.relatedTarget;\n if (next instanceof Node && this.element.nativeElement.contains(next)) {\n return;\n }\n this.markTouched();\n }\n\n /** Clears every cell (also serves a surrounding field's clear button). */\n public override clearValue(): boolean {\n if (this.disabled() || this.readonly()) return false;\n this.cells.set(Array.from({ length: this.appliedLength() }, () => ''));\n this._focusCell(0);\n return true;\n }\n\n private _setCell(index: number, value: string): void {\n const next = [...this.cells()];\n next[index] = value;\n this.cells.set(next);\n }\n\n /** Writes `text` into consecutive cells starting at `start`, then focuses the\n * next empty cell (or the last one). Used by typing and autofill. */\n private _fillFrom(start: number, text: string): void {\n const len = this.appliedLength();\n const next = [...this.cells()];\n let i = start;\n for (const ch of text.split('')) {\n if (i >= len) break;\n next[i] = ch;\n i++;\n }\n this.cells.set(next);\n this._focusCell(Math.min(i, len - 1));\n }\n\n private _focusCell(index: number): void {\n this._boxes()[index]?.nativeElement.focus();\n }\n}\n","@for (i of indices(); track i) {\n <input\n #box\n [ptInt]=\"this\"\n [ptClass]=\"'box'\"\n [type]=\"boxType()\"\n [attr.inputmode]=\"boxInputMode()\"\n [value]=\"cells()[i] ?? ''\"\n [disabled]=\"disabled()\"\n [attr.readonly]=\"readonly() || null\"\n [attr.aria-invalid]=\"invalidState() ? 'true' : null\"\n [attr.aria-label]=\"cellLabel(i)\"\n [attr.autocomplete]=\"i === 0 ? 'one-time-code' : 'off'\"\n maxlength=\"1\"\n autocapitalize=\"off\"\n autocorrect=\"off\"\n spellcheck=\"false\"\n (beforeinput)=\"onBeforeInput(i, $event)\"\n (keydown)=\"onKeyDown(i, $event)\"\n (paste)=\"onPaste($event)\"\n />\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -9,7 +9,7 @@ import { NgnIcon } from '@ngneers/controls/icon';
|
|
|
9
9
|
import { NgnInputField } from '@ngneers/controls/input-field';
|
|
10
10
|
import { NgnItemView } from '@ngneers/controls/item-view';
|
|
11
11
|
import { NgnSelect } from '@ngneers/controls/select';
|
|
12
|
-
import { throwExp } from '@ngneers/controls/utils';
|
|
12
|
+
import { throwExp, NgnError } from '@ngneers/controls/utils';
|
|
13
13
|
import { paginatorControlTemplate } from '@ngneers/controls-themes/templates/paginator';
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -19,15 +19,30 @@ class NgnPaginator extends NgnBase {
|
|
|
19
19
|
theme = this.injectThemeTemplate(paginatorControlTemplate, 'root');
|
|
20
20
|
i18n = inject(I18n).translations;
|
|
21
21
|
/**
|
|
22
|
-
* Total number of items to paginate.
|
|
22
|
+
* Total number of items to paginate. Required in `'pages'` mode (drives the
|
|
23
|
+
* page count); ignored in `'compact'` mode.
|
|
23
24
|
*/
|
|
24
|
-
totalItems = input
|
|
25
|
-
...(ngDevMode ? [{ debugName: "totalItems" }] : /* istanbul ignore next */ []));
|
|
25
|
+
totalItems = input(/* @ts-ignore */
|
|
26
|
+
...(ngDevMode ? [undefined, { debugName: "totalItems" }] : /* istanbul ignore next */ []));
|
|
26
27
|
/**
|
|
27
28
|
* Number of items per page. If not set, the first value from {@link possiblePageSizes} is used.
|
|
28
29
|
*/
|
|
29
30
|
pageSize = model(/* @ts-ignore */
|
|
30
31
|
...(ngDevMode ? [undefined, { debugName: "pageSize" }] : /* istanbul ignore next */ []));
|
|
32
|
+
/**
|
|
33
|
+
* Layout mode.
|
|
34
|
+
* - `'pages'`: full paginator with numbered page buttons (needs {@link totalItems}).
|
|
35
|
+
* - `'compact'`: prev/next buttons only, no page indicators, no total required.
|
|
36
|
+
* @default 'pages'
|
|
37
|
+
*/
|
|
38
|
+
mode = input('pages', /* @ts-ignore */
|
|
39
|
+
...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
40
|
+
/**
|
|
41
|
+
* `'compact'` mode only: whether a next page exists. Disables the "next" button
|
|
42
|
+
* when `false`. Bind to a lazy data source's `hasMore`.
|
|
43
|
+
* @default true
|
|
44
|
+
*/
|
|
45
|
+
hasNext = input(true, { ...(ngDevMode ? { debugName: "hasNext" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
31
46
|
/**
|
|
32
47
|
* Possible page sizes to choose from.
|
|
33
48
|
* @default [5, 10, 25, 50]
|
|
@@ -42,7 +57,7 @@ class NgnPaginator extends NgnBase {
|
|
|
42
57
|
this.possiblePageSizes()[0] ||
|
|
43
58
|
throwExp('NgnPaginator', 'At least one page size must be provided'), /* @ts-ignore */
|
|
44
59
|
...(ngDevMode ? [{ debugName: "appliedPageSize" }] : /* istanbul ignore next */ []));
|
|
45
|
-
pageCount = computed(() => Math.ceil(this.totalItems() / this.appliedPageSize()), /* @ts-ignore */
|
|
60
|
+
pageCount = computed(() => Math.ceil((this.totalItems() ?? 0) / this.appliedPageSize()), /* @ts-ignore */
|
|
46
61
|
...(ngDevMode ? [{ debugName: "pageCount" }] : /* istanbul ignore next */ []));
|
|
47
62
|
pages = computed(() => Array.from({ length: this.pageCount() }, (_, i) => ({ page: i })), /* @ts-ignore */
|
|
48
63
|
...(ngDevMode ? [{ debugName: "pages" }] : /* istanbul ignore next */ []));
|
|
@@ -77,13 +92,25 @@ class NgnPaginator extends NgnBase {
|
|
|
77
92
|
},
|
|
78
93
|
});
|
|
79
94
|
});
|
|
95
|
+
effect(() => {
|
|
96
|
+
if (this.mode() === 'pages' && this.totalItems() === undefined) {
|
|
97
|
+
throw new NgnError('paginator', "totalItems is required in 'pages' mode");
|
|
98
|
+
}
|
|
99
|
+
});
|
|
80
100
|
}
|
|
81
101
|
previousPage(event) {
|
|
82
|
-
|
|
102
|
+
// Compact mode is cursor pagination — sequential, so no shift/ctrl multi-jump.
|
|
103
|
+
const amount = this.mode() === 'compact' ? 1 : event.shiftKey ? 10 : event.ctrlKey ? 100 : 1;
|
|
83
104
|
const newPage = Math.max(this.page() - amount, 0);
|
|
84
105
|
this.page.set(newPage);
|
|
85
106
|
}
|
|
86
107
|
nextPage(event) {
|
|
108
|
+
if (this.mode() === 'compact') {
|
|
109
|
+
if (!this.hasNext())
|
|
110
|
+
return;
|
|
111
|
+
this.page.set(this.page() + 1);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
87
114
|
const amount = event.shiftKey ? 10 : event.ctrlKey ? 100 : 1;
|
|
88
115
|
const newPage = Math.min(this.page() + amount, this.pageCount() - 1);
|
|
89
116
|
this.page.set(newPage);
|
|
@@ -104,7 +131,7 @@ class NgnPaginator extends NgnBase {
|
|
|
104
131
|
};
|
|
105
132
|
}
|
|
106
133
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnPaginator, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
107
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: NgnPaginator, isStandalone: true, selector: "ngn-paginator", inputs: { totalItems: { classPropertyName: "totalItems", publicName: "totalItems", isSignal: true, isRequired:
|
|
134
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: NgnPaginator, isStandalone: true, selector: "ngn-paginator", inputs: { totalItems: { classPropertyName: "totalItems", publicName: "totalItems", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, hasNext: { classPropertyName: "hasNext", publicName: "hasNext", isSignal: true, isRequired: false, transformFunction: null }, possiblePageSizes: { classPropertyName: "possiblePageSizes", publicName: "possiblePageSizes", isSignal: true, isRequired: false, transformFunction: null }, fixedPageSize: { classPropertyName: "fixedPageSize", publicName: "fixedPageSize", isSignal: true, isRequired: false, transformFunction: null }, page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pageSize: "pageSizeChange", value: "value", page: "pageChange" }, providers: [provideSelf(NgnPaginator)], usesInheritance: true, ngImport: i0, template: "<button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'previous'\"\n [disabled]=\"page() === 0\"\n [attr.aria-label]=\"i18n['paginator_previousPage']()\"\n (click)=\"previousPage($event)\"\n>\n <ngn-icon [defaultIcon]=\"'paginator-previous'\" />\n</button>\n\n@if (mode() === 'pages') {\n <ngn-item-view\n #itemView\n [ptInt]=\"this\"\n [ptDep]=\"'item-view'\"\n [items]=\"pages()\"\n [idField]=\"'page'\"\n [overflowStrategy]=\"'aroundIndex'\"\n [overflowStrategyFreezeCount]=\"1\"\n [overflowStrategyIndex]=\"page()\"\n [sameWidthItems]=\"true\"\n >\n <ng-template #item let-item [ngnTemplate]=\"itemView.templateTypes.item\">\n @let buttonStyles = getButtonFontStyles(item.page);\n <button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'page-number'\"\n [ngStyle]=\"buttonStyles\"\n [attr.aria-current]=\"page() === item.page ? 'page' : null\"\n [ngClass]=\"\n theme.classes({\n 'active-page': page() === item.page,\n })\n \"\n (click)=\"page.set(item.page)\"\n >\n {{ item.page + 1 }}\n </button>\n </ng-template>\n <ng-template #overflow let-item [ngnTemplate]=\"itemView.templateTypes.overflow\">\n <button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'overflow'\"\n style=\"opacity: 0.5\"\n disabled\n >\n ...\n </button>\n </ng-template>\n </ngn-item-view>\n}\n\n@if (mode() === 'compact') {\n <span data-compact-page aria-current=\"page\">{{ page() + 1 }}</span>\n}\n\n<button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'next'\"\n [disabled]=\"mode() === 'compact' && !hasNext()\"\n [attr.aria-label]=\"i18n['paginator_nextPage']()\"\n (click)=\"nextPage($event)\"\n>\n <ngn-icon [defaultIcon]=\"'paginator-next'\" />\n</button>\n\n@if (!fixedPageSize()) {\n <ngn-input-field [ptInt]=\"this\" [ptClass]=\"'page-size-options'\">\n <ngn-select\n [options]=\"pageSizeOptions()\"\n [value]=\"appliedPageSize()\"\n (valueChange)=\"pageSize.set($event)\"\n />\n </ngn-input-field>\n}\n", dependencies: [{ kind: "directive", type: NgnButton, selector: "button[ngnButton], a[ngnButton]", inputs: ["ngnButtonInline"], exportAs: ["ngnButton"] }, { kind: "component", type: NgnIcon, selector: "ngn-icon", inputs: ["defaultIcon", "icon"] }, { kind: "component", type: NgnItemView, selector: "ngn-item-view", inputs: ["idField", "items", "separator", "iconItemSeparator", "overflowStrategy", "overflowStrategyFreezeCount", "overflowStrategyIndex", "sameWidthItems"] }, { kind: "component", type: NgnSelect, selector: "ngn-select", inputs: ["popoverOptions", "options", "filter", "filterText", "iconFilter", "iconDropdown", "virtual", "itemHeight", "editable", "editableAutoFilter", "multiple", "scrollToSelectedItemOnOpen", "tabindex", "checkbox", "placeholder"] }, { kind: "directive", type: NgnTemplate, selector: "ng-template[ngnTemplate]", inputs: ["ngnTemplate"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: NgnPt, selector: "[ptInt]", inputs: ["ptInt", "ptClass", "ptDep"] }, { kind: "component", type: NgnInputField, selector: "ngn-input-field", inputs: ["label", "labelledBy", "labelKind", "inputId", "showClearButton", "iconClearButton", "tabindex", "invalid", "readonly", "disabled"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
|
|
108
135
|
}
|
|
109
136
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnPaginator, decorators: [{
|
|
110
137
|
type: Component,
|
|
@@ -118,8 +145,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
|
|
|
118
145
|
NgnPt,
|
|
119
146
|
NgnInputField,
|
|
120
147
|
NgClass,
|
|
121
|
-
], providers: [provideSelf(NgnPaginator)], template: "<button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'previous'\"\n [attr.aria-label]=\"i18n['paginator_previousPage']()\"\n (click)=\"previousPage($event)\"\n>\n <ngn-icon [defaultIcon]=\"'paginator-previous'\" />\n</button>\n\n<ngn-item-view\n
|
|
122
|
-
}], ctorParameters: () => [], propDecorators: { totalItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalItems", required:
|
|
148
|
+
], providers: [provideSelf(NgnPaginator)], template: "<button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'previous'\"\n [disabled]=\"page() === 0\"\n [attr.aria-label]=\"i18n['paginator_previousPage']()\"\n (click)=\"previousPage($event)\"\n>\n <ngn-icon [defaultIcon]=\"'paginator-previous'\" />\n</button>\n\n@if (mode() === 'pages') {\n <ngn-item-view\n #itemView\n [ptInt]=\"this\"\n [ptDep]=\"'item-view'\"\n [items]=\"pages()\"\n [idField]=\"'page'\"\n [overflowStrategy]=\"'aroundIndex'\"\n [overflowStrategyFreezeCount]=\"1\"\n [overflowStrategyIndex]=\"page()\"\n [sameWidthItems]=\"true\"\n >\n <ng-template #item let-item [ngnTemplate]=\"itemView.templateTypes.item\">\n @let buttonStyles = getButtonFontStyles(item.page);\n <button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'page-number'\"\n [ngStyle]=\"buttonStyles\"\n [attr.aria-current]=\"page() === item.page ? 'page' : null\"\n [ngClass]=\"\n theme.classes({\n 'active-page': page() === item.page,\n })\n \"\n (click)=\"page.set(item.page)\"\n >\n {{ item.page + 1 }}\n </button>\n </ng-template>\n <ng-template #overflow let-item [ngnTemplate]=\"itemView.templateTypes.overflow\">\n <button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'overflow'\"\n style=\"opacity: 0.5\"\n disabled\n >\n ...\n </button>\n </ng-template>\n </ngn-item-view>\n}\n\n@if (mode() === 'compact') {\n <span data-compact-page aria-current=\"page\">{{ page() + 1 }}</span>\n}\n\n<button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'next'\"\n [disabled]=\"mode() === 'compact' && !hasNext()\"\n [attr.aria-label]=\"i18n['paginator_nextPage']()\"\n (click)=\"nextPage($event)\"\n>\n <ngn-icon [defaultIcon]=\"'paginator-next'\" />\n</button>\n\n@if (!fixedPageSize()) {\n <ngn-input-field [ptInt]=\"this\" [ptClass]=\"'page-size-options'\">\n <ngn-select\n [options]=\"pageSizeOptions()\"\n [value]=\"appliedPageSize()\"\n (valueChange)=\"pageSize.set($event)\"\n />\n </ngn-input-field>\n}\n" }]
|
|
149
|
+
}], ctorParameters: () => [], propDecorators: { totalItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalItems", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }, { type: i0.Output, args: ["pageSizeChange"] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], hasNext: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasNext", required: false }] }], possiblePageSizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "possiblePageSizes", required: false }] }], fixedPageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "fixedPageSize", required: false }] }], value: [{ type: i0.Output, args: ["value"] }], page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: false }] }, { type: i0.Output, args: ["pageChange"] }] } });
|
|
123
150
|
|
|
124
151
|
/**
|
|
125
152
|
* Generated bundle index. Do not edit.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngneers-controls-paginator.mjs","sources":["../../src/paginator/paginator.ts","../../src/paginator/paginator.html","../../src/paginator/ngneers-controls-paginator.ts"],"sourcesContent":["import { NgStyle, NgClass } from '@angular/common';\nimport {\n booleanAttribute,\n Component,\n computed,\n effect,\n inject,\n input,\n model,\n output,\n} from '@angular/core';\nimport { NgnTemplate } from '@ngneers/controls/api/ng';\nimport { NgnBase, NgnPt, provideSelf } from '@ngneers/controls/base';\nimport { NgnButton } from '@ngneers/controls/button';\nimport { I18n } from '@ngneers/controls/i18n';\nimport { NgnIcon } from '@ngneers/controls/icon';\nimport { NgnInputField } from '@ngneers/controls/input-field';\nimport { NgnItemView } from '@ngneers/controls/item-view';\nimport { NgnSelect } from '@ngneers/controls/select';\nimport { throwExp } from '@ngneers/controls/utils';\nimport { paginatorControlTemplate } from '@ngneers/controls-themes/templates/paginator';\n\nimport type { PaginationState } from './types';\nimport type { NgnItem } from '@ngneers/controls/api';\n\n/**\n * @category control\n */\n@Component({\n selector: 'ngn-paginator',\n templateUrl: './paginator.html',\n imports: [\n NgnButton,\n NgnIcon,\n NgnItemView,\n NgnSelect,\n NgnTemplate,\n NgStyle,\n NgnPt,\n NgnInputField,\n NgClass,\n ],\n providers: [provideSelf(NgnPaginator)],\n})\nexport class NgnPaginator extends NgnBase<'paginator'> {\n protected readonly theme = this.injectThemeTemplate(paginatorControlTemplate, 'root');\n protected readonly i18n = inject(I18n).translations;\n\n /**\n * Total number of items to paginate.\n */\n public readonly totalItems = input.required<number>();\n /**\n * Number of items per page. If not set, the first value from {@link possiblePageSizes} is used.\n */\n public readonly pageSize = model<number>();\n /**\n * Possible page sizes to choose from.\n * @default [5, 10, 25, 50]\n */\n public readonly possiblePageSizes = input([5, 10, 25, 50], {\n transform: (sizes: number[]) => sizes.sort((a, b) => a - b),\n });\n /**\n * If set to `true`, the page size selector will be hidden and the page size cannot be changed by the user.\n * @default false\n */\n public readonly fixedPageSize = input(false, { transform: booleanAttribute });\n\n protected readonly appliedPageSize = computed(\n () =>\n this.pageSize() ||\n this.possiblePageSizes()[0] ||\n throwExp('NgnPaginator', 'At least one page size must be provided')\n );\n protected readonly pageCount = computed(() =>\n Math.ceil(this.totalItems() / this.appliedPageSize())\n );\n protected readonly pages = computed(() =>\n Array.from({ length: this.pageCount() }, (_, i) => ({ page: i }))\n );\n protected readonly pageSizeOptions = computed<NgnItem[]>(() =>\n this.possiblePageSizes().map(size => ({\n label: size.toString(),\n value: size,\n }))\n );\n\n /**\n * Emits the current pagination state whenever the page or page size changes.\n */\n public readonly value = output<PaginationState>();\n /**\n * Current page index (zero-based).\n * @default 0\n */\n public readonly page = model(0);\n\n constructor() {\n super();\n effect(() => {\n const page = this.page();\n const pageSize = this.appliedPageSize();\n this.value.emit({\n page: {\n current: page,\n size: pageSize,\n },\n slice: {\n skip: page * pageSize,\n take: pageSize,\n },\n });\n });\n }\n\n protected previousPage(event: PointerEvent): void {\n const amount = event.shiftKey ? 10 : event.ctrlKey ? 100 : 1;\n const newPage = Math.max(this.page() - amount, 0);\n this.page.set(newPage);\n }\n\n protected nextPage(event: PointerEvent): void {\n const amount = event.shiftKey ? 10 : event.ctrlKey ? 100 : 1;\n const newPage = Math.min(this.page() + amount, this.pageCount() - 1);\n this.page.set(newPage);\n }\n\n protected getButtonFontStyles(page: number): {\n [klass: string]: string;\n } {\n function getFontSizeMultiplier() {\n const charCount = (page + 1).toString().length;\n if (charCount <= 2) {\n return 1;\n }\n return Math.pow(0.85, charCount - 2);\n }\n\n return {\n fontSize: `${getFontSizeMultiplier()}em`,\n width: 'calc(1rem + 2 * var(--padding))',\n height: 'calc(1rem + 2 * var(--padding))',\n lineHeight: '0',\n };\n }\n}\n","<button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'previous'\"\n [attr.aria-label]=\"i18n['paginator_previousPage']()\"\n (click)=\"previousPage($event)\"\n>\n <ngn-icon [defaultIcon]=\"'paginator-previous'\" />\n</button>\n\n<ngn-item-view\n #itemView\n [ptInt]=\"this\"\n [ptDep]=\"'item-view'\"\n [items]=\"pages()\"\n [idField]=\"'page'\"\n [overflowStrategy]=\"'aroundIndex'\"\n [overflowStrategyFreezeCount]=\"1\"\n [overflowStrategyIndex]=\"page()\"\n [sameWidthItems]=\"true\"\n>\n <ng-template #item let-item [ngnTemplate]=\"itemView.templateTypes.item\">\n @let buttonStyles = getButtonFontStyles(item.page);\n <button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'page-number'\"\n [ngStyle]=\"buttonStyles\"\n [attr.aria-current]=\"page() === item.page ? 'page' : null\"\n [ngClass]=\"\n theme.classes({\n 'active-page': page() === item.page,\n })\n \"\n (click)=\"page.set(item.page)\"\n >\n {{ item.page + 1 }}\n </button>\n </ng-template>\n <ng-template #overflow let-item [ngnTemplate]=\"itemView.templateTypes.overflow\">\n <button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'overflow'\"\n style=\"opacity: 0.5\"\n disabled\n >\n ...\n </button>\n </ng-template>\n</ngn-item-view>\n\n<button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'next'\"\n [attr.aria-label]=\"i18n['paginator_nextPage']()\"\n (click)=\"nextPage($event)\"\n>\n <ngn-icon [defaultIcon]=\"'paginator-next'\" />\n</button>\n\n@if (!fixedPageSize()) {\n <ngn-input-field [ptInt]=\"this\" [ptClass]=\"'page-size-options'\">\n <ngn-select\n [options]=\"pageSizeOptions()\"\n [value]=\"appliedPageSize()\"\n (valueChange)=\"pageSize.set($event)\"\n />\n </ngn-input-field>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngneers-controls-paginator.mjs","sources":["../../src/paginator/paginator.ts","../../src/paginator/paginator.html","../../src/paginator/ngneers-controls-paginator.ts"],"sourcesContent":["import { NgStyle, NgClass } from '@angular/common';\nimport {\n booleanAttribute,\n Component,\n computed,\n effect,\n inject,\n input,\n model,\n output,\n} from '@angular/core';\nimport { NgnTemplate } from '@ngneers/controls/api/ng';\nimport { NgnBase, NgnPt, provideSelf } from '@ngneers/controls/base';\nimport { NgnButton } from '@ngneers/controls/button';\nimport { I18n } from '@ngneers/controls/i18n';\nimport { NgnIcon } from '@ngneers/controls/icon';\nimport { NgnInputField } from '@ngneers/controls/input-field';\nimport { NgnItemView } from '@ngneers/controls/item-view';\nimport { NgnSelect } from '@ngneers/controls/select';\nimport { NgnError, throwExp } from '@ngneers/controls/utils';\nimport { paginatorControlTemplate } from '@ngneers/controls-themes/templates/paginator';\n\nimport type { PaginationState } from './types';\nimport type { NgnItem } from '@ngneers/controls/api';\n\n/**\n * @category control\n */\n@Component({\n selector: 'ngn-paginator',\n templateUrl: './paginator.html',\n imports: [\n NgnButton,\n NgnIcon,\n NgnItemView,\n NgnSelect,\n NgnTemplate,\n NgStyle,\n NgnPt,\n NgnInputField,\n NgClass,\n ],\n providers: [provideSelf(NgnPaginator)],\n})\nexport class NgnPaginator extends NgnBase<'paginator'> {\n protected readonly theme = this.injectThemeTemplate(paginatorControlTemplate, 'root');\n protected readonly i18n = inject(I18n).translations;\n\n /**\n * Total number of items to paginate. Required in `'pages'` mode (drives the\n * page count); ignored in `'compact'` mode.\n */\n public readonly totalItems = input<number>();\n /**\n * Number of items per page. If not set, the first value from {@link possiblePageSizes} is used.\n */\n public readonly pageSize = model<number>();\n /**\n * Layout mode.\n * - `'pages'`: full paginator with numbered page buttons (needs {@link totalItems}).\n * - `'compact'`: prev/next buttons only, no page indicators, no total required.\n * @default 'pages'\n */\n public readonly mode = input<'pages' | 'compact'>('pages');\n /**\n * `'compact'` mode only: whether a next page exists. Disables the \"next\" button\n * when `false`. Bind to a lazy data source's `hasMore`.\n * @default true\n */\n public readonly hasNext = input(true, { transform: booleanAttribute });\n /**\n * Possible page sizes to choose from.\n * @default [5, 10, 25, 50]\n */\n public readonly possiblePageSizes = input([5, 10, 25, 50], {\n transform: (sizes: number[]) => sizes.sort((a, b) => a - b),\n });\n /**\n * If set to `true`, the page size selector will be hidden and the page size cannot be changed by the user.\n * @default false\n */\n public readonly fixedPageSize = input(false, { transform: booleanAttribute });\n\n protected readonly appliedPageSize = computed(\n () =>\n this.pageSize() ||\n this.possiblePageSizes()[0] ||\n throwExp('NgnPaginator', 'At least one page size must be provided')\n );\n protected readonly pageCount = computed(() =>\n Math.ceil((this.totalItems() ?? 0) / this.appliedPageSize())\n );\n protected readonly pages = computed(() =>\n Array.from({ length: this.pageCount() }, (_, i) => ({ page: i }))\n );\n protected readonly pageSizeOptions = computed<NgnItem[]>(() =>\n this.possiblePageSizes().map(size => ({\n label: size.toString(),\n value: size,\n }))\n );\n\n /**\n * Emits the current pagination state whenever the page or page size changes.\n */\n public readonly value = output<PaginationState>();\n /**\n * Current page index (zero-based).\n * @default 0\n */\n public readonly page = model(0);\n\n constructor() {\n super();\n effect(() => {\n const page = this.page();\n const pageSize = this.appliedPageSize();\n this.value.emit({\n page: {\n current: page,\n size: pageSize,\n },\n slice: {\n skip: page * pageSize,\n take: pageSize,\n },\n });\n });\n effect(() => {\n if (this.mode() === 'pages' && this.totalItems() === undefined) {\n throw new NgnError('paginator', \"totalItems is required in 'pages' mode\");\n }\n });\n }\n\n protected previousPage(event: PointerEvent): void {\n // Compact mode is cursor pagination — sequential, so no shift/ctrl multi-jump.\n const amount = this.mode() === 'compact' ? 1 : event.shiftKey ? 10 : event.ctrlKey ? 100 : 1;\n const newPage = Math.max(this.page() - amount, 0);\n this.page.set(newPage);\n }\n\n protected nextPage(event: PointerEvent): void {\n if (this.mode() === 'compact') {\n if (!this.hasNext()) return;\n this.page.set(this.page() + 1);\n return;\n }\n const amount = event.shiftKey ? 10 : event.ctrlKey ? 100 : 1;\n const newPage = Math.min(this.page() + amount, this.pageCount() - 1);\n this.page.set(newPage);\n }\n\n protected getButtonFontStyles(page: number): {\n [klass: string]: string;\n } {\n function getFontSizeMultiplier() {\n const charCount = (page + 1).toString().length;\n if (charCount <= 2) {\n return 1;\n }\n return Math.pow(0.85, charCount - 2);\n }\n\n return {\n fontSize: `${getFontSizeMultiplier()}em`,\n width: 'calc(1rem + 2 * var(--padding))',\n height: 'calc(1rem + 2 * var(--padding))',\n lineHeight: '0',\n };\n }\n}\n","<button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'previous'\"\n [disabled]=\"page() === 0\"\n [attr.aria-label]=\"i18n['paginator_previousPage']()\"\n (click)=\"previousPage($event)\"\n>\n <ngn-icon [defaultIcon]=\"'paginator-previous'\" />\n</button>\n\n@if (mode() === 'pages') {\n <ngn-item-view\n #itemView\n [ptInt]=\"this\"\n [ptDep]=\"'item-view'\"\n [items]=\"pages()\"\n [idField]=\"'page'\"\n [overflowStrategy]=\"'aroundIndex'\"\n [overflowStrategyFreezeCount]=\"1\"\n [overflowStrategyIndex]=\"page()\"\n [sameWidthItems]=\"true\"\n >\n <ng-template #item let-item [ngnTemplate]=\"itemView.templateTypes.item\">\n @let buttonStyles = getButtonFontStyles(item.page);\n <button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'page-number'\"\n [ngStyle]=\"buttonStyles\"\n [attr.aria-current]=\"page() === item.page ? 'page' : null\"\n [ngClass]=\"\n theme.classes({\n 'active-page': page() === item.page,\n })\n \"\n (click)=\"page.set(item.page)\"\n >\n {{ item.page + 1 }}\n </button>\n </ng-template>\n <ng-template #overflow let-item [ngnTemplate]=\"itemView.templateTypes.overflow\">\n <button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'overflow'\"\n style=\"opacity: 0.5\"\n disabled\n >\n ...\n </button>\n </ng-template>\n </ngn-item-view>\n}\n\n@if (mode() === 'compact') {\n <span data-compact-page aria-current=\"page\">{{ page() + 1 }}</span>\n}\n\n<button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'next'\"\n [disabled]=\"mode() === 'compact' && !hasNext()\"\n [attr.aria-label]=\"i18n['paginator_nextPage']()\"\n (click)=\"nextPage($event)\"\n>\n <ngn-icon [defaultIcon]=\"'paginator-next'\" />\n</button>\n\n@if (!fixedPageSize()) {\n <ngn-input-field [ptInt]=\"this\" [ptClass]=\"'page-size-options'\">\n <ngn-select\n [options]=\"pageSizeOptions()\"\n [value]=\"appliedPageSize()\"\n (valueChange)=\"pageSize.set($event)\"\n />\n </ngn-input-field>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -23,7 +23,7 @@ const NGN_RADIO_GROUP = new InjectionToken('NGN_RADIO_GROUP');
|
|
|
23
23
|
class NgnRadioGroup extends ValueControlBase {
|
|
24
24
|
theme = this.injectThemeTemplate(radioGroupControlTemplate, {
|
|
25
25
|
root: true,
|
|
26
|
-
invalid: () => this.
|
|
26
|
+
invalid: () => this.invalidState(),
|
|
27
27
|
});
|
|
28
28
|
/** The roving-focus host directive that drives keyboard/tab coordination. */
|
|
29
29
|
roving = inject(NgnRovingGroup);
|
|
@@ -50,7 +50,7 @@ class NgnRadioGroup extends ValueControlBase {
|
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
52
|
this.value.set(radio.value());
|
|
53
|
-
this.
|
|
53
|
+
this.markTouched();
|
|
54
54
|
});
|
|
55
55
|
inject(DestroyRef).onDestroy(() => sub.unsubscribe());
|
|
56
56
|
// Keep the roving tab stop on the selected radio so Tab-in lands on the
|
|
@@ -75,7 +75,7 @@ class NgnRadioGroup extends ValueControlBase {
|
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
77
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnRadioGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
78
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.5", type: NgnRadioGroup, isStandalone: true, selector: "ngn-radio-group", host: { attributes: { "role": "radiogroup" }, properties: { "attr.aria-label": "label()", "attr.aria-labelledby": "labelledBy()", "attr.aria-orientation": "roving.orientation()", "attr.aria-invalid": "
|
|
78
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.5", type: NgnRadioGroup, isStandalone: true, selector: "ngn-radio-group", host: { attributes: { "role": "radiogroup" }, properties: { "attr.aria-label": "label()", "attr.aria-labelledby": "labelledBy()", "attr.aria-orientation": "roving.orientation()", "attr.aria-invalid": "invalidState() ? 'true' : null", "attr.aria-disabled": "disabled() ? 'true' : null" } }, providers: [provideSelf(NgnRadioGroup), { provide: NGN_RADIO_GROUP, useExisting: NgnRadioGroup }], usesInheritance: true, hostDirectives: [{ directive: i1.NgnRovingGroup, inputs: ["orientation", "orientation", "rovingWrap", "rovingWrap"] }], ngImport: i0, template: "<ng-content />\n" });
|
|
79
79
|
}
|
|
80
80
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnRadioGroup, decorators: [{
|
|
81
81
|
type: Component,
|
|
@@ -84,7 +84,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
|
|
|
84
84
|
'[attr.aria-label]': 'label()',
|
|
85
85
|
'[attr.aria-labelledby]': 'labelledBy()',
|
|
86
86
|
'[attr.aria-orientation]': 'roving.orientation()',
|
|
87
|
-
'[attr.aria-invalid]': "
|
|
87
|
+
'[attr.aria-invalid]': "invalidState() ? 'true' : null",
|
|
88
88
|
'[attr.aria-disabled]': "disabled() ? 'true' : null",
|
|
89
89
|
}, template: "<ng-content />\n" }]
|
|
90
90
|
}], ctorParameters: () => [] });
|