From 0d28d588c108c0fbe4c8fa88b3fc2fc5d90f95c4 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 3 Feb 2023 10:58:18 -0500 Subject: [PATCH] Preserve default index when starting out with no tabs (#2250) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Preserve default index when starting out with no tabs * Add test to Vue * Add test for Vue it already works here so… yeah --- .../src/components/tabs/tabs.test.tsx | 139 ++++++++++++++++++ .../src/components/tabs/tabs.tsx | 9 ++ .../src/components/tabs/tabs.test.ts | 134 +++++++++++++++++ 3 files changed, 282 insertions(+) diff --git a/packages/@headlessui-react/src/components/tabs/tabs.test.tsx b/packages/@headlessui-react/src/components/tabs/tabs.test.tsx index 310f847..3292579 100644 --- a/packages/@headlessui-react/src/components/tabs/tabs.test.tsx +++ b/packages/@headlessui-react/src/components/tabs/tabs.test.tsx @@ -804,6 +804,145 @@ describe('Rendering', () => { assertTabs({ active: 2 }) }) ) + + it( + 'should select first tab if no tabs were provided originally', + suppressConsoleLogs(async () => { + function Example({ defaultIndex = undefined }: { defaultIndex?: number } = {}) { + let [tabs, setTabs] = useState([]) + + return ( + <> + + + {tabs.map((tab, index) => ( + {tab} + ))} + + + {tabs.map((tab, index) => ( + content: {tab} + ))} + + + + + + ) + } + + render() + + assertActiveElement(document.body) + + // There are no tab initially + assertTabs({ active: -1 }) + + // There are not tabs so this should not change anything + await press(Keys.Tab) + assertTabs({ active: -1 }) + + // Add some tabs + await click(getByText('change')) + + // When going from no tabs to some tabs, the tab based on defaultIndex should be selected + assertTabs({ active: 0 }) + }) + ) + + it( + 'should select first tab if no tabs were provided originally (with a defaultIndex of 1)', + suppressConsoleLogs(async () => { + function Example({ defaultIndex = undefined }: { defaultIndex?: number } = {}) { + let [tabs, setTabs] = useState([]) + + return ( + <> + + + {tabs.map((tab, index) => ( + {tab} + ))} + + + {tabs.map((tab, index) => ( + content: {tab} + ))} + + + + + + ) + } + + render() + + assertActiveElement(document.body) + + // There are no tab initially + assertTabs({ active: -1 }) + + // There are not tabs so this should not change anything + await press(Keys.Tab) + assertTabs({ active: -1 }) + + // Add some tabs + await click(getByText('change')) + + // When going from no tabs to some tabs, the tab based on defaultIndex should be selected + assertTabs({ active: 1 }) + }) + ) + + it( + 'should select first tab if no tabs were provided originally (with a defaultIndex of 1)', + suppressConsoleLogs(async () => { + function Example({ defaultIndex = undefined }: { defaultIndex?: number } = {}) { + let [tabs, setTabs] = useState([]) + + return ( + <> + + + {tabs.map((tab, index) => ( + {tab} + ))} + + + {tabs.map((tab, index) => ( + content: {tab} + ))} + + + + + + + + ) + } + + render() + + assertActiveElement(document.body) + + // There are no tab initially + assertTabs({ active: -1 }) + + // There are not tabs so this should not change anything + await press(Keys.Tab) + assertTabs({ active: -1 }) + + // Add some tabs + await click(getByText('change 1')) + await click(getByText('change 2')) + await click(getByText('change 3')) + + // When going from no tabs to some tabs, the tab based on defaultIndex should be selected + assertTabs({ active: 1 }) + }) + ) }) describe('`selectedIndex`', () => { diff --git a/packages/@headlessui-react/src/components/tabs/tabs.tsx b/packages/@headlessui-react/src/components/tabs/tabs.tsx index 4a633b5..748ccd1 100644 --- a/packages/@headlessui-react/src/components/tabs/tabs.tsx +++ b/packages/@headlessui-react/src/components/tabs/tabs.tsx @@ -99,6 +99,15 @@ let reducers: { [Ordering.Greater]: () => Direction.Forwards, }) + // If there are no focusable tabs then. + // We won't change the selected index + // because it's likely the user is + // lazy loading tabs and there's + // nothing to focus on yet + if (focusableTabs.length === 0) { + return nextState + } + return { ...nextState, selectedIndex: match(direction, { diff --git a/packages/@headlessui-vue/src/components/tabs/tabs.test.ts b/packages/@headlessui-vue/src/components/tabs/tabs.test.ts index 64d7104..638a367 100644 --- a/packages/@headlessui-vue/src/components/tabs/tabs.test.ts +++ b/packages/@headlessui-vue/src/components/tabs/tabs.test.ts @@ -721,6 +721,140 @@ describe('Rendering', () => { // Nothing should change... assertTabs({ active: 2 }) }) + + it( + 'should select first tab if no tabs were provided originally', + suppressConsoleLogs(async () => { + renderTemplate({ + template: html` + + + {{ tab }} + + + content for: {{ tab }} + + + + + + `, + setup() { + let tabs = ref([]) + return { + tabs, + } + }, + }) + + assertActiveElement(document.body) + + // There are no tab initially + assertTabs({ active: -1 }) + + // There are not tabs so this should not change anything + await press(Keys.Tab) + assertTabs({ active: -1 }) + + // Add some tabs + await click(getByText('change')) + + // When going from no tabs to some tabs, the tab based on defaultIndex should be selected + assertTabs({ active: 0 }) + }) + ) + + it( + 'should select first tab if no tabs were provided originally (with a defaultIndex of 1)', + suppressConsoleLogs(async () => { + renderTemplate({ + template: html` + + + {{ tab }} + + + content for: {{ tab }} + + + + + + `, + setup() { + let tabs = ref([]) + return { + tabs, + } + }, + }) + + assertActiveElement(document.body) + + // There are no tab initially + assertTabs({ active: -1 }) + + // There are not tabs so this should not change anything + await press(Keys.Tab) + assertTabs({ active: -1 }) + + // Add some tabs + await click(getByText('change')) + + // When going from no tabs to some tabs, the tab based on defaultIndex should be selected + assertTabs({ active: 1 }) + }) + ) + + it( + 'should select first tab if no tabs were provided originally (with a defaultIndex of 1)', + suppressConsoleLogs(async () => { + renderTemplate({ + template: html` + + + {{ tab }} + + + content for: {{ tab }} + + + + + + + + `, + setup() { + let tabs = ref([]) + return { + tabs, + } + }, + }) + + assertActiveElement(document.body) + + // There are no tab initially + assertTabs({ active: -1 }) + + // There are not tabs so this should not change anything + await press(Keys.Tab) + assertTabs({ active: -1 }) + + // Add some tabs + await click(getByText('change 1')) + + // Add some tabs + await click(getByText('change 2')) + + // Add some tabs + await click(getByText('change 3')) + + // When going from no tabs to some tabs, the tab based on defaultIndex should be selected + assertTabs({ active: 1 }) + }) + ) }) })