Skip to content

Commit 884fecd

Browse files
authored
feat(textarea): support notch with label slot (#27660)
1 parent c2dffd0 commit 884fecd

10 files changed

+87
-5
lines changed

core/src/components/textarea/test/fill/textarea.e2e.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ configs({ modes: ['md'] }).forEach(({ title, screenshot, config }) => {
1717
helper-text="Enter your email"
1818
maxlength="20"
1919
counter="true"
20-
></ion-input>
20+
></ion-textarea>
2121
`,
2222
config
2323
);
@@ -196,3 +196,58 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
196196
});
197197
});
198198
});
199+
200+
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
201+
test.describe(title('textarea: label slot'), () => {
202+
test('should render the notch correctly with a slotted label', async ({ page }) => {
203+
await page.setContent(
204+
`
205+
<style>
206+
.custom-label {
207+
font-size: 30px;
208+
}
209+
</style>
210+
<ion-textarea
211+
fill="outline"
212+
label-placement="stacked"
213+
value="apple"
214+
>
215+
<div slot="label" class="custom-label">My Label Content</div>
216+
</ion-textarea>
217+
`,
218+
config
219+
);
220+
221+
const textarea = page.locator('ion-textarea');
222+
expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-fill-outline-slotted-label`));
223+
});
224+
test('should render the notch correctly with a slotted label after the textarea was originally hidden', async ({
225+
page,
226+
}) => {
227+
await page.setContent(
228+
`
229+
<style>
230+
.custom-label {
231+
font-size: 30px;
232+
}
233+
</style>
234+
<ion-textarea
235+
fill="outline"
236+
label-placement="stacked"
237+
value="apple"
238+
style="display: none"
239+
>
240+
<div slot="label" class="custom-label">My Label Content</div>
241+
</ion-textarea>
242+
`,
243+
config
244+
);
245+
246+
const textarea = page.locator('ion-textarea');
247+
248+
await textarea.evaluate((el: HTMLIonSelectElement) => el.style.removeProperty('display'));
249+
250+
expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-fill-outline-hidden-slotted-label`));
251+
});
252+
});
253+
});

core/src/components/textarea/textarea.md.outline.scss

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,16 @@
176176

177177
opacity: 0;
178178
pointer-events: none;
179+
180+
/**
181+
* The spacer currently inherits
182+
* border-box sizing from the Ionic reset styles.
183+
* However, we do not want to include padding in
184+
* the calculation of the element dimensions.
185+
* This code can be removed if textarea is updated
186+
* to use the Shadow DOM.
187+
*/
188+
box-sizing: content-box;
179189
}
180190

181191
:host(.textarea-fill-outline) .textarea-outline-start {

core/src/components/textarea/textarea.tsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { ComponentInterface, EventEmitter } from '@stencil/core';
22
import { Build, Component, Element, Event, Host, Method, Prop, State, Watch, h, writeTask } from '@stencil/core';
3-
import type { LegacyFormController } from '@utils/forms';
4-
import { createLegacyFormController } from '@utils/forms';
3+
import type { LegacyFormController, NotchController } from '@utils/forms';
4+
import { createLegacyFormController, createNotchController } from '@utils/forms';
55
import type { Attributes } from '@utils/helpers';
66
import { inheritAriaAttributes, debounceEvent, findItemLabel, inheritAttributes } from '@utils/helpers';
77
import { printIonWarning } from '@utils/logging';
@@ -40,6 +40,9 @@ export class Textarea implements ComponentInterface {
4040
private inheritedAttributes: Attributes = {};
4141
private originalIonInput?: EventEmitter<TextareaInputEventDetail>;
4242
private legacyFormController!: LegacyFormController;
43+
private notchSpacerEl: HTMLElement | undefined;
44+
45+
private notchController?: NotchController;
4346

4447
// This flag ensures we log the deprecation warning at most once.
4548
private hasLoggedDeprecationWarning = false;
@@ -292,6 +295,11 @@ export class Textarea implements ComponentInterface {
292295
connectedCallback() {
293296
const { el } = this;
294297
this.legacyFormController = createLegacyFormController(el);
298+
this.notchController = createNotchController(
299+
el,
300+
() => this.notchSpacerEl,
301+
() => this.labelSlot
302+
);
295303
this.emitStyle();
296304
this.debounceChanged();
297305
if (Build.isBrowser) {
@@ -311,6 +319,11 @@ export class Textarea implements ComponentInterface {
311319
})
312320
);
313321
}
322+
323+
if (this.notchController) {
324+
this.notchController.destroy();
325+
this.notchController = undefined;
326+
}
314327
}
315328

316329
componentWillLoad() {
@@ -325,6 +338,10 @@ export class Textarea implements ComponentInterface {
325338
this.runAutoGrow();
326339
}
327340

341+
componentDidRender() {
342+
this.notchController?.calculateNotchWidth();
343+
}
344+
328345
/**
329346
* Sets focus on the native `textarea` in `ion-textarea`. Use this method instead of the global
330347
* `textarea.focus()`.
@@ -591,7 +608,7 @@ Developers can use the "legacy" property to continue using the legacy form marku
591608
'textarea-outline-notch-hidden': !this.hasLabel,
592609
}}
593610
>
594-
<div class="notch-spacer" aria-hidden="true">
611+
<div class="notch-spacer" aria-hidden="true" ref={(el) => (this.notchSpacerEl = el)}>
595612
{this.label}
596613
</div>
597614
</div>

core/src/utils/forms/notch-controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { win } from '@utils/browser';
22
import { raf } from '@utils/helpers';
33

4-
type NotchElement = HTMLIonInputElement | HTMLIonSelectElement;
4+
type NotchElement = HTMLIonInputElement | HTMLIonSelectElement | HTMLIonTextareaElement;
55

66
/**
77
* A utility to calculate the size of an outline notch

0 commit comments

Comments
 (0)