@ngneers/controls 0.0.1-next.0 → 0.0.1-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/fesm2022/ngneers-controls-api-resize.mjs +7 -0
  2. package/fesm2022/ngneers-controls-api-resize.mjs.map +1 -1
  3. package/fesm2022/ngneers-controls-i18n-translations-de.mjs +3 -0
  4. package/fesm2022/ngneers-controls-i18n-translations-de.mjs.map +1 -1
  5. package/fesm2022/ngneers-controls-i18n-translations-en.mjs +3 -0
  6. package/fesm2022/ngneers-controls-i18n-translations-en.mjs.map +1 -1
  7. package/fesm2022/ngneers-controls-otp.mjs +251 -0
  8. package/fesm2022/ngneers-controls-otp.mjs.map +1 -0
  9. package/fesm2022/ngneers-controls-splitter.mjs +43 -12
  10. package/fesm2022/ngneers-controls-splitter.mjs.map +1 -1
  11. package/fesm2022/ngneers-controls-toggle-button.mjs +2 -2
  12. package/fesm2022/ngneers-controls-toggle-button.mjs.map +1 -1
  13. package/package.json +6 -2
  14. package/types/ngneers-controls-breadcrumb.d.ts +3 -0
  15. package/types/ngneers-controls-calendar.d.ts +3 -0
  16. package/types/ngneers-controls-chip.d.ts +3 -0
  17. package/types/ngneers-controls-dialog.d.ts +3 -0
  18. package/types/ngneers-controls-drawer.d.ts +3 -0
  19. package/types/ngneers-controls-edit-inplace.d.ts +3 -0
  20. package/types/ngneers-controls-filter.d.ts +3 -0
  21. package/types/ngneers-controls-i18n-translations-de.d.ts +3 -0
  22. package/types/ngneers-controls-i18n-translations-en.d.ts +3 -0
  23. package/types/ngneers-controls-i18n.d.ts +3 -0
  24. package/types/ngneers-controls-input-field.d.ts +3 -0
  25. package/types/ngneers-controls-list-box.d.ts +3 -0
  26. package/types/ngneers-controls-mask-input.d.ts +3 -0
  27. package/types/ngneers-controls-otp.d.ts +243 -0
  28. package/types/ngneers-controls-paginator.d.ts +3 -0
  29. package/types/ngneers-controls-select.d.ts +3 -0
  30. package/types/ngneers-controls-snackbar.d.ts +3 -0
  31. package/types/ngneers-controls-splitter.d.ts +12 -2
  32. package/types/ngneers-controls-table.d.ts +3 -0
  33. package/types/ngneers-controls-toast.d.ts +3 -0
  34. package/types/ngneers-controls-tree.d.ts +3 -0
  35. package/types/ngneers-controls-upload.d.ts +3 -0
