@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.
- package/package.json +9 -4
- package/src/lib/feedback/toast/tng-toast.component.d.ts +55 -6
- package/src/lib/feedback/toast/tng-toast.component.d.ts.map +1 -1
- package/src/lib/feedback/toast/tng-toast.component.js +234 -23
- package/src/lib/feedback/toast/tng-toast.component.js.map +1 -1
- package/src/lib/form/autocomplete/tng-autocomplete.component.js +1 -1
- package/src/lib/form/autocomplete/tng-autocomplete.component.js.map +1 -1
- package/src/lib/form/chips/tng-chips.component.d.ts +9 -1
- package/src/lib/form/chips/tng-chips.component.d.ts.map +1 -1
- package/src/lib/form/chips/tng-chips.component.js +39 -3
- package/src/lib/form/chips/tng-chips.component.js.map +1 -1
- package/src/lib/form/index.d.ts +2 -2
- package/src/lib/form/index.d.ts.map +1 -1
- package/src/lib/form/index.js +2 -2
- package/src/lib/form/index.js.map +1 -1
- package/src/lib/form/input-otp/index.d.ts +3 -0
- package/src/lib/form/input-otp/index.d.ts.map +1 -0
- package/src/lib/form/input-otp/index.js +3 -0
- package/src/lib/form/input-otp/index.js.map +1 -0
- package/src/lib/form/input-otp/tng-input-otp.component.d.ts +79 -7
- package/src/lib/form/input-otp/tng-input-otp.component.d.ts.map +1 -1
- package/src/lib/form/input-otp/tng-input-otp.component.js +441 -81
- package/src/lib/form/input-otp/tng-input-otp.component.js.map +1 -1
- package/src/lib/form/radio/tng-radio.component.d.ts +4 -0
- package/src/lib/form/radio/tng-radio.component.d.ts.map +1 -1
- package/src/lib/form/radio/tng-radio.component.js +15 -0
- package/src/lib/form/radio/tng-radio.component.js.map +1 -1
- package/src/lib/form/textarea/tng-textarea.component.d.ts +2 -0
- package/src/lib/form/textarea/tng-textarea.component.d.ts.map +1 -1
- package/src/lib/form/textarea/tng-textarea.component.js +7 -4
- package/src/lib/form/textarea/tng-textarea.component.js.map +1 -1
- package/src/lib/navigation/breadcrumb/tng-breadcrumb-item.component.d.ts +10 -0
- package/src/lib/navigation/breadcrumb/tng-breadcrumb-item.component.d.ts.map +1 -1
- package/src/lib/navigation/breadcrumb/tng-breadcrumb-item.component.js +52 -1
- package/src/lib/navigation/breadcrumb/tng-breadcrumb-item.component.js.map +1 -1
- package/src/lib/navigation/context-menu/tng-context-menu.component.d.ts +1 -0
- package/src/lib/navigation/context-menu/tng-context-menu.component.d.ts.map +1 -1
- package/src/lib/navigation/context-menu/tng-context-menu.component.js +20 -4
- package/src/lib/navigation/context-menu/tng-context-menu.component.js.map +1 -1
- package/src/lib/overlay/popover/tng-popover.component.d.ts +14 -19
- package/src/lib/overlay/popover/tng-popover.component.d.ts.map +1 -1
- package/src/lib/overlay/popover/tng-popover.component.js +24 -105
- package/src/lib/overlay/popover/tng-popover.component.js.map +1 -1
- package/src/lib/overlay/tng-overlay-focus-handoff.d.ts +10 -0
- package/src/lib/overlay/tng-overlay-focus-handoff.d.ts.map +1 -0
- package/src/lib/overlay/tng-overlay-focus-handoff.js +3 -0
- package/src/lib/overlay/tng-overlay-focus-handoff.js.map +1 -0
- package/src/lib/overlay/tng-overlay-runtime.d.ts +2 -2
- package/src/lib/overlay/tooltip/tng-tooltip.component.d.ts +1 -18
- package/src/lib/overlay/tooltip/tng-tooltip.component.d.ts.map +1 -1
- package/src/lib/overlay/tooltip/tng-tooltip.component.js +5 -85
- package/src/lib/overlay/tooltip/tng-tooltip.component.js.map +1 -1
- package/src/lib/utility/avatar/tng-avatar.component.d.ts.map +1 -1
- package/src/lib/utility/avatar/tng-avatar.component.js +8 -1
- package/src/lib/utility/avatar/tng-avatar.component.js.map +1 -1
- package/src/lib/utility/tag/tng-tag.component.d.ts +8 -0
- package/src/lib/utility/tag/tng-tag.component.d.ts.map +1 -1
- package/src/lib/utility/tag/tng-tag.component.js +16 -3
- 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 {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
72
|
+
chars.splice(index, 1);
|
|
73
|
+
return chars.join('');
|
|
45
74
|
}
|
|
46
|
-
function
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
81
|
+
return safeValue.length;
|
|
51
82
|
}
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
89
|
+
return Math.max(0, safeValue.length - 1);
|
|
62
90
|
}
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
|
|
91
|
+
function clampOtpIndex(index, length) {
|
|
92
|
+
const safeLength = normalizeTngOtpLength(length);
|
|
93
|
+
if (!Number.isFinite(index)) {
|
|
94
|
+
return 0;
|
|
66
95
|
}
|
|
67
|
-
if (index
|
|
68
|
-
return
|
|
96
|
+
if (index < 0) {
|
|
97
|
+
return 0;
|
|
69
98
|
}
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
284
|
+
if (event.key === 'Home') {
|
|
285
|
+
event.preventDefault();
|
|
286
|
+
this.focusSlot(0, true);
|
|
104
287
|
return;
|
|
105
288
|
}
|
|
106
|
-
event.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
112
|
-
const
|
|
113
|
-
if (
|
|
308
|
+
onSlotInput(index, event) {
|
|
309
|
+
const target = event.target;
|
|
310
|
+
if (!(target instanceof HTMLInputElement)) {
|
|
114
311
|
return;
|
|
115
312
|
}
|
|
116
|
-
|
|
117
|
-
|
|
313
|
+
if (this.disabled() || this.readonly()) {
|
|
314
|
+
target.value = this.slotValue(this.resolveEditableIndex(index));
|
|
118
315
|
return;
|
|
119
316
|
}
|
|
120
|
-
|
|
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.
|
|
124
|
-
if (
|
|
125
|
-
this.
|
|
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 };
|