@tailng-ui/components 0.1.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/package.json +9 -4
  2. package/src/lib/feedback/toast/tng-toast.component.d.ts +55 -6
  3. package/src/lib/feedback/toast/tng-toast.component.d.ts.map +1 -1
  4. package/src/lib/feedback/toast/tng-toast.component.js +234 -23
  5. package/src/lib/feedback/toast/tng-toast.component.js.map +1 -1
  6. package/src/lib/form/autocomplete/tng-autocomplete.component.js +1 -1
  7. package/src/lib/form/autocomplete/tng-autocomplete.component.js.map +1 -1
  8. package/src/lib/form/chips/tng-chips.component.d.ts +9 -1
  9. package/src/lib/form/chips/tng-chips.component.d.ts.map +1 -1
  10. package/src/lib/form/chips/tng-chips.component.js +39 -3
  11. package/src/lib/form/chips/tng-chips.component.js.map +1 -1
  12. package/src/lib/form/index.d.ts +2 -2
  13. package/src/lib/form/index.d.ts.map +1 -1
  14. package/src/lib/form/index.js +2 -2
  15. package/src/lib/form/index.js.map +1 -1
  16. package/src/lib/form/input-otp/index.d.ts +3 -0
  17. package/src/lib/form/input-otp/index.d.ts.map +1 -0
  18. package/src/lib/form/input-otp/index.js +3 -0
  19. package/src/lib/form/input-otp/index.js.map +1 -0
  20. package/src/lib/form/input-otp/tng-input-otp.component.d.ts +79 -7
  21. package/src/lib/form/input-otp/tng-input-otp.component.d.ts.map +1 -1
  22. package/src/lib/form/input-otp/tng-input-otp.component.js +441 -81
  23. package/src/lib/form/input-otp/tng-input-otp.component.js.map +1 -1
  24. package/src/lib/form/radio/tng-radio.component.d.ts +4 -0
  25. package/src/lib/form/radio/tng-radio.component.d.ts.map +1 -1
  26. package/src/lib/form/radio/tng-radio.component.js +15 -0
  27. package/src/lib/form/radio/tng-radio.component.js.map +1 -1
  28. package/src/lib/form/textarea/tng-textarea.component.d.ts +2 -0
  29. package/src/lib/form/textarea/tng-textarea.component.d.ts.map +1 -1
  30. package/src/lib/form/textarea/tng-textarea.component.js +7 -4
  31. package/src/lib/form/textarea/tng-textarea.component.js.map +1 -1
  32. package/src/lib/navigation/breadcrumb/tng-breadcrumb-item.component.d.ts +10 -0
  33. package/src/lib/navigation/breadcrumb/tng-breadcrumb-item.component.d.ts.map +1 -1
  34. package/src/lib/navigation/breadcrumb/tng-breadcrumb-item.component.js +52 -1
  35. package/src/lib/navigation/breadcrumb/tng-breadcrumb-item.component.js.map +1 -1
  36. package/src/lib/navigation/context-menu/tng-context-menu.component.d.ts +1 -0
  37. package/src/lib/navigation/context-menu/tng-context-menu.component.d.ts.map +1 -1
  38. package/src/lib/navigation/context-menu/tng-context-menu.component.js +20 -4
  39. package/src/lib/navigation/context-menu/tng-context-menu.component.js.map +1 -1
  40. package/src/lib/overlay/popover/tng-popover.component.d.ts +14 -19
  41. package/src/lib/overlay/popover/tng-popover.component.d.ts.map +1 -1
  42. package/src/lib/overlay/popover/tng-popover.component.js +24 -105
  43. package/src/lib/overlay/popover/tng-popover.component.js.map +1 -1
  44. package/src/lib/overlay/tng-overlay-focus-handoff.d.ts +10 -0
  45. package/src/lib/overlay/tng-overlay-focus-handoff.d.ts.map +1 -0
  46. package/src/lib/overlay/tng-overlay-focus-handoff.js +3 -0
  47. package/src/lib/overlay/tng-overlay-focus-handoff.js.map +1 -0
  48. package/src/lib/overlay/tng-overlay-runtime.d.ts +2 -2
  49. package/src/lib/overlay/tooltip/tng-tooltip.component.d.ts +1 -18
  50. package/src/lib/overlay/tooltip/tng-tooltip.component.d.ts.map +1 -1
  51. package/src/lib/overlay/tooltip/tng-tooltip.component.js +5 -85
  52. package/src/lib/overlay/tooltip/tng-tooltip.component.js.map +1 -1
  53. package/src/lib/utility/avatar/tng-avatar.component.d.ts.map +1 -1
  54. package/src/lib/utility/avatar/tng-avatar.component.js +8 -1
  55. package/src/lib/utility/avatar/tng-avatar.component.js.map +1 -1
  56. package/src/lib/utility/tag/tng-tag.component.d.ts +8 -0
  57. package/src/lib/utility/tag/tng-tag.component.d.ts.map +1 -1
  58. package/src/lib/utility/tag/tng-tag.component.js +16 -3
  59. package/src/lib/utility/tag/tng-tag.component.js.map +1 -1
