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 [