diff --git a/src/app/components/PaneToggleStrip.test.tsx b/src/app/components/PaneToggleStrip.test.tsx index d628e9d..ef948bf 100644 --- a/src/app/components/PaneToggleStrip.test.tsx +++ b/src/app/components/PaneToggleStrip.test.tsx @@ -56,6 +56,18 @@ describe('PaneToggleStrip', () => { expect(library.getAttribute('aria-label')).toBe('Library pane'); }); + test('aria-controls points at the pane only while it is shown (no dangling IDREF)', () => { + // App unmounts a hidden pane's
, so emitting aria-controls then would + // leave the IDREF pointing at nothing. It must appear only when pressed/shown. + const [library] = toggleButtons(); + expect(library.hasAttribute('aria-controls')).toBe(true); + + act(() => library.click()); // hide the pane + + expect(library.getAttribute('aria-pressed')).toBe('false'); + expect(library.hasAttribute('aria-controls')).toBe(false); + }); + test('roving tabindex: exactly one control is in the tab order at a time', () => { const all = [...toggleButtons(), datasetsButton()]; expect(all.filter((b) => b.tabIndex === 0)).toHaveLength(1); diff --git a/src/app/components/PaneToggleStrip.tsx b/src/app/components/PaneToggleStrip.tsx index 343fd98..148eb8f 100644 --- a/src/app/components/PaneToggleStrip.tsx +++ b/src/app/components/PaneToggleStrip.tsx @@ -102,11 +102,13 @@ export function PaneToggleStrip() { type="button" aria-pressed={visible[item.pane]} aria-label={item.label} - // TODO: when a pane is hidden, App unmounts its
, so this - // aria-controls IDREF dangles until the pane is shown again. Harmless - // (AT ignores unresolved IDREFs) but technically invalid — consider - // keeping the section mounted-but-hidden, or dropping aria-controls. - aria-controls={item.controls} + // Only point at the pane while it's actually mounted: App unmounts a + // hidden pane's
, so emitting aria-controls then would leave a + // dangling IDREF (invalid, even if AT ignores it). The association is + // meaningful only when the target exists, and the aria-pressed state + // already conveys hidden/shown either way (contract: arch/10 → toggle + // strip — the bullet lists aria-pressed + a stable name, not controls). + aria-controls={visible[item.pane] ? item.controls : undefined} // Description (not the name): hints what activating does. The name stays // stable (aria-label) per APG's toggle-button rule. title={`${visible[item.pane] ? 'Hide' : 'Show'} ${item.label.toLowerCase()}`}