Skip to content

Commit 00205ed

Browse files
committed
Port "Ensure correct DOM node order when performing focus actions"
tailwindlabs/headlessui#1038 This test didn't actually fail for me but w/e.
1 parent 9d4b2ca commit 00205ed

File tree

3 files changed

+52
-11
lines changed

3 files changed

+52
-11
lines changed

src/lib/components/tabs/tabs.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,45 @@ describe('Rendering', () => {
8383
assertTabs({ active: 0 })
8484
})
8585

86+
it('should guarantee the order of DOM nodes when performing actions', async () => {
87+
render(svelte`
88+
<script>
89+
let hide = false;
90+
</script>
91+
92+
<button on:click={() => hide = !hide}>toggle</button>
93+
<TabGroup>
94+
<TabList>
95+
<Tab>Tab 1</Tab>
96+
{#if !hide}
97+
<Tab>Tab 2</Tab>
98+
{/if}
99+
<Tab>Tab 3</Tab>
100+
</TabList>
101+
102+
<TabPanels>
103+
<TabPanel>Content 1</TabPanel>
104+
{#if !hide}
105+
<TabPanel>Content 2</TabPanel>
106+
{/if}
107+
<TabPanel>Content 3</TabPanel>
108+
</TabPanels>
109+
</TabGroup>
110+
`)
111+
112+
await click(getByText('toggle')) // Remove Tab 2
113+
await click(getByText('toggle')) // Re-add Tab 2
114+
115+
await press(Keys.Tab)
116+
assertTabs({ active: 0 })
117+
118+
await press(Keys.ArrowRight)
119+
assertTabs({ active: 1 })
120+
121+
await press(Keys.ArrowRight)
122+
assertTabs({ active: 2 })
123+
})
124+
86125
describe('`slot props`', () => {
87126
it('should expose the `selectedIndex` on the `TabGroup` component', async () => {
88127
render(svelte`

src/lib/test-utils/accessibility-assertions.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,12 +1359,8 @@ export function assertTabs(
13591359
expect(list).toHaveAttribute("role", "tablist");
13601360
expect(list).toHaveAttribute("aria-orientation", orientation);
13611361

1362-
let activeTab = tabs.find(
1363-
(tab) => tab.dataset.headlessuiIndex === "" + active
1364-
);
1365-
let activePanel = panels.find(
1366-
(panel) => panel.dataset.headlessuiIndex === "" + active
1367-
);
1362+
let activeTab = Array.from(list.querySelectorAll('[id^="headlessui-tabs-tab-"]'))[active]
1363+
let activePanel = panels.find(panel => panel.id === activeTab.getAttribute('aria-controls'))
13681364

13691365
for (let tab of tabs) {
13701366
expect(tab).toHaveAttribute("id");

src/lib/utils/focus-management.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ let focusableSelector = [
1616
.map(
1717
process.env.NODE_ENV === "test"
1818
? // TODO: Remove this once JSDOM fixes the issue where an element that is
19-
// "hidden" can be the document.activeElement, because this is not possible
20-
// in real browsers.
21-
(selector) =>
22-
`${selector}:not([tabindex='-1']):not([style*='display: none'])`
19+
// "hidden" can be the document.activeElement, because this is not possible
20+
// in real browsers.
21+
(selector) =>
22+
`${selector}:not([tabindex='-1']):not([style*='display: none'])`
2323
: (selector) => `${selector}:not([tabindex='-1'])`
2424
)
2525
.join(",");
@@ -100,7 +100,13 @@ export function focusElement(element: HTMLElement | null) {
100100

101101
export function focusIn(container: HTMLElement | HTMLElement[], focus: Focus) {
102102
let elements = Array.isArray(container)
103-
? container
103+
? container.slice().sort((a, b) => {
104+
let position = a.compareDocumentPosition(b)
105+
106+
if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1
107+
if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1
108+
return 0
109+
})
104110
: getFocusableElements(container);
105111
let active = document.activeElement as HTMLElement;
106112

0 commit comments

Comments
 (0)