diff --git a/angular/src/directives/proxies.ts b/angular/src/directives/proxies.ts index ff1b352f85..829d9b20e3 100644 --- a/angular/src/directives/proxies.ts +++ b/angular/src/directives/proxies.ts @@ -2114,14 +2114,14 @@ the user clears the textarea by performing a keydown event. @ProxyCmp({ defineCustomElementFn: undefined, - inputs: ['autoGrow', 'autocapitalize', 'autofocus', 'clearOnEdit', 'color', 'cols', 'debounce', 'disabled', 'enterkeyhint', 'inputmode', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'readonly', 'required', 'rows', 'spellcheck', 'useBase', 'value', 'wrap'], + inputs: ['autoGrow', 'autocapitalize', 'autofocus', 'clearOnEdit', 'color', 'cols', 'counter', 'counterFormatter', 'debounce', 'disabled', 'enterkeyhint', 'errorText', 'fill', 'helperText', 'inputmode', 'label', 'labelPlacement', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'readonly', 'required', 'rows', 'shape', 'spellcheck', 'useBase', 'value', 'wrap'], methods: ['setFocus', 'getInputElement'] }) @Component({ selector: 'ion-textarea', changeDetection: ChangeDetectionStrategy.OnPush, template: '', - inputs: ['autoGrow', 'autocapitalize', 'autofocus', 'clearOnEdit', 'color', 'cols', 'debounce', 'disabled', 'enterkeyhint', 'inputmode', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'readonly', 'required', 'rows', 'spellcheck', 'useBase', 'value', 'wrap'] + inputs: ['autoGrow', 'autocapitalize', 'autofocus', 'clearOnEdit', 'color', 'cols', 'counter', 'counterFormatter', 'debounce', 'disabled', 'enterkeyhint', 'errorText', 'fill', 'helperText', 'inputmode', 'label', 'labelPlacement', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'readonly', 'required', 'rows', 'shape', 'spellcheck', 'useBase', 'value', 'wrap'] }) export class IonTextarea { protected el: HTMLElement; diff --git a/core/api.txt b/core/api.txt index be7b0f4fd2..708527ad93 100644 --- a/core/api.txt +++ b/core/api.txt @@ -1413,10 +1413,17 @@ ion-textarea,prop,autofocus,boolean,false,false,false ion-textarea,prop,clearOnEdit,boolean,false,false,false ion-textarea,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-textarea,prop,cols,number | undefined,undefined,false,false +ion-textarea,prop,counter,boolean,false,false,false +ion-textarea,prop,counterFormatter,((inputLength: number, maxLength: number) => string) | undefined,undefined,false,false ion-textarea,prop,debounce,number | undefined,undefined,false,false ion-textarea,prop,disabled,boolean,false,false,false ion-textarea,prop,enterkeyhint,"done" | "enter" | "go" | "next" | "previous" | "search" | "send" | undefined,undefined,false,false +ion-textarea,prop,errorText,string | undefined,undefined,false,false +ion-textarea,prop,fill,"outline" | "solid" | undefined,undefined,false,false +ion-textarea,prop,helperText,string | undefined,undefined,false,false ion-textarea,prop,inputmode,"decimal" | "email" | "none" | "numeric" | "search" | "tel" | "text" | "url" | undefined,undefined,false,false +ion-textarea,prop,label,string | undefined,undefined,false,false +ion-textarea,prop,labelPlacement,"end" | "fixed" | "floating" | "stacked" | "start",'start',false,false ion-textarea,prop,maxlength,number | undefined,undefined,false,false ion-textarea,prop,minlength,number | undefined,undefined,false,false ion-textarea,prop,mode,"ios" | "md",undefined,false,false @@ -1425,6 +1432,7 @@ ion-textarea,prop,placeholder,string | undefined,undefined,false,false ion-textarea,prop,readonly,boolean,false,false,false ion-textarea,prop,required,boolean,false,false,false ion-textarea,prop,rows,number | undefined,undefined,false,false +ion-textarea,prop,shape,"round" | undefined,undefined,false,false ion-textarea,prop,spellcheck,boolean,false,false,false ion-textarea,prop,useBase,true | false,undefined,false,false ion-textarea,prop,value,null | string | undefined,'',false,false @@ -1436,8 +1444,14 @@ ion-textarea,event,ionChange,TextareaChangeEventDetail,true ion-textarea,event,ionFocus,FocusEvent,true ion-textarea,event,ionInput,TextareaInputEventDetail,true ion-textarea,css-prop,--background +ion-textarea,css-prop,--border-color ion-textarea,css-prop,--border-radius +ion-textarea,css-prop,--border-style +ion-textarea,css-prop,--border-width ion-textarea,css-prop,--color +ion-textarea,css-prop,--highlight-color-focused +ion-textarea,css-prop,--highlight-color-invalid +ion-textarea,css-prop,--highlight-color-valid ion-textarea,css-prop,--padding-bottom ion-textarea,css-prop,--padding-end ion-textarea,css-prop,--padding-start diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 6e0d0ff707..a853016628 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -3143,6 +3143,14 @@ export namespace Components { * The visible width of the text control, in average character widths. If it is specified, it must be a positive integer. */ "cols"?: number; + /** + * 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. + */ + "counter": boolean; + /** + * A callback used to format the counter text. By default the counter text is set to "itemLength / maxLength". + */ + "counterFormatter"?: (inputLength: number, maxLength: number) => string; /** * Set the amount of time, in milliseconds, to wait to trigger the `ionInput` event after each keystroke. */ @@ -3155,14 +3163,34 @@ export namespace Components { * A hint to the browser for which enter key to display. Possible values: `"enter"`, `"done"`, `"go"`, `"next"`, `"previous"`, `"search"`, and `"send"`. */ "enterkeyhint"?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'; + /** + * Text that is placed under the textarea and displayed when an error is detected. + */ + "errorText"?: string; + /** + * The fill for the item. If `'solid'` the item will have a background. If `'outline'` the item will be transparent with a border. Only available in `md` mode. + */ + "fill"?: 'outline' | 'solid'; /** * Returns the native ` + + {shouldRenderHighlight &&
} + + {this.renderBottomContent()} + + ); + } + + render() { + const { legacyFormController } = this; + + return legacyFormController.hasLegacyControl() ? this.renderLegacyTextarea() : this.renderTextarea(); + } } let textareaIds = 0; diff --git a/core/src/components/textarea/textarea.vars.scss b/core/src/components/textarea/textarea.vars.scss index e21c162d17..45279027f3 100644 --- a/core/src/components/textarea/textarea.vars.scss +++ b/core/src/components/textarea/textarea.vars.scss @@ -1 +1,10 @@ @import "../../themes/ionic.globals"; + +/// @prop - How much to scale the floating label by. +/// The value 0.75 is used to match the MD spec. +/// iOS does not have a floating label design spec, so we standardize on 0.75. +$textarea-floating-label-scale: 0.75 !default; + +/// @prop - The bottom padding of the textarea element. +/// The value 8px is to add additional spacing for auto grow and scrollable textareas. +$textarea-padding-bottom: 8px !default; diff --git a/packages/vue/src/proxies.ts b/packages/vue/src/proxies.ts index 65e16d861f..6e4ce470af 100644 --- a/packages/vue/src/proxies.ts +++ b/packages/vue/src/proxies.ts @@ -788,6 +788,7 @@ export const IonTextarea = /*@__PURE__*/ defineContainer('ion-t 'clearOnEdit', 'debounce', 'disabled', + 'fill', 'inputmode', 'enterkeyhint', 'maxlength', @@ -802,6 +803,13 @@ export const IonTextarea = /*@__PURE__*/ defineContainer('ion-t 'wrap', 'autoGrow', 'value', + 'counter', + 'counterFormatter', + 'errorText', + 'helperText', + 'label', + 'labelPlacement', + 'shape', 'ionChange', 'ionInput', 'ionStyle',