UNPKG

@progress/kendo-angular-inputs

Version:

Kendo UI for Angular Inputs Package - Everything you need to build professional form functionality (Checkbox, ColorGradient, ColorPalette, ColorPicker, FlatColorPicker, FormField, MaskedTextBox, NumericTextBox, RadioButton, RangeSlider, Slider, Switch, Te

931 lines (930 loc) 34.9 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import { ChangeDetectorRef, Component, ElementRef, EventEmitter, HostBinding, Injector, Input, NgZone, Output, QueryList, Renderer2, ViewChildren, forwardRef } from "@angular/core"; import { SharedInputEventsDirective } from "../shared/shared-events.directive"; import { NgFor, NgIf } from "@angular/common"; import { KendoInput, Keys, hasObservers, isPresent } from "@progress/kendo-angular-common"; import { TextBoxComponent } from "../textbox/textbox.component"; import { NG_VALUE_ACCESSOR, NgControl } from "@angular/forms"; import { SIZE_MAP, areSame, replaceMessagePlaceholder } from "../common/utils"; import { L10N_PREFIX, LocalizationService } from "@progress/kendo-angular-l10n"; import { OTPInputSeparatorComponent } from "./otpinput-separator.component"; import { take } from "rxjs/operators"; import { LocalizedOTPInputMessagesDirective } from "./localization/localized-textbox-messages.directive"; import * as i0 from "@angular/core"; import * as i1 from "@progress/kendo-angular-l10n"; const DEFAULT_SIZE = 'medium'; const DEFAULT_ROUNDED = 'medium'; const DEFAULT_FILL_MODE = 'solid'; const DEFAULT_OTPINPUT_LENGTH = 4; export class OTPInputComponent { hostElement; cdr; injector; renderer; localizationService; zone; /** * Configures the total number of input fields. * * @default 4 */ set length(value) { if (value < 1 || this._length === value) { return; } this._length = value; this.inputsArray = new Array(this._length); } get length() { return this._length; } /** * Configures the input type. * * * The possible values are: * * `text` (default) * * `number` * * `password` * * @default 'text' */ type = 'text'; /** * Configures whether the input fields are separate or adjacent to each other. * * @default true */ spacing = true; /** * Specifies the separator between groups of input fields. * * > The configuration can only be applied when `groupLength` is set. */ separator; /** * Configures whether the component is enabled or disabled. * * @default false */ disabled = false; /** * Configures whether the component is readonly. * * @default false */ readonly = false; /** * Configures the placeholder of the input fields. */ placeholder; /** * 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. */ get groupLength() { return this._groupLength; } set groupLength(length) { const isNumber = typeof length === 'number'; if (this._groupLength === length || isPresent(length) && ((isNumber && (length < 1 || length > this.length)) || (!isNumber && !this.isValidGroupArray(length)))) { return; } if (!isPresent(length)) { this.clearGroups(); } else if (isNumber) { this.populateGroupArray(length); } else { this.groupLengthArray = length; if (!this.spacing) { this.adjacentGroups = this.groupLengthArray; } } this._groupLength = length; this.populateSeparatorPositions(); } /** * Configures the value of the component. Unfilled input fields are represented with space. */ get value() { return this._value; } set value(input) { const isInvalidInput = this.type === 'number' && isPresent(input) && !this.containsDigitsOrSpaces(input); if (this._value === input || isInvalidInput) { return; } if (!isPresent(input)) { this.clearInputValues(); this._value = null; } else { this._value = input.slice(0, this.length); if (!this.inputFieldValueChanged) { this.fillInputs(input, 0, true); } } if (this.inputAttributes) { this.setInputAttributes(); } else { this.setDefaultAttributes(); } } /** * The `size` property specifies the padding of the input fields. * * The possible values are: * * `small` * * `medium` (default) * * `large` * * `none` */ set size(size) { const newSize = size || DEFAULT_SIZE; const elem = this.hostElement.nativeElement; this.renderer.removeClass(elem, `k-otp-${SIZE_MAP[this._size]}`); this.renderer.addClass(elem, `k-otp-${SIZE_MAP[newSize]}`); this._size = newSize; } get size() { return this._size; } /** * The `rounded` property specifies the border radius of the OTP Input. * * The possible values are: * * `small` * * `medium` (default) * * `large` * * `full` * * `none` */ set rounded(rounded) { this._rounded = rounded || DEFAULT_ROUNDED; } get rounded() { return this._rounded; } /** * The `fillMode` property specifies the background and border styles of the OTP Input. * * The possible values are: * * `flat` * * `solid` (default) * * `outline` * * `none` */ set fillMode(fillMode) { const newFillMode = fillMode || DEFAULT_FILL_MODE; this.setGroupFillMode(newFillMode, this._fillMode); this._fillMode = newFillMode; } get fillMode() { return this._fillMode; } /** * Sets the HTML attributes of the inner focusable input element. Attributes which are essential for certain component functionalities cannot be changed. */ set inputAttributes(attributes) { this._inputAttributes = attributes; this.parsedAttributes = this.inputAttributes ? { ...this.defaultAttributes, ...this.inputAttributes } : this.inputAttributes; this.setInputAttributes(); } get inputAttributes() { return this._inputAttributes; } /** * Fires each time the value is changed by the user&mdash; * When the value of the component is programmatically changed to `ngModel` or `formControl` * through its API or form binding, the `valueChange` event is not triggered because it * might cause a mix-up with the built-in `valueChange` mechanisms of the `ngModel` or `formControl` bindings. */ valueChange = new EventEmitter(); /** * Fires each time the user focuses the OTP Input. */ onFocus = new EventEmitter(); /** * Fires each time the user blurs the OTP Input. */ onBlur = new EventEmitter(); wrapperClass = true; get invalidClass() { return this.isControlInvalid; } direction; role = 'group'; /** * @hidden */ inputFields; /** * @hidden */ set inputGroups(elements) { this._inputGroups = elements; this.setGroupFillMode(this.fillMode); } get inputGroups() { return this._inputGroups; } /** * @hidden */ groupLengthArray; /** * @hidden */ inputsArray; /** * @hidden */ inputsValues = [].constructor(DEFAULT_OTPINPUT_LENGTH); /** * @hidden */ adjacentGroups; _length = DEFAULT_OTPINPUT_LENGTH; _groupLength; _inputGroups; separatorPositions = new Set(); _value = null; _size = DEFAULT_SIZE; _rounded = DEFAULT_ROUNDED; _fillMode = DEFAULT_FILL_MODE; _isFocused = false; focusChangedProgrammatically = false; inputFieldValueChanged = false; focusedInput; _inputAttributes; parsedAttributes = {}; get defaultAttributes() { return { autocomplete: 'off' }; } subscriptions; ngChange = (_) => { }; ngTouched = () => { }; constructor(hostElement, cdr, injector, renderer, localizationService, zone) { this.hostElement = hostElement; this.cdr = cdr; this.injector = injector; this.renderer = renderer; this.localizationService = localizationService; this.zone = zone; this.direction = localizationService.rtl ? 'rtl' : 'ltr'; } ngOnInit() { this.inputsArray = Array.from({ length: this._length }); this.subscriptions = this.localizationService.changes.subscribe(({ rtl }) => { this.direction = rtl ? 'rtl' : 'ltr'; }); this.zone.runOutsideAngular(() => { this.subscriptions.add(this.renderer.listen(this.hostElement.nativeElement, 'paste', this.handlePaste.bind(this))); this.subscriptions.add(this.renderer.listen(this.hostElement.nativeElement, 'keydown', this.handleKeydown.bind(this))); }); } ngAfterViewInit() { this.subscriptions.add(this.inputFields.changes.subscribe(this.handleInputChanges.bind(this))); this.handleInputChanges(); this.renderer.addClass(this.hostElement.nativeElement, `k-otp-${SIZE_MAP[this._size]}`); this.setGroupFillMode(this.fillMode); this.zone.onStable.pipe(take(1)).subscribe(() => { this.fillInputs(this.value); }); } ngOnChanges(changes) { if (changes.length) { if (typeof this.groupLength === 'number') { this.populateGroupArray(this.groupLength); } this.populateSeparatorPositions(); } if (changes.spacing) { if (this.spacing === true) { this.adjacentGroups = null; } else { this.adjacentGroups = this.groupLengthArray ?? [this.length]; } } if (changes.type && this.type === 'number') { if (isPresent(this.value) && !this.containsDigitsOrSpaces(this.value)) { this.value = null; this.zone.runOutsideAngular(() => setTimeout(() => this.zone.run(() => { this.ngChange(null); this.cdr.markForCheck(); }))); } } } ngOnDestroy() { this.subscriptions.unsubscribe(); } /** * @hidden */ get formControl() { const ngControl = this.injector.get(NgControl, null); return ngControl?.control || null; } /** * @hidden */ writeValue(value) { this.value = value; } /** * @hidden */ registerOnChange(fn) { this.ngChange = fn; } /** * @hidden */ registerOnTouched(fn) { this.ngTouched = fn; } /** * @hidden */ setDisabledState(isDisabled) { this.cdr.markForCheck(); this.disabled = isDisabled; } /** * @hidden */ get isControlInvalid() { return this.formControl?.touched && this.formControl.invalid; } /** * @hidden */ get isFocused() { return this._isFocused; } /** * @hidden */ set isFocused(value) { if (this._isFocused !== value && this.hostElement) { this._isFocused = value; } } /** * @hidden */ get hasGroups() { if (!this.spacing && isPresent(this.groupLength)) { return true; } } /** * @hidden */ showGroupSeparator(index) { return this.groupLengthArray && index < this.groupLengthArray.length - 1; } /** * @hidden */ showSeparator(index) { return this.groupLength ? this.separatorPositions.has(index) : false; } /** * @hidden */ handleValueChange(index, groupIndex) { this.inputFieldValueChanged = true; if (groupIndex) { index = this.getIndexByGroup(groupIndex, index); } let newValue = ''; this.inputFields.forEach((input) => newValue = newValue.concat(input.value?.toString() || ' ')); if (!areSame(this.value, newValue)) { this.zone.run(() => { this.value = newValue; this.ngChange(newValue); this.valueChange.emit(newValue); this.cdr.markForCheck(); }); } this.inputFieldValueChanged = false; if (isPresent(index) && isPresent(this.inputFields?.get(index).value)) { this.focusNext(); } } /** * @hidden */ handleInputFocus(index, groupIndex) { if (this.focusChangedProgrammatically) { return; } if (groupIndex) { index = this.getIndexByGroup(groupIndex, index); } this.focusedInput = index; } /** * @hidden */ handleInput(event, index, groupIndex) { if (this.type === 'number' && !this.isValidNumber(event?.data)) { const inputIndex = groupIndex ? this.getIndexByGroup(groupIndex, index) : index; const textbox = this.inputFields.get(inputIndex); if (this.value && this.isValidNumber(this.value[inputIndex])) { textbox.value = this.value[inputIndex]; } else { textbox.value = null; } this.showInvalidInput(inputIndex); return; } this.handleValueChange(index, groupIndex); } /** * @hidden */ fillInputs(text, start = 0, replaceLast = false) { if (!isPresent(text)) { return; } let charCounter = 0; this.inputFields?.forEach((otpInput, i) => { if (i < start) { return; } if (charCounter < text.length) { if (text[charCounter] === ' ') { otpInput.value = null; } else { otpInput.value = text[charCounter]; } charCounter++; } else if (replaceLast) { otpInput.value = null; } }); } /** * Focuses the OTP Input. */ focus(index) { if (!this.inputFields || index < 0 || index >= this.length) { return; } this.focusChangedProgrammatically = true; this.isFocused = true; this.inputFields.get(index || 0).focus(); this.focusedInput = index || 0; this.focusChangedProgrammatically = false; } /** * Blurs the OTP Input. */ blur() { this.focusChangedProgrammatically = true; const isFocusedElement = this.hostElement.nativeElement.querySelector(':focus'); if (isFocusedElement) { isFocusedElement.blur(); } this.isFocused = false; this.focusChangedProgrammatically = false; } /** * @hidden */ handleFocus() { this.zone.run(() => { if (!this.focusChangedProgrammatically && hasObservers(this.onFocus)) { this.onFocus.emit(); } this.isFocused = true; }); } /** * @hidden */ handleBlur() { this.zone.run(() => { if (!this.focusChangedProgrammatically) { this.ngTouched(); this.onBlur.emit(); } this.isFocused = false; }); } getIndexByGroup(groupIndex, itemIndex) { return this.groupLengthArray.slice(0, groupIndex).reduce((sum, current) => sum + current, 0) + itemIndex; } focusNext() { if (!this.inputFields || this.focusedInput === this.length - 1) { return; } this.focusChangedProgrammatically = true; this.isFocused = true; this.inputFields.get(this.focusedInput).blur(); this.inputFields.get(this.focusedInput + 1).focus(); this.focusedInput++; this.focusChangedProgrammatically = false; } focusPrevious() { if (!this.inputFields || this.focusedInput === 0) { return; } this.focusChangedProgrammatically = true; this.isFocused = true; this.inputFields.get(this.focusedInput).blur(); this.inputFields.get(this.focusedInput - 1).focus(); this.focusedInput--; this.focusChangedProgrammatically = false; } handlePaste(event) { event.preventDefault(); const text = event.clipboardData.getData('text').trim(); if (text === '') { return; } if (this.type === 'number' && !this.isValidNumber(text)) { this.showInvalidInput(this.focusedInput); return; } this.inputFieldValueChanged = true; this.fillInputs(text, this.focusedInput); this.handleValueChange(); this.inputFieldValueChanged = false; const focusedInput = this.focusedInput + text.length < this.inputFields?.length ? this.focusedInput + text.length : this.inputFields.length - 1; this.inputFields.get(this.focusedInput).blur(); this.focusedInput = focusedInput; this.inputFields.get(this.focusedInput).focus(); } handleKeydown(event) { if (this.readonly) { const isCopyCommand = (event.ctrlKey || event.metaKey) && event.keyCode === Keys.KeyC; if (!(event.keyCode === Keys.Tab || isCopyCommand)) { event.preventDefault(); return; } } switch (event.keyCode) { case Keys.ArrowRight: event.preventDefault(); this.direction === 'ltr' ? this.focusNext() : this.focusPrevious(); break; case Keys.ArrowLeft: event.preventDefault(); this.direction === 'ltr' ? this.focusPrevious() : this.focusNext(); break; case Keys.Backspace: event.preventDefault(); this.inputFields.get(this.focusedInput).value = null; this.handleValueChange(); this.focusPrevious(); break; case Keys.Delete: event.preventDefault(); this.inputFields.get(this.focusedInput).value = null; this.handleValueChange(); break; default: break; } } isValidGroupArray(groups) { if (!isPresent(groups)) { return; } const sum = groups.reduce((sum, current) => sum + current, 0); return sum === this.length; } populateGroupArray(length) { const groupsCount = Math.floor(this.length / length); const remainder = this.length % length; const result = Array(groupsCount).fill(length); if (remainder > 0) { result.push(remainder); } this.groupLengthArray = [...result]; // groups with spacing shouldn't be wrapped in `k-input-group` if (!this.spacing) { this.adjacentGroups = [...this.groupLengthArray]; } } populateSeparatorPositions() { let itemIndex = 0; this.separatorPositions.clear(); if (!isPresent(this.groupLengthArray)) { return; } for (let i = 0; i < this.groupLengthArray.length - 1; i++) { itemIndex += this.groupLengthArray[i]; this.separatorPositions.add(itemIndex - 1); } } clearGroups() { this.groupLengthArray = null; if (!this.spacing) { this.adjacentGroups = [this.length]; } else { this.adjacentGroups = null; } this.separatorPositions.clear(); } clearInputValues() { this.inputFields?.forEach((input) => input.value = null); } handleInputChanges() { this.zone.onStable.pipe(take(1)).subscribe(() => { this.fillInputs(this.value?.trim()); if (this.inputAttributes) { this.setInputAttributes(); } else { this.setDefaultAttributes(); } this.cdr.detectChanges(); }); } setGroupFillMode(fillMode, previousFillMode) { this.inputGroups?.forEach(element => { if (previousFillMode !== 'none') { this.renderer.removeClass(element.nativeElement, `k-input-group-${previousFillMode}`); } if (fillMode !== 'none') { this.renderer.addClass(element.nativeElement, `k-input-group-${fillMode}`); } }); } setInputAttributes() { this.inputFields?.forEach((input, index) => { if (!this.parsedAttributes || !this.parsedAttributes?.['aria-label']) { input.inputAttributes = { ...this.parsedAttributes, 'aria-label': this.ariaLabel(index) }; } else { input.inputAttributes = this.parsedAttributes; } }); } setDefaultAttributes() { this.inputFields?.forEach((input, index) => { input.inputAttributes = { autocomplete: 'off', 'aria-label': this.ariaLabel(index) }; }); } ariaLabel(index) { const localizationMsg = this.localizationService.get('ariaLabel') || ''; return replaceMessagePlaceholder(replaceMessagePlaceholder(replaceMessagePlaceholder(localizationMsg, 'currentInput', (index + 1).toString()), 'totalInputs', this.length.toString()), 'value', this.value); } isValidNumber(value) { if (!isPresent(value)) { return; } const trimmedValue = value.trim(); return trimmedValue !== '' && trimmedValue !== 'Infinity' && trimmedValue !== '-Infinity' && !isNaN(Number(trimmedValue)); } showInvalidInput(index) { const textbox = this.inputFields.get(index); const textboxElement = this.inputFields.get(index).hostElement.nativeElement; const inputElement = textbox.input.nativeElement; this.renderer.addClass(textboxElement, 'k-invalid'); if (textbox.value && this.isValidNumber(textbox.value)) { this.zone.onStable.pipe(take(1)).subscribe(() => inputElement.select()); } this.zone.runOutsideAngular(() => { setTimeout(() => { if (!this.isControlInvalid && textboxElement) { this.renderer.removeClass(textboxElement, 'k-invalid'); } }, 300); }); } containsDigitsOrSpaces(value) { // @ts-expect-error TS does not allow comparing string with number const isDigitOrSpace = (char) => (char == +char) || char === ' '; for (let i = 0; i < value.length; i++) { if (!isDigitOrSpace(value[i])) { return false; } } return true; } 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 }); 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: [ LocalizationService, { provide: L10N_PREFIX, useValue: 'kendo.otpinput' }, { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => OTPInputComponent), multi: true }, { provide: KendoInput, useExisting: forwardRef(() => OTPInputComponent) } ], viewQueries: [{ propertyName: "inputFields", predicate: TextBoxComponent, descendants: true }, { propertyName: "inputGroups", predicate: ["inputGroup"], descendants: true }], exportAs: ["kendoOTPInput"], usesOnChanges: true, ngImport: i0, template: ` <ng-container kendoOTPInputLocalizedMessages i18n-ariaLabel="kendo.otpinput.ariaLabel|The value of the aria-label attribute of the input fields." ariaLabel="{{ 'Input {currentInput} of {totalInputs}, current value {value}' }}" ></ng-container> <ng-container kendoInputSharedEvents [hostElement]="hostElement" [(isFocused)]="isFocused" (handleBlur)="handleBlur()" (onFocus)="handleFocus()" > <ng-container *ngIf="spacing; else groups"> <ng-container *ngFor="let input of inputsArray; let i = index"> <kendo-textbox class="k-otp-input" [class.k-invalid]="isControlInvalid" [selectOnFocus]="true" [maxlength]="1" [type]="type !== 'number' ? type : null" [placeholder]="placeholder" [size]="size" [rounded]="rounded" [fillMode]="fillMode" [disabled]="disabled" [readonly]="readonly" (focus)="handleInputFocus(i)" (input)="handleInput($event, i)" ></kendo-textbox> <kendo-otpinput-separator *ngIf="showSeparator(i)" [separator]="separator"></kendo-otpinput-separator> </ng-container> </ng-container> <ng-template #groups> <ng-container *ngFor="let group of adjacentGroups; let i = index"> <div #inputGroup class="k-input-group"> <kendo-textbox *ngFor="let input of [].constructor(group); let j = index" class="k-otp-input" [class.k-invalid]="isControlInvalid" [selectOnFocus]="true" [maxlength]="1" [type]="type !== 'number' ? type : null" [placeholder]="placeholder" [size]="size" [rounded]="rounded" [fillMode]="fillMode" [disabled]="disabled" [readonly]="readonly" (focus)="handleInputFocus(j, i)" (input)="handleInput($event, j, i)" ></kendo-textbox> </div> <kendo-otpinput-separator *ngIf="showGroupSeparator(i)" [separator]="separator"></kendo-otpinput-separator> </ng-container> </ng-template> <ng-container> `, 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]" }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: OTPInputComponent, decorators: [{ type: Component, args: [{ exportAs: 'kendoOTPInput', providers: [ LocalizationService, { provide: L10N_PREFIX, useValue: 'kendo.otpinput' }, { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => OTPInputComponent), multi: true }, { provide: KendoInput, useExisting: forwardRef(() => OTPInputComponent) } ], selector: 'kendo-otpinput', template: ` <ng-container kendoOTPInputLocalizedMessages i18n-ariaLabel="kendo.otpinput.ariaLabel|The value of the aria-label attribute of the input fields." ariaLabel="{{ 'Input {currentInput} of {totalInputs}, current value {value}' }}" ></ng-container> <ng-container kendoInputSharedEvents [hostElement]="hostElement" [(isFocused)]="isFocused" (handleBlur)="handleBlur()" (onFocus)="handleFocus()" > <ng-container *ngIf="spacing; else groups"> <ng-container *ngFor="let input of inputsArray; let i = index"> <kendo-textbox class="k-otp-input" [class.k-invalid]="isControlInvalid" [selectOnFocus]="true" [maxlength]="1" [type]="type !== 'number' ? type : null" [placeholder]="placeholder" [size]="size" [rounded]="rounded" [fillMode]="fillMode" [disabled]="disabled" [readonly]="readonly" (focus)="handleInputFocus(i)" (input)="handleInput($event, i)" ></kendo-textbox> <kendo-otpinput-separator *ngIf="showSeparator(i)" [separator]="separator"></kendo-otpinput-separator> </ng-container> </ng-container> <ng-template #groups> <ng-container *ngFor="let group of adjacentGroups; let i = index"> <div #inputGroup class="k-input-group"> <kendo-textbox *ngFor="let input of [].constructor(group); let j = index" class="k-otp-input" [class.k-invalid]="isControlInvalid" [selectOnFocus]="true" [maxlength]="1" [type]="type !== 'number' ? type : null" [placeholder]="placeholder" [size]="size" [rounded]="rounded" [fillMode]="fillMode" [disabled]="disabled" [readonly]="readonly" (focus)="handleInputFocus(j, i)" (input)="handleInput($event, j, i)" ></kendo-textbox> </div> <kendo-otpinput-separator *ngIf="showGroupSeparator(i)" [separator]="separator"></kendo-otpinput-separator> </ng-container> </ng-template> <ng-container> `, standalone: true, imports: [SharedInputEventsDirective, TextBoxComponent, OTPInputSeparatorComponent, NgFor, NgIf, LocalizedOTPInputMessagesDirective] }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }, { type: i0.Injector }, { type: i0.Renderer2 }, { type: i1.LocalizationService }, { type: i0.NgZone }]; }, propDecorators: { length: [{ type: Input }], type: [{ type: Input }], spacing: [{ type: Input }], separator: [{ type: Input }], disabled: [{ type: Input }], readonly: [{ type: Input }], placeholder: [{ type: Input }], groupLength: [{ type: Input }], value: [{ type: Input }], size: [{ type: Input }], rounded: [{ type: Input }], fillMode: [{ type: Input }], inputAttributes: [{ type: Input }], valueChange: [{ type: Output }], onFocus: [{ type: Output, args: ['focus'] }], onBlur: [{ type: Output, args: ['blur'] }], wrapperClass: [{ type: HostBinding, args: ['class.k-otp'] }], invalidClass: [{ type: HostBinding, args: ['class.k-invalid'] }], direction: [{ type: HostBinding, args: ['attr.dir'] }], role: [{ type: HostBinding, args: ['attr.role'] }], inputFields: [{ type: ViewChildren, args: [TextBoxComponent] }], inputGroups: [{ type: ViewChildren, args: ['inputGroup'] }] } });