@progress/kendo-angular-inputs 18.0.1-develop.3 → 18.1.0-develop.10

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.
@@ -0,0 +1,900 @@
1
+ /**-----------------------------------------------------------------------------------------
2
+ * Copyright © 2025 Progress Software Corporation. All rights reserved.
3
+ * Licensed under commercial license. See LICENSE.md in the project root for more information
4
+ *-------------------------------------------------------------------------------------------*/
5
+ import { ChangeDetectorRef, Component, ElementRef, EventEmitter, HostBinding, Injector, Input, NgZone, Output, QueryList, Renderer2, ViewChildren, forwardRef } from "@angular/core";
6
+ import { SharedInputEventsDirective } from "../shared/shared-events.directive";
7
+ import { NgFor, NgIf } from "@angular/common";
8
+ import { KendoInput, Keys, hasObservers, isPresent } from "@progress/kendo-angular-common";
9
+ import { TextBoxComponent } from "../textbox/textbox.component";
10
+ import { NG_VALUE_ACCESSOR, NgControl } from "@angular/forms";
11
+ import { SIZE_MAP, areSame, replaceMessagePlaceholder } from "../common/utils";
12
+ import { L10N_PREFIX, LocalizationService } from "@progress/kendo-angular-l10n";
13
+ import { OTPInputSeparatorComponent } from "./otpinput-separator.component";
14
+ import { take } from "rxjs/operators";
15
+ import { LocalizedOTPInputMessagesDirective } from "./localization/localized-textbox-messages.directive";
16
+ import * as i0 from "@angular/core";
17
+ import * as i1 from "@progress/kendo-angular-l10n";
18
+ const DEFAULT_SIZE = 'medium';
19
+ const DEFAULT_ROUNDED = 'medium';
20
+ const DEFAULT_FILL_MODE = 'solid';
21
+ const DEFAULT_OTPINPUT_LENGTH = 4;
22
+ export class OTPInputComponent {
23
+ hostElement;
24
+ cdr;
25
+ injector;
26
+ renderer;
27
+ localizationService;
28
+ zone;
29
+ /**
30
+ * Configures the total number of input fields.
31
+ *
32
+ * @default 4
33
+ */
34
+ set length(value) {
35
+ if (value < 1 || this._length === value) {
36
+ return;
37
+ }
38
+ this._length = value;
39
+ this.inputsArray = new Array(this._length);
40
+ }
41
+ get length() {
42
+ return this._length;
43
+ }
44
+ /**
45
+ * Configures the input type.
46
+ *
47
+ * * The possible values are:
48
+ * * `text` (default)
49
+ * * `number`
50
+ * * `password`
51
+ *
52
+ * @default 'text'
53
+ */
54
+ type = 'text';
55
+ /**
56
+ * Configures whether the input fields are separate or adjacent to each other.
57
+ *
58
+ * @default true
59
+ */
60
+ spacing = true;
61
+ /**
62
+ * Adds a separator between groups of input fields.
63
+ *
64
+ * > The configuration can only be applied when `groupLength` is set.
65
+ */
66
+ separator;
67
+ /**
68
+ * Configures whether the component is enabled or disabled.
69
+ *
70
+ * @default false
71
+ */
72
+ disabled = false;
73
+ /**
74
+ * Configures whether the component is readonly.
75
+ *
76
+ * @default false
77
+ */
78
+ readonly = false;
79
+ /**
80
+ * Configures the placeholder of the input fields.
81
+ */
82
+ placeholder;
83
+ /**
84
+ * Configures the length of the groups. If `groupLength` is a number, all groups will have the same length. If it's an array, each group can have a different length.
85
+ */
86
+ get groupLength() {
87
+ return this._groupLength;
88
+ }
89
+ set groupLength(length) {
90
+ const isNumber = typeof length === 'number';
91
+ if (this._groupLength === length ||
92
+ isPresent(length) &&
93
+ ((isNumber && (length < 1 || length > this.length)) ||
94
+ (!isNumber && !this.isValidGroupArray(length)))) {
95
+ return;
96
+ }
97
+ if (!isPresent(length)) {
98
+ this.clearGroups();
99
+ }
100
+ else if (isNumber) {
101
+ this.populateGroupArray(length);
102
+ }
103
+ else {
104
+ this.groupLengthArray = length;
105
+ if (!this.spacing) {
106
+ this.adjacentGroups = this.groupLengthArray;
107
+ }
108
+ }
109
+ this._groupLength = length;
110
+ this.populateSeparatorPosititons();
111
+ }
112
+ /**
113
+ * Configures the value of the component. Unfilled input fields are represented with space.
114
+ */
115
+ get value() {
116
+ return this._value;
117
+ }
118
+ set value(input) {
119
+ if (this._value === input ||
120
+ (this.type === 'number' && isPresent(input) && !this.isValidNumber(input))) {
121
+ return;
122
+ }
123
+ if (!isPresent(input)) {
124
+ this.clearInputValues();
125
+ this._value = null;
126
+ }
127
+ else {
128
+ this._value = input.slice(0, this.length);
129
+ if (!this.inputFieldValueChanged) {
130
+ this.fillInputs(input, 0, true);
131
+ }
132
+ }
133
+ if (this.inputAttributes) {
134
+ this.setInputAttributes();
135
+ }
136
+ else {
137
+ this.setDefaultAttributes();
138
+ }
139
+ }
140
+ /**
141
+ * The `size` property specifies the padding of the input fields.
142
+ *
143
+ * The possible values are:
144
+ * * `small`
145
+ * * `medium` (default)
146
+ * * `large`
147
+ * * `none`
148
+ */
149
+ set size(size) {
150
+ const newSize = size || DEFAULT_SIZE;
151
+ const elem = this.hostElement.nativeElement;
152
+ this.renderer.removeClass(elem, `k-otp-${SIZE_MAP[this._size]}`);
153
+ this.renderer.addClass(elem, `k-otp-${SIZE_MAP[newSize]}`);
154
+ this._size = newSize;
155
+ }
156
+ get size() {
157
+ return this._size;
158
+ }
159
+ /**
160
+ * The `rounded` property specifies the border radius of the OTP Input.
161
+ *
162
+ * The possible values are:
163
+ * * `small`
164
+ * * `medium` (default)
165
+ * * `large`
166
+ * * `full`
167
+ * * `none`
168
+ */
169
+ set rounded(rounded) {
170
+ this._rounded = rounded || DEFAULT_ROUNDED;
171
+ }
172
+ get rounded() {
173
+ return this._rounded;
174
+ }
175
+ /**
176
+ * The `fillMode` property specifies the background and border styles of the OTP Input.
177
+ *
178
+ * The possible values are:
179
+ * * `flat`
180
+ * * `solid` (default)
181
+ * * `outline`
182
+ * * `none`
183
+ */
184
+ set fillMode(fillMode) {
185
+ const newFillMode = fillMode || DEFAULT_FILL_MODE;
186
+ this.setGroupFillMode(newFillMode, this._fillMode);
187
+ this._fillMode = newFillMode;
188
+ }
189
+ get fillMode() {
190
+ return this._fillMode;
191
+ }
192
+ /**
193
+ * Sets the HTML attributes of the inner focusable input element. Attributes which are essential for certain component functionalities cannot be changed.
194
+ */
195
+ set inputAttributes(attributes) {
196
+ this._inputAttributes = attributes;
197
+ this.parsedAttributes = this.inputAttributes ?
198
+ { ...this.defaultAttributes, ...this.inputAttributes } :
199
+ this.inputAttributes;
200
+ this.setInputAttributes();
201
+ }
202
+ get inputAttributes() {
203
+ return this._inputAttributes;
204
+ }
205
+ /**
206
+ * Fires each time the value is changed by the user&mdash;
207
+ * When the value of the component is programmatically changed to `ngModel` or `formControl`
208
+ * through its API or form binding, the `valueChange` event is not triggered because it
209
+ * might cause a mix-up with the built-in `valueChange` mechanisms of the `ngModel` or `formControl` bindings.
210
+ */
211
+ valueChange = new EventEmitter();
212
+ /**
213
+ * Fires when the length of the trimmed value is equal to `length`.
214
+ */
215
+ inputFinish = new EventEmitter();
216
+ /**
217
+ * Fires each time the user focuses the OTP Input.
218
+ */
219
+ onFocus = new EventEmitter();
220
+ /**
221
+ * Fires each time the user blurs the OTP Input.
222
+ */
223
+ onBlur = new EventEmitter();
224
+ wrapperClass = true;
225
+ get invalidClass() {
226
+ return this.isControlInvalid;
227
+ }
228
+ direction;
229
+ role = 'group';
230
+ /**
231
+ * @hidden
232
+ */
233
+ inputFields;
234
+ /**
235
+ * @hidden
236
+ */
237
+ set inputGroups(elements) {
238
+ this._inputGroups = elements;
239
+ this.setGroupFillMode(this.fillMode);
240
+ }
241
+ get inputGroups() {
242
+ return this._inputGroups;
243
+ }
244
+ /**
245
+ * @hidden
246
+ */
247
+ groupLengthArray;
248
+ /**
249
+ * @hidden
250
+ */
251
+ inputsArray;
252
+ /**
253
+ * @hidden
254
+ */
255
+ inputsValues = [].constructor(DEFAULT_OTPINPUT_LENGTH);
256
+ /**
257
+ * @hidden
258
+ */
259
+ adjacentGroups;
260
+ _length = DEFAULT_OTPINPUT_LENGTH;
261
+ _groupLength;
262
+ _inputGroups;
263
+ separatorPositions = new Set();
264
+ _value = null;
265
+ _size = DEFAULT_SIZE;
266
+ _rounded = DEFAULT_ROUNDED;
267
+ _fillMode = DEFAULT_FILL_MODE;
268
+ _isFocused = false;
269
+ focusChangedProgrammatically = false;
270
+ inputFieldValueChanged = false;
271
+ focusedInput;
272
+ _inputAttributes;
273
+ parsedAttributes = {};
274
+ get defaultAttributes() {
275
+ return {
276
+ autocomplete: 'off'
277
+ };
278
+ }
279
+ subscriptions;
280
+ ngChange = (_) => { };
281
+ ngTouched = () => { };
282
+ constructor(hostElement, cdr, injector, renderer, localizationService, zone) {
283
+ this.hostElement = hostElement;
284
+ this.cdr = cdr;
285
+ this.injector = injector;
286
+ this.renderer = renderer;
287
+ this.localizationService = localizationService;
288
+ this.zone = zone;
289
+ this.direction = localizationService.rtl ? 'rtl' : 'ltr';
290
+ }
291
+ ngOnInit() {
292
+ this.inputsArray = Array.from({ length: this._length });
293
+ this.subscriptions = this.localizationService.changes.subscribe(({ rtl }) => {
294
+ this.direction = rtl ? 'rtl' : 'ltr';
295
+ });
296
+ this.zone.runOutsideAngular(() => {
297
+ this.subscriptions.add(this.renderer.listen(this.hostElement.nativeElement, 'paste', this.handlePaste.bind(this)));
298
+ this.subscriptions.add(this.renderer.listen(this.hostElement.nativeElement, 'keydown', this.handleKeydown.bind(this)));
299
+ });
300
+ }
301
+ ngAfterViewInit() {
302
+ this.subscriptions.add(this.inputFields.changes.subscribe(this.handleInputChanges.bind(this)));
303
+ this.handleInputChanges();
304
+ this.renderer.addClass(this.hostElement.nativeElement, `k-otp-${SIZE_MAP[this._size]}`);
305
+ this.setGroupFillMode(this.fillMode);
306
+ this.zone.onStable.pipe(take(1)).subscribe(() => {
307
+ this.fillInputs(this.value);
308
+ });
309
+ }
310
+ ngOnChanges(changes) {
311
+ if (changes.length) {
312
+ if (typeof this.groupLength === 'number') {
313
+ this.populateGroupArray(this.groupLength);
314
+ }
315
+ this.populateSeparatorPosititons();
316
+ }
317
+ if (changes.spacing) {
318
+ if (this.spacing === true) {
319
+ this.adjacentGroups = null;
320
+ }
321
+ else {
322
+ this.adjacentGroups = this.groupLengthArray ?? [this.length];
323
+ }
324
+ }
325
+ if (changes.type && this.type === 'number') {
326
+ if (isPresent(this.value) && !this.isValidNumber(this.value)) {
327
+ this.value = null;
328
+ }
329
+ }
330
+ }
331
+ ngOnDestroy() {
332
+ this.subscriptions.unsubscribe();
333
+ }
334
+ /**
335
+ * @hidden
336
+ */
337
+ get formControl() {
338
+ const ngControl = this.injector.get(NgControl, null);
339
+ return ngControl?.control || null;
340
+ }
341
+ /**
342
+ * @hidden
343
+ */
344
+ writeValue(value) {
345
+ this.value = value;
346
+ }
347
+ /**
348
+ * @hidden
349
+ */
350
+ registerOnChange(fn) {
351
+ this.ngChange = fn;
352
+ }
353
+ /**
354
+ * @hidden
355
+ */
356
+ registerOnTouched(fn) {
357
+ this.ngTouched = fn;
358
+ }
359
+ /**
360
+ * @hidden
361
+ */
362
+ setDisabledState(isDisabled) {
363
+ this.cdr.markForCheck();
364
+ this.disabled = isDisabled;
365
+ }
366
+ /**
367
+ * @hidden
368
+ */
369
+ get isControlInvalid() {
370
+ return this.formControl?.touched && this.formControl.invalid;
371
+ }
372
+ /**
373
+ * @hidden
374
+ */
375
+ get isFocused() {
376
+ return this._isFocused;
377
+ }
378
+ /**
379
+ * @hidden
380
+ */
381
+ set isFocused(value) {
382
+ if (this._isFocused !== value && this.hostElement) {
383
+ this._isFocused = value;
384
+ }
385
+ }
386
+ /**
387
+ * @hidden
388
+ */
389
+ get hasGroups() {
390
+ if (!this.spacing && isPresent(this.groupLength)) {
391
+ return true;
392
+ }
393
+ }
394
+ /**
395
+ * @hidden
396
+ */
397
+ showGroupSeparator(index) {
398
+ return this.separator && this.groupLengthArray && index < this.groupLengthArray.length - 1;
399
+ }
400
+ /**
401
+ * @hidden
402
+ */
403
+ showSeparator(index) {
404
+ return this.separator && this.groupLength ? this.separatorPositions.has(index) : false;
405
+ }
406
+ /**
407
+ * @hidden
408
+ */
409
+ handleValueChange(index, groupIndex) {
410
+ this.inputFieldValueChanged = true;
411
+ if (groupIndex) {
412
+ index = this.getIndexByGroup(groupIndex, index);
413
+ }
414
+ let newValue = '';
415
+ this.inputFields.forEach((input) => newValue = newValue.concat(input.value?.toString() || " "));
416
+ if (!areSame(this.value, newValue)) {
417
+ this.value = newValue;
418
+ this.ngChange(newValue);
419
+ this.valueChange.emit(newValue);
420
+ }
421
+ this.inputFieldValueChanged = false;
422
+ if (this.value && this.value.trim().length === this.length) {
423
+ this.inputFinish.emit(this.value);
424
+ }
425
+ if (isPresent(index) && isPresent(this.inputFields?.get(index).value)) {
426
+ this.focusNext();
427
+ }
428
+ }
429
+ /**
430
+ * @hidden
431
+ */
432
+ handleInputFocus(index, groupIndex) {
433
+ if (this.focusChangedProgrammatically) {
434
+ return;
435
+ }
436
+ if (groupIndex) {
437
+ index = this.getIndexByGroup(groupIndex, index);
438
+ }
439
+ this.focusedInput = index;
440
+ }
441
+ /**
442
+ * @hidden
443
+ */
444
+ handleInput(event, index, groupIndex) {
445
+ if (this.type === 'number' && !this.isValidNumber(event?.data)) {
446
+ const inputIndex = groupIndex ? this.getIndexByGroup(groupIndex, index) : index;
447
+ this.inputFields.get(inputIndex).value = null;
448
+ this.showInvalidInput(inputIndex);
449
+ return;
450
+ }
451
+ this.handleValueChange(index, groupIndex);
452
+ }
453
+ /**
454
+ * @hidden
455
+ */
456
+ fillInputs(text, start = 0, replaceLast = false) {
457
+ if (!isPresent(text)) {
458
+ return;
459
+ }
460
+ let charCounter = 0;
461
+ this.inputFields?.forEach((otpInput, i) => {
462
+ if (i < start) {
463
+ return;
464
+ }
465
+ if (charCounter < text.length) {
466
+ if (text[charCounter] === ' ') {
467
+ otpInput.value = null;
468
+ }
469
+ else {
470
+ otpInput.value = text[charCounter];
471
+ }
472
+ charCounter++;
473
+ }
474
+ else if (replaceLast) {
475
+ otpInput.value = null;
476
+ }
477
+ });
478
+ }
479
+ /**
480
+ * Focuses the OTP Input.
481
+ */
482
+ focus(index) {
483
+ if (!this.inputFields || index < 0 || index >= this.length) {
484
+ return;
485
+ }
486
+ this.focusChangedProgrammatically = true;
487
+ this.isFocused = true;
488
+ this.inputFields.get(index || 0).focus();
489
+ this.focusedInput = index || 0;
490
+ this.focusChangedProgrammatically = false;
491
+ }
492
+ /**
493
+ * Blurs the OTP Input.
494
+ */
495
+ blur() {
496
+ this.focusChangedProgrammatically = true;
497
+ const isFocusedElement = this.hostElement.nativeElement.querySelector(':focus');
498
+ if (isFocusedElement) {
499
+ isFocusedElement.blur();
500
+ }
501
+ this.isFocused = false;
502
+ this.focusChangedProgrammatically = false;
503
+ }
504
+ /**
505
+ * @hidden
506
+ */
507
+ handleFocus() {
508
+ this.zone.run(() => {
509
+ if (!this.focusChangedProgrammatically && hasObservers(this.onFocus)) {
510
+ this.onFocus.emit();
511
+ }
512
+ this.isFocused = true;
513
+ });
514
+ }
515
+ /**
516
+ * @hidden
517
+ */
518
+ handleBlur() {
519
+ this.zone.run(() => {
520
+ if (!this.focusChangedProgrammatically) {
521
+ this.ngTouched();
522
+ this.onBlur.emit();
523
+ }
524
+ this.isFocused = false;
525
+ });
526
+ }
527
+ getIndexByGroup(groupIndex, itemIndex) {
528
+ return this.groupLengthArray.slice(0, groupIndex).reduce((sum, current) => sum + current, 0) + itemIndex;
529
+ }
530
+ focusNext() {
531
+ if (!this.inputFields || this.focusedInput === this.length - 1) {
532
+ return;
533
+ }
534
+ this.focusChangedProgrammatically = true;
535
+ this.isFocused = true;
536
+ this.inputFields.get(this.focusedInput).blur();
537
+ this.inputFields.get(this.focusedInput + 1).focus();
538
+ this.focusedInput++;
539
+ this.focusChangedProgrammatically = false;
540
+ }
541
+ focusPrevious() {
542
+ if (!this.inputFields || this.focusedInput === 0) {
543
+ return;
544
+ }
545
+ this.focusChangedProgrammatically = true;
546
+ this.isFocused = true;
547
+ this.inputFields.get(this.focusedInput).blur();
548
+ this.inputFields.get(this.focusedInput - 1).focus();
549
+ this.focusedInput--;
550
+ this.focusChangedProgrammatically = false;
551
+ }
552
+ handlePaste(event) {
553
+ event.preventDefault();
554
+ const text = event.clipboardData.getData('text').trim();
555
+ if (text === '') {
556
+ return;
557
+ }
558
+ if (this.type === 'number' && !this.isValidNumber(text)) {
559
+ this.showInvalidInput(this.focusedInput);
560
+ return;
561
+ }
562
+ this.inputFieldValueChanged = true;
563
+ this.fillInputs(text, this.focusedInput);
564
+ this.handleValueChange();
565
+ this.inputFieldValueChanged = false;
566
+ const focusedInput = this.focusedInput + text.length < this.inputFields?.length ?
567
+ this.focusedInput + text.length :
568
+ this.inputFields.length - 1;
569
+ this.inputFields.get(this.focusedInput).blur();
570
+ this.focusedInput = focusedInput;
571
+ this.inputFields.get(this.focusedInput).focus();
572
+ }
573
+ handleKeydown(event) {
574
+ if (event.keyCode === Keys.ArrowRight) {
575
+ event.preventDefault();
576
+ this.direction === 'ltr' ? this.focusNext() : this.focusPrevious();
577
+ }
578
+ if (event.keyCode === Keys.ArrowLeft) {
579
+ event.preventDefault();
580
+ this.direction === 'ltr' ? this.focusPrevious() : this.focusNext();
581
+ }
582
+ if (event.keyCode === Keys.Backspace) {
583
+ event.preventDefault();
584
+ this.inputFields.get(this.focusedInput).value = null;
585
+ this.handleValueChange();
586
+ this.focusPrevious();
587
+ }
588
+ if (event.keyCode === Keys.Delete) {
589
+ event.preventDefault();
590
+ this.inputFields.get(this.focusedInput).value = null;
591
+ this.handleValueChange();
592
+ }
593
+ }
594
+ isValidGroupArray(groups) {
595
+ if (!isPresent(groups)) {
596
+ return;
597
+ }
598
+ const sum = groups.reduce((sum, current) => sum + current, 0);
599
+ return sum === this.length;
600
+ }
601
+ populateGroupArray(length) {
602
+ const groupsCount = Math.floor(this.length / length);
603
+ const remainder = this.length % length;
604
+ const result = Array(groupsCount).fill(length);
605
+ if (remainder > 0) {
606
+ result.push(remainder);
607
+ }
608
+ this.groupLengthArray = [...result];
609
+ // groups with spacing shouldn't be wrapped in `k-input-group`
610
+ if (!this.spacing) {
611
+ this.adjacentGroups = [...this.groupLengthArray];
612
+ }
613
+ }
614
+ populateSeparatorPosititons() {
615
+ let itemIndex = 0;
616
+ this.separatorPositions.clear();
617
+ if (!isPresent(this.groupLengthArray)) {
618
+ return;
619
+ }
620
+ for (let i = 0; i < this.groupLengthArray.length - 1; i++) {
621
+ itemIndex += this.groupLengthArray[i];
622
+ this.separatorPositions.add(itemIndex - 1);
623
+ }
624
+ }
625
+ clearGroups() {
626
+ this.groupLengthArray = null;
627
+ if (!this.spacing) {
628
+ this.adjacentGroups = [this.length];
629
+ }
630
+ else {
631
+ this.adjacentGroups = null;
632
+ }
633
+ this.separatorPositions.clear();
634
+ }
635
+ clearInputValues() {
636
+ this.inputFields?.forEach((input) => input.value = null);
637
+ }
638
+ handleInputChanges() {
639
+ this.zone.onStable.pipe(take(1)).subscribe(() => {
640
+ this.fillInputs(this.value?.trim());
641
+ if (this.inputAttributes) {
642
+ this.setInputAttributes();
643
+ }
644
+ else {
645
+ this.setDefaultAttributes();
646
+ }
647
+ this.cdr.detectChanges();
648
+ });
649
+ }
650
+ setGroupFillMode(fillMode, previousFillMode) {
651
+ this.inputGroups?.forEach(element => {
652
+ if (previousFillMode !== 'none') {
653
+ this.renderer.removeClass(element.nativeElement, `k-input-group-${previousFillMode}`);
654
+ }
655
+ if (fillMode !== 'none') {
656
+ this.renderer.addClass(element.nativeElement, `k-input-group-${fillMode}`);
657
+ }
658
+ });
659
+ }
660
+ setInputAttributes() {
661
+ this.inputFields?.forEach((input, index) => {
662
+ if (!this.parsedAttributes || !this.parsedAttributes?.['aria-label']) {
663
+ input.inputAttributes = { ...this.parsedAttributes, 'aria-label': this.ariaLabel(index) };
664
+ }
665
+ else {
666
+ input.inputAttributes = this.parsedAttributes;
667
+ }
668
+ });
669
+ }
670
+ setDefaultAttributes() {
671
+ this.inputFields?.forEach((input, index) => {
672
+ input.inputAttributes = {
673
+ autocomplete: 'off',
674
+ 'aria-label': this.ariaLabel(index)
675
+ };
676
+ });
677
+ }
678
+ ariaLabel(index) {
679
+ const localizationMsg = this.localizationService.get('ariaLabel') || '';
680
+ return replaceMessagePlaceholder(replaceMessagePlaceholder(replaceMessagePlaceholder(localizationMsg, 'currentInput', (index + 1).toString()), 'totalInputs', this.length.toString()), 'value', this.value);
681
+ }
682
+ isValidNumber(value) {
683
+ if (!isPresent(value)) {
684
+ return;
685
+ }
686
+ const trimmedValue = value.trim();
687
+ return trimmedValue !== '' &&
688
+ trimmedValue !== 'Infinity' &&
689
+ trimmedValue !== '-Infinity' &&
690
+ !isNaN(Number(trimmedValue));
691
+ }
692
+ showInvalidInput(index) {
693
+ const inputElement = this.inputFields.get(index).hostElement.nativeElement;
694
+ this.renderer.addClass(inputElement, 'k-invalid');
695
+ this.zone.runOutsideAngular(() => {
696
+ setTimeout(() => {
697
+ if (!this.isControlInvalid && inputElement) {
698
+ this.renderer.removeClass(inputElement, 'k-invalid');
699
+ }
700
+ }, 300);
701
+ });
702
+ }
703
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: OTPInputComponent, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }, { token: i0.Injector }, { token: i0.Renderer2 }, { token: i1.LocalizationService }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
704
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: OTPInputComponent, isStandalone: true, selector: "kendo-otpinput", inputs: { length: "length", type: "type", spacing: "spacing", separator: "separator", disabled: "disabled", readonly: "readonly", placeholder: "placeholder", groupLength: "groupLength", value: "value", size: "size", rounded: "rounded", fillMode: "fillMode", inputAttributes: "inputAttributes" }, outputs: { valueChange: "valueChange", inputFinish: "inputFinish", onFocus: "focus", onBlur: "blur" }, host: { properties: { "class.k-otp": "this.wrapperClass", "class.k-invalid": "this.invalidClass", "attr.dir": "this.direction", "attr.role": "this.role" } }, providers: [
705
+ LocalizationService,
706
+ { provide: L10N_PREFIX, useValue: 'kendo.otpinput' },
707
+ {
708
+ provide: NG_VALUE_ACCESSOR,
709
+ useExisting: forwardRef(() => OTPInputComponent),
710
+ multi: true
711
+ },
712
+ { provide: KendoInput, useExisting: forwardRef(() => OTPInputComponent) }
713
+ ], viewQueries: [{ propertyName: "inputFields", predicate: TextBoxComponent, descendants: true }, { propertyName: "inputGroups", predicate: ["inputGroup"], descendants: true }], exportAs: ["kendoOTPInput"], usesOnChanges: true, ngImport: i0, template: `
714
+ <ng-container kendoOTPInputLocalizedMessages
715
+ i18n-ariaLabel="kendo.otpinput.ariaLabel|The value of the aria-label attribute of the input fields."
716
+ ariaLabel="{{ 'Input {currentInput} of {totalInputs}, current value {value}' }}"
717
+ ></ng-container>
718
+ <ng-container
719
+ kendoInputSharedEvents
720
+ [hostElement]="hostElement"
721
+ [(isFocused)]="isFocused"
722
+ (handleBlur)="handleBlur()"
723
+ (onFocus)="handleFocus()"
724
+ >
725
+ <ng-container *ngIf="spacing; else groups">
726
+ <ng-container *ngFor="let input of inputsArray; let i = index">
727
+ <kendo-textbox
728
+ class="k-otp-input"
729
+ [class.k-invalid]="isControlInvalid"
730
+ [selectOnFocus]="true"
731
+ [maxlength]="1"
732
+ [type]="type !== 'number' ? type : null"
733
+ [placeholder]="placeholder"
734
+ [size]="size"
735
+ [rounded]="rounded"
736
+ [fillMode]="fillMode"
737
+ [disabled]="disabled"
738
+ [readonly]="readonly"
739
+ (focus)="handleInputFocus(i)"
740
+ (input)="handleInput($event, i)"
741
+ ></kendo-textbox>
742
+ <kendo-otpinput-separator *ngIf="showSeparator(i)" [separator]="separator"></kendo-otpinput-separator>
743
+ </ng-container>
744
+ </ng-container>
745
+ <ng-template #groups>
746
+ <ng-container *ngFor="let group of adjacentGroups; let i = index">
747
+ <div #inputGroup class="k-input-group">
748
+ <kendo-textbox
749
+ *ngFor="let input of [].constructor(group); let j = index"
750
+ class="k-otp-input"
751
+ [class.k-invalid]="isControlInvalid"
752
+ [selectOnFocus]="true"
753
+ [maxlength]="1"
754
+ [type]="type !== 'number' ? type : null"
755
+ [placeholder]="placeholder"
756
+ [size]="size"
757
+ [rounded]="rounded"
758
+ [fillMode]="fillMode"
759
+ [disabled]="disabled"
760
+ [readonly]="readonly"
761
+ (focus)="handleInputFocus(j, i)"
762
+ (input)="handleInput($event, j, i)"
763
+ ></kendo-textbox>
764
+ </div>
765
+ <kendo-otpinput-separator *ngIf="showGroupSeparator(i)" [separator]="separator"></kendo-otpinput-separator>
766
+ </ng-container>
767
+ </ng-template>
768
+ <ng-container>
769
+ `, isInline: true, dependencies: [{ kind: "directive", type: SharedInputEventsDirective, selector: "[kendoInputSharedEvents]", inputs: ["hostElement", "clearButtonClicked", "isFocused"], outputs: ["isFocusedChange", "onFocus", "handleBlur"] }, { kind: "component", type: TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: OTPInputSeparatorComponent, selector: "kendo-otpinput-separator", inputs: ["separator"], exportAs: ["kendoOTPInputSeparator"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: LocalizedOTPInputMessagesDirective, selector: "[kendoOTPInputLocalizedMessages]" }] });
770
+ }
771
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: OTPInputComponent, decorators: [{
772
+ type: Component,
773
+ args: [{
774
+ exportAs: 'kendoOTPInput',
775
+ providers: [
776
+ LocalizationService,
777
+ { provide: L10N_PREFIX, useValue: 'kendo.otpinput' },
778
+ {
779
+ provide: NG_VALUE_ACCESSOR,
780
+ useExisting: forwardRef(() => OTPInputComponent),
781
+ multi: true
782
+ },
783
+ { provide: KendoInput, useExisting: forwardRef(() => OTPInputComponent) }
784
+ ],
785
+ selector: 'kendo-otpinput',
786
+ template: `
787
+ <ng-container kendoOTPInputLocalizedMessages
788
+ i18n-ariaLabel="kendo.otpinput.ariaLabel|The value of the aria-label attribute of the input fields."
789
+ ariaLabel="{{ 'Input {currentInput} of {totalInputs}, current value {value}' }}"
790
+ ></ng-container>
791
+ <ng-container
792
+ kendoInputSharedEvents
793
+ [hostElement]="hostElement"
794
+ [(isFocused)]="isFocused"
795
+ (handleBlur)="handleBlur()"
796
+ (onFocus)="handleFocus()"
797
+ >
798
+ <ng-container *ngIf="spacing; else groups">
799
+ <ng-container *ngFor="let input of inputsArray; let i = index">
800
+ <kendo-textbox
801
+ class="k-otp-input"
802
+ [class.k-invalid]="isControlInvalid"
803
+ [selectOnFocus]="true"
804
+ [maxlength]="1"
805
+ [type]="type !== 'number' ? type : null"
806
+ [placeholder]="placeholder"
807
+ [size]="size"
808
+ [rounded]="rounded"
809
+ [fillMode]="fillMode"
810
+ [disabled]="disabled"
811
+ [readonly]="readonly"
812
+ (focus)="handleInputFocus(i)"
813
+ (input)="handleInput($event, i)"
814
+ ></kendo-textbox>
815
+ <kendo-otpinput-separator *ngIf="showSeparator(i)" [separator]="separator"></kendo-otpinput-separator>
816
+ </ng-container>
817
+ </ng-container>
818
+ <ng-template #groups>
819
+ <ng-container *ngFor="let group of adjacentGroups; let i = index">
820
+ <div #inputGroup class="k-input-group">
821
+ <kendo-textbox
822
+ *ngFor="let input of [].constructor(group); let j = index"
823
+ class="k-otp-input"
824
+ [class.k-invalid]="isControlInvalid"
825
+ [selectOnFocus]="true"
826
+ [maxlength]="1"
827
+ [type]="type !== 'number' ? type : null"
828
+ [placeholder]="placeholder"
829
+ [size]="size"
830
+ [rounded]="rounded"
831
+ [fillMode]="fillMode"
832
+ [disabled]="disabled"
833
+ [readonly]="readonly"
834
+ (focus)="handleInputFocus(j, i)"
835
+ (input)="handleInput($event, j, i)"
836
+ ></kendo-textbox>
837
+ </div>
838
+ <kendo-otpinput-separator *ngIf="showGroupSeparator(i)" [separator]="separator"></kendo-otpinput-separator>
839
+ </ng-container>
840
+ </ng-template>
841
+ <ng-container>
842
+ `,
843
+ standalone: true,
844
+ imports: [SharedInputEventsDirective, TextBoxComponent, OTPInputSeparatorComponent, NgFor, NgIf, LocalizedOTPInputMessagesDirective]
845
+ }]
846
+ }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }, { type: i0.Injector }, { type: i0.Renderer2 }, { type: i1.LocalizationService }, { type: i0.NgZone }]; }, propDecorators: { length: [{
847
+ type: Input
848
+ }], type: [{
849
+ type: Input
850
+ }], spacing: [{
851
+ type: Input
852
+ }], separator: [{
853
+ type: Input
854
+ }], disabled: [{
855
+ type: Input
856
+ }], readonly: [{
857
+ type: Input
858
+ }], placeholder: [{
859
+ type: Input
860
+ }], groupLength: [{
861
+ type: Input
862
+ }], value: [{
863
+ type: Input
864
+ }], size: [{
865
+ type: Input
866
+ }], rounded: [{
867
+ type: Input
868
+ }], fillMode: [{
869
+ type: Input
870
+ }], inputAttributes: [{
871
+ type: Input
872
+ }], valueChange: [{
873
+ type: Output
874
+ }], inputFinish: [{
875
+ type: Output
876
+ }], onFocus: [{
877
+ type: Output,
878
+ args: ['focus']
879
+ }], onBlur: [{
880
+ type: Output,
881
+ args: ['blur']
882
+ }], wrapperClass: [{
883
+ type: HostBinding,
884
+ args: ['class.k-otp']
885
+ }], invalidClass: [{
886
+ type: HostBinding,
887
+ args: ['class.k-invalid']
888
+ }], direction: [{
889
+ type: HostBinding,
890
+ args: ['attr.dir']
891
+ }], role: [{
892
+ type: HostBinding,
893
+ args: ['attr.role']
894
+ }], inputFields: [{
895
+ type: ViewChildren,
896
+ args: [TextBoxComponent]
897
+ }], inputGroups: [{
898
+ type: ViewChildren,
899
+ args: ['inputGroup']
900
+ }] } });