fix(textarea): support floating labels with slotted content - #31321
fix(textarea): support floating labels with slotted content#31321brandyscarney wants to merge 15 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| <slot name="start"></slot> | ||
| </div> | ||
| {hasOutlineFill && <div class="textarea-outline-container">{this.renderOutlineDecorations()}</div>} | ||
| <div class="textarea-start"> |
There was a problem hiding this comment.
I renamed the slot wrapper divs from the following:
start-slot-wrapper→textarea-startend-slot-wrapper→textarea-end
The goal was to align the naming with the wrapper elements added to Input.
This also matches our existing naming pattern with elements like input-bottom and textarea-bottom.
There was a problem hiding this comment.
I renamed these screenshots from textarea-slots to textarea-slot to match the folder name.
|
|
||
| configs().forEach(({ title, screenshot, config }) => { | ||
| test.describe(title('textarea: start and end slots (visual checks)'), () => { | ||
| test.describe(title('textarea: slot'), () => { |
There was a problem hiding this comment.
This was updated to match the folder name, following how we title other tests.
ShaneK
left a comment
There was a problem hiding this comment.
Looking really good! I found a few things that I believe are problems, please let me know if I'm off base here!
| // ---------------------------------------------------------------- | ||
|
|
||
| .textarea-control { | ||
| @include padding(var(--padding-top), 0px, var(--padding-bottom), 0px); |
There was a problem hiding this comment.
The padding moved here but .native-wrapper::after still has its own copy, so auto-grow counts it twice. An auto-grow textarea with 3 lines comes out 26px taller than the same content with rows="3" on md, and 18px on ios, which matches --padding-top + --padding-bottom exactly.
The ::after is the sizing replica so it has to mirror the real textarea's box, and the textarea has no padding now. Dropping the padding include from ::after and re-recording the auto-grow snapshots should do it.
I'd want to see this one fixed before approving since it hits every auto-grow textarea.
|
|
||
| align-items: start; | ||
|
|
||
| width: 100%; |
There was a problem hiding this comment.
Missing min-height: inherit here. The old .textarea-wrapper-inner had it, and that's what passed the host's min-height down to .native-wrapper. Without it .native-wrapper inherits auto, so min-height: 200px on the textarea gives you a 200px box with a 45px editable area and 155px you can't type in. Works fine on major-9.0.
The obvious fix overshoots a little though, since .native-wrapper re-inherits the same value and pushes the control out by its own padding, so it may want align-self: stretch instead.
|
|
||
| :host(.textarea-label-placement-floating) .textarea-control, | ||
| :host(.textarea-label-placement-stacked) .textarea-control { | ||
| --padding-top: 0px; |
There was a problem hiding this comment.
This kills --padding-top as a public property for floating and stacked. Before, the zeroing was on :host() so an outer-tree declaration still won. Now it's on an internal element nobody can outrank, so --padding-top: 40px on a floating textarea does nothing, while it still applies on start.
The host sets it to 20px a few lines up only so the ::slotted rule can read it for the slot offset, so I think a separate internal property for that would let --padding-top: 0px go back on :host(). If losing the override is intentional it should be in BREAKING.md.
| if (ev.target === ev.currentTarget) { | ||
| ev.stopPropagation(); | ||
| } | ||
| ev.stopPropagation(); |
There was a problem hiding this comment.
Was there a reason for dropping the target check? It came in with #30384 for #30165.
Since the label now wraps .textarea-start and .textarea-end, clicking slotted content doesn't propagate past ion-textarea any more. Clicking a slotted ion-button still fires its own handler, but a listener on a wrapping ion-item or on document gets nothing, where on major-9.0 both fire once. That takes out click tracking and click-outside handlers over that part of the field.
The item tests don't catch it because onClickCapture intercepts the native textarea path before the label handler runs. Same change is in the input PR.
This also happens if you use something like a dynamic font and it lazy loads in after the input renders and the size is different, but in that case it doesn't fix itself ever.
| */ | ||
| return this.renderLabel(); | ||
|
|
||
| const startSlotWidth = startSlot.getBoundingClientRect().width; |
There was a problem hiding this comment.
Reading layout during render() is a bit shaky here. There's no re-measure once the element becomes visible, so a textarea that first renders in a hidden container (inline modal, inactive tab, collapsed accordion) gets 0px and keeps it, and the floating label ends up sitting over the start icon. It fixes itself on first focus, which makes it easy to miss in manual testing.
A ResizeObserver on .textarea-start set up in componentDidLoad would cover this and the stale-measurement case together, and notch-controller.ts already solves the same hidden-element problem for the notch.
Since this method is identical in the input PR, might be worth pulling into utils/forms/ next to createNotchController.
|
|
||
| const startSlotWidth = startSlot.getBoundingClientRect().width; | ||
| const roundedWidth = Math.round(startSlotWidth * 10) / 10; | ||
| const isRTL = document.dir === 'rtl'; |
There was a problem hiding this comment.
Using document.dir here misses element-level direction, so a textarea inside a dir="rtl" container on an LTR page gets the sign flipped the wrong way. The RTL screenshots won't catch it since the test harness sets dir on <html>.
The transform mixin auto-flips translate3d but not translate, so switching the outline rule to translate3d(var(--start-slot-adjustment), -32%, 0) would let the mixin handle direction and this sign logic could go away.
|
|
||
| :host(.textarea-label-placement-floating), | ||
| :host(.textarea-label-placement-stacked) { | ||
| --padding-top: 20px; |
There was a problem hiding this comment.
This block is identical in textarea.ios.scss, and it replaced a rule that used to live in the shared textarea.scss. Could it go back there? Same values in both mode files means they can drift.


Issue number: N/A
What is the current behavior?
Textareas with a floating label and a start or end slot always display the label in the floated state, regardless of whether the textarea contains a value:
What is the new behavior?
mdspecification.Does this introduce a breaking change?
Internal DOM Structure Changes
The internal DOM structure has been modified 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 elements have been modified:
<div class="textarea-wrapper-inner"><div class="start-slot-wrapper">is now<div class="textarea-start"><div class="textarea-control">wrapper for the label and native control<div class="end-slot-wrapper">is now<div class="textarea-end">While the public API has not changed, selectors or style overrides targeting the previous markup will need to be updated to reference the new element names and their organization. If you have custom CSS targeting the internal structure of textarea, update your selectors to account for these structural changes.
Minimum Height Change
The minimum height of textarea in Material Design (
mdmode) has been increased from56pxto72px. This change ensures consistent heights across textareas regardless of thefillproperty orlabelPlacement, providing a more uniform and predictable user experience. If you were relying on textareas being56pxtall or had custom CSS based on that value, you will need to either update your styles to accommodate the new72pxheight or override it back to56pxif needed.Other information
Preview