UNPKG

@ionic/core

Version:
1,056 lines (1,055 loc) • 44.4 kB
/*! * (C) Ionic http://ionicframework.com - MIT License */ import { Build, Host, forceUpdate, h, writeTask, } from "@stencil/core"; import { createNotchController } 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 { getIonMode } from "../../global/ionic-global"; import { getCounterText } from "../input/input.utils"; /** * @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use. * * @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 = {}; // `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); }; this.hasFocus = false; this.color = undefined; this.autocapitalize = 'none'; this.autofocus = false; this.clearOnEdit = false; this.debounce = undefined; this.disabled = false; this.fill = undefined; this.inputmode = undefined; this.enterkeyhint = undefined; this.maxlength = undefined; this.minlength = undefined; this.name = this.inputId; this.placeholder = undefined; this.readonly = false; this.required = false; this.spellcheck = false; this.cols = undefined; this.rows = undefined; this.wrap = undefined; this.autoGrow = false; this.value = ''; this.counter = false; this.counterFormatter = undefined; this.errorText = undefined; this.helperText = undefined; this.label = undefined; this.labelPlacement = 'start'; this.shape = undefined; } 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); } connectedCallback() { const { el } = this; this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this)); this.notchController = createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot); 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; } } 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; } /** * Renders the border container when fill="outline". */ renderLabelContainer() { const mode = getIonMode(this); const hasOutlineFill = mode === '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 } = this; return [ h("div", { id: helperTextId, class: "helper-text" }, helperText), h("div", { id: errorTextId, class: "error-text" }, errorText), ]; } getHintTextID() { const { el, helperText, errorText, helperTextId, errorTextId } = this; if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && 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, fill, shape, labelPlacement, el, hasFocus } = this; const mode = getIonMode(this); const value = this.getValue(); const inItem = hostContext('ion-item', this.el); const shouldRenderHighlight = mode === '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: '3bf42ea1fa739f334427c70f91a89b8b0e0f21ec', class: createColorClasses(this.color, { [mode]: true, 'has-value': hasValue, 'has-focus': hasFocus, 'label-floating': labelShouldFloat, [`textarea-fill-${fill}`]: fill !== undefined, [`textarea-shape-${shape}`]: shape !== undefined, [`textarea-label-placement-${labelPlacement}`]: true, 'textarea-disabled': disabled, }) }, h("label", { key: 'f7acceeb74849d13a6af8d39b66ea2701384c955', class: "textarea-wrapper", htmlFor: inputId }, this.renderLabelContainer(), h("div", { key: '05e9f2ebad1742a9e66d243d18c22ddd4c83ee04', class: "textarea-wrapper-inner" }, h("div", { key: '70a9935351c38413ced05f5e4dc0055b9b001ee9', class: "start-slot-wrapper" }, h("slot", { key: '11206eaa31d112c01f2218bf2ff46375ad819d1f', name: "start" })), h("div", { key: 'bfd215dbb5f4d60f5fb62d37226fdc6ce61ec107', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, h("textarea", Object.assign({ key: '2fa5cdf4c6614dd8cd4b7d2a19811a79c96ac50d', 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.getHintTextID() === this.errorTextId }, this.inheritedAttributes), value)), h("div", { key: '8a67ad8e3b5650680f770cefc562d190f6760718', class: "end-slot-wrapper" }, h("slot", { key: '03bb6bc5f2808c1f474a6c3168169d17f7569fda', name: "end" }))), shouldRenderHighlight && h("div", { key: 'cd564bd01a1ca3f914218e34a78d296ad0838e4a', 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"] }; } static get styleUrls() { return { "ios": ["textarea.ios.css"], "md": ["textarea.md.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)." }, "attribute": "color", "reflect": true }, "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\"`." }, "attribute": "autocapitalize", "reflect": false, "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." }, "attribute": "autofocus", "reflect": false, "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." }, "attribute": "clear-on-edit", "reflect": false, "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." }, "attribute": "debounce", "reflect": false }, "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." }, "attribute": "disabled", "reflect": false, "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 in `md` mode." }, "attribute": "fill", "reflect": false }, "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\"`." }, "attribute": "inputmode", "reflect": false }, "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\"`." }, "attribute": "enterkeyhint", "reflect": false }, "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." }, "attribute": "maxlength", "reflect": false }, "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." }, "attribute": "minlength", "reflect": false }, "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." }, "attribute": "name", "reflect": false, "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." }, "attribute": "placeholder", "reflect": false }, "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." }, "attribute": "readonly", "reflect": false, "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." }, "attribute": "required", "reflect": false, "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." }, "attribute": "spellcheck", "reflect": false, "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." }, "attribute": "cols", "reflect": true }, "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." }, "attribute": "rows", "reflect": false }, "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." }, "attribute": "wrap", "reflect": false }, "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." }, "attribute": "auto-grow", "reflect": true, "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." }, "attribute": "value", "reflect": false, "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." }, "attribute": "counter", "reflect": false, "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." } }, "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." }, "attribute": "error-text", "reflect": false }, "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." }, "attribute": "helper-text", "reflect": false }, "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." }, "attribute": "label", "reflect": false }, "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 (\"...\")." }, "attribute": "label-placement", "reflect": false, "defaultValue": "'start'" }, "shape": { "type": "string", "mutable": false, "complexType": { "original": "'round'", "resolved": "\"round\" | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "The shape of the textarea. If \"round\" it will have an increased border radius." }, "attribute": "shape", "reflect": false } }; } static get states() { return { "hasFocus": {} }; } 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" }]; } } let textareaIds = 0;