From d2bb3e407cb2668b5f873779297529a98eaa3dd1 Mon Sep 17 00:00:00 2001 From: amandaesmith3 Date: Fri, 20 Oct 2023 10:51:59 -0500 Subject: [PATCH 01/13] update slotMutationController to allow multiple slot names --- core/src/utils/slot-mutation-controller.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/src/utils/slot-mutation-controller.ts b/core/src/utils/slot-mutation-controller.ts index 663b781a367..3827c28f9fa 100644 --- a/core/src/utils/slot-mutation-controller.ts +++ b/core/src/utils/slot-mutation-controller.ts @@ -6,18 +6,20 @@ import { raf } from '@utils/helpers'; * This is not needed for components using native slots in the Shadow DOM. * @internal * @param el The host element to observe - * @param slotName mutationCallback will fire when nodes on this slot change + * @param slotName mutationCallback will fire when nodes on these slot(s) change * @param mutationCallback The callback to fire whenever the slotted content changes */ export const createSlotMutationController = ( el: HTMLElement, - slotName: string, + slotName: string | string[], mutationCallback: () => void ): SlotMutationController => { let hostMutationObserver: MutationObserver | undefined; let slottedContentMutationObserver: MutationObserver | undefined; if (win !== undefined && 'MutationObserver' in win) { + const slots = Array.isArray(slotName) ? slotName : [slotName]; + hostMutationObserver = new MutationObserver((entries) => { for (const entry of entries) { for (const node of entry.addedNodes) { @@ -25,7 +27,7 @@ export const createSlotMutationController = ( * Check to see if the added node * is our slotted content. */ - if (node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).slot === slotName) { + if (node.nodeType === Node.ELEMENT_NODE && slots.includes((node as HTMLElement).slot)) { /** * If so, we want to watch the slotted * content itself for changes. This lets us From 1792644150993bb8f728dae886ef7b21d6e33850 Mon Sep 17 00:00:00 2001 From: amandaesmith3 Date: Fri, 20 Oct 2023 13:31:39 -0500 Subject: [PATCH 02/13] add slots and tests --- core/src/components/input/input.scss | 12 ++ core/src/components/input/input.tsx | 6 +- .../src/components/input/test/slot/index.html | 139 +++++++++++++++++- 3 files changed, 149 insertions(+), 8 deletions(-) diff --git a/core/src/components/input/input.scss b/core/src/components/input/input.scss index 84688d80907..85ecbea50a6 100644 --- a/core/src/components/input/input.scss +++ b/core/src/components/input/input.scss @@ -299,6 +299,7 @@ // ---------------------------------------------------------------- .native-wrapper { + align-items: center; display: flex; position: relative; @@ -635,3 +636,14 @@ */ max-width: calc(100% / #{$form-control-label-stacked-scale}); } + +// Start/End Slots +// ---------------------------------------------------------------- + +::slotted([slot="start"]) { + margin-inline-end: $form-control-label-margin; +} + +::slotted([slot="end"]) { + margin-inline-start: $form-control-label-margin; +} \ No newline at end of file diff --git a/core/src/components/input/input.tsx b/core/src/components/input/input.tsx index 4a6e50723c6..6801d93d6df 100644 --- a/core/src/components/input/input.tsx +++ b/core/src/components/input/input.tsx @@ -20,6 +20,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) + * @slot start - Content to display at the leading edge of the input. (EXPERIMENTAL) + * @slot end - Content to display at the trailing edge of the input. (EXPERIMENTAL) */ @Component({ tag: 'ion-input', @@ -363,7 +365,7 @@ export class Input implements ComponentInterface { const { el } = this; this.legacyFormController = createLegacyFormController(el); - this.slotMutationController = createSlotMutationController(el, 'label', () => forceUpdate(this)); + this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this)); this.notchController = createNotchController( el, () => this.notchSpacerEl, @@ -707,6 +709,7 @@ export class Input implements ComponentInterface { diff --git a/core/src/components/input/test/slot/index.html b/core/src/components/input/test/slot/index.html index 56b14181bea..d3de774ebeb 100644 --- a/core/src/components/input/test/slot/index.html +++ b/core/src/components/input/test/slot/index.html @@ -37,6 +37,11 @@ .required { color: red; } + + ion-input ion-button { + --padding-start: 0; + --padding-end: 0; + } @@ -51,49 +56,169 @@
-

No Fill / Start

+

No Fill / Start Label

Email *
-

Solid / Start

+

Solid / Start Label

Email *
-

Outline / Start

+

Outline / Start Label

Email *
-

No Fill / Floating

+

No Fill / Floating Label

Email *
-

Solid / Floating

+

Solid / Floating Label

Email *
-

Outline / Floating

+

Outline / Floating Label

Email *
-

Outline / Floating / Async

+

No Fill / Start Label / Buttons

+ + + + + + + + +
+ +
+

Solid / Start Label / Buttons

+ + + + + + + + +
+ +
+

Outline / Start Label / Buttons

+ + + + + + + + +
+ +
+

No Fill / Floating Label / Buttons

+ + + + + + + + +
+ +
+

Solid / Floating Label / Buttons

+ + + + + + + + +
+ +
+

Outline / Floating Label / Buttons

+ + + + + + + + +
+ +
+

No Fill / Start Label / Decorations

+ + + lbs + +
+ +
+

Solid / Start Label / Decorations

+ + + lbs + +
+ +
+

Outline / Start Label / Decorations

+ + + lbs + +
+ +
+

No Fill / Floating Label / Decorations

+ + + lbs + +
+ +
+

Solid / Floating Label / Decorations

+ + + lbs + +
+ +
+

Outline / Floating Label / Decorations

+ + + lbs + +
+ +
+

Outline / Floating Label / Async

From 47163b59e2e754e12373c200cb7eb98c72b0759f Mon Sep 17 00:00:00 2001 From: amandaesmith3 Date: Fri, 20 Oct 2023 14:43:57 -0500 Subject: [PATCH 03/13] always float label if slot content is present --- .../components/input/input.md.outline.scss | 8 +-- core/src/components/input/input.md.solid.scss | 4 +- core/src/components/input/input.scss | 4 +- core/src/components/input/input.tsx | 11 ++-- .../src/components/input/test/slot/index.html | 53 ++++++++++++++++--- 5 files changed, 59 insertions(+), 21 deletions(-) diff --git a/core/src/components/input/input.md.outline.scss b/core/src/components/input/input.md.outline.scss index 08338676553..15897641182 100644 --- a/core/src/components/input/input.md.outline.scss +++ b/core/src/components/input/input.md.outline.scss @@ -87,9 +87,7 @@ /** * This makes the label sit above the input. */ -:host(.has-focus.input-fill-outline.input-label-placement-floating) .label-text-wrapper, -:host(.has-value.input-fill-outline.input-label-placement-floating) .label-text-wrapper, -:host(.input-fill-outline.input-label-placement-stacked) .label-text-wrapper { +:host(.label-floating.input-fill-outline) .label-text-wrapper { @include transform(translateY(-32%), scale(#{$form-control-label-stacked-scale})); @include margin(0); @@ -214,8 +212,6 @@ * the floating/stacked label. We simulate this "cut out" * by removing the top border from the notch fragment. */ -:host(.has-focus.input-fill-outline.input-label-placement-floating) .input-outline-notch, -:host(.has-value.input-fill-outline.input-label-placement-floating) .input-outline-notch, -:host(.input-fill-outline.input-label-placement-stacked) .input-outline-notch { +:host(.label-floating.input-fill-outline) .input-outline-notch { border-top: none; } diff --git a/core/src/components/input/input.md.solid.scss b/core/src/components/input/input.md.solid.scss index 125b1cf00a1..4db55bd3d43 100644 --- a/core/src/components/input/input.md.solid.scss +++ b/core/src/components/input/input.md.solid.scss @@ -65,9 +65,7 @@ // Input Label // ---------------------------------------------------------------- -:host(.input-fill-solid.input-label-placement-stacked) .label-text-wrapper, -:host(.has-focus.input-fill-solid.input-label-placement-floating) .label-text-wrapper, -:host(.has-value.input-fill-solid.input-label-placement-floating) .label-text-wrapper { +:host(.label-floating.input-fill-solid.input-label-placement-floating) .label-text-wrapper { /** * Label text should not extend * beyond the bounds of the input. diff --git a/core/src/components/input/input.scss b/core/src/components/input/input.scss index 85ecbea50a6..8f7618b4675 100644 --- a/core/src/components/input/input.scss +++ b/core/src/components/input/input.scss @@ -625,9 +625,7 @@ /** * This makes the label sit above the input. */ -:host(.input-label-placement-stacked) .label-text-wrapper, -:host(.has-focus.input-label-placement-floating) .label-text-wrapper, -:host(.has-value.input-label-placement-floating) .label-text-wrapper { +:host(.label-floating) .label-text-wrapper { @include transform(translateY(50%), scale(#{$form-control-label-stacked-scale})); /** diff --git a/core/src/components/input/input.tsx b/core/src/components/input/input.tsx index 6801d93d6df..5732863e5d5 100644 --- a/core/src/components/input/input.tsx +++ b/core/src/components/input/input.tsx @@ -686,18 +686,23 @@ export class Input implements ComponentInterface { } private renderInput() { - const { disabled, fill, readonly, shape, inputId, labelPlacement } = this; + const { disabled, fill, readonly, shape, inputId, labelPlacement, el, hasFocus } = this; const mode = getIonMode(this); const value = this.getValue(); const inItem = hostContext('ion-item', this.el); const shouldRenderHighlight = mode === 'md' && fill !== 'outline' && !inItem; + const hasValue = this.hasValue(); + const hasStartEndSlots = el.querySelector('[slot="start"], [slot="end"]') !== null; + const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots)); + return ( Outline / Floating Label / Decorations
-

Outline / Floating Label / Async

+

Outline / Async Label

+ +
+

Outline / Async Decorations

+ +
Add Slotted Content @@ -231,29 +236,65 @@

Outline / Floating Label / Async