diff --git a/core/src/components.d.ts b/core/src/components.d.ts index c17d097daf..b65f10b179 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -1214,7 +1214,7 @@ export namespace Components { */ "inputmode"?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'; /** - * The visible label associated with the input. + * The visible label associated with the input. Use this if you need to render a plaintext label. The `label` property will take priority over the `label` slot if both are used. */ "label"?: string; /** @@ -5248,7 +5248,7 @@ declare namespace LocalJSX { */ "inputmode"?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'; /** - * The visible label associated with the input. + * The visible label associated with the input. Use this if you need to render a plaintext label. The `label` property will take priority over the `label` slot if both are used. */ "label"?: string; /** diff --git a/core/src/components/input/input.md.outline.scss b/core/src/components/input/input.md.outline.scss index 30367cf807..4efbd2fcdd 100644 --- a/core/src/components/input/input.md.outline.scss +++ b/core/src/components/input/input.md.outline.scss @@ -172,6 +172,16 @@ opacity: 0; pointer-events: none; + + /** + * The spacer currently inherits + * border-box sizing from the Ionic reset styles. + * However, we do not want to include padding in + * the calculation of the element dimensions. + * This code can be removed if input is updated + * to use the Shadow DOM. + */ + box-sizing: content-box; } :host(.input-fill-outline) .input-outline-start { diff --git a/core/src/components/input/input.scss b/core/src/components/input/input.scss index ab0cf76b37..4a18956e01 100644 --- a/core/src/components/input/input.scss +++ b/core/src/components/input/input.scss @@ -463,7 +463,8 @@ * works on block-level elements. A flex item is * considered blockified (https://www.w3.org/TR/css-display-3/#blockify). */ -.label-text { +.label-text, +::slotted([slot="label"]) { text-overflow: ellipsis; white-space: nowrap; @@ -471,6 +472,16 @@ overflow: hidden; } +/** + * If no label text is placed into the slot + * then the element should be hidden otherwise + * there will be additional margins added. + */ +.label-text-wrapper-hidden, +.input-outline-notch-hidden { + display: none; +} + .input-wrapper input { /** * When the floating label appears on top of the diff --git a/core/src/components/input/input.tsx b/core/src/components/input/input.tsx index e76bf1dafc..7e138469ab 100644 --- a/core/src/components/input/input.tsx +++ b/core/src/components/input/input.tsx @@ -1,10 +1,12 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core'; -import { Build, Component, Element, Event, Host, Method, Prop, State, Watch, h } from '@stencil/core'; -import type { LegacyFormController } from '@utils/forms'; -import { createLegacyFormController } from '@utils/forms'; +import { Build, Component, Element, Event, Host, Method, Prop, State, Watch, forceUpdate, h } from '@stencil/core'; +import type { LegacyFormController, NotchController } from '@utils/forms'; +import { createLegacyFormController, createNotchController } from '@utils/forms'; import type { Attributes } from '@utils/helpers'; import { inheritAriaAttributes, debounceEvent, findItemLabel, inheritAttributes } 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'; import { closeCircle, closeSharp } from 'ionicons/icons'; @@ -16,6 +18,8 @@ import { getCounterText } from './input.utils'; /** * @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use. + * + * @slot label - The label text to associate with the input. Use the `labelPlacement` property to control where the label is placed relative to the input. Use this if you need to render a label with custom HTML. (EXPERIMENTAL) */ @Component({ tag: 'ion-input', @@ -31,6 +35,9 @@ export class Input implements ComponentInterface { private inheritedAttributes: Attributes = {}; private isComposing = false; private legacyFormController!: LegacyFormController; + private slotMutationController?: SlotMutationController; + private notchController?: NotchController; + private notchSpacerEl: HTMLElement | undefined; // This flag ensures we log the deprecation warning at most once. private hasLoggedDeprecationWarning = false; @@ -165,6 +172,10 @@ export class Input implements ComponentInterface { /** * The visible label associated with the input. + * + * Use this if you need to render a plaintext label. + * + * The `label` property will take priority over the `label` slot if both are used. */ @Prop() label?: string; @@ -353,6 +364,12 @@ export class Input implements ComponentInterface { const { el } = this; this.legacyFormController = createLegacyFormController(el); + this.slotMutationController = createSlotMutationController(el, 'label', () => forceUpdate(this)); + this.notchController = createNotchController( + el, + () => this.notchSpacerEl, + () => this.labelSlot + ); this.emitStyle(); this.debounceChanged(); @@ -369,6 +386,10 @@ export class Input implements ComponentInterface { this.originalIonInput = this.ionInput; } + componentDidRender() { + this.notchController?.calculateNotchWidth(); + } + disconnectedCallback() { if (Build.isBrowser) { document.dispatchEvent( @@ -377,6 +398,16 @@ export class Input implements ComponentInterface { }) ); } + + if (this.slotMutationController) { + this.slotMutationController.destroy(); + this.slotMutationController = undefined; + } + + if (this.notchController) { + this.notchController.destroy(); + this.notchController = undefined; + } } /** @@ -578,17 +609,37 @@ export class Input implements ComponentInterface { private renderLabel() { const { label } = this; - if (label === undefined) { - return; - } return ( -
-
{this.label}
+
+ {label === undefined ? :
{label}
}
); } + /** + * Gets any content passed into the `label` slot, + * not the definition. + */ + private 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. + */ + private get hasLabel() { + return this.label !== undefined || this.labelSlot !== null; + } + /** * Renders the border container * when fill="outline". @@ -608,8 +659,13 @@ export class Input implements ComponentInterface { return [
-
-