UNPKG

@ionic/core

Version:

Base components for Ionic

785 lines (784 loc) • 22.7 kB
import { Build, Component, Element, Event, Host, Method, Prop, State, Watch, h, readTask } from '@stencil/core'; import { getIonMode } from '../../global/ionic-global'; import { debounceEvent, findItemLabel, inheritAttributes, raf } from '../../utils/helpers'; import { createColorClasses } from '../../utils/theme'; /** * @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use. */ export class Textarea { constructor() { this.inputId = `ion-textarea-${textareaIds++}`; this.didBlurAfterEdit = false; this.inheritedAttributes = {}; /** * This is required for a WebKit bug which requires us to * blur and focus an input to properly focus the input in * an item with delegatesFocus. It will no longer be needed * with iOS 14. * * @internal */ this.fireFocusEvents = true; this.hasFocus = false; /** * Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user. */ this.autocapitalize = 'none'; /** * This Boolean attribute lets you specify that a form control should have input focus when the page loads. */ this.autofocus = false; /** * If `true`, the value will be cleared after focus upon edit. Defaults to `true` when `type` is `"password"`, `false` for all other types. */ this.clearOnEdit = false; /** * Set the amount of time, in milliseconds, to wait to trigger the `ionChange` event after each keystroke. This also impacts form bindings such as `ngModel` or `v-model`. */ this.debounce = 0; /** * 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 element height will increase based on the value. */ this.autoGrow = false; /** * The value of the textarea. */ this.value = ''; this.onInput = (ev) => { if (this.nativeInput) { this.value = this.nativeInput.value; } this.emitStyle(); this.ionInput.emit(ev); }; this.onFocus = (ev) => { this.hasFocus = true; this.focusChange(); if (this.fireFocusEvents) { this.ionFocus.emit(ev); } }; this.onBlur = (ev) => { this.hasFocus = false; this.focusChange(); if (this.fireFocusEvents) { this.ionBlur.emit(ev); } }; this.onKeyDown = () => { this.checkClearOnEdit(); }; } debounceChanged() { this.ionChange = debounceEvent(this.ionChange, this.debounce); } disabledChanged() { this.emitStyle(); } /** * 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(); this.emitStyle(); this.ionChange.emit({ value }); } connectedCallback() { this.emitStyle(); this.debounceChanged(); if (Build.isBrowser) { document.dispatchEvent(new CustomEvent('ionInputDidLoad', { detail: this.el })); } } disconnectedCallback() { if (Build.isBrowser) { document.dispatchEvent(new CustomEvent('ionInputDidUnload', { detail: this.el })); } } componentWillLoad() { this.inheritedAttributes = inheritAttributes(this.el, ['title']); } componentDidLoad() { raf(() => this.runAutoGrow()); } runAutoGrow() { const nativeInput = this.nativeInput; if (nativeInput && this.autoGrow) { readTask(() => { nativeInput.style.height = 'auto'; nativeInput.style.height = nativeInput.scrollHeight + 'px'; if (this.textareaWrapper) { this.textareaWrapper.style.height = nativeInput.scrollHeight + 'px'; } }); } } /** * Sets focus on the native `textarea` in `ion-textarea`. Use this method instead of the global * `textarea.focus()`. */ async setFocus() { if (this.nativeInput) { this.nativeInput.focus(); } } /** * Sets blur on the native `textarea` in `ion-textarea`. Use this method instead of the global * `textarea.blur()`. * @internal */ async setBlur() { if (this.nativeInput) { this.nativeInput.blur(); } } /** * Returns the native `<textarea>` element used under the hood. */ getInputElement() { return Promise.resolve(this.nativeInput); } emitStyle() { this.ionStyle.emit({ 'interactive': true, 'textarea': true, 'input': true, 'interactive-disabled': this.disabled, 'has-placeholder': this.placeholder != null, 'has-value': this.hasValue(), 'has-focus': this.hasFocus }); } /** * Check if we need to clear the text input if clearOnEdit is enabled */ checkClearOnEdit() { if (!this.clearOnEdit) { return; } // Did the input value change after it was blurred and edited? if (this.didBlurAfterEdit && this.hasValue()) { // Clear the input this.value = ''; } // Reset the flag this.didBlurAfterEdit = false; } focusChange() { // If clearOnEdit is enabled and the input blurred but has a value, set a flag if (this.clearOnEdit && !this.hasFocus && this.hasValue()) { this.didBlurAfterEdit = true; } this.emitStyle(); } hasValue() { return this.getValue() !== ''; } getValue() { return this.value || ''; } render() { const mode = getIonMode(this); const value = this.getValue(); const labelId = this.inputId + '-lbl'; const label = findItemLabel(this.el); if (label) { label.id = labelId; } return (h(Host, { "aria-disabled": this.disabled ? 'true' : null, class: createColorClasses(this.color, { [mode]: true, }) }, h("div", { class: "textarea-wrapper", ref: el => this.textareaWrapper = el }, h("textarea", Object.assign({ class: "native-textarea", "aria-labelledby": label ? labelId : null, ref: el => this.nativeInput = el, autoCapitalize: this.autocapitalize, autoFocus: this.autofocus, enterKeyHint: this.enterkeyhint, inputMode: this.inputmode, disabled: this.disabled, maxLength: this.maxlength, minLength: this.minlength, 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, onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeyDown }, this.inheritedAttributes), value)))); } 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 { "fireFocusEvents": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [{ "text": undefined, "name": "internal" }], "text": "This is required for a WebKit bug which requires us to\nblur and focus an input to properly focus the input in\nan item with delegatesFocus. It will no longer be needed\nwith iOS 14." }, "attribute": "fire-focus-events", "reflect": false, "defaultValue": "true" }, "color": { "type": "string", "mutable": false, "complexType": { "original": "Color", "resolved": "string | undefined", "references": { "Color": { "location": "import", "path": "../../interface" } } }, "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." }, "attribute": "autocapitalize", "reflect": false, "defaultValue": "'none'" }, "autofocus": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "This Boolean attribute lets you specify that a form control should have input focus when the page loads." }, "attribute": "autofocus", "reflect": false, "defaultValue": "false" }, "clearOnEdit": { "type": "boolean", "mutable": true, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "If `true`, the value will be cleared after focus upon edit. Defaults to `true` when `type` is `\"password\"`, `false` for all other types." }, "attribute": "clear-on-edit", "reflect": false, "defaultValue": "false" }, "debounce": { "type": "number", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Set the amount of time, in milliseconds, to wait to trigger the `ionChange` event after each keystroke. This also impacts form bindings such as `ngModel` or `v-model`." }, "attribute": "debounce", "reflect": false, "defaultValue": "0" }, "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" }, "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": "If the value of the type attribute is `text`, `email`, `search`, `password`, `tel`, or `url`, 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": "If the value of the type attribute is `text`, `email`, `search`, `password`, `tel`, or `url`, 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 | null", "resolved": "null | 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": false }, "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 element height will increase based on the value." }, "attribute": "auto-grow", "reflect": false, "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": "''" } }; } static get states() { return { "hasFocus": {} }; } static get events() { return [{ "method": "ionChange", "name": "ionChange", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emitted when the input value has changed." }, "complexType": { "original": "TextareaChangeEventDetail", "resolved": "TextareaChangeEventDetail", "references": { "TextareaChangeEventDetail": { "location": "import", "path": "../../interface" } } } }, { "method": "ionInput", "name": "ionInput", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emitted when a keyboard input occurred." }, "complexType": { "original": "KeyboardEvent", "resolved": "KeyboardEvent", "references": { "KeyboardEvent": { "location": "global" } } } }, { "method": "ionStyle", "name": "ionStyle", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [{ "text": undefined, "name": "internal" }], "text": "Emitted when the styles change." }, "complexType": { "original": "StyleEventDetail", "resolved": "StyleEventDetail", "references": { "StyleEventDetail": { "location": "import", "path": "../../interface" } } } }, { "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" } } } }, { "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" } } } }]; } static get methods() { return { "setFocus": { "complexType": { "signature": "() => Promise<void>", "parameters": [], "references": { "Promise": { "location": "global" } }, "return": "Promise<void>" }, "docs": { "text": "Sets focus on the native `textarea` in `ion-textarea`. Use this method instead of the global\n`textarea.focus()`.", "tags": [] } }, "setBlur": { "complexType": { "signature": "() => Promise<void>", "parameters": [], "references": { "Promise": { "location": "global" } }, "return": "Promise<void>" }, "docs": { "text": "Sets blur on the native `textarea` in `ion-textarea`. Use this method instead of the global\n`textarea.blur()`.", "tags": [{ "name": "internal", "text": undefined }] } }, "getInputElement": { "complexType": { "signature": "() => Promise<HTMLTextAreaElement>", "parameters": [], "references": { "Promise": { "location": "global" }, "HTMLTextAreaElement": { "location": "global" } }, "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": "disabled", "methodName": "disabledChanged" }, { "propName": "value", "methodName": "valueChanged" }]; } } let textareaIds = 0;