diff --git a/core/src/components/input/input.md.outline.scss b/core/src/components/input/input.md.outline.scss index be54121d0e0..fd8028f4959 100644 --- a/core/src/components/input/input.md.outline.scss +++ b/core/src/components/input/input.md.outline.scss @@ -8,6 +8,7 @@ --border-radius: 4px; --padding-start: 16px; --padding-end: 16px; + --start-slot-adjustment: 0px; min-height: 56px; } @@ -90,7 +91,7 @@ * This makes the label sit above the input. */ :host(.label-floating.input-fill-outline) .label-text-wrapper { - @include transform(translateY(-32%), scale(#{$form-control-label-stacked-scale})); + @include transform(translate(var(--start-slot-adjustment), -32%), scale(#{$form-control-label-stacked-scale})); @include margin(0); /** @@ -100,16 +101,6 @@ max-width: calc((100% - var(--padding-start) - var(--padding-end) - #{$input-md-floating-label-padding * 2}) / #{$form-control-label-stacked-scale}); } -/** - * This ensures that the input does not - * overlap the floating label while still - * remaining visually centered. - */ -:host(.input-fill-outline.input-label-placement-stacked) input, -:host(.input-fill-outline.input-label-placement-floating) input { - @include margin(6px, 0, 6px, 0); -} - // Input Fill: Outline Outline Container // ---------------------------------------------------------------- diff --git a/core/src/components/input/input.scss b/core/src/components/input/input.scss index 476cb012bee..7b0314e0bd8 100644 --- a/core/src/components/input/input.scss +++ b/core/src/components/input/input.scss @@ -189,7 +189,6 @@ // -------------------------------------------------- .input-clear-icon { - @include margin(auto); @include padding(0); @include background-position(center); @@ -259,6 +258,19 @@ line-height: normal; } +// Input Control (Label + Input) +// ---------------------------------------------------------------- + +.input-control { + display: flex; + + position: relative; + + flex-grow: 1; + + width: 100%; +} + // Input Native Wrapper // ---------------------------------------------------------------- @@ -269,12 +281,24 @@ flex-grow: 1; - // ensure start/end slot content is vertically centered - align-items: center; - width: 100%; } +// Input Start/End Containers +// ---------------------------------------------------------------- + +.input-start, +.input-end { + display: flex; + + position: relative; + + flex-shrink: 0; + + // Ensure start/end content is vertically centered + align-items: center; +} + // Input Highlight // ---------------------------------------------------------------- @@ -467,7 +491,7 @@ * Label is on the left of the input in LTR and * on the right in RTL. */ -:host(.input-label-placement-start) .input-wrapper { +:host(.input-label-placement-start) .input-control { flex-direction: row; } @@ -487,7 +511,7 @@ * Label is on the right of the input in LTR and * on the left in RTL. */ -:host(.input-label-placement-end) .input-wrapper { +:host(.input-label-placement-end) .input-control { flex-direction: row-reverse; } @@ -532,10 +556,9 @@ * Floating: Label sits over the input when the input has no * value and is blurred. Label sits above the input and is scaled * down when the input is focused or has a value. - * */ -:host(.input-label-placement-stacked) .input-wrapper, -:host(.input-label-placement-floating) .input-wrapper { +:host(.input-label-placement-stacked) .input-control, +:host(.input-label-placement-floating) .input-control { flex-direction: column; align-items: start; } @@ -559,15 +582,6 @@ z-index: 2; } -/** - * Ensures the input does not - * overlap the label. - */ -:host(.input-label-placement-stacked) input, -:host(.input-label-placement-floating) input { - @include margin(1px, 0, 0, 0); -} - /** * This makes the label sit over the input * when the input is blurred and has no value. diff --git a/core/src/components/input/input.tsx b/core/src/components/input/input.tsx index 89efdce43dd..b0fc69e7862 100644 --- a/core/src/components/input/input.tsx +++ b/core/src/components/input/input.tsx @@ -20,7 +20,7 @@ import { inheritAriaAttributes, debounceEvent, inheritAttributes, componentOnRea 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'; +import { closeCircle, closeCircleSharp } from 'ionicons/icons'; import { getIonMode } from '../../global/ionic-global'; import type { AutocompleteTypes, Color, TextFieldTypes } from '../../interface'; @@ -466,6 +466,26 @@ export class Input implements ComponentInterface { this.notchController?.calculateNotchWidth(); } + /** + * Gets the width of the start slot, rounded to 1 decimal place. + * Only applies to inputs with `md` mode and `fill="outline"`. + * In RTL mode, the adjustment is positive; in LTR mode, it's negative. + */ + private getStartSlotAdjustment(): string { + const startSlot = this.el.querySelector('.input-start') as HTMLElement | null; + if (!startSlot || !Build.isBrowser || getIonMode(this) !== 'md' || this.fill !== 'outline') { + return ''; + } + + // Round the width to a half pixel to ensure the label is + // placed properly when a start slot is added or removed. + const startSlotWidth = startSlot.getBoundingClientRect().width; + const roundedWidth = Math.round(startSlotWidth * 10) / 10; + const isRTL = document.dir === 'rtl'; + const sign = isRTL ? '' : '-'; + return roundedWidth ? `${sign}${roundedWidth}px` : '0px'; + } + disconnectedCallback() { if (Build.isBrowser) { document.dispatchEvent( @@ -800,48 +820,31 @@ export class Input implements ComponentInterface { * otherwise, two clicks will be triggered. */ private onLabelClick = (ev: MouseEvent) => { - // Only stop propagation if the click was directly on the label - // and not on the input or other child elements - if (ev.target === ev.currentTarget) { - ev.stopPropagation(); - } + ev.stopPropagation(); }; /** * Renders the border container * when fill="outline". */ - private renderLabelContainer() { - const mode = getIonMode(this); - const hasOutlineFill = mode === 'md' && this.fill === 'outline'; - - if (hasOutlineFill) { - /** - * The outline fill has a special outline - * that appears around the input and the label. - * Certain stacked and floating label placements cause the - * label to translate up and create a "cut out" - * inside of that border by using the notch-spacer element. - */ - return [ -