/*!
 * Custom elements for Nanopore-Digital Web applications
 */
'use strict';

var index = require('./index-DGttnXif.js');
var slot = require('./slot-CfuOT0U4.js');

const inputOtpCss = ".sc-nano-input-otp-h,*.sc-nano-input-otp,*.sc-nano-input-otp::before,*.sc-nano-input-otp::after{box-sizing:border-box}[hidden].sc-nano-input-otp{display:none !important}@media (prefers-reduced-motion: reduce){.sc-nano-input-otp-h,*.sc-nano-input-otp,*.sc-nano-input-otp::before,*.sc-nano-input-otp::after{animation-duration:0.01ms !important;animation-iteration-count:1 !important;transition-duration:0.01ms !important;scroll-behavior:auto !important}}.sc-nano-input-otp-h,*.sc-nano-input-otp,*.sc-nano-input-otp::before,*.sc-nano-input-otp::after{box-sizing:border-box}[hidden].sc-nano-input-otp{display:none !important}@media (prefers-reduced-motion: reduce){.sc-nano-input-otp-h,*.sc-nano-input-otp,*.sc-nano-input-otp::before,*.sc-nano-input-otp::after{animation-duration:0.01ms !important;animation-iteration-count:1 !important;transition-duration:0.01ms !important;scroll-behavior:auto !important}}.sc-nano-input-otp-h{--color:initial;--min-width:40px;--width:40px;--height:50px;--gap:8px;--icon-size:var(--nano-pictogram-size-small);--inline-padding:calc(var(--icon-size) + var(--gap));--spacing:var(--nano-spacing-xl);--separator-width:7px;--separator-height:1px;--separator-border-radius:0;--separator-color:var(--nano-color-primary-1000);--highlight-color-valid:var(--nano-color-success-1100);--highlight-color-invalid:var(--nano-color-warning-1100);--border-radius:8px;--border-color:var(--nano-color-neutral-700);--border-width:1px;display:block;position:relative;margin-inline:var(--inline-padding)}.input-otp-container.sc-nano-input-otp{position:relative;inline-size:fit-content;block-size:fit-content;margin:0 auto}.input-otp-group.sc-nano-input-otp{display:flex;align-items:center;justify-content:center;gap:var(--gap)}.native-wrapper.sc-nano-input-otp{display:flex;align-items:center;justify-content:center;min-inline-size:var(--min-width)}.native-input.sc-nano-input-otp{border-radius:var(--border-radius);inline-size:var(--width);min-inline-size:inherit;block-size:var(--height);border-width:var(--border-width);border-style:solid;border-color:var(--border-color);background:var(--background);color:var(--color);font-size:inherit;text-align:center;appearance:none}.input-otp-description.sc-nano-input-otp{color:currentcolor;font-size:var(--nano-font-size-xs);line-height:var(--nano-line-height-normal);text-align:center;position:relative;margin-block-start:var(--spacing)}.input-otp-description-hidden.sc-nano-input-otp{display:none}.input-otp-separator.sc-nano-input-otp{border-radius:var(--separator-border-radius);flex-shrink:0;inline-size:var(--separator-width);block-size:var(--separator-height);background:var(--separator-color)}.input-otp-disabled.sc-nano-input-otp-h .input-otp-description.sc-nano-input-otp,.input-otp-disabled.sc-nano-input-otp-h .input-otp-group.sc-nano-input-otp{--color:var(--nano-color-neutral-700);opacity:0.5}.input-otp-disabled.sc-nano-input-otp-h,.input-otp-disabled.sc-nano-input-otp-h .native-input.sc-nano-input-otp:disabled{cursor:not-allowed}.has-focus.sc-nano-input-otp-h .native-input.sc-nano-input-otp:focus{outline:var(--nano-focus-ring)}.nano-invalid.sc-nano-input-otp-h .native-input.sc-nano-input-otp{border-color:var(--highlight-color-invalid)}.nano-valid.sc-nano-input-otp-h .native-input.sc-nano-input-otp{border-color:var(--highlight-color-valid)}.input-otp-icon.sc-nano-input-otp{position:absolute;inset-block-start:50%;transform:translateY(-43%);inset-inline-end:calc((var(--gap) + var(--icon-size)) * -1);inline-size:var(--icon-size);block-size:var(--icon-size);display:flex;align-items:center}.input-otp-icon--valid.sc-nano-input-otp{color:var(--nano-color-success-1100)}.input-otp-icon--invalid.sc-nano-input-otp{color:var(--nano-color-warning-1100)}";

