diff --git a/src/lib/components/tabs/tabs.test.ts b/src/lib/components/tabs/tabs.test.ts
index 613b903..07cf63f 100644
--- a/src/lib/components/tabs/tabs.test.ts
+++ b/src/lib/components/tabs/tabs.test.ts
@@ -83,6 +83,45 @@ describe('Rendering', () => {
assertTabs({ active: 0 })
})
+ it('should guarantee the order of DOM nodes when performing actions', async () => {
+ render(svelte`
+
+
+
+
+
+ Tab 1
+ {#if !hide}
+ Tab 2
+ {/if}
+ Tab 3
+
+
+
+ Content 1
+ {#if !hide}
+ Content 2
+ {/if}
+ Content 3
+
+
+ `)
+
+ await click(getByText('toggle')) // Remove Tab 2
+ await click(getByText('toggle')) // Re-add Tab 2
+
+ await press(Keys.Tab)
+ assertTabs({ active: 0 })
+
+ await press(Keys.ArrowRight)
+ assertTabs({ active: 1 })
+
+ await press(Keys.ArrowRight)
+ assertTabs({ active: 2 })
+ })
+
describe('`slot props`', () => {
it('should expose the `selectedIndex` on the `TabGroup` component', async () => {
render(svelte`
diff --git a/src/lib/test-utils/accessibility-assertions.ts b/src/lib/test-utils/accessibility-assertions.ts
index 9e8d2e2..73201ee 100644
--- a/src/lib/test-utils/accessibility-assertions.ts
+++ b/src/lib/test-utils/accessibility-assertions.ts
@@ -1359,12 +1359,8 @@ export function assertTabs(
expect(list).toHaveAttribute("role", "tablist");
expect(list).toHaveAttribute("aria-orientation", orientation);
- let activeTab = tabs.find(
- (tab) => tab.dataset.headlessuiIndex === "" + active
- );
- let activePanel = panels.find(
- (panel) => panel.dataset.headlessuiIndex === "" + active
- );
+ let activeTab = Array.from(list.querySelectorAll('[id^="headlessui-tabs-tab-"]'))[active]
+ let activePanel = panels.find(panel => panel.id === activeTab.getAttribute('aria-controls'))
for (let tab of tabs) {
expect(tab).toHaveAttribute("id");
diff --git a/src/lib/utils/focus-management.ts b/src/lib/utils/focus-management.ts
index 2590cd8..f9c8118 100644
--- a/src/lib/utils/focus-management.ts
+++ b/src/lib/utils/focus-management.ts
@@ -16,10 +16,10 @@ let focusableSelector = [
.map(
process.env.NODE_ENV === "test"
? // TODO: Remove this once JSDOM fixes the issue where an element that is
- // "hidden" can be the document.activeElement, because this is not possible
- // in real browsers.
- (selector) =>
- `${selector}:not([tabindex='-1']):not([style*='display: none'])`
+ // "hidden" can be the document.activeElement, because this is not possible
+ // in real browsers.
+ (selector) =>
+ `${selector}:not([tabindex='-1']):not([style*='display: none'])`
: (selector) => `${selector}:not([tabindex='-1'])`
)
.join(",");
@@ -100,7 +100,13 @@ export function focusElement(element: HTMLElement | null) {
export function focusIn(container: HTMLElement | HTMLElement[], focus: Focus) {
let elements = Array.isArray(container)
- ? container
+ ? container.slice().sort((a, b) => {
+ let position = a.compareDocumentPosition(b)
+
+ if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1
+ if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1
+ return 0
+ })
: getFocusableElements(container);
let active = document.activeElement as HTMLElement;