@@ -1,137 +1,497 @@
1
1
  import { __decorate } from "tslib";
2
- import { Component, computed, input, output } from '@angular/core';
3
- import { normalizeTngOtpLength, TngInputOtp as TngInputOtpPrimitive } from '@tailng-ui/primitives';
4
- function toOtpSlots(length, value) {
5
- const slots = Array.from({ length }, () => '');
6
- for (const [index, char] of Array.from(value).entries()) {
7
- if (index >= length) {
2
+ import { Component, computed, effect, ElementRef, forwardRef, inject, input, output, Renderer2, signal, } from '@angular/core';
3
+ import { booleanAttribute } from '@angular/core';
4
+ import { NG_VALUE_ACCESSOR } from '@angular/forms';
5
+ import { clampTngOtpValue, normalizeTngOtpLength, resolveTngOtpState, TngInputOtp as TngInputOtpPrimitive, } from '@tailng-ui/primitives';
6
+ const tngOtpDigitPattern = /^\d$/;
7
+ const tngOtpAlphanumericPattern = /^[a-zA-Z0-9]$/;
8
+ let tngInputOtpInstanceCounter = 0;
9
+ function toNonGlobalRegex(pattern) {
10
+ const flags = pattern.flags.replace(/g/g, '');
11
+ return new RegExp(pattern.source, flags);
12
+ }
13
+ export function sanitizeTngOtpCharacters(value, mode, pattern) {
14
+ const compiledPattern = mode !== 'custom'
15
+ ? null
16
+ : typeof pattern === 'string'
17
+ ? new RegExp(pattern)
18
+ : pattern === null
19
+ ? null
20
+ : toNonGlobalRegex(pattern);
21
+ const acceptedChars = [];
22
+ for (const char of Array.from(value)) {
23
+ if (mode === 'numeric' && !tngOtpDigitPattern.test(char)) {
24
+ continue;
25
+ }
26
+ if (mode === 'alphanumeric' && !tngOtpAlphanumericPattern.test(char)) {
27
+ continue;
28
+ }
29
+ if (compiledPattern && !compiledPattern.test(char)) {
30
+ continue;
31
+ }
32
+ acceptedChars.push(char);
33
+ }
34
+ return acceptedChars;
35
+ }
36
+ export function toTngOtpSlots(length, value) {
37
+ const safeLength = normalizeTngOtpLength(length);
38
+ const slots = Array.from({ length: safeLength }, () => '');
39
+ const chars = Array.from(clampTngOtpValue(value, safeLength));
40
+ for (const [index, char] of chars.entries()) {
41
+ if (index >= safeLength) {
8
42
  break;
9
43
  }
10
44
  slots[index] = char;
11
45
  }
12
46
  return slots;
13
47
  }
14
- function joinOtpSlots(slots) {
15
- return slots.join('');
16
- }
17
- export function sanitizeTngOtpValue(value) {
18
- return value.replace(/[^a-zA-Z0-9]/g, '');
19
- }
20
- export function applyTngOtpSlotValue(slots, index, nextValue) {
21
- if (index < 0 || index >= slots.length) {
22
- return slots;
48
+ export function applyTngOtpCharacters(value, startIndex, characters, maxLength) {
49
+ const safeLength = normalizeTngOtpLength(maxLength);
50
+ const nextChars = Array.from(clampTngOtpValue(value, safeLength));
51
+ const safeStart = Math.max(0, Math.min(startIndex, safeLength - 1));
52
+ let cursor = nextChars.length < safeLength ? Math.min(safeStart, nextChars.length) : safeStart;
53
+ for (const char of characters) {
54
+ if (cursor >= safeLength) {
55
+ break;
56
+ }
57
+ if (cursor < nextChars.length) {
58
+ nextChars[cursor] = char;
59
+ }
60
+ else {
61
+ nextChars.push(char);
62
+ }
63
+ cursor += 1;
23
64
  }
24
- const nextSlots = [...slots];
25
- nextSlots[index] = sanitizeTngOtpValue(nextValue).slice(0, 1);
26
- return nextSlots;
27
- }
28
- function isFilledOtp(slots) {
29
- return slots.every((slot) => slot.length === 1);
65
+ return nextChars.slice(0, safeLength).join('');
30
66
  }
31
- function focusOtpInput(index) {
32
- const target = document.querySelector(`[data-tng-otp-slot='${index}']`);
33
- target?.focus();
34
- target?.select();
35
- }
36
- function readInputValue(event) {
37
- if (!(event instanceof Event)) {
38
- return null;
39
- }
40
- const target = event.target;
41
- if (!(target instanceof HTMLInputElement)) {
42
- return null;
67
+ export function removeTngOtpCharacter(value, index) {
68
+ const chars = Array.from(value);
69
+ if (index < 0 || index >= chars.length) {
70
+ return value;
43
71
  }
44
- return target.value;
72
+ chars.splice(index, 1);
73
+ return chars.join('');
45
74
  }
46
- function readClipboardText(event) {
47
- if (!(event instanceof ClipboardEvent)) {
48
- return null;
75
+ export function resolveTngOtpEntryIndex(value, length) {
76
+ const safeLength = normalizeTngOtpLength(length);
77
+ const safeValue = clampTngOtpValue(value, safeLength);
78
+ if (safeValue.length >= safeLength) {
79
+ return safeLength - 1;
49
80
  }
50
- return event.clipboardData?.getData('text') ?? null;
81
+ return safeValue.length;
51
82
  }
52
- function handleOtpArrowNavigation(index, key, total) {
53
- if (key === 'ArrowLeft' && index > 0) {
54
- focusOtpInput(index - 1);
55
- return true;
56
- }
57
- if (key === 'ArrowRight' && index < total - 1) {
58
- focusOtpInput(index + 1);
59
- return true;
83
+ export function resolveTngOtpEndIndex(value, length) {
84
+ const safeLength = normalizeTngOtpLength(length);
85
+ const safeValue = clampTngOtpValue(value, safeLength);
86
+ if (safeValue.length === 0) {
87
+ return safeLength - 1;
60
88
  }
61
- return false;
89
+ return Math.max(0, safeValue.length - 1);
62
90
  }
63
- function shouldMoveBackForDelete(slots, index, key) {
64
- if (key !== 'Backspace') {
65
- return false;
91
+ function clampOtpIndex(index, length) {
92
+ const safeLength = normalizeTngOtpLength(length);
93
+ if (!Number.isFinite(index)) {
94
+ return 0;
66
95
  }
67
- if (index === 0) {
68
- return false;
96
+ if (index < 0) {
97
+ return 0;
69
98
  }
70
- return slots[index] === '';
99
+ if (index >= safeLength) {
100
+ return safeLength - 1;
101
+ }
102
+ return Math.trunc(index);
71
103
  }
72
104
  let TngInputOtpComponent = class TngInputOtpComponent {
105
+ hostRef = inject(ElementRef);
106
+ renderer = inject(Renderer2);
107
+ generatedId = `tng-input-otp-${++tngInputOtpInstanceCounter}`;
108
+ resetUnlisten = null;
109
+ hasInitializedUncontrolled = false;
110
+ onChangeCallback = () => undefined;
111
+ onTouchedCallback = () => undefined;
112
+ uncontrolledValue = signal('');
113
+ formsDisabled = signal(false);
114
+ focusedState = signal(false);
115
+ focusVisibleState = signal(false);
116
+ keyboardInteraction = signal(false);
117
+ activeIndexState = signal(0);
73
118
  length = input(6, {
74
119
  transform: (value) => normalizeTngOtpLength(typeof value === 'number' ? value : Number(value)),
75
120
  });
76
- value = input('');
121
+ valueInput = input(undefined, { alias: 'value' });
122
+ defaultValue = input('');
123
+ type = input('numeric');
124
+ pattern = input(null);
125
+ mask = input(false, {
126
+ transform: booleanAttribute,
127
+ });
128
+ disabledInput = input(false, {
129
+ alias: 'disabled',
130
+ transform: booleanAttribute,
131
+ });
132
+ readonly = input(false, {
133
+ transform: booleanAttribute,
134
+ });
135
+ required = input(false, {
136
+ transform: booleanAttribute,
137
+ });
138
+ invalid = input(false, {
139
+ transform: booleanAttribute,
140
+ });
141
+ autoFocus = input(false, {
142
+ transform: booleanAttribute,
143
+ });
144
+ selectOnFocus = input(true, {
145
+ transform: booleanAttribute,
146
+ });
147
+ placeholderChar = input('');
148
+ name = input(null);
149
+ id = input(null);
150
+ form = input(null);
151
+ autocomplete = input('one-time-code');
152
+ inputMode = input(null);
153
+ ariaLabel = input(null);
154
+ ariaLabelledby = input(null);
155
+ ariaDescribedby = input(null);
77
156
  valueChange = output();
78
157
  complete = output();
79
- slots = computed(() => toOtpSlots(this.length(), sanitizeTngOtpValue(this.value())));
80
- onSlotInput(index, event) {
81
- const value = readInputValue(event);
82
- if (value === null) {
158
+ disabled = computed(() => this.disabledInput() || this.formsDisabled());
159
+ focused = computed(() => this.focusedState());
160
+ focusVisible = computed(() => this.focusVisibleState());
161
+ activeIndex = computed(() => clampOtpIndex(this.activeIndexState(), this.length()));
162
+ rootId = computed(() => this.id() ?? this.generatedId);
163
+ currentValue = computed(() => {
164
+ const controlled = this.valueInput();
165
+ if (controlled !== undefined) {
166
+ return this.normalizeAndClamp(controlled ?? '');
167
+ }
168
+ return this.normalizeAndClamp(this.uncontrolledValue());
169
+ });
170
+ slots = computed(() => toTngOtpSlots(this.length(), this.currentValue()));
171
+ completionState = computed(() => resolveTngOtpState(this.length(), this.currentValue()));
172
+ resolvedInputMode = computed(() => {
173
+ const explicit = this.inputMode();
174
+ if (explicit !== null) {
175
+ return explicit;
176
+ }
177
+ return this.type() === 'numeric' ? 'numeric' : 'text';
178
+ });
179
+ slotInputType = computed(() => this.mask() ? 'password' : 'text');
180
+ syncUncontrolledState = effect(() => {
181
+ const controlled = this.valueInput();
182
+ const length = this.length();
183
+ this.type();
184
+ this.pattern();
185
+ if (controlled !== undefined) {
186
+ if (!this.focusedState()) {
187
+ this.activeIndexState.set(resolveTngOtpEntryIndex(this.normalizeAndClamp(controlled ?? ''), length));
188
+ }
83
189
  return;
84
190
  }
85
- const nextSlots = applyTngOtpSlotValue(this.slots(), index, value);
86
- const nextValue = joinOtpSlots(nextSlots);
87
- this.valueChange.emit(nextValue);
88
- if (nextSlots[index] !== '' && index < nextSlots.length - 1) {
89
- focusOtpInput(index + 1);
191
+ const normalizedDefault = this.normalizeAndClamp(this.defaultValue());
192
+ if (!this.hasInitializedUncontrolled) {
193
+ this.hasInitializedUncontrolled = true;
194
+ this.uncontrolledValue.set(normalizedDefault);
195
+ this.activeIndexState.set(resolveTngOtpEntryIndex(normalizedDefault, length));
196
+ return;
90
197
  }
91
- if (isFilledOtp(nextSlots)) {
92
- this.complete.emit(nextValue);
198
+ const normalizedCurrent = this.normalizeAndClamp(this.uncontrolledValue());
199
+ if (normalizedCurrent !== this.uncontrolledValue()) {
200
+ this.uncontrolledValue.set(normalizedCurrent);
93
201
  }
202
+ this.activeIndexState.update((index) => clampOtpIndex(index, length));
203
+ });
204
+ ngAfterViewInit() {
205
+ this.attachFormResetListener();
206
+ if (!this.autoFocus() || this.disabled()) {
207
+ return;
208
+ }
209
+ queueMicrotask(() => {
210
+ if (this.disabled()) {
211
+ return;
212
+ }
213
+ this.focusSlot(resolveTngOtpEntryIndex(this.currentValue(), this.length()), this.selectOnFocus());
214
+ });
215
+ }
216
+ ngOnDestroy() {
217
+ this.resetUnlisten?.();
218
+ this.resetUnlisten = null;
219
+ }
220
+ writeValue(value) {
221
+ const normalized = this.normalizeAndClamp(value ?? '');
222
+ this.hasInitializedUncontrolled = true;
223
+ this.uncontrolledValue.set(normalized);
224
+ if (!this.focusedState()) {
225
+ this.activeIndexState.set(resolveTngOtpEntryIndex(normalized, this.length()));
226
+ }
227
+ }
228
+ registerOnChange(fn) {
229
+ this.onChangeCallback = fn;
230
+ }
231
+ registerOnTouched(fn) {
232
+ this.onTouchedCallback = fn;
233
+ }
234
+ setDisabledState(isDisabled) {
235
+ this.formsDisabled.set(isDisabled);
236
+ }
237
+ onSlotPointerDown() {
238
+ this.keyboardInteraction.set(false);
239
+ }
240
+ onSlotFocus(index, event) {
241
+ const target = event.target;
242
+ const nextIndex = this.resolveEditableIndex(index);
243
+ this.activeIndexState.set(nextIndex);
244
+ this.focusedState.set(true);
245
+ if (target instanceof HTMLElement && target.matches(':focus-visible')) {
246
+ this.focusVisibleState.set(true);
247
+ }
248
+ else {
249
+ this.focusVisibleState.set(this.keyboardInteraction());
250
+ }
251
+ if (this.selectOnFocus() && target instanceof HTMLInputElement) {
252
+ target.select();
253
+ }
254
+ }
255
+ onSlotBlur(event) {
256
+ const host = this.hostRef.nativeElement;
257
+ const relatedTarget = event.relatedTarget;
258
+ if (relatedTarget instanceof Node && host.contains(relatedTarget)) {
259
+ return;
260
+ }
261
+ queueMicrotask(() => {
262
+ if (this.isFocusWithinHost()) {
263
+ return;
264
+ }
265
+ this.focusedState.set(false);
266
+ this.focusVisibleState.set(false);
267
+ this.onTouchedCallback();
268
+ });
94
269
  }
95
270
  onSlotKeydown(index, event) {
96
- if (!(event instanceof KeyboardEvent)) {
271
+ this.keyboardInteraction.set(true);
272
+ const editableIndex = this.resolveEditableIndex(index);
273
+ const total = this.length();
274
+ if (event.key === 'ArrowLeft') {
275
+ event.preventDefault();
276
+ this.focusSlot(Math.max(0, editableIndex - 1), true);
97
277
  return;
98
278
  }
99
- if (handleOtpArrowNavigation(index, event.key, this.slots().length)) {
279
+ if (event.key === 'ArrowRight') {
100
280
  event.preventDefault();
281
+ this.focusSlot(Math.min(total - 1, editableIndex + 1), true);
101
282
  return;
102
283
  }
103
- if (!shouldMoveBackForDelete(this.slots(), index, event.key)) {
284
+ if (event.key === 'Home') {
285
+ event.preventDefault();
286
+ this.focusSlot(0, true);
104
287
  return;
105
288
  }
106
- event.preventDefault();
107
- const nextSlots = applyTngOtpSlotValue(this.slots(), index - 1, '');
108
- this.valueChange.emit(joinOtpSlots(nextSlots));
109
- focusOtpInput(index - 1);
289
+ if (event.key === 'End') {
290
+ event.preventDefault();
291
+ this.focusSlot(resolveTngOtpEndIndex(this.currentValue(), total), true);
292
+ return;
293
+ }
294
+ if (event.key === 'Backspace') {
295
+ event.preventDefault();
296
+ this.handleBackspace(editableIndex);
297
+ return;
298
+ }
299
+ if (event.key === 'Delete') {
300
+ event.preventDefault();
301
+ this.handleDelete(editableIndex);
302
+ return;
303
+ }
304
+ if ((this.disabled() || this.readonly()) && event.key.length === 1) {
305
+ event.preventDefault();
306
+ }
110
307
  }
111
- onSlotsPaste(event) {
112
- const clipboardValue = readClipboardText(event);
113
- if (clipboardValue === null) {
308
+ onSlotInput(index, event) {
309
+ const target = event.target;
310
+ if (!(target instanceof HTMLInputElement)) {
114
311
  return;
115
312
  }
116
- const cleanedValue = sanitizeTngOtpValue(clipboardValue).slice(0, this.length());
117
- if (cleanedValue.length === 0) {
313
+ if (this.disabled() || this.readonly()) {
314
+ target.value = this.slotValue(this.resolveEditableIndex(index));
118
315
  return;
119
316
  }
120
- if (event instanceof ClipboardEvent) {
317
+ const chars = sanitizeTngOtpCharacters(target.value, this.type(), this.pattern());
318
+ if (chars.length === 0) {
319
+ const nextValue = removeTngOtpCharacter(this.currentValue(), this.resolveEditableIndex(index));
320
+ this.commitValue(nextValue, this.resolveEditableIndex(index));
321
+ return;
322
+ }
323
+ const previousValue = this.currentValue();
324
+ const totalLength = this.length();
325
+ const editableIndex = this.resolveEditableIndex(index);
326
+ const wasCompleteBeforeInput = previousValue.length >= totalLength;
327
+ const wasReplacingExistingCharacter = editableIndex < previousValue.length;
328
+ const nextValue = applyTngOtpCharacters(previousValue, editableIndex, chars, totalLength);
329
+ const nextFocus = wasCompleteBeforeInput || wasReplacingExistingCharacter
330
+ ? Math.min(totalLength - 1, editableIndex + chars.length)
331
+ : nextValue.length >= totalLength
332
+ ? totalLength - 1
333
+ : Math.min(totalLength - 1, Math.max(editableIndex + chars.length, nextValue.length));
334
+ this.commitValue(nextValue, nextFocus);
335
+ }
336
+ onSlotPaste(index, event) {
337
+ if (this.disabled() || this.readonly()) {
338
+ return;
339
+ }
340
+ const clipboardValue = event.clipboardData?.getData('text') ?? '';
341
+ const chars = sanitizeTngOtpCharacters(clipboardValue, this.type(), this.pattern());
342
+ if (chars.length === 0) {
121
343
  event.preventDefault();
344
+ return;
345
+ }
346
+ event.preventDefault();
347
+ const editableIndex = this.resolveEditableIndex(index);
348
+ const nextValue = applyTngOtpCharacters(this.currentValue(), editableIndex, chars, this.length());
349
+ const nextFocus = nextValue.length >= this.length()
350
+ ? this.length() - 1
351
+ : Math.min(this.length() - 1, nextValue.length);
352
+ this.commitValue(nextValue, nextFocus);
353
+ }
354
+ slotValue(index) {
355
+ const slot = this.slots()[index];
356
+ return slot ?? '';
357
+ }
358
+ slotPlaceholder(index) {
359
+ if (this.placeholderChar().length === 0) {
360
+ return null;
361
+ }
362
+ return this.slotValue(index) === '' ? this.placeholderChar() : null;
363
+ }
364
+ slotAriaLabel(index) {
365
+ const prefix = this.ariaLabel();
366
+ const base = prefix?.trim() ? `${prefix.trim()} - ` : '';
367
+ return `${base}Character ${index + 1} of ${this.length()}`;
368
+ }
369
+ isSlotTabbable(index) {
370
+ return this.activeIndex() === index;
371
+ }
372
+ handleBackspace(index) {
373
+ if (this.disabled() || this.readonly()) {
374
+ return;
375
+ }
376
+ const currentValue = this.currentValue();
377
+ if (currentValue.length === 0) {
378
+ return;
379
+ }
380
+ if (index < currentValue.length) {
381
+ const nextValue = removeTngOtpCharacter(currentValue, index);
382
+ const nextFocus = Math.min(index, Math.max(0, nextValue.length));
383
+ this.commitValue(nextValue, nextFocus);
384
+ return;
385
+ }
386
+ const previousIndex = Math.max(0, currentValue.length - 1);
387
+ const nextValue = removeTngOtpCharacter(currentValue, previousIndex);
388
+ this.commitValue(nextValue, Math.max(0, previousIndex));
389
+ }
390
+ handleDelete(index) {
391
+ if (this.disabled() || this.readonly()) {
392
+ return;
393
+ }
394
+ if (index >= this.currentValue().length) {
395
+ return;
396
+ }
397
+ const nextValue = removeTngOtpCharacter(this.currentValue(), index);
398
+ this.commitValue(nextValue, index);
399
+ }
400
+ commitValue(nextValue, nextFocusIndex) {
401
+ const previousValue = this.currentValue();
402
+ const normalized = this.normalizeAndClamp(nextValue);
403
+ const didChange = normalized !== previousValue;
404
+ if (this.valueInput() === undefined) {
405
+ this.uncontrolledValue.set(normalized);
122
406
  }
123
- this.valueChange.emit(cleanedValue);
124
- if (cleanedValue.length >= this.length()) {
125
- this.complete.emit(cleanedValue);
407
+ this.activeIndexState.set(clampOtpIndex(nextFocusIndex, this.length()));
408
+ if (didChange) {
409
+ this.valueChange.emit(normalized);
410
+ this.onChangeCallback(normalized);
411
+ if (resolveTngOtpState(this.length(), normalized) === 'complete') {
412
+ this.complete.emit(normalized);
413
+ }
414
+ }
415
+ queueMicrotask(() => {
416
+ this.syncSlotInputValues();
417
+ if (!this.focusedState() && !this.isFocusWithinHost()) {
418
+ return;
419
+ }
420
+ this.focusSlot(this.activeIndex(), this.selectOnFocus());
421
+ });
422
+ }
423
+ resolveEditableIndex(index) {
424
+ const safeIndex = clampOtpIndex(index, this.length());
425
+ const currentValueLength = this.currentValue().length;
426
+ if (currentValueLength >= this.length()) {
427
+ return safeIndex;
428
+ }
429
+ return Math.min(safeIndex, currentValueLength);
430
+ }
431
+ focusSlot(index, select) {
432
+ const safeIndex = clampOtpIndex(index, this.length());
433
+ const slot = this.hostRef.nativeElement.querySelector(`[data-tng-otp-slot='${safeIndex}']`);
434
+ if (!(slot instanceof HTMLInputElement)) {
435
+ return;
436
+ }
437
+ slot.focus();
438
+ if (select) {
439
+ slot.select();
440
+ }
441
+ this.activeIndexState.set(safeIndex);
442
+ }
443
+ syncSlotInputValues() {
444
+ const slotValues = this.slots();
445
+ const slotInputs = this.hostRef.nativeElement.querySelectorAll('.tng-input-otp-slot');
446
+ slotInputs.forEach((slotInput, index) => {
447
+ slotInput.value = slotValues[index] ?? '';
448
+ });
449
+ }
450
+ isFocusWithinHost() {
451
+ const ownerDocument = this.hostRef.nativeElement.ownerDocument;
452
+ const activeElement = ownerDocument?.activeElement;
453
+ return activeElement instanceof Node && this.hostRef.nativeElement.contains(activeElement);
454
+ }
455
+ normalizeAndClamp(value) {
456
+ const chars = sanitizeTngOtpCharacters(value, this.type(), this.pattern());
457
+ const safeLength = this.length();
458
+ return chars.slice(0, safeLength).join('');
459
+ }
460
+ attachFormResetListener() {
461
+ const formId = this.form();
462
+ const ownerForm = formId !== null
463
+ ? this.hostRef.nativeElement.ownerDocument?.getElementById(formId)
464
+ : this.hostRef.nativeElement.closest('form');
465
+ if (!(ownerForm instanceof HTMLFormElement)) {
466
+ return;
126
467
  }
468
+ this.resetUnlisten?.();
469
+ this.resetUnlisten = this.renderer.listen(ownerForm, 'reset', () => {
470
+ if (this.valueInput() !== undefined) {
471
+ return;
472
+ }
473
+ const resetValue = this.normalizeAndClamp(this.defaultValue());
474
+ this.uncontrolledValue.set(resetValue);
475
+ this.activeIndexState.set(resolveTngOtpEntryIndex(resetValue, this.length()));
476
+ this.valueChange.emit(resetValue);
477
+ this.onChangeCallback(resetValue);
478
+ });
127
479
  }
128
480
  };
129
481
  TngInputOtpComponent = __decorate([
130
482
  Component({
131
483
  selector: 'tng-input-otp',
484
+ standalone: true,
132
485
  imports: [TngInputOtpPrimitive],
133
486
  templateUrl: './tng-input-otp.component.html',
134
487
  styleUrl: './tng-input-otp.component.css',
488
+ providers: [
489
+ {
490
+ provide: NG_VALUE_ACCESSOR,
491
+ useExisting: forwardRef(() => TngInputOtpComponent),
492
+ multi: true,
493
+ },
494
+ ],
135
495
  })
136
496
  ], TngInputOtpComponent);
137
497
  export { TngInputOtpComponent };