@@ -0,0 +1,251 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, input, numberAttribute, booleanAttribute, output, signal, computed, viewChildren, effect, untracked, Component } from '@angular/core';
3
+ import { ValueControlBase, NgnPt, provideSelf } from '@ngneers/controls/base';
4
+ import { I18n } from '@ngneers/controls/i18n';
5
+ import { otpControlTemplate } from '@ngneers/controls-themes/templates/otp';
6
+
7
+ /**
8
+ * Coerce a requested length to a sane cell count (at least one). Coerces
9
+ * strings too, so a value set without the `numberAttribute` transform (e.g. via
10
+ * a dynamic property write) still yields a number instead of collapsing to 1.
11
+ */
12
+ function normalizeLength(value) {
13
+ const n = Number(value);
14
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : 1;
15
+ }
16
+ /**
17
+ * A one-time-password / verification-code input that splits a short code across
18
+ * a row of single-character cells (like the PrimeNG `InputOtp`).
19
+ *
20
+ * - Typing a character fills the active cell and advances focus to the next.
21
+ * - **Backspace** clears the active cell, or steps back and clears the previous
22
+ * one when the active cell is already empty.
23
+ * - **←/→** move between cells, **Home/End** jump to the first/last.
24
+ * - Pasting a code distributes its characters across the cells.
25
+ *
26
+ * It is a self-contained value control — bind `[value]`/`(valueChange)` (or
27
+ * `[(value)]`) directly on `<ngn-otp>`. The value is the composed string
28
+ * (`null` while every cell is empty); {@link completed} fires once the whole
29
+ * code is filled.
30
+ *
31
+ * @category control
32
+ */
33
+ class NgnOtp extends ValueControlBase {
34
+ theme = this.injectThemeTemplate(otpControlTemplate, {
35
+ root: true,
36
+ invalid: () => this.invalid(),
37
+ });
38
+ i18n = inject(I18n).translations;
39
+ /**
40
+ * The number of character cells the code is split across.
41
+ * @default 6
42
+ */
43
+ length = input(6, { ...(ngDevMode ? { debugName: "length" } : /* istanbul ignore next */ {}), transform: numberAttribute });
44
+ /**
45
+ * Render each entered character as a masked dot instead of the character
46
+ * itself (like a password field).
47
+ * @default false
48
+ */
49
+ mask = input(false, { ...(ngDevMode ? { debugName: "mask" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
50
+ /**
51
+ * Restrict entry to digits (`0`–`9`) only. Also switches the on-screen
52
+ * keyboard to numeric on touch devices.
53
+ * @default false
54
+ */
55
+ integerOnly = input(false, { ...(ngDevMode ? { debugName: "integerOnly" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
56
+ /** Emits the fully composed code once every cell holds a character. */
57
+ completed = output();
58
+ /** Per-cell characters — the source of truth the composed value derives from. */
59
+ cells = signal([], /* @ts-ignore */
60
+ ...(ngDevMode ? [{ debugName: "cells" }] : /* istanbul ignore next */ []));
61
+ /** Effective, sanitized cell count. */
62
+ appliedLength = computed(() => normalizeLength(this.length()), /* @ts-ignore */
63
+ ...(ngDevMode ? [{ debugName: "appliedLength" }] : /* istanbul ignore next */ []));
64
+ /** Cell indices for the template `@for` loop. */
65
+ indices = computed(() => Array.from({ length: this.appliedLength() }, (_, i) => i), /* @ts-ignore */
66
+ ...(ngDevMode ? [{ debugName: "indices" }] : /* istanbul ignore next */ []));
67
+ boxType = computed(() => (this.mask() ? 'password' : 'text'), /* @ts-ignore */
68
+ ...(ngDevMode ? [{ debugName: "boxType" }] : /* istanbul ignore next */ []));
69
+ boxInputMode = computed(() => (this.integerOnly() ? 'numeric' : 'text'), /* @ts-ignore */
70
+ ...(ngDevMode ? [{ debugName: "boxInputMode" }] : /* istanbul ignore next */ []));
71
+ _boxes = viewChildren('box', /* @ts-ignore */
72
+ ...(ngDevMode ? [{ debugName: "_boxes" }] : /* istanbul ignore next */ []));
73
+ constructor() {
74
+ super();
75
+ // Keep the cell array sized to `length`, preserving already-typed chars so
76
+ // shrinking/growing the field does not wipe what the user entered.
77
+ effect(() => {
78
+ const len = this.appliedLength();
79
+ const cur = untracked(() => this.cells());
80
+ if (cur.length !== len) {
81
+ this.cells.set(Array.from({ length: len }, (_, i) => cur[i] ?? ''));
82
+ }
83
+ });
84
+ // External value → cells. Guard the write-loop by comparing to the current
85
+ // composed value so internal typing never re-triggers a reset.
86
+ effect(() => {
87
+ const len = this.appliedLength();
88
+ const ext = this.value() ?? '';
89
+ const current = untracked(() => this.cells().join(''));
90
+ if (ext !== current) {
91
+ const chars = ext.split('').slice(0, len);
92
+ this.cells.set(Array.from({ length: len }, (_, i) => chars[i] ?? ''));
93
+ }
94
+ });
95
+ // Cells → external value + completion. `null` while the field is empty.
96
+ effect(() => {
97
+ const cells = this.cells();
98
+ const composed = cells.join('');
99
+ this.value.set(composed === '' ? null : composed);
100
+ if (cells.length > 0 && cells.every(c => c !== '')) {
101
+ this.completed.emit(composed);
102
+ }
103
+ });
104
+ }
105
+ /** Accessible name for a cell — its 1-based position within the code. */
106
+ cellLabel(index) {
107
+ return this.i18n['otp_cellLabel']({ index: index + 1, total: this.appliedLength() });
108
+ }
109
+ /**
110
+ * The control fully owns each cell's value: every text mutation is cancelled
111
+ * and the character is written to the {@link cells} signal instead, which the
112
+ * `[value]` binding reflects back. This makes overtype work from any caret
113
+ * position (no `maxlength`/selection tricks) and lets a single multi-character
114
+ * insert — e.g. SMS one-time-code autofill — spread across the cells.
115
+ */
116
+ onBeforeInput(index, event) {
117
+ if (this.disabled() || this.readonly())
118
+ return;
119
+ const type = event.inputType;
120
+ // Deletions: desktop goes through keydown, but mobile soft keyboards emit
121
+ // these directly, so handle them here too.
122
+ if (type.startsWith('delete')) {
123
+ event.preventDefault();
124
+ if (this.cells()[index]) {
125
+ this._setCell(index, '');
126
+ }
127
+ else if (index > 0) {
128
+ this._setCell(index - 1, '');
129
+ this._focusCell(index - 1);
130
+ }
131
+ return;
132
+ }
133
+ // Paste/drop are handled by the dedicated (paste) handler.
134
+ if (type === 'insertFromPaste' || type === 'insertFromDrop')
135
+ return;
136
+ if (!type.startsWith('insert'))
137
+ return;
138
+ event.preventDefault();
139
+ let data = event.data ?? '';
140
+ if (this.integerOnly())
141
+ data = data.replace(/\D/g, '');
142
+ if (!data)
143
+ return;
144
+ this._fillFrom(index, data);
145
+ }
146
+ onKeyDown(index, event) {
147
+ if (this.disabled() || this.readonly())
148
+ return;
149
+ if (event.ctrlKey || event.metaKey || event.altKey)
150
+ return;
151
+ switch (event.key) {
152
+ case 'Backspace': {
153
+ event.preventDefault();
154
+ const cells = this.cells();
155
+ if (cells[index]) {
156
+ this._setCell(index, '');
157
+ }
158
+ else if (index > 0) {
159
+ this._setCell(index - 1, '');
160
+ this._focusCell(index - 1);
161
+ }
162
+ break;
163
+ }
164
+ case 'Delete':
165
+ event.preventDefault();
166
+ this._setCell(index, '');
167
+ break;
168
+ case 'ArrowLeft':
169
+ event.preventDefault();
170
+ if (index > 0)
171
+ this._focusCell(index - 1);
172
+ break;
173
+ case 'ArrowRight':
174
+ event.preventDefault();
175
+ if (index < this.appliedLength() - 1)
176
+ this._focusCell(index + 1);
177
+ break;
178
+ case 'Home':
179
+ event.preventDefault();
180
+ this._focusCell(0);
181
+ break;
182
+ case 'End':
183
+ event.preventDefault();
184
+ this._focusCell(this.appliedLength() - 1);
185
+ break;
186
+ }
187
+ }
188
+ onPaste(event) {
189
+ if (this.disabled() || this.readonly())
190
+ return;
191
+ event.preventDefault();
192
+ let text = event.clipboardData?.getData('text') ?? '';
193
+ text = this.integerOnly() ? text.replace(/\D/g, '') : text.replace(/\s/g, '');
194
+ if (!text)
195
+ return;
196
+ // A paste replaces the whole code from the first cell, clearing the rest.
197
+ const len = this.appliedLength();
198
+ const chars = text.split('').slice(0, len);
199
+ this.cells.set(Array.from({ length: len }, (_, i) => chars[i] ?? ''));
200
+ this._focusCell(chars.length >= len ? len - 1 : chars.length);
201
+ }
202
+ /** Clears every cell (also serves a surrounding field's clear button). */
203
+ clearValue() {
204
+ if (this.disabled() || this.readonly())
205
+ return false;
206
+ this.cells.set(Array.from({ length: this.appliedLength() }, () => ''));
207
+ this._focusCell(0);
208
+ return true;
209
+ }
210
+ _setCell(index, value) {
211
+ const next = [...this.cells()];
212
+ next[index] = value;
213
+ this.cells.set(next);
214
+ }
215
+ /** Writes `text` into consecutive cells starting at `start`, then focuses the
216
+ * next empty cell (or the last one). Used by typing and autofill. */
217
+ _fillFrom(start, text) {
218
+ const len = this.appliedLength();
219
+ const next = [...this.cells()];
220
+ let i = start;
221
+ for (const ch of text.split('')) {
222
+ if (i >= len)
223
+ break;
224
+ next[i] = ch;
225
+ i++;
226
+ }
227
+ this.cells.set(next);
228
+ this._focusCell(Math.min(i, len - 1));
229
+ }
230
+ _focusCell(index) {
231
+ this._boxes()[index]?.nativeElement.focus();
232
+ }
233
+ 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": "invalid() ? \"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]=\"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", dependencies: [{ kind: "directive", type: NgnPt, selector: "[ptInt]", inputs: ["ptInt", "ptClass", "ptDep"] }] });
235
+ }
236
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnOtp, decorators: [{
237
+ type: Component,
238
+ args: [{ selector: 'ngn-otp', imports: [NgnPt], providers: [provideSelf(NgnOtp)], host: {
239
+ role: 'group',
240
+ '[attr.aria-label]': 'label()',
241
+ '[attr.aria-labelledby]': 'labelledBy()',
242
+ '[attr.aria-invalid]': 'invalid() ? "true" : null',
243
+ }, 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]=\"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" }]
244
+ }], 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
+
246
+ /**
247
+ * Generated bundle index. Do not edit.
248
+ */
249
+
250
+ export { NgnOtp };
251
+ //# sourceMappingURL=ngneers-controls-otp.mjs.map
@@ -0,0 +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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"}
@@ -365,6 +365,19 @@ class NgnSplitter extends NgnBase {
365
365
  const panels = this.panels();
366
366
  this.updateDividers(panels);
367
367
  });
368
+ // Measure divider positions in the after-render phase (post-layout) and cache
369
+ // them in a signal. Reading DOM offsets directly in the template binding causes
370
+ // NG0100, since the grid sizes are applied in the same CD cycle and the reflow
371
+ // settles the measured value between the CD and verify passes.
372
+ afterRenderEffect(() => {
373
+ const values = this.measureDividerValues();
374
+ untracked(() => {
375
+ const prev = this._dividerValues();
376
+ if (values.length !== prev.length || values.some((v, i) => v !== prev[i])) {
377
+ this._dividerValues.set(values);
378
+ }
379
+ });
380
+ });
368
381
  registerState({
369
382
  storage: () => this.stateStorage(),
370
383
  key: () => this.stateKey(),
@@ -527,28 +540,46 @@ class NgnSplitter extends NgnBase {
527
540
  }
528
541
  }
529
542
  }
543
+ /** Cached divider positions (percentage) measured post-layout; see the after-render effect in the constructor. */
544
+ _dividerValues = signal([], /* @ts-ignore */
545
+ ...(ngDevMode ? [{ debugName: "_dividerValues" }] : /* istanbul ignore next */ []));
530
546
  /**
531
547
  * The divider's current position as a percentage (0–100) of the total panel
532
548
  * content size, used for `aria-valuenow` on the `role="separator"` handle.
533
- * Reads `gridTemplateSizes()` to stay reactive to resizes, then measures the
534
- * rendered panel sizes on either side of the divider.
549
+ * Reads the cached value measured in the after-render phase.
535
550
  */
536
551
  dividerValueNow(index) {
552
+ return this._dividerValues()[index] ?? 0;
553
+ }
554
+ /**
555
+ * Measures each divider's position as a percentage (0–100) of the total panel
556
+ * content size. Reads `gridTemplateSizes()`, `elementSize()` and `dragContext()`
557
+ * as reactive triggers, then measures the rendered panel sizes.
558
+ */
559
+ measureDividerValues() {
537
560
  // ponytail: DOM-offset heuristic; one CD-frame lag during drag is fine for
538
561
  // an aria value. Swap for an engine-reported px signal if exactness matters.
539
- this.calculator().gridTemplateSizes(); // reactive trigger
540
- const panels = this.calculator().orderedPanels();
562
+ const calculator = this.calculator();
563
+ calculator.gridTemplateSizes(); // reactive trigger
564
+ calculator.dragContext(); // reactive trigger (live update during drag)
565
+ this.elementSize(); // reactive trigger (container resize)
566
+ const panels = calculator.orderedPanels();
541
567
  const horizontal = this.layout() === 'horizontal';
542
- let before = 0;
543
- let total = 0;
544
- panels.forEach((panel, i) => {
568
+ const sizes = panels.map(panel => {
545
569
  const el = panel.element.nativeElement;
546
- const size = horizontal ? el.offsetWidth : el.offsetHeight;
547
- total += size;
548
- if (i <= index)
549
- before += size;
570
+ return horizontal ? el.offsetWidth : el.offsetHeight;
550
571
  });
551
- return total > 0 ? Math.round((before / total) * 100) : 0;
572
+ const total = sizes.reduce((a, b) => a + b, 0);
573
+ if (total <= 0)
574
+ return [];
575
+ // One value per divider: cumulative size up to and including panel i.
576
+ const values = [];
577
+ let before = 0;
578
+ for (let i = 0; i < panels.length - 1; i++) {
579
+ before += sizes[i];
580
+ values.push(Math.round((before / total) * 100));
581
+ }
582
+ return values;
552
583
  }
553
584
  getStepInPx() {
554
585
  const step = this.step();