fix(input): support floating labels with slotted content - #31309
fix(input): support floating labels with slotted content#31309brandyscarney wants to merge 28 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
I renamed these screenshots from input-slots to input-slot to match the folder name.
|
|
||
| configs().forEach(({ title, screenshot, config }) => { | ||
| test.describe(title('input: start and end slots (visual checks)'), () => { | ||
| test.describe(title('input: slot'), () => { |
There was a problem hiding this comment.
This was updated to match the folder name, following how we title other tests.
There was a problem hiding this comment.
This is the correct height and label position for Material Design filled inputs. See: https://m2.material.io/components/text-fields
There was a problem hiding this comment.
This 1px shift was caused by the removal of this rule:
ionic-framework/core/src/components/input/input.scss
Lines 562 to 569 in 048788f
However, this is actually more aligned than before:
There was a problem hiding this comment.
This 1px shift was caused by the removal of this rule:
ionic-framework/core/src/components/input/input.scss
Lines 562 to 569 in 048788f
There was a problem hiding this comment.
This 1px shift was caused by the removal of this rule:
ionic-framework/core/src/components/input/input.scss
Lines 562 to 569 in 048788f
There was a problem hiding this comment.
This 1px shift was caused by the removal of this rule:
ionic-framework/core/src/components/input/input.scss
Lines 562 to 569 in 048788f
ShaneK
left a comment
There was a problem hiding this comment.
This is looking really great! I had some feedback on a few issues and some minor things, but really looking good!
ShaneK
left a comment
There was a problem hiding this comment.
Thanks for the changes! I found a few more issues in this one, some are just test cleanup, most should be easy to implement I hope 🤞
| } | ||
| ); | ||
|
|
||
| this.startContainerController.calculateStartContainerWidth(); |
There was a problem hiding this comment.
Calling this only from connectedCallback and the slot mutation callback means a runtime fill change never re-measures, which is why calculateNotchWidth runs from componentDidRender.
With a [fill] binding resolving to outline after the component settles, the adjustment stays at 0px and the label sits 35px right of the notch, on the border instead of inside the cut-out. A static fill="outline" gets -32px. The ResizeObserver never attaches either, so widening the start slot afterwards drifts it 115px off. A runtime dir flip goes stale the same way.
Calling this from componentDidRender covers both.
| @@ -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}); | |||
There was a problem hiding this comment.
The percentage now resolves against .input-control, but the formula still subtracts --padding-start and --padding-end, which .input-control already excludes. So the padding comes off twice.
With no slots the available label width drops from 427px to 384px, which is the 32px of padding over the 0.75 scale. With start and end slots it's 187px. Long labels truncate earlier than intended and no screenshot covers that.
| * input field) to prevent double-click events. Allows clicks on slotted | ||
| * content to propagate so event delegation works for parent handlers. | ||
| */ | ||
| private onLabelClick = (ev: MouseEvent) => { |
There was a problem hiding this comment.
Since .input-control sits inside the label's content box, it never covers the padding. With fill="solid" that's 16px each side, and a click there targets the label itself, so nothing stops it. That strip goes from one click on major-9.0 to two here. Default fill is fine since its padding is 0.
The earlier commit that dropped the ev.target === ev.currentTarget guard is what broke slotted propagation. Excluding slotted content instead covers every case, and also the fill="outline" strip, which is two clicks on both branches:
if (target.closest('[slot="start"], [slot="end"]') === null) {
ev.stopPropagation();
}| }); | ||
|
|
||
| test('should propagate clicks from start slot button to parent', async ({ page }) => { | ||
| page.setContent( |
There was a problem hiding this comment.
| page.setContent( | |
| await page.setContent( |
Missing an await here, and on the end slot test below. Every other setContent in this file awaits it, and it's what waits for hydration.
| return computedStyle.getPropertyValue('--internal-start-container-adjustment'); | ||
| }); | ||
|
|
||
| expect(adjustment).toMatch(/-?\d+\.?\d*px/); |
There was a problem hiding this comment.
The stylesheet declares this property as 0px, which satisfies that regex, so this passes whether or not the measurement ran. The runtime fill case computes 0px and would still pass here. Asserting the value tracks the measured start width would catch it.
|
|
||
| **Internal DOM Structure Changes** | ||
|
|
||
| New wrapper elements have been added to the component's internal DOM structure to support floating labels with slotted start and end content. Additionally, the structure of the component has been reorganized, with some elements now grouped differently than before. This may introduce breaking changes for developers who rely on the component's internal DOM structure or apply custom styling to internal elements. |
There was a problem hiding this comment.
This covers the three added wrappers but not what moved out of .native-wrapper, which is the part anyone with custom CSS needs. The start slot, end slot and clear button all left it, and the label is no longer a direct child of the wrapper, so a selector like .native-wrapper [slot="start"] stops matching.
The behavior change is missing too. Floating labels no longer auto-float with a start or end slot present, which also hides the placeholder at rest.
The same text is in the v9 migration guide PR, so both would want the update.
| * The solid and outline fills are only supported by `md` mode. | ||
| */ | ||
| configs({ modes: ['md'] }).forEach(({ title, screenshot, config }) => { | ||
| test.describe(title('input: slot'), () => { |
There was a problem hiding this comment.
Both describe blocks have the same title now. Textarea and select split theirs into visual checks and functionality checks, and this file did too before.

Issue number: resolves #29449 resolves #28665
What is the current behavior?
Inputs with a floating label and a start or end slot always display the label in the floated state, regardless of whether the input contains a value:
What is the new behavior?
mdspecification.mdspecification.Does this introduce a breaking change?
Internal DOM Structure Changes
New wrapper elements have been added to the component's internal DOM structure to support floating labels with slotted start and end content. Additionally, the structure of the component has been reorganized, with some elements now grouped differently than before. This may introduce breaking changes for developers who rely on the component's internal DOM structure or apply custom styling to internal elements.
The following internal wrapper elements have been added:
<div class="input-start">wrapper for the start slot<div class="input-control">wrapper for the label and native control<div class="input-end">wrapper for the end slot and clear buttonWhile the public API has not changed, selectors or style overrides targeting the previous markup may need to be updated to reference the new wrapper elements and their organization. If you have custom CSS targeting the internal structure of input, update your selectors to account for these structural changes.
Other information
Preview