Skip to content

Commit 58e9491

Browse files
CopilotHotell
andauthored
fix(web-components): TextArea auto-resize moves instead of grows in Firefox (#35849)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Martin Hochel <martinhochel@microsoft.com>
1 parent dd187e2 commit 58e9491

6 files changed

Lines changed: 62 additions & 2 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "fix(web-components): resolve auto-resize issues for TextArea in Firefox",
4+
"packageName": "@fluentui/web-components",
5+
"email": "198982749+Copilot@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}

packages/web-components/docs/api-report.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,8 @@ export class BaseTextArea extends FASTElement {
833833
// (undocumented)
834834
protected readOnlyChanged(): void;
835835
reportValidity(): boolean;
836+
// @internal
837+
rootEl: HTMLDivElement;
836838
required: boolean;
837839
// (undocumented)
838840
protected requiredChanged(): void;

packages/web-components/docs/web-components.api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -867,6 +867,8 @@ export class BaseTextArea extends FASTElement {
867867
resize: TextAreaResize;
868868
// (undocumented)
869869
protected resizeChanged(prev: TextAreaResize | undefined, next: TextAreaResize | undefined): void;
870+
// @internal
871+
rootEl: HTMLDivElement;
870872
select(): void;
871873
setCustomValidity(message: string | null): void;
872874
// @internal

packages/web-components/src/textarea/textarea.base.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ export class BaseTextArea extends FASTElement {
4040
*/
4141
public labelEl!: HTMLLabelElement;
4242

43+
/**
44+
* The root container element.
45+
* @internal
46+
*/
47+
public rootEl!: HTMLDivElement;
48+
4349
/**
4450
* The `<textarea>` element.
4551
* @internal
@@ -587,7 +593,10 @@ export class BaseTextArea extends FASTElement {
587593
this.autoSizerEl.classList.add('auto-sizer');
588594
this.autoSizerEl.ariaHidden = 'true';
589595
}
590-
this.shadowRoot!.prepend(this.autoSizerEl);
596+
// `rootEl` uses optional chaining because `autoResizeChanged` may be called before
597+
// the element connects and the template renders. `connectedCallback` will call this
598+
// method again once `rootEl` is available.
599+
this.rootEl?.prepend(this.autoSizerEl);
591600

592601
// The `ResizeObserver` is used to observe when the component gains
593602
// explicit block size, when so, the `autoSizerEl` element should be

packages/web-components/src/textarea/textarea.spec.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,46 @@ test.describe('TextArea', () => {
138138
await expect(element).not.toHaveAttribute('auto-resize');
139139
});
140140

141+
test('should place the auto-sizer element inside the root element when `field-sizing: content` is not supported', async ({
142+
fastPage,
143+
page,
144+
}) => {
145+
const { element } = fastPage;
146+
147+
// Mock CSS.supports to simulate a browser that doesn't support `field-sizing: content` (e.g. Firefox)
148+
// and restore it after to avoid affecting subsequent tests
149+
await page.evaluate(() => {
150+
const originalSupports = CSS.supports.bind(CSS);
151+
(window as Window & { __originalCSSSupports?: typeof CSS.supports }).__originalCSSSupports = originalSupports;
152+
CSS.supports = (property: string, value?: string) => {
153+
if (property === 'field-sizing: content' || (property === 'field-sizing' && value === 'content')) {
154+
return false;
155+
}
156+
return originalSupports(property, value as string);
157+
};
158+
});
159+
160+
try {
161+
await fastPage.setTemplate({ attributes: { 'auto-resize': true } });
162+
163+
const autoSizerIsInsideRoot = await element.evaluate((node: TextArea) => {
164+
const root = node.shadowRoot?.querySelector('.root');
165+
return root?.contains(node.autoSizerEl ?? null) ?? false;
166+
});
167+
168+
expect(autoSizerIsInsideRoot).toBe(true);
169+
} finally {
170+
// Restore the original CSS.supports to avoid affecting other tests
171+
await page.evaluate(() => {
172+
const w = window as Window & { __originalCSSSupports?: typeof CSS.supports };
173+
if (w.__originalCSSSupports) {
174+
CSS.supports = w.__originalCSSSupports;
175+
delete w.__originalCSSSupports;
176+
}
177+
});
178+
}
179+
});
180+
141181
test('should toggle block attribute', async ({ fastPage }) => {
142182
const { element } = fastPage;
143183

packages/web-components/src/textarea/textarea.template.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export function textAreaTemplate<T extends TextArea>(): ElementViewTemplate<T> {
1212
<label ${ref('labelEl')} for="control" part="label">
1313
<slot name="label" ${slotted('labelSlottedNodes')}></slot>
1414
</label>
15-
<div class="root" part="root">
15+
<div class="root" part="root" ${ref('rootEl')}>
1616
<textarea
1717
${ref('controlEl')}
1818
id="control"

0 commit comments

Comments
 (0)