/*!
 * (C) Ionic http://ionicframework.com - MIT License
 */
import { Build, Host, forceUpdate, h, writeTask, } from "@stencil/core";
import { createNotchController, checkInvalidState } from "../../utils/forms/index";
import { inheritAriaAttributes, debounceEvent, inheritAttributes, componentOnReady } from "../../utils/helpers";
import { createSlotMutationController } from "../../utils/slot-mutation-controller";
import { createColorClasses, hostContext } from "../../utils/theme";
import { getIonTheme } from "../../global/ionic-global";
import { getCounterText } from "../input/input.utils";
/**
 * @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of the component.
 * @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the visual appearance of the component.
 *
 * @slot label - The label text to associate with the textarea. Use the `labelPlacement` property to control where the label is placed relative to the textarea. Use this if you need to render a label with custom HTML. (EXPERIMENTAL)
 * @slot start - Content to display at the leading edge of the textarea. (EXPERIMENTAL)
 * @slot end - Content to display at the trailing edge of the textarea. (EXPERIMENTAL)
 */
export class Textarea {
    constructor() {
        this.inputId = `ion-textarea-${textareaIds++}`;
        this.helperTextId = `${this.inputId}-helper-text`;
        this.errorTextId = `${this.inputId}-error-text`;
        /**
         * `true` if the textarea was cleared as a result of the user typing
         * with `clearOnEdit` enabled.
         *
         * Resets when the textarea loses focus.
         */
        this.didTextareaClearOnEdit = false;
        this.inheritedAttributes = {};
        /**
         * The `hasFocus` state ensures the focus class is
         * added regardless of how the element is focused.
         * The `ion-focused` class only applies when focused
         * via tabbing, not by clicking.
         * The `has-focus` logic was added to ensure the class
         * is applied in both cases.
         */
        this.hasFocus = false;
        /**
         * Track validation state for proper aria-live announcements
         */
        this.isInvalid = 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 = 'none';
        /**
         * Sets the [`autofocus` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) on the native input element.
         *
         * This may not be sufficient for the element to be focused on page load. See [managing focus](/docs/developing/managing-focus) for more information.
         */
        this.autofocus = false;
        /**
         * If `true`, the value will be cleared after focus upon edit.
         */
        this.clearOnEdit = false;
        /**
         * If `true`, the user cannot interact with the textarea.
         */
        this.disabled = false;
        /**
         * The name of the control, which is submitted with the form data.
         */
        this.name = this.inputId;
        /**
         * If `true`, the user cannot modify the value.
         */
        this.readonly = false;
        /**
         * If `true`, the user must fill in a value before submitting a form.
         */
        this.required = false;
        /**
         * If `true`, the element will have its spelling and grammar checked.
         */
        this.spellcheck = false;
        /**
         * If `true`, the textarea container will grow and shrink based
         * on the contents of the textarea.
         */
        this.autoGrow = false;
        /**
         * The value of the textarea.
         */
        this.value = '';
        /**
         * If `true`, a character counter will display the ratio of characters used and the total character limit.
         * Developers must also set the `maxlength` property for the counter to be calculated correctly.
         */
        this.counter = false;
        /**
         * Where to place the label relative to the textarea.
         * `"start"`: The label will appear to the left of the textarea in LTR and to the right in RTL.
         * `"end"`: The label will appear to the right of the textarea in LTR and to the left in RTL.
         * `"floating"`: The label will appear smaller and above the textarea when the textarea is focused or it has a value. Otherwise it will appear on top of the textarea.
         * `"stacked"`: The label will appear smaller and above the textarea regardless even when the textarea is blurred or has no value.
         * `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("...").
         */
        this.labelPlacement = 'start';
        /**
         * The size of the textarea. If "large" it will increase the height of the textarea, while
         * "small" and "medium" provide progressively smaller heights. The default size is "medium".
         * This property only applies to the `"ionic"` theme.
         */
        this.size = 'medium';
        // `Event` type is used instead of `InputEvent`
        // since the types from Stencil are not derived
        // from the element (e.g. textarea and input
        // should be InputEvent, but all other elements
        // should be Event).
        this.onInput = (ev) => {
            const input = ev.target;
            if (input) {
                this.value = input.value || '';
            }
            this.emitInputChange(ev);
        };
        this.onChange = (ev) => {
            this.emitValueChange(ev);
        };
        this.onFocus = (ev) => {
            this.hasFocus = true;
            this.focusedValue = this.value;
            this.ionFocus.emit(ev);
        };
        this.onBlur = (ev) => {
            this.hasFocus = false;
            if (this.focusedValue !== this.value) {
                /**
                 * Emits the `ionChange` event when the textarea value
                 * is different than the value when the textarea was focused.
                 */
                this.emitValueChange(ev);
            }
            this.didTextareaClearOnEdit = false;
            this.ionBlur.emit(ev);
        };
        this.onKeyDown = (ev) => {
            this.checkClearOnEdit(ev);
        };
        /**
         * Stops propagation when the label is clicked,
         * otherwise, two clicks will be triggered.
         */
        this.onLabelClick = (ev) => {
            // Only stop propagation if the click was directly on the label
            // and not on the input or other child elements
            if (ev.target === ev.currentTarget) {
                ev.stopPropagation();
            }
        };
    }
    debounceChanged() {
        const { ionInput, debounce, originalIonInput } = this;
        /**
         * If debounce is undefined, we have to manually revert the ionInput emitter in case
         * debounce used to be set to a number. Otherwise, the event would stay debounced.
         */
        this.ionInput = debounce === undefined ? originalIonInput !== null && originalIonInput !== void 0 ? originalIonInput : ionInput : debounceEvent(ionInput, debounce);
    }
    /**
     * Update the native input element when the value changes
     */
    valueChanged() {
        const nativeInput = this.nativeInput;
        const value = this.getValue();
        if (nativeInput && nativeInput.value !== value) {
            nativeInput.value = value;
        }
        this.runAutoGrow();
    }
    /**
     * dir is a globally enumerated attribute.
     * As a result, creating these as properties
     * can have unintended side effects. Instead, we
     * listen for attribute changes and inherit them
     * to the inner `<textarea>` element.
     */
    onDirChanged(newValue) {
        this.inheritedAttributes = Object.assign(Object.assign({}, this.inheritedAttributes), { dir: newValue });
        forceUpdate(this);
    }
    /**
     * This prevents the native input from emitting the click event.
     * Instead, the click event from the ion-textarea is emitted.
     */
    onClickCapture(ev) {
        const nativeInput = this.nativeInput;
        if (nativeInput && ev.target === nativeInput) {
            ev.stopPropagation();
            this.el.click();
        }
    }
    connectedCallback() {
        const { el } = this;
        this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this));
        this.notchController = createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot);
        // Watch for class changes to update validation state
        if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
            this.validationObserver = new MutationObserver(() => {
                const newIsInvalid = checkInvalidState(this.el);
                if (this.isInvalid !== newIsInvalid) {
                    this.isInvalid = newIsInvalid;
                    // Force a re-render to update aria-describedby immediately
                    forceUpdate(this);
                }
            });
            this.validationObserver.observe(el, {
                attributes: true,
                attributeFilter: ['class'],
            });
        }
        // Always set initial state
        this.isInvalid = checkInvalidState(this.el);
        this.debounceChanged();
        if (Build.isBrowser) {
            document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
                detail: el,
            }));
        }
    }
    disconnectedCallback() {
        if (Build.isBrowser) {
            document.dispatchEvent(new CustomEvent('ionInputDidUnload', {
                detail: this.el,
            }));
        }
        if (this.slotMutationController) {
            this.slotMutationController.destroy();
            this.slotMutationController = undefined;
        }
        if (this.notchController) {
            this.notchController.destroy();
            this.notchController = undefined;
        }
        // Clean up validation observer to prevent memory leaks
        if (this.validationObserver) {
            this.validationObserver.disconnect();
            this.validationObserver = undefined;
        }
    }
    componentWillLoad() {
        this.inheritedAttributes = Object.assign(Object.assign({}, inheritAriaAttributes(this.el)), inheritAttributes(this.el, ['data-form-type', 'title', 'tabindex', 'dir']));
    }
    componentDidLoad() {
        this.originalIonInput = this.ionInput;
        this.runAutoGrow();
    }
    componentDidRender() {
        var _a;
        (_a = this.notchController) === null || _a === void 0 ? void 0 : _a.calculateNotchWidth();
    }
    /**
     * Sets focus on the native `textarea` in `ion-textarea`. Use this method instead of the global
     * `textarea.focus()`.
     *
     * See [managing focus](/docs/developing/managing-focus) for more information.
     */
    async setFocus() {
        if (this.nativeInput) {
            this.nativeInput.focus();
        }
    }
    /**
     * Returns the native `<textarea>` element used under the hood.
     */
    async getInputElement() {
        /**
         * If this gets called in certain early lifecycle hooks (ex: Vue onMounted),
         * nativeInput won't be defined yet with the custom elements build, so wait for it to load in.
         */
        if (!this.nativeInput) {
            await new Promise((resolve) => componentOnReady(this.el, resolve));
        }
        return Promise.resolve(this.nativeInput);
    }
    /**
     * Emits an `ionChange` event.
     *
     * This API should be called for user committed changes.
     * This API should not be used for external value changes.
     */
    emitValueChange(event) {
        const { value } = this;
        // Checks for both null and undefined values
        const newValue = value == null ? value : value.toString();
        // Emitting a value change should update the internal state for tracking the focused value
        this.focusedValue = newValue;
        this.ionChange.emit({ value: newValue, event });
    }
    /**
     * Emits an `ionInput` event.
     */
    emitInputChange(event) {
        const { value } = this;
        this.ionInput.emit({ value, event });
    }
    runAutoGrow() {
        if (this.nativeInput && this.autoGrow) {
            writeTask(() => {
                var _a;
                if (this.textareaWrapper) {
                    // Replicated value is an attribute to be used in the stylesheet
                    // to set the inner contents of a pseudo element.
                    this.textareaWrapper.dataset.replicatedValue = (_a = this.value) !== null && _a !== void 0 ? _a : '';
                }
            });
        }
    }
    /**
     * Check if we need to clear the text input if clearOnEdit is enabled
     */
    checkClearOnEdit(ev) {
        if (!this.clearOnEdit) {
            return;
        }
        /**
         * The following keys do not modify the
         * contents of the input. As a result, pressing
         * them should not edit the textarea.
         *
         * We can't check to see if the value of the textarea
         * was changed because we call checkClearOnEdit
         * in a keydown listener, and the key has not yet
         * been added to the textarea.
         *
         * Unlike ion-input, the "Enter" key does modify the
         * textarea by adding a new line, so "Enter" is not
         * included in the IGNORED_KEYS array.
         */
        const IGNORED_KEYS = ['Tab', 'Shift', 'Meta', 'Alt', 'Control'];
        const pressedIgnoredKey = IGNORED_KEYS.includes(ev.key);
        /**
         * Clear the textarea if the control has not been previously cleared
         * during focus.
         */
        if (!this.didTextareaClearOnEdit && this.hasValue() && !pressedIgnoredKey) {
            this.value = '';
            this.emitInputChange(ev);
        }
        /**
         * Pressing an IGNORED_KEYS first and
         * then an allowed key will cause the input to not
         * be cleared.
         */
        if (!pressedIgnoredKey) {
            this.didTextareaClearOnEdit = true;
        }
    }
    hasValue() {
        return this.getValue() !== '';
    }
    getValue() {
        return this.value || '';
    }
    renderLabel() {
        const { label } = this;
        return (h("div", { class: {
                'label-text-wrapper': true,
                'label-text-wrapper-hidden': !this.hasLabel,
            } }, label === undefined ? h("slot", { name: "label" }) : h("div", { class: "label-text" }, label)));
    }
    /**
     * Gets any content passed into the `label` slot,
     * not the <slot> definition.
     */
    get labelSlot() {
        return this.el.querySelector('[slot="label"]');
    }
    /**
     * Returns `true` if label content is provided
     * either by a prop or a content. If you want
     * to get the plaintext value of the label use
     * the `labelText` getter instead.
     */
    get hasLabel() {
        return this.label !== undefined || this.labelSlot !== null;
    }
    getFill() {
        const theme = getIonTheme(this);
        const { fill } = this;
        if (theme === 'ionic' && fill === undefined) {
            return 'outline';
        }
        return fill;
    }
    getShape() {
        const theme = getIonTheme(this);
        const { shape } = this;
        // TODO(ROU-11050): Update theme check when shapes are defined for all themes.
        if (theme === 'ionic' && shape === undefined) {
            return 'round';
        }
        return shape;
    }
    /**
     * Renders the border container when fill="outline".
     */
    renderLabelContainer() {
        const theme = getIonTheme(this);
        const hasOutlineFill = theme === 'md' && this.fill === 'outline';
        if (hasOutlineFill) {
            /**
             * The outline fill has a special outline
             * that appears around the textarea and the label.
             * Certain stacked and floating label placements cause the
             * label to translate up and create a "cut out"
             * inside of that border by using the notch-spacer element.
             */
            return [
                h("div", { class: "textarea-outline-container" }, h("div", { class: "textarea-outline-start" }), h("div", { class: {
                        'textarea-outline-notch': true,
                        'textarea-outline-notch-hidden': !this.hasLabel,
                    } }, h("div", { class: "notch-spacer", "aria-hidden": "true", ref: (el) => (this.notchSpacerEl = el) }, this.label)), h("div", { class: "textarea-outline-end" })),
                this.renderLabel(),
            ];
        }
        /**
         * If not using the outline style,
         * we can render just the label.
         */
        return this.renderLabel();
    }
    /**
     * Renders the helper text or error text values
     */
    renderHintText() {
        const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
        return [
            h("div", { id: helperTextId, class: "helper-text", "aria-live": "polite" }, !isInvalid ? helperText : null),
            h("div", { id: errorTextId, class: "error-text", role: "alert" }, isInvalid ? errorText : null),
        ];
    }
    getHintTextID() {
        const { isInvalid, helperText, errorText, helperTextId, errorTextId } = this;
        if (isInvalid && errorText) {
            return errorTextId;
        }
        if (helperText) {
            return helperTextId;
        }
        return undefined;
    }
    renderCounter() {
        const { counter, maxlength, counterFormatter, value } = this;
        if (counter !== true || maxlength === undefined) {
            return;
        }
        return h("div", { class: "counter" }, getCounterText(value, maxlength, counterFormatter));
    }
    /**
     * Responsible for rendering helper text,
     * error text, and counter. This element should only
     * be rendered if hint text is set or counter is enabled.
     */
    renderBottomContent() {
        const { counter, helperText, errorText, maxlength } = this;
        /**
         * undefined and empty string values should
         * be treated as not having helper/error text.
         */
        const hasHintText = !!helperText || !!errorText;
        const hasCounter = counter === true && maxlength !== undefined;
        if (!hasHintText && !hasCounter) {
            return;
        }
        return (h("div", { class: "textarea-bottom" }, this.renderHintText(), this.renderCounter()));
    }
    render() {
        const { inputId, disabled, readonly, size, labelPlacement, el, hasFocus } = this;
        const fill = this.getFill();
        const theme = getIonTheme(this);
        const shape = this.getShape();
        const value = this.getValue();
        const inItem = hostContext('ion-item', this.el);
        const shouldRenderHighlight = theme === 'md' && fill !== 'outline' && !inItem;
        const hasValue = this.hasValue();
        const hasStartEndSlots = el.querySelector('[slot="start"], [slot="end"]') !== null;
        /**
         * If the label is stacked, it should always sit above the textarea.
         * For floating labels, the label should move above the textarea if
         * the textarea has a value, is focused, or has anything in either
         * the start or end slot.
         *
         * If there is content in the start slot, the label would overlap
         * it if not forced to float. This is also applied to the end slot
         * because with the default or solid fills, the textarea is not
         * vertically centered in the container, but the label is. This
         * causes the slots and label to appear vertically offset from each
         * other when the label isn't floating above the input. This doesn't
         * apply to the outline fill, but this was not accounted for to keep
         * things consistent.
         *
         * TODO(FW-5592): Remove hasStartEndSlots condition
         */
        const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
        return (h(Host, { key: '3a3030f319b29d21d049221d67978c04bbda9e70', class: createColorClasses(this.color, {
                [theme]: true,
                'has-value': hasValue,
                'has-focus': hasFocus,
                'label-floating': labelShouldFloat,
                [`textarea-fill-${fill}`]: fill !== undefined,
                [`textarea-shape-${shape}`]: shape !== undefined,
                [`textarea-size-${size}`]: true,
                [`textarea-label-placement-${labelPlacement}`]: true,
                'textarea-disabled': disabled,
                'textarea-readonly': readonly,
            }) }, h("label", { key: 'f1fa84c3818dc97a8f42ff1296f93b7de865ab47', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: '5cadc4944b88f4d9762e244b2ea2c7300c6ce722', class: "textarea-wrapper-inner" }, 
        /**
         * For the ionic theme, we render the outline container here
         * instead of higher up, so it can be positioned relative to
         * the native wrapper instead of the <label> element or the
         * entire component. This allows the label text to be positioned
         * above the outline, while staying within the bounds of the
         * <label> element, ensuring that clicking the label text
         * focuses the textarea.
         */
        theme === 'ionic' && fill === 'outline' && h("div", { key: '6bbf8ffa4279047982e4ce42eb93a34da739bb60', class: "textarea-outline" }), h("div", { key: '492e9c6757f5475b0cb1b599ec829005ced19f61', class: "start-slot-wrapper" }, h("slot", { key: '746790cb2b2eb2f438a3309d5ce2de9cf9d93204', name: "start" })), h("div", { key: '7da04995627dbf0728e623f41184f85c8ffffe83', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, h("textarea", Object.assign({ key: 'a5f2c80d229732ece2602020c0da197f93d7a2e0', class: "native-textarea", ref: (el) => (this.nativeInput = el), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoFocus: this.autofocus, enterKeyHint: this.enterkeyhint, inputMode: this.inputmode, minLength: this.minlength, maxLength: this.maxlength, name: this.name, placeholder: this.placeholder || '', readOnly: this.readonly, required: this.required, spellcheck: this.spellcheck, cols: this.cols, rows: this.rows, wrap: this.wrap, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeyDown, "aria-describedby": this.getHintTextID(), "aria-invalid": this.isInvalid ? 'true' : undefined }, this.inheritedAttributes), value)), h("div", { key: '8d33c7f6d450642f6f4ceff9624793ca3a47d36f', class: "end-slot-wrapper" }, h("slot", { key: 'd1ec6a465874956720c0f034cd445557450f34fd', name: "end" }))), shouldRenderHighlight && h("div", { key: 'c20a26e67bb8d1c1328eb806a24dfe4222c3d4d5', class: "textarea-highlight" })), this.renderBottomContent()));
    }
    static get is() { return "ion-textarea"; }
    static get encapsulation() { return "scoped"; }
    static get originalStyleUrls() {
        return {
            "ios": ["textarea.ios.scss"],
            "md": ["textarea.md.scss"],
            "ionic": ["textarea.ionic.scss"]
        };
    }
    static get styleUrls() {
        return {
            "ios": ["textarea.ios.css"],
            "md": ["textarea.md.css"],
            "ionic": ["textarea.ionic.css"]
        };
    }
    static get properties() {
        return {
            "color": {
                "type": "string",
                "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,
                "attribute": "color"
            },
            "autocapitalize": {
                "type": "string",
                "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,
                "attribute": "autocapitalize",
                "defaultValue": "'none'"
            },
            "autofocus": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Sets the [`autofocus` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) on the native input element.\n\nThis may not be sufficient for the element to be focused on page load. See [managing focus](/docs/developing/managing-focus) for more information."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "autofocus",
                "defaultValue": "false"
            },
            "clearOnEdit": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "If `true`, the value will be cleared after focus upon edit."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "clear-on-edit",
                "defaultValue": "false"
            },
            "debounce": {
                "type": "number",
                "mutable": false,
                "complexType": {
                    "original": "number",
                    "resolved": "number | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "Set the amount of time, in milliseconds, to wait to trigger the `ionInput` event after each keystroke."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "debounce"
            },
            "disabled": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "If `true`, the user cannot interact with the textarea."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "disabled",
                "defaultValue": "false"
            },
            "fill": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "'outline' | 'solid'",
                    "resolved": "\"outline\" | \"solid\" | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "The fill for the item. If `\"solid\"` the item will have a background. If\n`\"outline\"` the item will be transparent with a border. Only available when the theme is `\"md\"`."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "fill"
            },
            "inputmode": {
                "type": "string",
                "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\"`."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "inputmode"
            },
            "enterkeyhint": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'",
                    "resolved": "\"done\" | \"enter\" | \"go\" | \"next\" | \"previous\" | \"search\" | \"send\" | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "A hint to the browser for which enter key to display.\nPossible values: `\"enter\"`, `\"done\"`, `\"go\"`, `\"next\"`,\n`\"previous\"`, `\"search\"`, and `\"send\"`."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "enterkeyhint"
            },
            "maxlength": {
                "type": "number",
                "mutable": false,
                "complexType": {
                    "original": "number",
                    "resolved": "number | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "This attribute specifies the maximum number of characters that the user can enter."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "maxlength"
            },
            "minlength": {
                "type": "number",
                "mutable": false,
                "complexType": {
                    "original": "number",
                    "resolved": "number | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "This attribute specifies the minimum number of characters that the user can enter."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "minlength"
            },
            "name": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The name of the control, which is submitted with the form data."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "name",
                "defaultValue": "this.inputId"
            },
            "placeholder": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "Instructional text that shows before the input has a value."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "placeholder"
            },
            "readonly": {
                "type": "boolean",
                "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": false,
                "attribute": "readonly",
                "defaultValue": "false"
            },
            "required": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "If `true`, the user must fill in a value before submitting a form."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "required",
                "defaultValue": "false"
            },
            "spellcheck": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "If `true`, the element will have its spelling and grammar checked."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "spellcheck",
                "defaultValue": "false"
            },
            "cols": {
                "type": "number",
                "mutable": false,
                "complexType": {
                    "original": "number",
                    "resolved": "number | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "The visible width of the text control, in average character widths. If it is specified, it must be a positive integer."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "cols"
            },
            "rows": {
                "type": "number",
                "mutable": false,
                "complexType": {
                    "original": "number",
                    "resolved": "number | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "The number of visible text lines for the control."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "rows"
            },
            "wrap": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "'hard' | 'soft' | 'off'",
                    "resolved": "\"hard\" | \"off\" | \"soft\" | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "Indicates how the control wraps text."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "wrap"
            },
            "autoGrow": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "If `true`, the textarea container will grow and shrink based\non the contents of the textarea."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "auto-grow",
                "defaultValue": "false"
            },
            "value": {
                "type": "string",
                "mutable": true,
                "complexType": {
                    "original": "string | null",
                    "resolved": "null | string | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "The value of the textarea."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "value",
                "defaultValue": "''"
            },
            "counter": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "If `true`, a character counter will display the ratio of characters used and the total character limit.\nDevelopers must also set the `maxlength` property for the counter to be calculated correctly."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "counter",
                "defaultValue": "false"
            },
            "counterFormatter": {
                "type": "unknown",
                "mutable": false,
                "complexType": {
                    "original": "(inputLength: number, maxLength: number) => string",
                    "resolved": "((inputLength: number, maxLength: number) => string) | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "A callback used to format the counter text.\nBy default the counter text is set to \"itemLength / maxLength\".\n\nSee https://ionicframework.com/docs/troubleshooting/runtime#accessing-this\nif you need to access `this` from within the callback."
                },
                "getter": false,
                "setter": false
            },
            "errorText": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "Text that is placed under the textarea and displayed when an error is detected."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "error-text"
            },
            "helperText": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "Text that is placed under the textarea and displayed when no error is detected."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "helper-text"
            },
            "label": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "The visible label associated with the textarea.\n\nUse this if you need to render a plaintext label.\n\nThe `label` property will take priority over the `label` slot if both are used."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "label"
            },
            "labelPlacement": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "'start' | 'end' | 'floating' | 'stacked' | 'fixed'",
                    "resolved": "\"end\" | \"fixed\" | \"floating\" | \"stacked\" | \"start\"",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Where to place the label relative to the textarea.\n`\"start\"`: The label will appear to the left of the textarea in LTR and to the right in RTL.\n`\"end\"`: The label will appear to the right of the textarea in LTR and to the left in RTL.\n`\"floating\"`: The label will appear smaller and above the textarea when the textarea is focused or it has a value. Otherwise it will appear on top of the textarea.\n`\"stacked\"`: The label will appear smaller and above the textarea regardless even when the textarea is blurred or has no value.\n`\"fixed\"`: The label has the same behavior as `\"start\"` except it also has a fixed width. Long text will be truncated with ellipses (\"...\")."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "label-placement",
                "defaultValue": "'start'"
            },
            "shape": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "'soft' | 'round' | 'rectangular'",
                    "resolved": "\"rectangular\" | \"round\" | \"soft\" | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "Set to `\"soft\"` for a textarea with slightly rounded corners,\n`\"round\"` for a textarea with fully rounded corners, or `\"rectangular\"`\nfor a textarea without rounded corners.\n\nDefaults to `\"round\"` for the `ionic` theme, undefined for all other themes."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "shape"
            },
            "size": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "'small' | 'medium' | 'large'",
                    "resolved": "\"large\" | \"medium\" | \"small\" | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "The size of the textarea. If \"large\" it will increase the height of the textarea, while\n\"small\" and \"medium\" provide progressively smaller heights. The default size is \"medium\".\nThis property only applies to the `\"ionic\"` theme."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "size",
                "defaultValue": "'medium'"
            }
        };
    }
    static get states() {
        return {
            "hasFocus": {},
            "isInvalid": {}
        };
    }
    static get events() {
        return [{
                "method": "ionChange",
                "name": "ionChange",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "The `ionChange` event is fired when the user modifies the textarea's value.\nUnlike the `ionInput` event, the `ionChange` event is fired when\nthe element loses focus after its value has been modified.\n\nThis event will not emit when programmatically setting the `value` property."
                },
                "complexType": {
                    "original": "TextareaChangeEventDetail",
                    "resolved": "TextareaChangeEventDetail",
                    "references": {
                        "TextareaChangeEventDetail": {
                            "location": "import",
                            "path": "./textarea-interface",
                            "id": "src/components/textarea/textarea-interface.ts::TextareaChangeEventDetail"
                        }
                    }
                }
            }, {
                "method": "ionInput",
                "name": "ionInput",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "The `ionInput` event is fired each time the user modifies the textarea's value.\nUnlike the `ionChange` event, the `ionInput` event is fired for each alteration\nto the textarea's value. This typically happens for each keystroke as the user types.\n\nWhen `clearOnEdit` is enabled, the `ionInput` event will be fired when\nthe user clears the textarea by performing a keydown event."
                },
                "complexType": {
                    "original": "TextareaInputEventDetail",
                    "resolved": "TextareaInputEventDetail",
                    "references": {
                        "TextareaInputEventDetail": {
                            "location": "import",
                            "path": "./textarea-interface",
                            "id": "src/components/textarea/textarea-interface.ts::TextareaInputEventDetail"
                        }
                    }
                }
            }, {
                "method": "ionBlur",
                "name": "ionBlur",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when the input 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 has focus."
                },
                "complexType": {
                    "original": "FocusEvent",
                    "resolved": "FocusEvent",
                    "references": {
                        "FocusEvent": {
                            "location": "global",
                            "id": "global::FocusEvent"
                        }
                    }
                }
            }];
    }
    static get methods() {
        return {
            "setFocus": {
                "complexType": {
                    "signature": "() => Promise<void>",
                    "parameters": [],
                    "references": {
                        "Promise": {
                            "location": "global",
                            "id": "global::Promise"
                        }
                    },
                    "return": "Promise<void>"
                },
                "docs": {
                    "text": "Sets focus on the native `textarea` in `ion-textarea`. Use this method instead of the global\n`textarea.focus()`.\n\nSee [managing focus](/docs/developing/managing-focus) for more information.",
                    "tags": []
                }
            },
            "getInputElement": {
                "complexType": {
                    "signature": "() => Promise<HTMLTextAreaElement>",
                    "parameters": [],
                    "references": {
                        "Promise": {
                            "location": "global",
                            "id": "global::Promise"
                        },
                        "HTMLTextAreaElement": {
                            "location": "global",
                            "id": "global::HTMLTextAreaElement"
                        }
                    },
                    "return": "Promise<HTMLTextAreaElement>"
                },
                "docs": {
                    "text": "Returns the native `<textarea>` element used under the hood.",
                    "tags": []
                }
            }
        };
    }
    static get elementRef() { return "el"; }
    static get watchers() {
        return [{
                "propName": "debounce",
                "methodName": "debounceChanged"
            }, {
                "propName": "value",
                "methodName": "valueChanged"
            }, {
                "propName": "dir",
                "methodName": "onDirChanged"
            }];
    }
    static get listeners() {
        return [{
                "name": "click",
                "method": "onClickCapture",
                "target": undefined,
                "capture": true,
                "passive": false
            }];
    }
}
let textareaIds = 0;
