UNPKG

@ionic/core

Version:
1,004 lines (1,003 loc) • 43.6 kB
/*! * (C) Ionic http://ionicframework.com - MIT License */ import { Fragment, Host, h } from "@stencil/core"; import { inheritAriaAttributes } from "../../utils/helpers"; import { printIonWarning } from "../../utils/logging/index"; import { isRTL } from "../../utils/rtl/index"; import { createColorClasses } from "../../utils/theme"; import { getIonMode } from "../../global/ionic-global"; export class InputOTP { constructor() { this.inheritedAttributes = {}; this.inputRefs = []; this.inputId = `ion-input-otp-${inputIds++}`; this.parsedSeparators = []; /** * Tracks whether the user is navigating through input boxes using keyboard navigation * (arrow keys, tab) versus mouse clicks. This is used to determine the appropriate * focus behavior when an input box is focused. */ this.isKeyboardNavigation = false; this.inputValues = []; this.hasFocus = false; /** * Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user. * Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`. */ this.autocapitalize = 'off'; /** * If `true`, the user cannot interact with the input. */ this.disabled = false; /** * The fill for the input boxes. If `"solid"` the input boxes will have a background. If * `"outline"` the input boxes will be transparent with a border. */ this.fill = 'outline'; /** * The number of input boxes to display. */ this.length = 4; /** * If `true`, the user cannot modify the value. */ this.readonly = false; /** * The shape of the input boxes. * If "round" they will have an increased border radius. * If "rectangular" they will have no border radius. * If "soft" they will have a soft border radius. */ this.shape = 'round'; /** * The size of the input boxes. */ this.size = 'medium'; /** * The type of input allowed in the input boxes. */ this.type = 'number'; /** * The value of the input group. */ this.value = ''; /** * Handles the focus behavior for the input OTP component. * * Focus behavior: * 1. Keyboard navigation: Allow normal focus movement * 2. Mouse click: * - If clicked box has value: Focus that box * - If clicked box is empty: Focus first empty box * * Emits the `ionFocus` event when the input group gains focus. */ this.onFocus = (index) => (event) => { var _a; const { inputRefs } = this; // Only emit ionFocus and set the focusedValue when the // component first gains focus if (!this.hasFocus) { this.ionFocus.emit(event); this.focusedValue = this.value; } this.hasFocus = true; let finalIndex = index; if (!this.isKeyboardNavigation) { // If the clicked box has a value, focus it // Otherwise focus the first empty box const targetIndex = this.inputValues[index] ? index : this.getFirstEmptyIndex(); finalIndex = targetIndex === -1 ? this.length - 1 : targetIndex; // Focus the target box (_a = this.inputRefs[finalIndex]) === null || _a === void 0 ? void 0 : _a.focus(); } // Update tabIndexes to match the focused box inputRefs.forEach((input, i) => { input.tabIndex = i === finalIndex ? 0 : -1; }); // Reset the keyboard navigation flag this.isKeyboardNavigation = false; }; /** * Handles the blur behavior for the input OTP component. * Emits the `ionBlur` event when the input group loses focus. */ this.onBlur = (event) => { const { inputRefs } = this; const relatedTarget = event.relatedTarget; // Do not emit blur if we're moving to another input box in the same component const isInternalFocus = relatedTarget != null && inputRefs.includes(relatedTarget); if (!isInternalFocus) { this.hasFocus = false; // Reset tabIndexes when focus leaves the component this.updateTabIndexes(); // Always emit ionBlur when focus leaves the component this.ionBlur.emit(event); // Only emit ionChange if the value has actually changed if (this.focusedValue !== this.value) { this.emitIonChange(event); } } }; /** * Handles keyboard navigation and input for the OTP component. * * Navigation: * - Backspace: Clears current input and moves to previous box if empty * - Arrow Left/Right: Moves focus between input boxes * - Tab: Allows normal tab navigation between components * * Input Behavior: * - Validates input against the allowed pattern * - When entering a key in a filled box: * - Shifts existing values right if there is room * - Updates the value of the input group * - Prevents default behavior to avoid automatic focus shift */ this.onKeyDown = (index) => (event) => { const { length } = this; const rtl = isRTL(this.el); const input = event.target; // Meta shortcuts are used to copy, paste, and select text // We don't want to handle these keys here const metaShortcuts = ['a', 'c', 'v', 'x', 'r', 'z', 'y']; const isTextSelection = input.selectionStart !== input.selectionEnd; // Return if the key is a meta shortcut or the input value // text is selected and let the onPaste / onInput handler manage it if (isTextSelection || ((event.metaKey || event.ctrlKey) && metaShortcuts.includes(event.key.toLowerCase()))) { return; } if (event.key === 'Backspace') { if (this.inputValues[index]) { // Shift all values to the right of the current index left by one for (let i = index; i < length - 1; i++) { this.inputValues[i] = this.inputValues[i + 1]; } // Clear the last box this.inputValues[length - 1] = ''; // Update all inputRefs to match inputValues for (let i = 0; i < length; i++) { this.inputRefs[i].value = this.inputValues[i] || ''; } this.updateValue(event); event.preventDefault(); } else if (!this.inputValues[index] && index > 0) { // If current input is empty, move to previous input this.focusPrevious(index); } } else if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') { this.isKeyboardNavigation = true; event.preventDefault(); const isLeft = event.key === 'ArrowLeft'; const shouldMoveNext = (isLeft && rtl) || (!isLeft && !rtl); // Only allow moving to the next input if the current has a value if (shouldMoveNext) { if (this.inputValues[index] && index < length - 1) { this.focusNext(index); } } else { this.focusPrevious(index); } } else if (event.key === 'Tab') { this.isKeyboardNavigation = true; // Let all tab events proceed normally return; } // If the input box contains a value and the key being // entered is a valid key for the input box update the value // and shift the values to the right if there is room. if (this.inputValues[index] && this.validKeyPattern.test(event.key)) { if (!this.inputValues[length - 1]) { for (let i = length - 1; i > index; i--) { this.inputValues[i] = this.inputValues[i - 1]; this.inputRefs[i].value = this.inputValues[i] || ''; } } this.inputValues[index] = event.key; this.inputRefs[index].value = event.key; this.updateValue(event); // Prevent default to avoid the browser from // automatically moving the focus to the next input event.preventDefault(); } }; this.onInput = (index) => (event) => { const { length, validKeyPattern } = this; const value = event.target.value; // If the value is longer than 1 character (autofill), split it into // characters and filter out invalid ones if (value.length > 1) { const validChars = value .split('') .filter((char) => validKeyPattern.test(char)) .slice(0, length); // If there are no valid characters coming from the // autofill, all input refs have to be cleared after the // browser has finished the autofill behavior if (validChars.length === 0) { requestAnimationFrame(() => { this.inputRefs.forEach((input) => { input.value = ''; }); }); } // Update the value of the input group and emit the input change event this.value = validChars.join(''); this.updateValue(event); // Focus the first empty input box or the last input box if all boxes // are filled after a small delay to ensure the input boxes have been // updated before moving the focus setTimeout(() => { var _a; const nextIndex = validChars.length < length ? validChars.length : length - 1; (_a = this.inputRefs[nextIndex]) === null || _a === void 0 ? void 0 : _a.focus(); }, 20); return; } // Only allow input if it matches the pattern if (value.length > 0 && !validKeyPattern.test(value)) { this.inputRefs[index].value = ''; this.inputValues[index] = ''; return; } // For single character input, fill the current box this.inputValues[index] = value; this.updateValue(event); if (value.length > 0) { this.focusNext(index); } }; /** * Handles pasting text into the input OTP component. * This function prevents the default paste behavior and * validates the pasted text against the allowed pattern. * It then updates the value of the input group and focuses * the next empty input after pasting. */ this.onPaste = (event) => { var _a, _b, _c; const { inputRefs, length, validKeyPattern } = this; event.preventDefault(); const pastedText = (_a = event.clipboardData) === null || _a === void 0 ? void 0 : _a.getData('text'); // If there is no pasted text, still emit the input change event // because this is how the native input element behaves // but return early because there is nothing to paste. if (!pastedText) { this.emitIonInput(event); return; } const validChars = pastedText .split('') .filter((char) => validKeyPattern.test(char)) .slice(0, length); // Always paste starting at the first box validChars.forEach((char, index) => { if (index < length) { this.inputRefs[index].value = char; this.inputValues[index] = char; } }); // Update the value so that all input boxes are updated this.value = validChars.join(''); this.updateValue(event); // Focus the next empty input after pasting // If all boxes are filled, focus the last input const nextEmptyIndex = validChars.length; if (nextEmptyIndex < length) { (_b = inputRefs[nextEmptyIndex]) === null || _b === void 0 ? void 0 : _b.focus(); } else { (_c = inputRefs[length - 1]) === null || _c === void 0 ? void 0 : _c.focus(); } }; } /** * Sets focus to an input box. * @param index - The index of the input box to focus (0-based). * If provided and the input box has a value, the input box at that index will be focused. * Otherwise, the first empty input box or the last input if all are filled will be focused. */ async setFocus(index) { var _a, _b; if (typeof index === 'number') { const validIndex = Math.max(0, Math.min(index, this.length - 1)); (_a = this.inputRefs[validIndex]) === null || _a === void 0 ? void 0 : _a.focus(); } else { const tabbableIndex = this.getTabbableIndex(); (_b = this.inputRefs[tabbableIndex]) === null || _b === void 0 ? void 0 : _b.focus(); } } valueChanged() { this.initializeValues(); this.updateTabIndexes(); } /** * Processes the separators prop into an array of numbers. * * If the separators prop is not provided, returns an empty array. * If the separators prop is 'all', returns an array of all valid positions (1 to length-1). * If the separators prop is an array, returns it as is. * If the separators prop is a string, splits it by commas and parses each part as a number. * * If the separators are greater than the input length, it will warn and ignore the separators. */ processSeparators() { const { separators, length } = this; if (separators === undefined) { this.parsedSeparators = []; return; } if (typeof separators === 'string' && separators !== 'all') { const isValidFormat = /^(\d+)(,\d+)*$/.test(separators); if (!isValidFormat) { printIonWarning(`[ion-input-otp] - Invalid separators format. Expected a comma-separated list of numbers, an array of numbers, or "all". Received: ${separators}`, this.el); this.parsedSeparators = []; return; } } let separatorValues; if (separators === 'all') { separatorValues = Array.from({ length: length - 1 }, (_, i) => i + 1); } else if (Array.isArray(separators)) { separatorValues = separators; } else { separatorValues = separators .split(',') .map((pos) => parseInt(pos, 10)) .filter((pos) => !isNaN(pos)); } // Check for duplicate separator positions const duplicates = separatorValues.filter((pos, index) => separatorValues.indexOf(pos) !== index); if (duplicates.length > 0) { printIonWarning(`[ion-input-otp] - Duplicate separator positions are not allowed. Received: ${separators}`, this.el); } const invalidSeparators = separatorValues.filter((pos) => pos > length); if (invalidSeparators.length > 0) { printIonWarning(`[ion-input-otp] - The following separator positions are greater than the input length (${length}): ${invalidSeparators.join(', ')}. These separators will be ignored.`, this.el); } this.parsedSeparators = separatorValues.filter((pos) => pos <= length); } componentWillLoad() { this.inheritedAttributes = inheritAriaAttributes(this.el); this.processSeparators(); this.initializeValues(); } componentDidLoad() { this.updateTabIndexes(); } /** * Get the regex pattern for allowed characters. * If a pattern is provided, use it to create a regex pattern * Otherwise, use the default regex pattern based on type */ get validKeyPattern() { return new RegExp(`^${this.getPattern()}$`, 'u'); } /** * Gets the string pattern to pass to the input element * and use in the regex for allowed characters. */ getPattern() { const { pattern, type } = this; if (pattern) { return pattern; } return type === 'number' ? '[\\p{N}]' : '[\\p{L}\\p{N}]'; } /** * Get the default value for inputmode. * If inputmode is provided, use it. * Otherwise, use the default inputmode based on type */ getInputmode() { const { inputmode } = this; if (inputmode) { return inputmode; } if (this.type == 'number') { return 'numeric'; } else { return 'text'; } } /** * Initializes the input values array based on the current value prop. * This splits the value into individual characters and validates them against * the allowed pattern. The values are then used as the values in the native * input boxes and the value of the input group is updated. */ initializeValues() { // Clear all input values this.inputValues = Array(this.length).fill(''); // If the value is null, undefined, or an empty string, return if (this.value == null || String(this.value).length === 0) { return; } // Split the value into individual characters and validate // them against the allowed pattern const chars = String(this.value).split('').slice(0, this.length); chars.forEach((char, index) => { if (this.validKeyPattern.test(char)) { this.inputValues[index] = char; } }); // Update the value without emitting events this.value = this.inputValues.join(''); } /** * Updates the value of the input group. * This updates the value of the input group and emits an `ionChange` event. * If all of the input boxes are filled, it emits an `ionComplete` event. */ updateValue(event) { const { inputValues, length } = this; const newValue = inputValues.join(''); this.value = newValue; this.emitIonInput(event); if (newValue.length === length) { this.ionComplete.emit({ value: newValue }); } } /** * Emits an `ionChange` event. * This API should be called for user committed changes. * This API should not be used for external value changes. */ emitIonChange(event) { const { value } = this; // Checks for both null and undefined values const newValue = value == null ? value : value.toString(); this.ionChange.emit({ value: newValue, event }); } /** * Emits an `ionInput` event. * This is used to emit the input value when the user types, * backspaces, or pastes. */ emitIonInput(event) { const { value } = this; // Checks for both null and undefined values const newValue = value == null ? value : value.toString(); this.ionInput.emit({ value: newValue, event }); } /** * Focuses the next input box. */ focusNext(currentIndex) { var _a; const { inputRefs, length } = this; if (currentIndex < length - 1) { (_a = inputRefs[currentIndex + 1]) === null || _a === void 0 ? void 0 : _a.focus(); } } /** * Focuses the previous input box. */ focusPrevious(currentIndex) { var _a; const { inputRefs } = this; if (currentIndex > 0) { (_a = inputRefs[currentIndex - 1]) === null || _a === void 0 ? void 0 : _a.focus(); } } /** * Searches through the input values and returns the index * of the first empty input. * Returns -1 if all inputs are filled. */ getFirstEmptyIndex() { var _a; const { inputValues, length } = this; // Create an array of the same length as the input OTP // and fill it with the input values const values = Array.from({ length }, (_, i) => inputValues[i] || ''); return (_a = values.findIndex((value) => !value || value === '')) !== null && _a !== void 0 ? _a : -1; } /** * Returns the index of the input that should be tabbed to. * If all inputs are filled, returns the last input's index. * Otherwise, returns the index of the first empty input. */ getTabbableIndex() { const { length } = this; const firstEmptyIndex = this.getFirstEmptyIndex(); return firstEmptyIndex === -1 ? length - 1 : firstEmptyIndex; } /** * Updates the tabIndexes for the input boxes. * This is used to ensure that the correct input is * focused when the user navigates using the tab key. */ updateTabIndexes() { const { inputRefs, inputValues, length } = this; // Find first empty index after any filled boxes let firstEmptyIndex = -1; for (let i = 0; i < length; i++) { if (!inputValues[i] || inputValues[i] === '') { firstEmptyIndex = i; break; } } // Update tabIndex and aria-hidden for all inputs inputRefs.forEach((input, index) => { const shouldBeTabbable = firstEmptyIndex === -1 ? index === length - 1 : firstEmptyIndex === index; input.tabIndex = shouldBeTabbable ? 0 : -1; // If the input is empty and not the first empty input, // it should be hidden from screen readers. const isEmpty = !inputValues[index] || inputValues[index] === ''; input.setAttribute('aria-hidden', isEmpty && !shouldBeTabbable ? 'true' : 'false'); }); } /** * Determines if a separator should be shown for a given index by * checking if the index is included in the parsed separators array. */ showSeparator(index) { const { length } = this; return this.parsedSeparators.includes(index + 1) && index < length - 1; } render() { var _a, _b; const { autocapitalize, color, disabled, el, fill, hasFocus, inheritedAttributes, inputId, inputRefs, inputValues, length, readonly, shape, size, } = this; const mode = getIonMode(this); const inputmode = this.getInputmode(); const tabbableIndex = this.getTabbableIndex(); const pattern = this.getPattern(); const hasDescription = ((_b = (_a = el.querySelector('.input-otp-description')) === null || _a === void 0 ? void 0 : _a.textContent) === null || _b === void 0 ? void 0 : _b.trim()) !== ''; return (h(Host, { key: 'df8fca036cedea0812185a02e3b655d7d76285e0', class: createColorClasses(color, { [mode]: true, 'has-focus': hasFocus, [`input-otp-size-${size}`]: true, [`input-otp-shape-${shape}`]: true, [`input-otp-fill-${fill}`]: true, 'input-otp-disabled': disabled, 'input-otp-readonly': readonly, }) }, h("div", Object.assign({ key: '831be3f939cf037f0eb8d7e37e0afd4ef9a3c2c5', role: "group", "aria-label": "One-time password input", class: "input-otp-group" }, inheritedAttributes), Array.from({ length }).map((_, index) => (h(Fragment, null, h("div", { class: "native-wrapper" }, h("input", { class: "native-input", id: `${inputId}-${index}`, "aria-label": `Input ${index + 1} of ${length}`, type: "text", autoCapitalize: autocapitalize, inputmode: inputmode, pattern: pattern, disabled: disabled, readOnly: readonly, tabIndex: index === tabbableIndex ? 0 : -1, value: inputValues[index] || '', autocomplete: "one-time-code", ref: (el) => (inputRefs[index] = el), onInput: this.onInput(index), onBlur: this.onBlur, onFocus: this.onFocus(index), onKeyDown: this.onKeyDown(index), onPaste: this.onPaste })), this.showSeparator(index) && h("div", { class: "input-otp-separator" }))))), h("div", { key: '5311fedc34f7af3efd5f69e5a3d768055119c4f1', class: { 'input-otp-description': true, 'input-otp-description-hidden': !hasDescription, } }, h("slot", { key: '9e8afa2f7fa76c3092582dc27770fdf565a1b9ba' })))); } static get is() { return "ion-input-otp"; } static get encapsulation() { return "scoped"; } static get originalStyleUrls() { return { "ios": ["input-otp.ios.scss"], "md": ["input-otp.md.scss"] }; } static get styleUrls() { return { "ios": ["input-otp.ios.css"], "md": ["input-otp.md.css"] }; } static get properties() { return { "autocapitalize": { "type": "string", "attribute": "autocapitalize", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.\nAvailable options: `\"off\"`, `\"none\"`, `\"on\"`, `\"sentences\"`, `\"words\"`, `\"characters\"`." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "'off'" }, "color": { "type": "string", "attribute": "color", "mutable": false, "complexType": { "original": "Color", "resolved": "\"danger\" | \"dark\" | \"light\" | \"medium\" | \"primary\" | \"secondary\" | \"success\" | \"tertiary\" | \"warning\" | string & Record<never, never> | undefined", "references": { "Color": { "location": "import", "path": "../../interface", "id": "src/interface.d.ts::Color" } } }, "required": false, "optional": true, "docs": { "tags": [], "text": "The color to use from your application's color palette.\nDefault options are: `\"primary\"`, `\"secondary\"`, `\"tertiary\"`, `\"success\"`, `\"warning\"`, `\"danger\"`, `\"light\"`, `\"medium\"`, and `\"dark\"`.\nFor more information on colors, see [theming](/docs/theming/basics)." }, "getter": false, "setter": false, "reflect": true }, "disabled": { "type": "boolean", "attribute": "disabled", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "If `true`, the user cannot interact with the input." }, "getter": false, "setter": false, "reflect": true, "defaultValue": "false" }, "fill": { "type": "string", "attribute": "fill", "mutable": false, "complexType": { "original": "'outline' | 'solid'", "resolved": "\"outline\" | \"solid\" | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "The fill for the input boxes. If `\"solid\"` the input boxes will have a background. If\n`\"outline\"` the input boxes will be transparent with a border." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "'outline'" }, "inputmode": { "type": "string", "attribute": "inputmode", "mutable": false, "complexType": { "original": "'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'", "resolved": "\"decimal\" | \"email\" | \"none\" | \"numeric\" | \"search\" | \"tel\" | \"text\" | \"url\" | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "A hint to the browser for which keyboard to display.\nPossible values: `\"none\"`, `\"text\"`, `\"tel\"`, `\"url\"`,\n`\"email\"`, `\"numeric\"`, `\"decimal\"`, and `\"search\"`.\n\nFor numbers (type=\"number\"): \"numeric\"\nFor text (type=\"text\"): \"text\"" }, "getter": false, "setter": false, "reflect": false }, "length": { "type": "number", "attribute": "length", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "The number of input boxes to display." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "4" }, "pattern": { "type": "string", "attribute": "pattern", "mutable": false, "complexType": { "original": "string", "resolved": "string | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "A regex pattern string for allowed characters. Defaults based on type.\n\nFor numbers (`type=\"number\"`): `\"[\\p{N}]\"`\nFor text (`type=\"text\"`): `\"[\\p{L}\\p{N}]\"`" }, "getter": false, "setter": false, "reflect": false }, "readonly": { "type": "boolean", "attribute": "readonly", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "If `true`, the user cannot modify the value." }, "getter": false, "setter": false, "reflect": true, "defaultValue": "false" }, "separators": { "type": "string", "attribute": "separators", "mutable": false, "complexType": { "original": "'all' | string | number[]", "resolved": "number[] | string | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Where separators should be shown between input boxes.\nCan be a comma-separated string or an array of numbers.\n\nFor example:\n`\"3\"` will show a separator after the 3rd input box.\n`[1,4]` will show a separator after the 1st and 4th input boxes.\n`\"all\"` will show a separator between every input box." }, "getter": false, "setter": false, "reflect": false }, "shape": { "type": "string", "attribute": "shape", "mutable": false, "complexType": { "original": "'round' | 'rectangular' | 'soft'", "resolved": "\"rectangular\" | \"round\" | \"soft\"", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "The shape of the input boxes.\nIf \"round\" they will have an increased border radius.\nIf \"rectangular\" they will have no border radius.\nIf \"soft\" they will have a soft border radius." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "'round'" }, "size": { "type": "string", "attribute": "size", "mutable": false, "complexType": { "original": "'small' | 'medium' | 'large'", "resolved": "\"large\" | \"medium\" | \"small\"", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "The size of the input boxes." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "'medium'" }, "type": { "type": "string", "attribute": "type", "mutable": false, "complexType": { "original": "'text' | 'number'", "resolved": "\"number\" | \"text\"", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "The type of input allowed in the input boxes." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "'number'" }, "value": { "type": "any", "attribute": "value", "mutable": true, "complexType": { "original": "string | number | null", "resolved": "null | number | string | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "The value of the input group." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "''" } }; } static get states() { return { "inputValues": {}, "hasFocus": {} }; } static get events() { return [{ "method": "ionInput", "name": "ionInput", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "The `ionInput` event is fired each time the user modifies the input's value.\nUnlike the `ionChange` event, the `ionInput` event is fired for each alteration\nto the input's value. This typically happens for each keystroke as the user types.\n\nFor elements that accept text input (`type=text`, `type=tel`, etc.), the interface\nis [`InputEvent`](https://developer.mozilla.org/en-US/docs/Web/API/InputEvent); for others,\nthe interface is [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event). If\nthe input is cleared on edit, the type is `null`." }, "complexType": { "original": "InputOtpInputEventDetail", "resolved": "InputOtpInputEventDetail", "references": { "InputOtpInputEventDetail": { "location": "import", "path": "./input-otp-interface", "id": "src/components/input-otp/input-otp-interface.ts::InputOtpInputEventDetail" } } } }, { "method": "ionChange", "name": "ionChange", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "The `ionChange` event is fired when the user modifies the input's value.\nUnlike the `ionInput` event, the `ionChange` event is only fired when changes\nare committed, not as the user types.\n\nThe `ionChange` event fires when the `<ion-input-otp>` component loses\nfocus after its value has changed.\n\nThis event will not emit when programmatically setting the `value` property." }, "complexType": { "original": "InputOtpChangeEventDetail", "resolved": "InputOtpChangeEventDetail", "references": { "InputOtpChangeEventDetail": { "location": "import", "path": "./input-otp-interface", "id": "src/components/input-otp/input-otp-interface.ts::InputOtpChangeEventDetail" } } } }, { "method": "ionComplete", "name": "ionComplete", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emitted when all input boxes have been filled with valid values." }, "complexType": { "original": "InputOtpCompleteEventDetail", "resolved": "InputOtpCompleteEventDetail", "references": { "InputOtpCompleteEventDetail": { "location": "import", "path": "./input-otp-interface", "id": "src/components/input-otp/input-otp-interface.ts::InputOtpCompleteEventDetail" } } } }, { "method": "ionBlur", "name": "ionBlur", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emitted when the input group loses focus." }, "complexType": { "original": "FocusEvent", "resolved": "FocusEvent", "references": { "FocusEvent": { "location": "global", "id": "global::FocusEvent" } } } }, { "method": "ionFocus", "name": "ionFocus", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emitted when the input group has focus." }, "complexType": { "original": "FocusEvent", "resolved": "FocusEvent", "references": { "FocusEvent": { "location": "global", "id": "global::FocusEvent" } } } }]; } static get methods() { return { "setFocus": { "complexType": { "signature": "(index?: number) => Promise<void>", "parameters": [{ "name": "index", "type": "number | undefined", "docs": "- The index of the input box to focus (0-based).\nIf provided and the input box has a value, the input box at that index will be focused.\nOtherwise, the first empty input box or the last input if all are filled will be focused." }], "references": { "Promise": { "location": "global", "id": "global::Promise" } }, "return": "Promise<void>" }, "docs": { "text": "Sets focus to an input box.", "tags": [{ "name": "param", "text": "index - The index of the input box to focus (0-based).\nIf provided and the input box has a value, the input box at that index will be focused.\nOtherwise, the first empty input box or the last input if all are filled will be focused." }] } } }; } static get elementRef() { return "el"; } static get watchers() { return [{ "propName": "value", "methodName": "valueChanged" }, { "propName": "separators", "methodName": "processSeparators" }, { "propName": "length", "methodName": "processSeparators" }]; } } let inputIds = 0;