const InputOTP = class {
    constructor(hostRef) {
        index.registerInstance(this, hostRef);
        this.nanoInput = index.createEvent(this, "nanoInput", 7);
        this.nanoChange = index.createEvent(this, "nanoChange", 7);
        this.nanoComplete = index.createEvent(this, "nanoComplete", 7);
        this.nanoBlur = index.createEvent(this, "nanoBlur", 7);
        this.nanoClear = index.createEvent(this, "nanoClear", 7);
        this.nanoFocus = index.createEvent(this, "nanoFocus", 7);
    }
    inputRefs = [];
    inputId = `nano-input-otp-${inputIds++}`;
    slotCtrl = new slot.HasSlotController(this, '[default]');
    /**
     * Stores the initial value of the input when it receives focus.
     * Used to determine if the value changed during the focus session
     * to avoid emitting unnecessary change events on blur.
     */
    focusedValue;
    /**
     * 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.
     */
    isKeyboardNavigation = false;
    get el() { return index.getElement(this); }
    inputValues = [];
    hasFocus = false;
    previousInputValues = [];
    invalid = false;
    valid = false;
    /**
     * If `true`, the user cannot interact with the input.
     */
    disabled = false;
    /**
     * A hint to the browser for which keyboard to display.
     * Possible values: `"none"`, `"text"`, `"tel"`, `"url"`,
     * `"email"`, `"numeric"`, `"decimal"`, and `"search"`.
     *
     * For numbers (type="number"): "numeric"
     * For text (type="text"): "text"
     */
    inputmode;
    /**
     * The number of input boxes to display.
     */
    length = 6;
    /**
     * A regex pattern string for allowed characters. Defaults based on type.
     *
     * For numbers (`type="number"`): `"[\p{N}]"`
     * For text (`type="text"`): `"[\p{L}\p{N}]"`
     */
    pattern;
    /**
     * If `true`, the user cannot modify the value.
     */
    readonly = false;
    /**
     * The type of input allowed in the input boxes.
     */
    type = 'number';
    /**
     * The value of the input group.
     */
    value = '';
    /**
     * If `true`, allows the input to be completely filled with invalid values.
     * By default, if any input box contains an invalid value, the entire input group
     * is marked as invalid.
     * Default is `false`.
     */
    allowInvalid = false;
    /**
     * If `true`, shows a validity icon when all input boxes are filled.
     * A check icon is shown if all values are valid, and an X icon is shown if any value is invalid.
     * This does not affect the `invalid` or `valid` states of the input group.
     * Default is `false`.
     * */
    showValidity = false;
    /**
     * The `nanoInput` event is fired each time the user modifies the input's value.
     * Unlike the `nanoChange` event, the `nanoInput` event is fired for each alteration
     * to the input's value. This typically happens for each keystroke as the user types.
     *
     * For elements that accept text input (`type=text`, `type=tel`, etc.), the interface
     * is [`InputEvent`](https://developer.mozilla.org/en-US/docs/Web/API/InputEvent); for others,
     * the interface is [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event). If
     * the input is cleared on edit, the type is `null`.
     */
    nanoInput;
    /**
     * The `nanoChange` event is fired when the user modifies the input's value.
     * Unlike the `nanoInput` event, the `nanoChange` event is only fired when changes
     * are committed, not as the user types.
     *
     * The `nanoChange` event fires when the `<nano-input-otp>` component loses
     * focus after its value has changed.
     *
     * This event will not emit when programmatically setting the `value` property.
     */
    nanoChange;
    /**
     * Emitted when all input boxes have been filled with valid values.
     */
    nanoComplete;
    /**
     * Emitted when the input group loses focus.
     */
    nanoBlur;
    /**
     * Emitted when the input group is cleared.
     */
    nanoClear;
    /**
     * Emitted when the input group has focus.
     */
    nanoFocus;
    /**
     * 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) {
        if (typeof index === 'number') {
            const validIndex = Math.max(0, Math.min(index, this.length - 1));
            this.inputRefs[validIndex]?.focus();
        }
        else {
            const tabbableIndex = this.getTabbableIndex();
            this.inputRefs[tabbableIndex]?.focus();
        }
    }
    /**
     * Resets the input boxes to empty values.
     */
    async clear() {
        this.value = '';
        this.inputRefs.forEach((input, index) => {
            input.value = this.inputValues[index] || '';
        });
        this.nanoClear.emit();
    }
    valueChanged() {
        this.initializeValues();
        this.updateTabIndexes();
        this.computeValidity();
    }
    componentWillLoad() {
        this.initializeValues();
        this.computeValidity();
        // if a code is provided on load, we want to let the consumer know
        if (this.valid) {
            this.nanoComplete.emit({
                valid: this.valid,
                value: this.value?.toString(),
            });
        }
    }
    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');
    }
    get specialCharsPattern() {
        return new RegExp(`^[\\p{S}\\p{P}]$`, 'u');
    }
    /**
     * Gets the string pattern to pass to the input element
     * and use in the regex for allowed characters.
     * @returns {string} The regex pattern string 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
     * @returns {string} The inputmode to use for the input boxes.
     */
    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 if invalid characters are allowed, else just put them in
        const chars = String(this.value).split('').slice(0, this.length);
        chars.forEach((char, index) => {
            if (this.allowInvalid && !this.specialCharsPattern.test(char)) {
                this.inputValues[index] = char;
            }
            else if (this.validKeyPattern.test(char)) {
                this.inputValues[index] = char;
            }
        });
        // Update the value without emitting events
        this.value = this.inputValues.join('');
        this.previousInputValues = [...this.inputValues];
    }
    /**
     * Returns `true` if the document or host element
     * has a `dir` set to `rtl`. The host value will always
     * take priority over the root document value.
     * @returns {boolean} True if RTL direction is set, false otherwise.
     */
    isRTL = (hostEl) => {
        if (hostEl) {
            if (hostEl.dir !== '') {
                return hostEl.dir.toLowerCase() === 'rtl';
            }
        }
        return document?.dir.toLowerCase() === 'rtl';
    };
    /**
     * Updates the value of the input group.
     * This updates the value of the input group and emits an `nanoChange` event.
     * If all of the input boxes are filled, it emits an `nanoComplete` event.
     */
    updateValue(event) {
        const { inputValues, length } = this;
        const newValue = this.getValidChars(inputValues.join('')).join('');
        this.value = newValue;
        this.computeValidity();
        this.emitNanoInput(event);
        if (newValue.length === length) {
            this.nanoComplete.emit({ value: newValue, valid: this.valid });
        }
    }
    /**
     * Emits an `nanoChange` event.
     * This API should be called for user committed changes.
     * This API should not be used for external value changes.
     */
    emitNanoChange(event) {
        const { value } = this;
        // Checks for both null and undefined values
        const newValue = value == null ? value : value.toString();
        this.computeValidity();
        this.nanoChange.emit({
            value: newValue?.toString(),
            event,
            valid: this.valid,
        });
    }
    /**
     * Emits an `nanoInput` event.
     * This is used to emit the input value when the user types,
     * backspaces, or pastes.
     */
    emitNanoInput(event) {
        const { value } = this;
        // Checks for both null and undefined values
        const newValue = value == null ? value : value.toString();
        this.computeValidity();
        this.nanoInput.emit({
            value: newValue?.toString(),
            event,
            valid: this.valid,
        });
    }
    /**
     * 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 `nanoFocus` event when the input group gains focus.
     * @returns {(event: FocusEvent) => void} Event handler for focus events.
     */
    onFocus = (index) => (event) => {
        const { inputRefs } = this;
        // Only emit nanoFocus and set the focusedValue when the
        // component first gains focus
        if (!this.hasFocus) {
            this.nanoFocus.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
            this.inputRefs[finalIndex]?.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 `nanoBlur` event when the input group loses focus.
     */
    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 nanoBlur when focus leaves the component
            this.nanoBlur.emit(event);
            // Only emit nanoChange if the value has actually changed
            if (this.focusedValue !== this.value) {
                this.emitNanoChange(event);
            }
        }
    };
    /**
     * Focuses the next input box.
     */
    focusNext(currentIndex) {
        const { inputRefs, length } = this;
        if (currentIndex < length - 1) {
            inputRefs[currentIndex + 1]?.focus();
        }
    }
    /**
     * Focuses the previous input box.
     */
    focusPrevious(currentIndex) {
        const { inputRefs } = this;
        if (currentIndex > 0) {
            inputRefs[currentIndex - 1]?.focus();
        }
    }
    /**
     * Searches through the input values and returns the index
     * of the first empty input.
     * Returns -1 if all inputs are filled.
     * @returns {number} The index of the first empty input, or -1 if all are filled.
     */
    getFirstEmptyIndex() {
        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 values.findIndex((value) => !value || value === '') ?? -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.
     * @returns {number} The index to tab to.
     */
    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');
        });
    }
    /**
     * Handles keyboard navigation 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
     * @returns {(event: KeyboardEvent) => void} Event handler for keydown events.
     */
    onKeyDown = (index) => (event) => {
        const { length } = this;
        const rtl = this.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;
        }
    };
    /**
     * Processes all input scenarios for each input box.
     *
     * This function manages:
     * 1. Autofill handling
     * 2. Input validation
     * 3. Full selection replacement or typing in an empty box
     * 4. Inserting in the middle with available space (shifting)
     * 5. Single character replacement
     * @returns {(event: InputEvent) => void} Event handler for input events.
     */
    onInput = (index) => (event) => {
        const { length, validKeyPattern, allowInvalid } = this;
        const input = event.target;
        const value = input.value;
        const previousValue = this.previousInputValues[index] || '';
        // 1. Autofill handling
        // If the length of the value increases by more than 1 from the previous
        // value, treat this as autofill. This is to prevent the case where the
        // user is typing a single character into an input box containing a value
        // as that will trigger this function with a value length of 2 characters.
        const isAutofill = value.length - previousValue.length > 1;
        if (isAutofill) {
            // Distribute valid characters across input boxes
            const validChars = this.getValidChars(value);
            // 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 = '';
                    });
                });
            }
            for (let i = 0; i < length; i++) {
                this.inputValues[i] = validChars[i] || '';
                this.inputRefs[i].value = validChars[i] || '';
            }
            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(() => {
                const nextIndex = validChars.length < length ? validChars.length : length - 1;
                this.inputRefs[nextIndex]?.focus();
            }, 20);
            this.previousInputValues = [...this.inputValues];
            return;
        }
        // 2. Input validation
        // If the character entered is invalid (does not match the pattern),
        // restore the previous value and exit
        if ((!allowInvalid &&
            value.length > 0 &&
            !validKeyPattern.test(value[value.length - 1])) ||
            (allowInvalid && this.specialCharsPattern.test(value[value.length - 1]))) {
            input.value = this.inputValues[index] || '';
            this.previousInputValues = [...this.inputValues];
            return;
        }
        // 3. Full selection replacement or typing in an empty box
        // If the user selects all text in the input box and types, or if the
        // input box is empty, replace only this input box. If the box is empty,
        // move to the next box, otherwise stay focused on this box.
        const isAllSelected = input.selectionStart === 0 && input.selectionEnd === value.length;
        const isEmpty = !this.inputValues[index];
        if (isAllSelected || isEmpty) {
            this.inputValues[index] = value;
            input.value = value;
            this.updateValue(event);
            this.focusNext(index);
            this.previousInputValues = [...this.inputValues];
            return;
        }
        // 4. Inserting in the middle with available space (shifting)
        // If typing in a filled input box and there are empty boxes at the end,
        // shift all values starting at the current box to the right, and insert
        // the new character at the current box.
        const hasAvailableBoxAtEnd = this.inputValues[this.inputValues.length - 1] === '';
        if (this.inputValues[index] && hasAvailableBoxAtEnd && value.length === 2) {
            // Get the inserted character (from event or by diffing value/previousValue)
            let newChar = event.data;
            if (!newChar) {
                newChar =
                    value.split('').find((c, i) => c !== previousValue[i]) ||
                        value[value.length - 1];
            }
            // Validate the new character before shifting
            if (!allowInvalid && !validKeyPattern.test(newChar)) {
                input.value = this.inputValues[index] || '';
                this.previousInputValues = [...this.inputValues];
                return;
            }
            // Shift values right from the end to the insertion point
            for (let i = this.inputValues.length - 1; i > index; i--) {
                this.inputValues[i] = this.inputValues[i - 1];
                this.inputRefs[i].value = this.inputValues[i] || '';
            }
            this.inputValues[index] = newChar;
            this.inputRefs[index].value = newChar;
            this.updateValue(event);
            this.previousInputValues = [...this.inputValues];
            return;
        }
        // 5. Single character replacement
        // Handles replacing a single character in a box containing a value based
        // on the cursor position. We need the cursor position to determine which
        // character was the last character typed. For example, if the user types "2"
        // in an input box with the cursor at the beginning of the value of "6",
        // the value will be "26", but we want to grab the "2" as the last character
        // typed.
        const cursorPos = input.selectionStart ?? value.length;
        const newCharIndex = cursorPos - 1;
        const newChar = value[newCharIndex] ?? value[0];
        // Check if the new character is valid before updating the value
        if (!allowInvalid && !validKeyPattern.test(newChar)) {
            input.value = this.inputValues[index] || '';
            this.previousInputValues = [...this.inputValues];
            return;
        }
        this.inputValues[index] = newChar;
        input.value = newChar;
        this.updateValue(event);
        this.previousInputValues = [...this.inputValues];
    };
    /**
     * 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.
     */
    onPaste = (event) => {
        const { inputRefs, length } = this;
        event.preventDefault();
        const pastedText = event.clipboardData?.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.emitNanoInput(event);
            return;
        }
        const validChars = this.getValidChars(pastedText);
        // 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 < length ? validChars.length : length - 1;
        inputRefs[nextEmptyIndex]?.focus();
    };
    /**
     * Determines if a separator should be shown for a given index by:
     * - Checking if the total length is even (no separator for odd lengths)
     * - If even, showing a separator after the middle index
     *
     * @param index - The index to check for a separator.
     * @returns {boolean} True if a separator should be shown after the given index, false otherwise.
     */
    showSeparator(index) {
        const { length } = this;
        if (length < 3 || length % 2 !== 0)
            return false;
        return index === length / 2 - 1;
    }
    /**
     * Filters the input string to only include valid characters based on the
     * `validKeyPattern` regex. If `allowInvalid` is true, it will also
     * exclude special characters. The resulting array is sliced to the
     * maximum length of the input OTP.
     * @param str - The input string to filter.
     * @returns {string[]} An array of valid characters.
     */
    getValidChars(str) {
        const { allowInvalid, validKeyPattern, length } = this;
        const allChars = str.split('');
        return (allowInvalid
            ? allChars.filter((char) => char.trim() !== '' && !this.specialCharsPattern.test(char))
            : allChars.filter((char) => char.trim() !== '' && validKeyPattern.test(char))).slice(0, length);
    }
    /**
     * Computes the validity of the input group based on the current input values.
     * Sets the following `invalid` and `valid` states:
     * - `invalid` is true if `allowInvalid` is true and any input value is invalid.
     * - `valid` is true if all input boxes are filled with valid values.
     */
    computeValidity() {
        this.invalid =
            this.allowInvalid &&
                this.inputValues.some((val) => val !== '' && !this.validKeyPattern.test(val));
        this.valid =
            this.inputValues.length === this.length &&
                this.inputValues.every((val) => val !== '' && this.validKeyPattern.test(val));
    }
    render() {
        const { disabled, hasFocus, inputId, inputRefs, inputValues, length, readonly, invalid, valid, } = this;
        const inputmode = this.getInputmode();
        const tabbableIndex = this.getTabbableIndex();
        const pattern = this.getPattern();
        return (index.h(index.Host, { key: '69c1b3c4a4cae21665bb7e3646a27a89f8d0a73b', class: {
                'has-focus': hasFocus,
                'input-otp-disabled': disabled,
                'input-otp-readonly': readonly,
                'nano-invalid': invalid,
                'nano-valid': valid,
            } }, index.h("div", { key: '9fcd9882d552cde88adfa7578e556155651bbb4d', class: "input-otp-container" }, index.h("div", { key: '3e2c1a83282c08c1ccef81c4f13a895f1efeec13', class: "input-otp-group", role: "group", "aria-label": "One-time password input" }, Array.from({ length }).map((_, index$1) => (index.h(index.Fragment, null, index.h("div", { class: "native-wrapper" }, index.h("input", { class: "native-input", id: `${inputId}-${index$1}`, "aria-label": `Input ${index$1 + 1} of ${length}`, type: "text", inputmode: inputmode, pattern: pattern, disabled: disabled, readOnly: readonly, tabIndex: index$1 === tabbableIndex ? 0 : -1, value: inputValues[index$1] || '', autocomplete: "one-time-code", ref: (el) => (inputRefs[index$1] = el), onInput: this.onInput(index$1), onBlur: this.onBlur, onFocus: this.onFocus(index$1), onKeyDown: this.onKeyDown(index$1), onPaste: this.onPaste })), this.showSeparator(index$1) && (index.h("div", { class: "input-otp-separator" })))))), this.showValidity && (index.h("div", { key: 'b5d9d9f3023cca6fcf9d8a394287de2333eeff54', class: {
                'input-otp-icon': true,
                'input-otp-icon--valid': valid,
                'input-otp-icon--invalid': invalid,
            }, part: "validity-icon" }, index.h("slot", { key: 'd4a3123b713487f579a503ea1158e73b97a35198', name: "validity-icon" }, index.h("nano-icon", { key: '2d7886aadaaa786f3594ace3d4fa3d2ebbdc30c3', hidden: !valid, name: "light/circle-check", class: "input-otp-valid-icon", "aria-label": "Valid input" }))))), this.slotCtrl.has('[default]') && (index.h("div", { key: '32b6883425e8f62bda344ad6f92ff63a5fef98f4', class: "input-otp-description" }, index.h("slot", { key: '81b5c65f7c8bab2f8bf371c03bc5a2ae455b6c3f' })))));
    }
    static get watchers() { return {
        "value": ["valueChanged"]
    }; }
};
let inputIds = 0;
InputOTP.style = inputOtpCss;

exports.nano_input_otp = InputOTP;
