{"version":3,"file":"useInputHandler-Cv7NmM5J.mjs","sources":["../../src/composables/useInputHandler.ts"],"sourcesContent":["import {\n    nextTick,\n    ref,\n    computed,\n    triggerRef,\n    watch,\n    watchEffect,\n    type ExtractPropTypes,\n    type MaybeRefOrGetter,\n    type Component,\n} from \"vue\";\nimport { injectField } from \"@/components/field/fieldInjection\";\nimport { unrefElement } from \"./unrefElement\";\nimport { getOption } from \"@/utils/config\";\nimport { isSSR } from \"@/utils/ssr\";\nimport { isDefined } from \"@/utils/helpers\";\n\n// This should cover all types of HTML elements that have properties related to\n// HTML constraint validation, e.g. .form and .validity.\nconst validatableFormElementTypes = isSSR\n    ? []\n    : [\n          HTMLButtonElement,\n          HTMLFieldSetElement,\n          HTMLInputElement,\n          HTMLObjectElement,\n          HTMLOutputElement,\n          HTMLSelectElement,\n          HTMLTextAreaElement,\n      ];\n\nexport type ValidatableFormElement = InstanceType<\n    (typeof validatableFormElementTypes)[number]\n>;\n\nfunction asValidatableFormElement(el: unknown): ValidatableFormElement | null {\n    return validatableFormElementTypes.some((t) => el instanceof t)\n        ? (el as ValidatableFormElement)\n        : null;\n}\n\nconst constraintValidationAttributes = [\n    \"disabled\",\n    \"required\",\n    \"pattern\",\n    \"maxlength\",\n    \"minlength\",\n    \"max\",\n    \"min\",\n    \"step\",\n];\n\n/**\n * Form input handler functionalities\n */\nexport function useInputHandler<T extends ValidatableFormElement>(\n    /** input ref element - can be a html element or a vue component*/\n    inputRef: Readonly<MaybeRefOrGetter<T | Component>>,\n    /** emitted input events */\n    emits: {\n        /** on input focus event */\n        (e: \"focus\", value: Event): void;\n        /** on input blur event */\n        (e: \"blur\", value: Event): void;\n        /** on input invalid event */\n        (e: \"invalid\", value: Event): void;\n    },\n    /** validation configuration props */\n    props: Readonly<\n        ExtractPropTypes<{\n            modelValue?: unknown;\n            useHtml5Validation?: boolean;\n            customValidity?:\n                | string\n                | ((currentValue: any, v: ValidityState) => string);\n        }>\n    >,\n) {\n    // inject parent field component if used inside one\n    const { parentField } = injectField();\n\n    /// Allows access to the native element in cases where it might be missing,\n    /// e.g. because the component hasn't been mounted yet or has been suspended\n    /// by a <KeepAlive>\n    const maybeElement = computed<T | undefined>(() => {\n        const el = unrefElement<Component | HTMLElement>(inputRef);\n        if (!el) return undefined;\n\n        if (el.getAttribute(\"data-oruga-input\"))\n            // if element is the input element\n            return el as T;\n\n        const inputs = el.querySelector(\"[data-oruga-input]\");\n\n        if (!inputs) {\n            console.warn(\n                \"useInputHandler: Underlaying Oruga input component not found\",\n            );\n            return undefined;\n        }\n        // return underlaying the input element\n        return inputs as T;\n    });\n\n    /// Should be used for most accesses to the native element; we generally\n    /// expect it to be present, especially in event handlers.\n    const element = computed(() => {\n        const el = maybeElement.value;\n        if (!el) console.warn(\"useInputHandler: inputRef contains no element\");\n        return el;\n    });\n\n    // --- Input Focus Feature ---\n\n    const isFocused = ref(false);\n\n    /** Focus the underlaying input element. */\n    function setFocus(): void {\n        nextTick(() => {\n            if (element.value) element.value.focus();\n        });\n    }\n\n    /** Click the underlaying input element. */\n    function doClick(): void {\n        nextTick(() => {\n            if (element.value) element.value.click();\n        });\n    }\n\n    /** Unset focused and emit blur event. */\n    function onBlur(event?: Event): void {\n        isFocused.value = false;\n        if (parentField?.value) parentField.value.setFocus(false);\n        emits(\"blur\", event ? event : new Event(\"blur\"));\n        checkHtml5Validity();\n    }\n\n    /** Set focused and emit focus event. */\n    function onFocus(event?: Event): void {\n        isFocused.value = true;\n        if (parentField?.value) parentField.value.setFocus(true);\n        emits(\"focus\", event ? event : new Event(\"focus\"));\n    }\n\n    // --- Validation Feature ---\n\n    const isValid = ref(true);\n\n    function setFieldValidity(variant, message): void {\n        nextTick(() => {\n            if (parentField?.value) {\n                // Set type only if not defined\n                if (!parentField.value.props.variant)\n                    parentField.value.setVariant(variant);\n\n                // Set message only if not defined\n                if (!parentField.value.props.message)\n                    parentField.value.setMessage(message);\n            }\n        });\n    }\n\n    /**\n     * Check HTML5 validation, set isValid property.\n     * If validation fail, send 'danger' type,\n     * and error message to parent if it's a Field.\n     */\n    function checkHtml5Validity(): void {\n        if (!props.useHtml5Validation) return;\n        if (!element.value) return;\n\n        if (element.value.validity.valid) {\n            setFieldValidity(null, null);\n            isValid.value = true;\n        } else {\n            setInvalid();\n            isValid.value = false;\n        }\n    }\n\n    function setInvalid(): void {\n        const variant = \"danger\";\n        const message = element.value?.validationMessage;\n        setFieldValidity(variant, message);\n    }\n\n    function onInvalid(event: Event): void {\n        checkHtml5Validity();\n        const validatable = asValidatableFormElement(event.target);\n\n        if (validatable && parentField?.value && props.useHtml5Validation) {\n            // We provide our own error message on the field, so we should suppress the browser's default tooltip.\n            // We still want to focus the form's first invalid input, though.\n            event.preventDefault();\n\n            let isFirstInvalid = false;\n\n            if (validatable.form != null) {\n                const formElements = validatable.form.elements;\n                for (let i = 0; i < formElements.length; ++i) {\n                    const element = asValidatableFormElement(\n                        formElements.item(i),\n                    );\n                    if (element?.willValidate && !element.validity.valid) {\n                        isFirstInvalid = validatable === element;\n                        break;\n                    }\n                }\n            }\n\n            if (isFirstInvalid) {\n                const fieldElement = parentField.value.$el;\n                const invalidHandler = getOption(\"invalidHandler\");\n\n                if (invalidHandler instanceof Function) {\n                    invalidHandler(validatable, fieldElement ?? undefined);\n                } else {\n                    // We'll scroll to put the whole field in view, not just the element that triggered the event,\n                    // which should mean that the message will be visible onscreen.\n                    // scrollIntoViewIfNeeded() is a non-standard method (but a very common extension).\n                    // If we can't use it, we'll just fall back to focusing the field.\n                    const canScrollToField =\n                        fieldElement?.scrollIntoView != undefined;\n                    validatable.focus({ preventScroll: canScrollToField });\n                    if (canScrollToField && fieldElement) {\n                        fieldElement.scrollIntoView({ block: \"nearest\" });\n                    }\n                }\n            }\n        }\n        emits(\"invalid\", event);\n    }\n\n    if (!isSSR) {\n        /**\n         * Provides a way to force the watcher on `updateCustomValidationMessage` to re-run\n         *\n         * There are some cases (e.g. changes to the element's validation attributes) that can\n         * force changes to the element's `validityState`, which isn't a reactive property.\n         * Note that just calling the watcher's internal function directly (outside the watcher)\n         * wouldn't be a complete solution; the watcher would then miss any new reactive dependencies\n         * that show up, e.g. because `props.customValidity` starts taking a branch that the watcher\n         * hasn't seen before.\n         */\n        const forceValidationUpdate = ref(null);\n\n        // Propagate any custom constraint validation message to the underlying DOM element.\n        // Note that using watchEffect will implicitly pick up any reactive dependencies used\n        // inside props.customValidity, which should help the computed message stay up to date.\n        watchEffect((): void => {\n            // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n            forceValidationUpdate.value;\n            if (!(props.useHtml5Validation ?? true)) return;\n\n            const element = maybeElement.value;\n            if (!isDefined(element)) return;\n\n            const validity = props.customValidity ?? \"\";\n            if (typeof validity === \"string\") {\n                element.setCustomValidity(validity);\n            } else {\n                // The custom validation message may depend on `element.validity`,\n                // which isn't a reactive property. `element.validity` depends on\n                // the element's current value and the native constraint validation\n                // attributes. We can use `props.modelValue` as a reasonable proxy\n                // for the DOM element's value, and `props.modelValue` _is_ reactive,\n                // so we can read it to help solve that reactivity problem.\n                element.setCustomValidity(\n                    validity(props.modelValue, element.validity),\n                );\n            }\n\n            // Updates the user-visible validation message if necessary\n            if (!isValid.value) checkHtml5Validity();\n        });\n\n        // Clean up validation state if we stop controlling it.\n        watch(\n            [maybeElement, (): boolean => props.useHtml5Validation ?? true],\n            (newItems, oldItems) => {\n                const newElement = newItems[0];\n                const newUseValidation = newItems[1];\n                const oldElement = oldItems[0];\n                const oldUseValidation = oldItems[1];\n                if (newElement !== oldElement) {\n                    // Since we're no longer managing the element, we might\n                    // as well clean up any custom validity we set up.\n                    oldElement?.setCustomValidity(\"\");\n                } else if (oldUseValidation && !newUseValidation) {\n                    newElement?.setCustomValidity(\"\");\n                }\n            },\n        );\n\n        // Respond to attribute changes that could affect validation messages.\n        //\n        // Technically, having the `required` attribute on one element in a radio button\n        // group affects the validity of the entire group.\n        // See https://html.spec.whatwg.org/multipage/input.html#radio-button-group.\n        // We're not checking for that here because it would require more expensive logic.\n        // Because of that, this will only work properly if the `required` attributes of all radio\n        // buttons in the group are synchronized with each other, which is likely anyway.\n        // (We're also expecting the use of radio buttons with our default validation message handling\n        // to be fairly uncommon because the overall visual experience is clunky with such a configuration.)\n        const onAttributeChange = (): void => {\n            triggerRef(forceValidationUpdate);\n        };\n\n        let validationAttributeObserver: MutationObserver | null = null;\n\n        watch(\n            [\n                maybeElement,\n                isValid,\n                (): boolean => props.useHtml5Validation ?? true,\n                ():\n                    | string\n                    | ((s: ValidityState, v: any) => string)\n                    | undefined => props.customValidity,\n            ],\n            (newData, oldData) => {\n                // Not using destructuring assignment because browser support is just a little too weak at the moment\n                const el = newData[0];\n                const valid = newData[1];\n                const useValidation = newData[2];\n                const functionalValidation = newData[3] instanceof Function;\n                const oldEl = oldData[0];\n\n                const needWatcher =\n                    isDefined(el) &&\n                    useValidation &&\n                    // For inputs known to be invalid, changes in constraint validation properties\n                    // may make it so the field is now valid and the message needs to be hidden.\n                    // For browser-implemented constraint validation (e.g. the `required` attribute),\n                    // we just care about the message displayed to the user, which is hidden for valid inputs\n                    // until the next interaction with the control.\n                    (!valid ||\n                        // For inputs with complex custom validation, any changes to validation-related attributes\n                        // may affect the results of `props.customValidity`.\n                        functionalValidation);\n\n                // Clean up previous state.\n                if (\n                    (!needWatcher || el !== oldEl) &&\n                    validationAttributeObserver != null\n                ) {\n                    // Process any pending events.\n                    if (validationAttributeObserver.takeRecords().length > 0)\n                        onAttributeChange();\n                    validationAttributeObserver.disconnect();\n                    validationAttributeObserver = null;\n                }\n\n                // Update the watcher.\n                // Note that this branch is also used for the initial setup of the watcher.\n                // We're assuming that `maybeElement` will start out null when the watcher is created, which will\n                // cause the watcher to be triggered (with `oldEl == undefined`) once the component is mounted.\n                if (\n                    needWatcher &&\n                    isDefined(el) &&\n                    (validationAttributeObserver == null || el !== oldEl)\n                ) {\n                    if (validationAttributeObserver == null) {\n                        validationAttributeObserver = new MutationObserver(\n                            onAttributeChange,\n                        );\n                    }\n                    validationAttributeObserver.observe(el, {\n                        attributeFilter: constraintValidationAttributes,\n                    });\n\n                    // Note that this doesn't react to changes in the list of ancestors.\n                    // Based on testing, Vue seems to rarely, if ever, re-parent DOM nodes;\n                    // it generally prefers to create new ones under the new parent.\n                    // That means this simpler solution is likely good enough for now.\n                    let ancestor: Node | null = el;\n                    while ((ancestor = ancestor.parentNode)) {\n                        // Form controls can be disabled by their ancestor fieldsets.\n                        if (ancestor instanceof HTMLFieldSetElement) {\n                            validationAttributeObserver.observe(ancestor, {\n                                attributeFilter: [\"disabled\"],\n                            });\n                        }\n                    }\n                }\n            },\n        );\n    }\n\n    return {\n        input: element,\n        isFocused,\n        isValid,\n        setFocus,\n        doClick,\n        onFocus,\n        onBlur,\n        onInvalid,\n        checkHtml5Validity,\n    };\n}\n"],"names":["element"],"mappings":"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"}