diff --git a/field/internal/field.ts b/field/internal/field.ts index 4e747b3a13..58843333a1 100644 --- a/field/internal/field.ts +++ b/field/internal/field.ts @@ -320,6 +320,10 @@ export class Field extends LitElement { } = restingLabelEl.getBoundingClientRect(); const floatingScrollWidth = floatingLabelEl.scrollWidth; const restingScrollWidth = restingLabelEl.scrollWidth; + // If either label has no dimensions (e.g., display: none), skip animation + if (floatingScrollWidth === 0 || restingScrollWidth === 0) { + return []; + } // Scale by width ratio instead of font size since letter-spacing will scale // incorrectly. Using the width we can better approximate the adjusted // scale and compensate for tracking and overflow. diff --git a/field/internal/field_test.ts b/field/internal/field_test.ts index 8785e19551..93571f123d 100644 --- a/field/internal/field_test.ts +++ b/field/internal/field_test.ts @@ -408,4 +408,28 @@ describe('Field', () => { .toBeTrue(); }); }); + + describe('label animation', () => { + it('should not produce NaN transforms when populated while hidden', async () => { + const {instance} = await setupTest({label: 'Hidden Label'}); + instance.style.display = 'none'; + await env.waitForStability(); + const animateCalls: unknown[] = []; + spyOn(Element.prototype, 'animate').and.callFake((keyframes, options) => { + animateCalls.push(keyframes); + return new Animation(); + }); + const consoleErrorSpy = spyOn(console, 'error'); + instance.populated = true; + await env.waitForStability(); + for (const keyframe of animateCalls) { + const frames = Array.isArray(keyframe) ? keyframe : [keyframe]; + for (const frame of frames) { + const transform = (frame as any)?.transform ?? ''; + expect(transform).not.toMatch(/NaN/); + } + } + expect(consoleErrorSpy).not.toHaveBeenCalled(); + }); + }); });