From 884cad9c982f619efd7547465500aea5dfa0a696 Mon Sep 17 00:00:00 2001 From: tristan Date: Wed, 3 Jun 2026 17:34:06 +0200 Subject: [PATCH] =?UTF-8?q?fix(ui)=20:=20TabList=20=E2=80=94=20roving=20ta?= =?UTF-8?q?bindex=20+=20aria=20panels=20+=20layout=20non-fen=C3=AAtr=C3=A9?= =?UTF-8?q?=20(review)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - focusedKey assure exactement un tab focalisable quand l'actif est hors fenêtre - aria-labelledby conditionnel + fallback aria-label pour panneaux dont le bouton n'est pas rendu - DOM non-fenêtré identique à l'origine (plus de wrapper flex parasite) - reset de la fenêtre (startIndex) au remplacement des tabs Co-Authored-By: Claude Opus 4.8 (1M context) --- app/components/malio/tab/TabList.test.ts | 17 +++++++ app/components/malio/tab/TabList.vue | 57 +++++++++++++++++++++--- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/app/components/malio/tab/TabList.test.ts b/app/components/malio/tab/TabList.test.ts index 7e91ad2..12a90c0 100644 --- a/app/components/malio/tab/TabList.test.ts +++ b/app/components/malio/tab/TabList.test.ts @@ -304,6 +304,23 @@ describe('MalioTabList — fenêtrage maxVisibleTabs', () => { expect(wrapper.text()).toContain('Panel 1') }) + it('keeps exactly one rendered tab with tabindex=0 when the active tab scrolls out of the window', async () => { + const wrapper = mountComponent({tabs: sevenTabs, maxVisibleTabs: 5}) + + // active tab is the first one (t1) by default; scroll it out of the window + await wrapper.find('[data-test="tab-next"]').trigger('click') + await wrapper.find('[data-test="tab-next"]').trigger('click') + + // t1 is no longer rendered + const labels = wrapper.findAll('[role="tab"]').map(b => b.text()) + expect(labels.some(l => l.includes('Tab 1'))).toBe(false) + + const focusable = wrapper.findAll('[role="tab"]').filter(b => b.attributes('tabindex') === '0') + expect(focusable).toHaveLength(1) + // falls back to the first visible tab (Tab 3) + expect(focusable[0].text()).toContain('Tab 3') + }) + it('arrows expose aria-labels', () => { const wrapper = mountComponent({tabs: sevenTabs, maxVisibleTabs: 5}) expect(wrapper.find('[data-test="tab-prev"]').attributes('aria-label')).toBe('Onglets précédents') diff --git a/app/components/malio/tab/TabList.vue b/app/components/malio/tab/TabList.vue index 969e26a..0e58b7e 100644 --- a/app/components/malio/tab/TabList.vue +++ b/app/components/malio/tab/TabList.vue @@ -1,8 +1,7 @@