@progress/kendo-angular-inputs 18.1.0-develop.2 → 18.1.0-develop.20

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 (51) hide show
  1. package/colorpicker/adaptiveness/adaptive-close-button.component.d.ts +20 -0
  2. package/colorpicker/adaptiveness/adaptive-renderer.component.d.ts +38 -0
  3. package/colorpicker/color-gradient-text-label.directive.d.ts +19 -0
  4. package/colorpicker/color-gradient.component.d.ts +19 -1
  5. package/colorpicker/color-input.component.d.ts +15 -3
  6. package/colorpicker/color-palette.component.d.ts +16 -6
  7. package/colorpicker/colorpicker.component.d.ts +55 -8
  8. package/colorpicker/flatcolorpicker-actions.component.d.ts +3 -1
  9. package/colorpicker/flatcolorpicker-header.component.d.ts +3 -1
  10. package/colorpicker/flatcolorpicker.component.d.ts +19 -1
  11. package/colorpicker/localization/messages.d.ts +9 -1
  12. package/colorpicker/models/adaptive-mode.d.ts +23 -0
  13. package/common/utils.d.ts +4 -0
  14. package/directives.d.ts +7 -1
  15. package/esm2022/colorpicker/adaptiveness/adaptive-close-button.component.mjs +62 -0
  16. package/esm2022/colorpicker/adaptiveness/adaptive-renderer.component.mjs +205 -0
  17. package/esm2022/colorpicker/color-gradient-text-label.directive.mjs +34 -0
  18. package/esm2022/colorpicker/color-gradient.component.mjs +75 -17
  19. package/esm2022/colorpicker/color-input.component.mjs +56 -23
  20. package/esm2022/colorpicker/color-palette.component.mjs +45 -15
  21. package/esm2022/colorpicker/colorpicker.component.mjs +182 -41
  22. package/esm2022/colorpicker/flatcolorpicker-actions.component.mjs +22 -7
  23. package/esm2022/colorpicker/flatcolorpicker-header.component.mjs +8 -3
  24. package/esm2022/colorpicker/flatcolorpicker.component.mjs +72 -17
  25. package/esm2022/colorpicker/localization/messages.mjs +13 -1
  26. package/esm2022/colorpicker/models/adaptive-mode.mjs +27 -0
  27. package/esm2022/colorpicker/services/flatcolorpicker.service.mjs +3 -3
  28. package/esm2022/colorpicker.module.mjs +3 -2
  29. package/esm2022/common/utils.mjs +4 -0
  30. package/esm2022/directives.mjs +10 -0
  31. package/esm2022/index.mjs +3 -0
  32. package/esm2022/inputs.module.mjs +6 -3
  33. package/esm2022/otpinput/localization/custom-messages.component.mjs +43 -0
  34. package/esm2022/otpinput/localization/localized-textbox-messages.directive.mjs +39 -0
  35. package/esm2022/otpinput/localization/messages.mjs +34 -0
  36. package/esm2022/otpinput/models/otpinput-type.mjs +5 -0
  37. package/esm2022/otpinput/models/separator-icon.mjs +5 -0
  38. package/esm2022/otpinput/otpinput-separator.component.mjs +83 -0
  39. package/esm2022/otpinput/otpinput.component.mjs +930 -0
  40. package/esm2022/package-metadata.mjs +2 -2
  41. package/fesm2022/progress-kendo-angular-inputs.mjs +2694 -956
  42. package/index.d.ts +4 -0
  43. package/inputs.module.d.ts +3 -1
  44. package/otpinput/localization/custom-messages.component.d.ts +17 -0
  45. package/otpinput/localization/localized-textbox-messages.directive.d.ts +16 -0
  46. package/otpinput/localization/messages.d.ts +24 -0
  47. package/otpinput/models/otpinput-type.d.ts +10 -0
  48. package/otpinput/models/separator-icon.d.ts +12 -0
  49. package/otpinput/otpinput-separator.component.d.ts +25 -0
  50. package/otpinput/otpinput.component.d.ts +284 -0
  51. package/package.json +12 -10
