diff --git a/core/api.txt b/core/api.txt index 977f9e9ee8..98512aed32 100644 --- a/core/api.txt +++ b/core/api.txt @@ -910,7 +910,7 @@ ion-input,prop,fill,"outline" | "solid" | undefined,undefined,false,false ion-input,prop,helperText,string | undefined,undefined,false,false ion-input,prop,inputmode,"decimal" | "email" | "none" | "numeric" | "search" | "tel" | "text" | "url" | undefined,undefined,false,false ion-input,prop,label,string | undefined,undefined,false,false -ion-input,prop,labelPlacement,"end" | "fixed" | "floating" | "stacked" | "start",'start',false,false +ion-input,prop,labelPlacement,"end" | "fixed" | "floating" | "stacked" | "start" | undefined,undefined,false,false ion-input,prop,max,number | string | undefined,undefined,false,false ion-input,prop,maxlength,number | undefined,undefined,false,false ion-input,prop,min,number | string | undefined,undefined,false,false @@ -922,7 +922,8 @@ ion-input,prop,pattern,string | undefined,undefined,false,false ion-input,prop,placeholder,string | undefined,undefined,false,false ion-input,prop,readonly,boolean,false,false,true ion-input,prop,required,boolean,false,false,false -ion-input,prop,shape,"round" | undefined,undefined,false,false +ion-input,prop,shape,"rectangular" | "round" | "soft" | undefined,undefined,false,false +ion-input,prop,size,"large" | "medium" | "xlarge" | undefined,'medium',false,false ion-input,prop,spellcheck,boolean,false,false,false ion-input,prop,step,string | undefined,undefined,false,false ion-input,prop,theme,"ios" | "md" | "ionic",undefined,false,false @@ -988,6 +989,9 @@ ion-input,css-prop,--placeholder-font-weight,md ion-input,css-prop,--placeholder-opacity,ionic ion-input,css-prop,--placeholder-opacity,ios ion-input,css-prop,--placeholder-opacity,md +ion-input,css-prop,--text-color-invalid,ionic +ion-input,css-prop,--text-color-invalid,ios +ion-input,css-prop,--text-color-invalid,md ion-input-password-toggle,shadow ion-input-password-toggle,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 0caf827446..c3905ecdf4 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -1435,9 +1435,9 @@ export namespace Components { */ "label"?: string; /** - * Where to place the label relative to the input. `"start"`: The label will appear to the left of the input in LTR and to the right in RTL. `"end"`: The label will appear to the right of the input in LTR and to the left in RTL. `"floating"`: The label will appear smaller and above the input when the input is focused or it has a value. Otherwise it will appear on top of the input. `"stacked"`: The label will appear smaller and above the input regardless even when the input 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 ("..."). + * Where to place the label relative to the input. `"start"`: The label will appear to the left of the input in LTR and to the right in RTL. `"end"`: The label will appear to the right of the input in LTR and to the left in RTL. `"floating"`: The label will appear smaller and above the input when the input is focused or it has a value. Otherwise it will appear on top of the input. `"stacked"`: The label will appear smaller and above the input regardless even when the input 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 ("..."). Defaults to "stacked" for the ionic theme, or "start" for all other themes. In the ionic theme, only the values "stacked" and "floating" are supported. */ - "labelPlacement": 'start' | 'end' | 'floating' | 'stacked' | 'fixed'; + "labelPlacement"?: 'start' | 'end' | 'floating' | 'stacked' | 'fixed'; /** * The maximum value, which must not be less than its minimum (min attribute) value. */ @@ -1487,9 +1487,13 @@ export namespace Components { */ "setFocus": () => Promise; /** - * The shape of the input. If "round" it will have an increased border radius. + * Set to `"soft"` for an input with slightly rounded corners, `"round"` for an input with fully rounded corners, or `"rectangular"` for an input without rounded corners. Defaults to `"round"` for the ionic theme, and `undefined` for all other themes. Only applies when the fill is set to `"solid"` or `"outline"`. */ - "shape"?: 'round'; + "shape"?: 'soft' | 'round' | 'rectangular'; + /** + * The size of the input. If "large", it will have an increased height. By default the size is medium. This property only applies to the `"ionic"` theme. + */ + "size"?: 'medium' | 'large' | 'xlarge'; /** * If `true`, the element will have its spelling and grammar checked. */ @@ -6715,7 +6719,7 @@ declare namespace LocalJSX { */ "label"?: string; /** - * Where to place the label relative to the input. `"start"`: The label will appear to the left of the input in LTR and to the right in RTL. `"end"`: The label will appear to the right of the input in LTR and to the left in RTL. `"floating"`: The label will appear smaller and above the input when the input is focused or it has a value. Otherwise it will appear on top of the input. `"stacked"`: The label will appear smaller and above the input regardless even when the input 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 ("..."). + * Where to place the label relative to the input. `"start"`: The label will appear to the left of the input in LTR and to the right in RTL. `"end"`: The label will appear to the right of the input in LTR and to the left in RTL. `"floating"`: The label will appear smaller and above the input when the input is focused or it has a value. Otherwise it will appear on top of the input. `"stacked"`: The label will appear smaller and above the input regardless even when the input 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 ("..."). Defaults to "stacked" for the ionic theme, or "start" for all other themes. In the ionic theme, only the values "stacked" and "floating" are supported. */ "labelPlacement"?: 'start' | 'end' | 'floating' | 'stacked' | 'fixed'; /** @@ -6779,9 +6783,13 @@ declare namespace LocalJSX { */ "required"?: boolean; /** - * The shape of the input. If "round" it will have an increased border radius. + * Set to `"soft"` for an input with slightly rounded corners, `"round"` for an input with fully rounded corners, or `"rectangular"` for an input without rounded corners. Defaults to `"round"` for the ionic theme, and `undefined` for all other themes. Only applies when the fill is set to `"solid"` or `"outline"`. */ - "shape"?: 'round'; + "shape"?: 'soft' | 'round' | 'rectangular'; + /** + * The size of the input. If "large", it will have an increased height. By default the size is medium. This property only applies to the `"ionic"` theme. + */ + "size"?: 'medium' | 'large' | 'xlarge'; /** * If `true`, the element will have its spelling and grammar checked. */ diff --git a/core/src/components/input/input.ionic.outline.scss b/core/src/components/input/input.ionic.outline.scss new file mode 100644 index 0000000000..d5af9301b9 --- /dev/null +++ b/core/src/components/input/input.ionic.outline.scss @@ -0,0 +1,157 @@ +@import "./input.vars"; +@import "../../foundations/ionic.vars"; + +// Input Fill: Outline (Ionic Theme) +// ---------------------------------------------------------------- + +:host(.input-fill-outline) { + --border-radius: #{$ionic-border-radius-100}; + --padding-start: 12px; + --padding-end: 12px; +} + +:host(.input-fill-outline.input-size-large) { + --padding-start: 16px; + --padding-end: 16px; +} + +:host(.input-fill-outline.input-size-xlarge) { + --padding-start: 20px; + --padding-end: 20px; +} + +/** + * The bottom content should never have + * a border with the outline style. + */ +:host(.input-fill-outline) .input-bottom { + border-top: none; +} + +:host(.input-fill-outline.input-shape-round) .input-bottom, +:host(.input-fill-outline.input-label-placement-floating) .input-bottom { + /** + * The bottom content should take on the start and end + * padding so it is always aligned with either the label + * or the start of the text input. + */ + @include padding-horizontal(var(--padding-start), var(--padding-end)); +} + +:host(.input-fill-outline) .input-wrapper { + /** + * For the ionic theme, the padding needs to sit on the + * native wrapper instead, so that it sits within the + * outline container but does not always affect the + * label text. + */ + @include padding(0); + + /** + * Outline inputs do not have a bottom border. + * Instead, they have a border that wraps the + * input + label. + */ + border-bottom: none; + + /** + * Do not show a background on the input wrapper as + * this includes the label, instead we apply the + * background to the native wrapper. + */ + background: transparent; +} + +:host(.input-fill-outline.input-shape-round) .label-text-wrapper { + @include padding(null, var(--padding-end), null, var(--padding-start)); +} + +:host(.input-fill-outline.input-label-placement-stacked) .label-text-wrapper { + @include transform-origin(start, top); + + /** + * Label text should not extend + * beyond the bounds of the input. + */ + max-width: calc(100% - var(--padding-start) - var(--padding-end)); +} + +:host(.input-fill-outline) .label-text-wrapper { + /** + * The label should appear on top of an outline + * container that overlaps it so it is always clickable. + */ + position: relative; +} + +:host(.input-fill-outline) .native-wrapper { + @include border-radius(inherit); + @include padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start)); + + min-height: 40px; + + /** + * Apply the background to the native input + * wrapper to only style the input. + */ + background: var(--background); +} + +// Input Fill: Outline, Outline Container +// ---------------------------------------------------------------- + +:host(.input-fill-outline) .input-outline { + @include position(0, 0, 0, 0); + @include border-radius(var(--border-radius)); + + position: absolute; + + width: 100%; + height: 100%; + + pointer-events: none; + + border: var(--border-width) var(--border-style) var(--border-color); +} + +// Input Fill: Outline, Label Placement: Stacked +// ---------------------------------------------------------------- + +// This makes the label sit above the input. +:host(.label-floating.input-fill-outline.input-label-placement-stacked) .label-text-wrapper { + @include margin(0); + @include padding(4px, null); +} + +// Start/End Slots +// ---------------------------------------------------------------- + +:host(.input-fill-outline) ::slotted([slot="start"]) { + margin-inline-end: 8px; +} + +:host(.input-fill-outline) ::slotted([slot="end"]) { + margin-inline-start: 8px; +} + +// Input Shapes +// -------------------------------------------------- + +:host(.input-fill-outline.input-shape-soft) { + --border-radius: #{$ionic-border-radius-200}; +} + +:host(.input-fill-outline.input-shape-round) { + --border-radius: #{$ionic-border-radius-full}; +} + +:host(.input-fill-outline.input-shape-rectangular) { + --border-radius: #{$ionic-border-radius-0}; +} + +// Input Focus +// ---------------------------------------------------------------- + +:host(.input-fill-outline.has-focus) { + --border-width: #{tokens.$ionic-border-size-050}; +} diff --git a/core/src/components/input/input.ionic.scss b/core/src/components/input/input.ionic.scss new file mode 100644 index 0000000000..1e0874449f --- /dev/null +++ b/core/src/components/input/input.ionic.scss @@ -0,0 +1,229 @@ +@use "../../foundations/ionic.vars.scss" as tokens; +@import "./input"; +@import "./input.ionic.vars"; +@import "./input.ionic.outline.scss"; + +// Ionic Input +// -------------------------------------------------- + +:host { + --color: #{tokens.$ionic-color-neutral-1000}; + --border-width: #{tokens.$ionic-border-size-025}; + --border-color: #{tokens.$ionic-color-neutral-300}; + --highlight-color-valid: #{tokens.$ionic-color-success-base}; + --highlight-color-invalid: #{tokens.$ionic-color-danger-base}; + --placeholder-color: #{tokens.$ionic-color-neutral-800}; + --placeholder-opacity: 1; + --text-color-invalid: #{tokens.$ionic-color-danger-800}; + + font-size: tokens.$ionic-font-size-350; +} + +// Ionic Input Sizes +// -------------------------------------------------- + +:host(.input-size-medium) .native-wrapper { + min-height: 40px; +} + +:host(.input-size-large) .native-wrapper { + min-height: 48px; +} + +:host(.input-size-xlarge) .native-wrapper { + min-height: 56px; +} + +// Target area +// -------------------------------------------------- +:host .native-wrapper::after { + @include position(50%, 0, null, 0); + + position: absolute; + + height: 100%; + min-height: 48px; + + transform: translateY(-50%); + + content: ""; + + // Cursor should match the native input when hovering over the target area. + cursor: text; + + z-index: 1; +} + +::slotted([slot="start"]), +::slotted([slot="end"]), +.input-clear-icon { + /** + * The target area has a z-index of 1, so the slotted elements + * should be higher. Otherwise, the slotted elements will not + * be interactable. This is especially important for the clear + * button, which should be clickable. + */ + z-index: 2; +} + +// Input Clear Button +// ---------------------------------------------------------------- + +.input-clear-icon { + width: 16px; + height: 16px; + + color: #{tokens.$ionic-color-neutral-500}; +} + +.input-clear-icon:focus-visible { + @include border-radius(tokens.$ionic-border-radius-100); + + outline: #{tokens.$ionic-border-size-050} solid #{tokens.$ionic-state-focus-1}; + + opacity: 1; +} + +.input-clear-icon ion-icon { + width: 100%; + height: 100%; +} + +/** + * The clear button should be visible if the native input + * OR any other interactive elements in the component (the + * clear button, slotted buttons, etc.) are focused. If we + * only looked at the native input, tabbing to the clear + * button would immediately hide it. + * + * Note that the clear button also requires the native input + * to have any value, but this is not specific to the ionic + * theme, so it is handled elsewhere. + */ +:host(:not(:focus-within)) .input-clear-icon { + display: none; +} + +// Input Label +// ---------------------------------------------------------------- + +.label-text-wrapper { + color: tokens.$ionic-color-neutral-1000; + + font-size: tokens.$ionic-font-size-300; + font-weight: tokens.$ionic-font-weight-medium; + + line-height: tokens.$ionic-line-height-500; +} + +:host(.label-floating) .label-text-wrapper { + @include transform(none); +} + +// Input Bottom Content +// ---------------------------------------------------------------- + +.input-bottom { + @include padding(7px, 0); + + font-weight: tokens.$ionic-font-weight-medium; +} + +.input-bottom .helper-text, +.input-bottom .counter { + color: tokens.$ionic-color-neutral-800; +} + +:host(.has-focus.ion-valid) .helper-text { + color: tokens.$ionic-color-success-900; +} + +:host(.ion-touched.ion-invalid) .error-text { + color: var(--text-color-invalid); +} + +:host(.has-focus.ion-valid), +:host(.ion-touched.ion-invalid) { + --border-width: #{tokens.$ionic-border-size-025}; +} + +// Input Hover +// ---------------------------------------------------------------- + +@media (any-hover: hover) { + :host(:hover) { + --border-color: #{tokens.$ionic-color-neutral-600}; + } +} + +// Input - Disabled +// ---------------------------------------------------------------- + +:host(.input-disabled) { + // color for the text within the input + --color: #{tokens.$ionic-color-neutral-400}; + --background: #{tokens.$ionic-color-neutral-100}; + + pointer-events: none; +} + +:host(.input-disabled:not(.ion-valid)) .input-bottom .helper-text, +:host(.input-disabled) .input-bottom .counter, +:host(.input-disabled) .label-text-wrapper { + color: tokens.$ionic-color-neutral-400; +} + +:host(.input-disabled.has-focus.ion-valid) { + --border-color: rgba(#{tokens.$ionic-color-success-base-rgb}, 0.6); +} + +:host(.input-disabled.ion-touched.ion-invalid) { + --border-color: rgba(#{tokens.$ionic-color-danger-base-rgb}, 0.6); +} + +:host(.input-disabled.ion-color) { + --border-color: #{current-color(base, 0.6)}; +} + +:host(.input-disabled.has-focus.ion-valid) .input-bottom .helper-text, +:host(.input-disabled.ion-touched.ion-invalid) .error-text, +:host(.input-disabled.ion-color) .input-bottom .helper-text, +:host(.input-disabled.ion-color) .helper-text { + opacity: 0.6; +} + +// Input - Readonly +// ---------------------------------------------------------------- + +:host(.input-readonly) { + --background: #{tokens.$ionic-color-neutral-100}; +} + +// Input Highlight +// ---------------------------------------------------------------- + +.input-highlight { + @include position(null, null, -1px, 0); + + position: absolute; + + width: 100%; + height: tokens.$ionic-border-size-050; + + transform: scale(0); + + transition: transform 200ms; + + background: var(--border-color); +} + +// Input Focus +// ---------------------------------------------------------------- + +:host(.has-focus) { + --border-color: #{tokens.$ionic-color-primary-base}; +} + +:host(.has-focus) .input-highlight { + transform: scale(1); +} diff --git a/core/src/components/input/input.ionic.vars.scss b/core/src/components/input/input.ionic.vars.scss new file mode 100644 index 0000000000..7772c81e40 --- /dev/null +++ b/core/src/components/input/input.ionic.vars.scss @@ -0,0 +1,2 @@ +// Ionic Input +// -------------------------------------------------- diff --git a/core/src/components/input/input.scss b/core/src/components/input/input.scss index dd21ad8eaa..19fd7aa696 100644 --- a/core/src/components/input/input.scss +++ b/core/src/components/input/input.scss @@ -23,6 +23,7 @@ * @prop --highlight-color-focused: The color of the highlight on the input when focused * @prop --highlight-color-valid: The color of the highlight on the input when valid * @prop --highlight-color-invalid: The color of the highlight on the input when invalid + * @prop --text-color-invalid: The color of the error text on the input when invalid. Only applies to ionic theme. * * @prop --border-color: Color of the border below the input when using helper text, error text, or counter * @prop --border-radius: Radius of the input. A large radius may display unevenly when using fill="outline"; if needed, use shape="round" instead or increase --padding-start. diff --git a/core/src/components/input/input.tsx b/core/src/components/input/input.tsx index c963218b12..abf6bd7807 100644 --- a/core/src/components/input/input.tsx +++ b/core/src/components/input/input.tsx @@ -4,6 +4,7 @@ import type { NotchController } from '@utils/forms'; import { createNotchController } from '@utils/forms'; import type { Attributes } from '@utils/helpers'; import { inheritAriaAttributes, debounceEvent, inheritAttributes, componentOnReady } from '@utils/helpers'; +import { printIonWarning } from '@utils/logging'; import { createSlotMutationController } from '@utils/slot-mutation-controller'; import type { SlotMutationController } from '@utils/slot-mutation-controller'; import { createColorClasses, hostContext } from '@utils/theme'; @@ -29,7 +30,7 @@ import { getCounterText } from './input.utils'; styleUrls: { ios: 'input.ios.scss', md: 'input.md.scss', - ionic: 'input.md.scss', + ionic: 'input.ionic.scss', }, scoped: true, }) @@ -186,8 +187,12 @@ export class Input implements ComponentInterface { * `"floating"`: The label will appear smaller and above the input when the input is focused or it has a value. Otherwise it will appear on top of the input. * `"stacked"`: The label will appear smaller and above the input regardless even when the input 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 ("..."). + * + * Defaults to "stacked" for the ionic theme, or "start" for all other themes. + * + * In the ionic theme, only the values "stacked" and "floating" are supported. */ - @Prop() labelPlacement: 'start' | 'end' | 'floating' | 'stacked' | 'fixed' = 'start'; + @Prop({ mutable: true }) labelPlacement?: 'start' | 'end' | 'floating' | 'stacked' | 'fixed'; /** * The maximum value, which must not be less than its minimum (min attribute) value. @@ -242,9 +247,12 @@ export class Input implements ComponentInterface { @Prop() required = false; /** - * The shape of the input. If "round" it will have an increased border radius. + * Set to `"soft"` for an input with slightly rounded corners, `"round"` for an input with fully + * rounded corners, or `"rectangular"` for an input without rounded corners. + * Defaults to `"round"` for the ionic theme, and `undefined` for all other themes. + * Only applies when the fill is set to `"solid"` or `"outline"`. */ - @Prop() shape?: 'round'; + @Prop() shape?: 'soft' | 'round' | 'rectangular'; /** * If `true`, the element will have its spelling and grammar checked. @@ -257,6 +265,12 @@ export class Input implements ComponentInterface { */ @Prop() step?: string; + /** + * The size of the input. If "large", it will have an increased height. By default the + * size is medium. This property only applies to the `"ionic"` theme. + */ + @Prop() size?: 'medium' | 'large' | 'xlarge' = 'medium'; + /** * The type of control to display. The default type is text. */ @@ -344,6 +358,10 @@ export class Input implements ComponentInterface { ...inheritAriaAttributes(this.el), ...inheritAttributes(this.el, ['tabindex', 'title', 'data-form-type']), }; + + if (this.labelPlacement === undefined) { + this.labelPlacement = getIonTheme(this) === 'ionic' ? ionicThemeDefaultLabelPlacement : 'start'; + } } connectedCallback() { @@ -473,6 +491,55 @@ export class Input implements ComponentInterface { return typeof this.value === 'number' ? this.value.toString() : (this.value || '').toString(); } + private getLabelPlacement() { + const theme = getIonTheme(this); + const { el, labelPlacement } = this; + + if (theme === 'ionic' && labelPlacement !== 'stacked' && labelPlacement !== 'floating') { + printIonWarning( + `The "${labelPlacement}" label placement is not supported in the ${theme} theme. The default value of "${ionicThemeDefaultLabelPlacement}" will be used instead.`, + el + ); + return ionicThemeDefaultLabelPlacement; + } + + return labelPlacement; + } + + // TODO(FW-6201): Remove this method when size is supported in ios and md + private getSize() { + const theme = getIonTheme(this); + const { size } = this; + if (theme !== 'ionic' && (size === 'large' || size === 'xlarge')) { + printIonWarning(`The "${size}" size is not supported in the ${theme} theme.`); + // Fallback to medium size, which is the default size for all themes. + return 'medium'; + } + return size; + } + + private getShape() { + const theme = getIonTheme(this); + const { shape } = this; + // TODO(ROU-5475): Remove the check for `soft` when the shape is supported in ios and md. + if ((theme === 'ios' && shape === 'round') || (theme !== 'ionic' && shape === 'soft')) { + printIonWarning(`The "${shape}" shape is not supported in the ${theme} theme.`); + return undefined; + } + + if (shape !== undefined) { + return shape; + } + + // TODO(FW-6229): Update this when the default shape has been decided. + if (theme !== 'ionic') { + return undefined; + } + + // Fallback to round shape, which is the default shape for the ionic theme. + return 'round'; + } + private onInput = (ev: InputEvent | Event) => { const input = ev.target as HTMLInputElement | null; if (input) { @@ -655,9 +722,9 @@ export class Input implements ComponentInterface { */ private renderLabelContainer() { const theme = getIonTheme(this); - const hasOutlineFill = theme === 'md' && this.fill === 'outline'; + const hasOutlineFill = this.fill === 'outline'; - if (hasOutlineFill) { + if (hasOutlineFill && theme === 'md') { /** * The outline fill has a special outline * that appears around the input and the label. @@ -685,8 +752,9 @@ export class Input implements ComponentInterface { } /** - * If not using the outline style, - * we can render just the label. + * If not using the outline style, OR if using the + * ionic theme, just render the label. For the ionic + * theme, the outline will be rendered elsewhere. */ return this.renderLabel(); } @@ -714,11 +782,14 @@ export class Input implements ComponentInterface { } render() { - const { disabled, fill, readonly, shape, inputId, labelPlacement, el, hasFocus, inputClearIcon } = this; + const { disabled, fill, readonly, inputId, el, hasFocus, clearInput, inputClearIcon } = this; const theme = getIonTheme(this); const value = this.getValue(); + const size = this.getSize(); + const shape = this.getShape(); const inItem = hostContext('ion-item', this.el); - const shouldRenderHighlight = theme === 'md' && fill !== 'outline' && !inItem; + const shouldRenderHighlight = (theme === 'md' || theme === 'ionic') && fill !== 'outline' && !inItem; + const labelPlacement = this.getLabelPlacement(); const hasValue = this.hasValue(); const hasStartEndSlots = el.querySelector('[slot="start"], [slot="end"]') !== null; @@ -752,10 +823,12 @@ export class Input implements ComponentInterface { 'label-floating': labelShouldFloat, [`input-fill-${fill}`]: fill !== undefined, [`input-shape-${shape}`]: shape !== undefined, + [`input-size-${size}`]: true, [`input-label-placement-${labelPlacement}`]: true, 'in-item': inItem, 'in-item-color': hostContext('ion-item.ion-color', this.el), 'input-disabled': disabled, + 'input-readonly': readonly, })} > {/** @@ -767,6 +840,18 @@ export class Input implements ComponentInterface {