fix(html): avoid menu item value render loop (#1791)

This commit is contained in:
Sam Potts
2026-07-02 15:11:19 +10:00
committed by GitHub
parent 6f6657a198
commit 47e74a139e
4 changed files with 49 additions and 4 deletions
@@ -335,12 +335,24 @@ export function observeMenuViewContent(content: HTMLElement, onChange: () => voi
});
}
const observer = new MutationObserver(scheduleChange);
const observer = new MutationObserver((records) => {
const changed = records.some((record) => {
if (record.type !== 'attributes') return true;
const name = record.attributeName;
if (!name) return true;
return record.oldValue !== (record.target as Element).getAttribute(name);
});
if (changed) scheduleChange();
});
observer.observe(content, {
childList: true,
subtree: true,
attributes: true,
attributeOldValue: true,
attributeFilter: MENU_VIEW_LAYOUT_ATTRS,
});
@@ -172,11 +172,21 @@ describe('menu-viewport-transition', () => {
expect(onChange).toHaveBeenCalledTimes(2);
cleanup();
item.setAttribute('data-availability', 'unavailable');
item.setAttribute('data-availability', 'available');
await waitForMutationFrame();
expect(onChange).toHaveBeenCalledTimes(2);
item.setAttribute('data-availability', 'unavailable');
await waitForMutationFrame();
expect(onChange).toHaveBeenCalledTimes(3);
cleanup();
item.setAttribute('data-availability', 'available');
await waitForMutationFrame();
expect(onChange).toHaveBeenCalledTimes(3);
});
it('toggles menu viewport data attributes for active and exiting phases', () => {
@@ -17,6 +17,9 @@ export class MenuItemValueElement extends MediaElement {
protected override update(_changed: PropertyValues): void {
super.update(_changed);
this.textContent = this.#ctx.value?.label ?? '';
const label = this.#ctx.value?.label ?? '';
if (this.textContent !== label) {
this.textContent = label;
}
}
}
@@ -172,6 +172,26 @@ describe('MenuItemValueElement', () => {
});
});
it('does not rewrite an unchanged label', async () => {
const { value } = setup(createPlaybackRateStore({ playbackRate: 1.5 }), 'playback-rate');
await value.updateComplete;
await waitForAssertion(() => {
expect(value.textContent).toBe('1.5×');
});
const mutations: MutationRecord[] = [];
const observer = new MutationObserver((records) => mutations.push(...records));
observer.observe(value, { childList: true, characterData: true, subtree: true });
value.requestUpdate();
await value.updateComplete;
await nextFrame();
observer.disconnect();
expect(mutations).toHaveLength(0);
});
it('renders Off when captions are disabled', async () => {
const { value } = setup(
createTextTrackStore({