@@ -0,0 +1,930 @@
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
+ * Specifies the 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.populateSeparatorPositions();
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
+ const isInvalidInput = this.type === 'number' && isPresent(input) && !this.containsDigitsOrSpaces(input);
120
+ if (this._value === input || isInvalidInput) {
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 each time the user focuses the OTP Input.
214
+ */
215
+ onFocus = new EventEmitter();
216
+ /**
217
+ * Fires each time the user blurs the OTP Input.
218
+ */
219
+ onBlur = new EventEmitter();
220
+ wrapperClass = true;
221
+ get invalidClass() {
222
+ return this.isControlInvalid;
223
+ }
224
+ direction;
225
+ role = 'group';
226
+ /**
227
+ * @hidden
228
+ */
229
+ inputFields;
230
+ /**
231
+ * @hidden
232
+ */
233
+ set inputGroups(elements) {
234
+ this._inputGroups = elements;
235
+ this.setGroupFillMode(this.fillMode);
236
+ }
237
+ get inputGroups() {
238
+ return this._inputGroups;
239
+ }
240
+ /**
241
+ * @hidden
242
+ */
243
+ groupLengthArray;
244
+ /**
245
+ * @hidden
246
+ */
247
+ inputsArray;
248
+ /**
249
+ * @hidden
250
+ */
251
+ inputsValues = [].constructor(DEFAULT_OTPINPUT_LENGTH);
252
+ /**
253
+ * @hidden
254
+ */
255
+ adjacentGroups;
256
+ _length = DEFAULT_OTPINPUT_LENGTH;
257
+ _groupLength;
258
+ _inputGroups;
259
+ separatorPositions = new Set();
260
+ _value = null;
261
+ _size = DEFAULT_SIZE;
262
+ _rounded = DEFAULT_ROUNDED;
263
+ _fillMode = DEFAULT_FILL_MODE;
264
+ _isFocused = false;
265
+ focusChangedProgrammatically = false;
266
+ inputFieldValueChanged = false;
267
+ focusedInput;
268
+ _inputAttributes;
269
+ parsedAttributes = {};
270
+ get defaultAttributes() {
271
+ return {
272
+ autocomplete: 'off'
273
+ };
274
+ }
275
+ subscriptions;
276
+ ngChange = (_) => { };
277
+ ngTouched = () => { };
278
+ constructor(hostElement, cdr, injector, renderer, localizationService, zone) {
279
+ this.hostElement = hostElement;
280
+ this.cdr = cdr;
281
+ this.injector = injector;
282
+ this.renderer = renderer;
283
+ this.localizationService = localizationService;
284
+ this.zone = zone;
285
+ this.direction = localizationService.rtl ? 'rtl' : 'ltr';
286
+ }
287
+ ngOnInit() {
288
+ this.inputsArray = Array.from({ length: this._length });
289
+ this.subscriptions = this.localizationService.changes.subscribe(({ rtl }) => {
290
+ this.direction = rtl ? 'rtl' : 'ltr';
291
+ });
292
+ this.zone.runOutsideAngular(() => {
293
+ this.subscriptions.add(this.renderer.listen(this.hostElement.nativeElement, 'paste', this.handlePaste.bind(this)));
294
+ this.subscriptions.add(this.renderer.listen(this.hostElement.nativeElement, 'keydown', this.handleKeydown.bind(this)));
295
+ });
296
+ }
297
+ ngAfterViewInit() {
298
+ this.subscriptions.add(this.inputFields.changes.subscribe(this.handleInputChanges.bind(this)));
299
+ this.handleInputChanges();
300
+ this.renderer.addClass(this.hostElement.nativeElement, `k-otp-${SIZE_MAP[this._size]}`);
301
+ this.setGroupFillMode(this.fillMode);
302
+ this.zone.onStable.pipe(take(1)).subscribe(() => {
303
+ this.fillInputs(this.value);
304
+ });
305
+ }
306
+ ngOnChanges(changes) {
307
+ if (changes.length) {
308
+ if (typeof this.groupLength === 'number') {
309
+ this.populateGroupArray(this.groupLength);
310
+ }
311
+ this.populateSeparatorPositions();
312
+ }
313
+ if (changes.spacing) {
314
+ if (this.spacing === true) {
315
+ this.adjacentGroups = null;
316
+ }
317
+ else {
318
+ this.adjacentGroups = this.groupLengthArray ?? [this.length];
319
+ }
320
+ }
321
+ if (changes.type && this.type === 'number') {
322
+ if (isPresent(this.value) && !this.containsDigitsOrSpaces(this.value)) {
323
+ this.value = null;
324
+ this.zone.runOutsideAngular(() => setTimeout(() => this.zone.run(() => {
325
+ this.ngChange(null);
326
+ this.cdr.markForCheck();
327
+ })));
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.groupLengthArray && index < this.groupLengthArray.length - 1;
399
+ }
400
+ /**
401
+ * @hidden
402
+ */
403
+ showSeparator(index) {
404
+ return 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.zone.run(() => {
418
+ this.value = newValue;
419
+ this.ngChange(newValue);
420
+ this.valueChange.emit(newValue);
421
+ this.cdr.markForCheck();
422
+ });
423
+ }
424
+ this.inputFieldValueChanged = false;
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
+ const textbox = this.inputFields.get(inputIndex);
448
+ if (this.value && this.isValidNumber(this.value[inputIndex])) {
449
+ textbox.value = this.value[inputIndex];
450
+ }
451
+ else {
452
+ textbox.value = null;
453
+ }
454
+ this.showInvalidInput(inputIndex);
455
+ return;
456
+ }
457
+ this.handleValueChange(index, groupIndex);
458
+ }
459
+ /**
460
+ * @hidden
461
+ */
462
+ fillInputs(text, start = 0, replaceLast = false) {
463
+ if (!isPresent(text)) {
464
+ return;
465
+ }
466
+ let charCounter = 0;
467
+ this.inputFields?.forEach((otpInput, i) => {
468
+ if (i < start) {
469
+ return;
470
+ }
471
+ if (charCounter < text.length) {
472
+ if (text[charCounter] === ' ') {
473
+ otpInput.value = null;
474
+ }
475
+ else {
476
+ otpInput.value = text[charCounter];
477
+ }
478
+ charCounter++;
479
+ }
480
+ else if (replaceLast) {
481
+ otpInput.value = null;
482
+ }
483
+ });
484
+ }
485
+ /**
486
+ * Focuses the OTP Input.
487
+ */
488
+ focus(index) {
489
+ if (!this.inputFields || index < 0 || index >= this.length) {
490
+ return;
491
+ }
492
+ this.focusChangedProgrammatically = true;
493
+ this.isFocused = true;
494
+ this.inputFields.get(index || 0).focus();
495
+ this.focusedInput = index || 0;
496
+ this.focusChangedProgrammatically = false;
497
+ }
498
+ /**
499
+ * Blurs the OTP Input.
500
+ */
501
+ blur() {
502
+ this.focusChangedProgrammatically = true;
503
+ const isFocusedElement = this.hostElement.nativeElement.querySelector(':focus');
504
+ if (isFocusedElement) {
505
+ isFocusedElement.blur();
506
+ }
507
+ this.isFocused = false;
508
+ this.focusChangedProgrammatically = false;
509
+ }
510
+ /**
511
+ * @hidden
512
+ */
513
+ handleFocus() {
514
+ this.zone.run(() => {
515
+ if (!this.focusChangedProgrammatically && hasObservers(this.onFocus)) {
516
+ this.onFocus.emit();
517
+ }
518
+ this.isFocused = true;
519
+ });
520
+ }
521
+ /**
522
+ * @hidden
523
+ */
524
+ handleBlur() {
525
+ this.zone.run(() => {
526
+ if (!this.focusChangedProgrammatically) {
527
+ this.ngTouched();
528
+ this.onBlur.emit();
529
+ }
530
+ this.isFocused = false;
531
+ });
532
+ }
533
+ getIndexByGroup(groupIndex, itemIndex) {
534
+ return this.groupLengthArray.slice(0, groupIndex).reduce((sum, current) => sum + current, 0) + itemIndex;
535
+ }
536
+ focusNext() {
537
+ if (!this.inputFields || this.focusedInput === this.length - 1) {
538
+ return;
539
+ }
540
+ this.focusChangedProgrammatically = true;
541
+ this.isFocused = true;
542
+ this.inputFields.get(this.focusedInput).blur();
543
+ this.inputFields.get(this.focusedInput + 1).focus();
544
+ this.focusedInput++;
545
+ this.focusChangedProgrammatically = false;
546
+ }
547
+ focusPrevious() {
548
+ if (!this.inputFields || this.focusedInput === 0) {
549
+ return;
550
+ }
551
+ this.focusChangedProgrammatically = true;
552
+ this.isFocused = true;
553
+ this.inputFields.get(this.focusedInput).blur();
554
+ this.inputFields.get(this.focusedInput - 1).focus();
555
+ this.focusedInput--;
556
+ this.focusChangedProgrammatically = false;
557
+ }
558
+ handlePaste(event) {
559
+ event.preventDefault();
560
+ const text = event.clipboardData.getData('text').trim();
561
+ if (text === '') {
562
+ return;
563
+ }
564
+ if (this.type === 'number' && !this.isValidNumber(text)) {
565
+ this.showInvalidInput(this.focusedInput);
566
+ return;
567
+ }
568
+ this.inputFieldValueChanged = true;
569
+ this.fillInputs(text, this.focusedInput);
570
+ this.handleValueChange();
571
+ this.inputFieldValueChanged = false;
572
+ const focusedInput = this.focusedInput + text.length < this.inputFields?.length ?
573
+ this.focusedInput + text.length :
574
+ this.inputFields.length - 1;
575
+ this.inputFields.get(this.focusedInput).blur();
576
+ this.focusedInput = focusedInput;
577
+ this.inputFields.get(this.focusedInput).focus();
578
+ }
579
+ handleKeydown(event) {
580
+ if (this.readonly) {
581
+ const isCopyCommand = (event.ctrlKey || event.metaKey) && event.keyCode === Keys.KeyC;
582
+ if (!(event.keyCode === Keys.Tab || isCopyCommand)) {
583
+ event.preventDefault();
584
+ return;
585
+ }
586
+ }
587
+ switch (event.keyCode) {
588
+ case Keys.ArrowRight:
589
+ event.preventDefault();
590
+ this.direction === 'ltr' ? this.focusNext() : this.focusPrevious();
591
+ break;
592
+ case Keys.ArrowLeft:
593
+ event.preventDefault();
594
+ this.direction === 'ltr' ? this.focusPrevious() : this.focusNext();
595
+ break;
596
+ case Keys.Backspace:
597
+ event.preventDefault();
598
+ this.inputFields.get(this.focusedInput).value = null;
599
+ this.handleValueChange();
600
+ this.focusPrevious();
601
+ break;
602
+ case Keys.Delete:
603
+ event.preventDefault();
604
+ this.inputFields.get(this.focusedInput).value = null;
605
+ this.handleValueChange();
606
+ break;
607
+ default:
608
+ break;
609
+ }
610
+ }
611
+ isValidGroupArray(groups) {
612
+ if (!isPresent(groups)) {
613
+ return;
614
+ }
615
+ const sum = groups.reduce((sum, current) => sum + current, 0);
616
+ return sum === this.length;
617
+ }
618
+ populateGroupArray(length) {
619
+ const groupsCount = Math.floor(this.length / length);
620
+ const remainder = this.length % length;
621
+ const result = Array(groupsCount).fill(length);
622
+ if (remainder > 0) {
623
+ result.push(remainder);
624
+ }
625
+ this.groupLengthArray = [...result];
626
+ // groups with spacing shouldn't be wrapped in `k-input-group`
627
+ if (!this.spacing) {
628
+ this.adjacentGroups = [...this.groupLengthArray];
629
+ }
630
+ }
631
+ populateSeparatorPositions() {
632
+ let itemIndex = 0;
633
+ this.separatorPositions.clear();
634
+ if (!isPresent(this.groupLengthArray)) {
635
+ return;
636
+ }
637
+ for (let i = 0; i < this.groupLengthArray.length - 1; i++) {
638
+ itemIndex += this.groupLengthArray[i];
639
+ this.separatorPositions.add(itemIndex - 1);
640
+ }
641
+ }
642
+ clearGroups() {
643
+ this.groupLengthArray = null;
644
+ if (!this.spacing) {
645
+ this.adjacentGroups = [this.length];
646
+ }
647
+ else {
648
+ this.adjacentGroups = null;
649
+ }
650
+ this.separatorPositions.clear();
651
+ }
652
+ clearInputValues() {
653
+ this.inputFields?.forEach((input) => input.value = null);
654
+ }
655
+ handleInputChanges() {
656
+ this.zone.onStable.pipe(take(1)).subscribe(() => {
657
+ this.fillInputs(this.value?.trim());
658
+ if (this.inputAttributes) {
659
+ this.setInputAttributes();
660
+ }
661
+ else {
662
+ this.setDefaultAttributes();
663
+ }
664
+ this.cdr.detectChanges();
665
+ });
666
+ }
667
+ setGroupFillMode(fillMode, previousFillMode) {
668
+ this.inputGroups?.forEach(element => {
669
+ if (previousFillMode !== 'none') {
670
+ this.renderer.removeClass(element.nativeElement, `k-input-group-${previousFillMode}`);
671
+ }
672
+ if (fillMode !== 'none') {
673
+ this.renderer.addClass(element.nativeElement, `k-input-group-${fillMode}`);
674
+ }
675
+ });
676
+ }
677
+ setInputAttributes() {
678
+ this.inputFields?.forEach((input, index) => {
679
+ if (!this.parsedAttributes || !this.parsedAttributes?.['aria-label']) {
680
+ input.inputAttributes = { ...this.parsedAttributes, 'aria-label': this.ariaLabel(index) };
681
+ }
682
+ else {
683
+ input.inputAttributes = this.parsedAttributes;
684
+ }
685
+ });
686
+ }
687
+ setDefaultAttributes() {
688
+ this.inputFields?.forEach((input, index) => {
689
+ input.inputAttributes = {
690
+ autocomplete: 'off',
691
+ 'aria-label': this.ariaLabel(index)
692
+ };
693
+ });
694
+ }
695
+ ariaLabel(index) {
696
+ const localizationMsg = this.localizationService.get('ariaLabel') || '';
697
+ return replaceMessagePlaceholder(replaceMessagePlaceholder(replaceMessagePlaceholder(localizationMsg, 'currentInput', (index + 1).toString()), 'totalInputs', this.length.toString()), 'value', this.value);
698
+ }
699
+ isValidNumber(value) {
700
+ if (!isPresent(value)) {
701
+ return;
702
+ }
703
+ const trimmedValue = value.trim();
704
+ return trimmedValue !== '' &&
705
+ trimmedValue !== 'Infinity' &&
706
+ trimmedValue !== '-Infinity' &&
707
+ !isNaN(Number(trimmedValue));
708
+ }
709
+ showInvalidInput(index) {
710
+ const textbox = this.inputFields.get(index);
711
+ const textboxElement = this.inputFields.get(index).hostElement.nativeElement;
712
+ const inputElement = textbox.input.nativeElement;
713
+ this.renderer.addClass(textboxElement, 'k-invalid');
714
+ if (textbox.value && this.isValidNumber(textbox.value)) {
715
+ this.zone.onStable.pipe(take(1)).subscribe(() => inputElement.select());
716
+ }
717
+ this.zone.runOutsideAngular(() => {
718
+ setTimeout(() => {
719
+ if (!this.isControlInvalid && textboxElement) {
720
+ this.renderer.removeClass(textboxElement, 'k-invalid');
721
+ }
722
+ }, 300);
723
+ });
724
+ }
725
+ containsDigitsOrSpaces(value) {
726
+ // @ts-expect-error TS does not allow comparing string with number
727
+ const isDigitOrSpace = (char) => (char == +char) || char === ' ';
728
+ for (let i = 0; i < value.length; i++) {
729
+ if (!isDigitOrSpace(value[i])) {
730
+ return false;
731
+ }
732
+ }
733
+ return true;
734
+ }
735
+ 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 });
736
+ 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", 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: [
737
+ LocalizationService,
738
+ { provide: L10N_PREFIX, useValue: 'kendo.otpinput' },
739
+ {
740
+ provide: NG_VALUE_ACCESSOR,
741
+ useExisting: forwardRef(() => OTPInputComponent),
742
+ multi: true
743
+ },
744
+ { provide: KendoInput, useExisting: forwardRef(() => OTPInputComponent) }
745
+ ], viewQueries: [{ propertyName: "inputFields", predicate: TextBoxComponent, descendants: true }, { propertyName: "inputGroups", predicate: ["inputGroup"], descendants: true }], exportAs: ["kendoOTPInput"], usesOnChanges: true, ngImport: i0, template: `
746
+ <ng-container kendoOTPInputLocalizedMessages
747
+ i18n-ariaLabel="kendo.otpinput.ariaLabel|The value of the aria-label attribute of the input fields."
748
+ ariaLabel="{{ 'Input {currentInput} of {totalInputs}, current value {value}' }}"
749
+ ></ng-container>
750
+ <ng-container
751
+ kendoInputSharedEvents
752
+ [hostElement]="hostElement"
753
+ [(isFocused)]="isFocused"
754
+ (handleBlur)="handleBlur()"
755
+ (onFocus)="handleFocus()"
756
+ >
757
+ <ng-container *ngIf="spacing; else groups">
758
+ <ng-container *ngFor="let input of inputsArray; let i = index">
759
+ <kendo-textbox
760
+ class="k-otp-input"
761
+ [class.k-invalid]="isControlInvalid"
762
+ [selectOnFocus]="true"
763
+ [maxlength]="1"
764
+ [type]="type !== 'number' ? type : null"
765
+ [placeholder]="placeholder"
766
+ [size]="size"
767
+ [rounded]="rounded"
768
+ [fillMode]="fillMode"
769
+ [disabled]="disabled"
770
+ [readonly]="readonly"
771
+ (focus)="handleInputFocus(i)"
772
+ (input)="handleInput($event, i)"
773
+ ></kendo-textbox>
774
+ <kendo-otpinput-separator *ngIf="showSeparator(i)" [separator]="separator"></kendo-otpinput-separator>
775
+ </ng-container>
776
+ </ng-container>
777
+ <ng-template #groups>
778
+ <ng-container *ngFor="let group of adjacentGroups; let i = index">
779
+ <div #inputGroup class="k-input-group">
780
+ <kendo-textbox
781
+ *ngFor="let input of [].constructor(group); let j = index"
782
+ class="k-otp-input"
783
+ [class.k-invalid]="isControlInvalid"
784
+ [selectOnFocus]="true"
785
+ [maxlength]="1"
786
+ [type]="type !== 'number' ? type : null"
787
+ [placeholder]="placeholder"
788
+ [size]="size"
789
+ [rounded]="rounded"
790
+ [fillMode]="fillMode"
791
+ [disabled]="disabled"
792
+ [readonly]="readonly"
793
+ (focus)="handleInputFocus(j, i)"
794
+ (input)="handleInput($event, j, i)"
795
+ ></kendo-textbox>
796
+ </div>
797
+ <kendo-otpinput-separator *ngIf="showGroupSeparator(i)" [separator]="separator"></kendo-otpinput-separator>
798
+ </ng-container>
799
+ </ng-template>
800
+ <ng-container>
801
+ `, 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]" }] });
802
+ }
803
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: OTPInputComponent, decorators: [{
804
+ type: Component,
805
+ args: [{
806
+ exportAs: 'kendoOTPInput',
807
+ providers: [
808
+ LocalizationService,
809
+ { provide: L10N_PREFIX, useValue: 'kendo.otpinput' },
810
+ {
811
+ provide: NG_VALUE_ACCESSOR,
812
+ useExisting: forwardRef(() => OTPInputComponent),
813
+ multi: true
814
+ },
815
+ { provide: KendoInput, useExisting: forwardRef(() => OTPInputComponent) }
816
+ ],
817
+ selector: 'kendo-otpinput',
818
+ template: `
819
+ <ng-container kendoOTPInputLocalizedMessages
820
+ i18n-ariaLabel="kendo.otpinput.ariaLabel|The value of the aria-label attribute of the input fields."
821
+ ariaLabel="{{ 'Input {currentInput} of {totalInputs}, current value {value}' }}"
822
+ ></ng-container>
823
+ <ng-container
824
+ kendoInputSharedEvents
825
+ [hostElement]="hostElement"
826
+ [(isFocused)]="isFocused"
827
+ (handleBlur)="handleBlur()"
828
+ (onFocus)="handleFocus()"
829
+ >
830
+ <ng-container *ngIf="spacing; else groups">
831
+ <ng-container *ngFor="let input of inputsArray; let i = index">
832
+ <kendo-textbox
833
+ class="k-otp-input"
834
+ [class.k-invalid]="isControlInvalid"
835
+ [selectOnFocus]="true"
836
+ [maxlength]="1"
837
+ [type]="type !== 'number' ? type : null"
838
+ [placeholder]="placeholder"
839
+ [size]="size"
840
+ [rounded]="rounded"
841
+ [fillMode]="fillMode"
842
+ [disabled]="disabled"
843
+ [readonly]="readonly"
844
+ (focus)="handleInputFocus(i)"
845
+ (input)="handleInput($event, i)"
846
+ ></kendo-textbox>
847
+ <kendo-otpinput-separator *ngIf="showSeparator(i)" [separator]="separator"></kendo-otpinput-separator>
848
+ </ng-container>
849
+ </ng-container>
850
+ <ng-template #groups>
851
+ <ng-container *ngFor="let group of adjacentGroups; let i = index">
852
+ <div #inputGroup class="k-input-group">
853
+ <kendo-textbox
854
+ *ngFor="let input of [].constructor(group); let j = index"
855
+ class="k-otp-input"
856
+ [class.k-invalid]="isControlInvalid"
857
+ [selectOnFocus]="true"
858
+ [maxlength]="1"
859
+ [type]="type !== 'number' ? type : null"
860
+ [placeholder]="placeholder"
861
+ [size]="size"
862
+ [rounded]="rounded"
863
+ [fillMode]="fillMode"
864
+ [disabled]="disabled"
865
+ [readonly]="readonly"
866
+ (focus)="handleInputFocus(j, i)"
867
+ (input)="handleInput($event, j, i)"
868
+ ></kendo-textbox>
869
+ </div>
870
+ <kendo-otpinput-separator *ngIf="showGroupSeparator(i)" [separator]="separator"></kendo-otpinput-separator>
871
+ </ng-container>
872
+ </ng-template>
873
+ <ng-container>
874
+ `,
875
+ standalone: true,
876
+ imports: [SharedInputEventsDirective, TextBoxComponent, OTPInputSeparatorComponent, NgFor, NgIf, LocalizedOTPInputMessagesDirective]
877
+ }]
878
+ }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }, { type: i0.Injector }, { type: i0.Renderer2 }, { type: i1.LocalizationService }, { type: i0.NgZone }]; }, propDecorators: { length: [{
879
+ type: Input
880
+ }], type: [{
881
+ type: Input
882
+ }], spacing: [{
883
+ type: Input
884
+ }], separator: [{
885
+ type: Input
886
+ }], disabled: [{
887
+ type: Input
888
+ }], readonly: [{
889
+ type: Input
890
+ }], placeholder: [{
891
+ type: Input
892
+ }], groupLength: [{
893
+ type: Input
894
+ }], value: [{
895
+ type: Input
896
+ }], size: [{
897
+ type: Input
898
+ }], rounded: [{
899
+ type: Input
900
+ }], fillMode: [{
901
+ type: Input
902
+ }], inputAttributes: [{
903
+ type: Input
904
+ }], valueChange: [{
905
+ type: Output
906
+ }], onFocus: [{
907
+ type: Output,
908
+ args: ['focus']
909
+ }], onBlur: [{
910
+ type: Output,
911
+ args: ['blur']
912
+ }], wrapperClass: [{
913
+ type: HostBinding,
914
+ args: ['class.k-otp']
915
+ }], invalidClass: [{
916
+ type: HostBinding,
917
+ args: ['class.k-invalid']
918
+ }], direction: [{
919
+ type: HostBinding,
920
+ args: ['attr.dir']
921
+ }], role: [{
922
+ type: HostBinding,
923
+ args: ['attr.role']
924
+ }], inputFields: [{
925
+ type: ViewChildren,
926
+ args: [TextBoxComponent]
927
+ }], inputGroups: [{
928
+ type: ViewChildren,
929
+ args: ['inputGroup']
930
+ }] } });