Tab: fix aria-controls

When porting, I thought I was being clever by just using ids instead of refs in $api.panels
But I didn't notice that the refs let you distinguish those panels which are rendered from those which are not.
My implementation had aria-controls= set for all tabs, even when their panels were not in the DOM.
This commit is contained in:
Ryan Gossiaux
2021-12-28 16:51:36 -10:00
parent 90e633f8a8
commit 70c68d00fe
3 changed files with 17 additions and 7 deletions

View File

@@ -87,11 +87,12 @@
$api.setSelectedIndex(myIndex); $api.setSelectedIndex(myIndex);
} }
$: myPanelRef = $api.panels[myIndex]?.ref;
$: propsWeControl = { $: propsWeControl = {
id, id,
role: "tab", role: "tab",
type: resolveButtonType({ type: $$props.type, as }, tabRef), type: resolveButtonType({ type: $$props.type, as }, tabRef),
"aria-controls": $api.panels[myIndex], "aria-controls": $myPanelRef ? $api.panels[myIndex]?.id : undefined,
"aria-selected": selected, "aria-selected": selected,
tabIndex: selected ? 0 : -1, tabIndex: selected ? 0 : -1,
disabled: disabled ? true : undefined, disabled: disabled ? true : undefined,

View File

@@ -1,4 +1,8 @@
<script lang="ts" context="module"> <script lang="ts" context="module">
interface PanelData {
id: string;
ref: Readable<HTMLElement | null>;
}
export type StateDefinition = { export type StateDefinition = {
// State // State
selectedIndex: number | null; selectedIndex: number | null;
@@ -6,14 +10,14 @@
activation: "auto" | "manual"; activation: "auto" | "manual";
tabs: (HTMLElement | null)[]; tabs: (HTMLElement | null)[];
panels: string[]; panels: PanelData[];
// State mutators // State mutators
setSelectedIndex(index: number): void; setSelectedIndex(index: number): void;
registerTab(tab: HTMLElement | null): void; registerTab(tab: HTMLElement | null): void;
unregisterTab(tab: HTMLElement | null): void; unregisterTab(tab: HTMLElement | null): void;
registerPanel(panel: string): void; registerPanel(panel: PanelData): void;
unregisterPanel(panel: string): void; unregisterPanel(panel: PanelData): void;
}; };
const TABS_CONTEXT_NAME = "headlessui-tabs-context"; const TABS_CONTEXT_NAME = "headlessui-tabs-context";

View File

@@ -7,20 +7,24 @@
import type { SupportedAs } from "$lib/internal/elements"; import type { SupportedAs } from "$lib/internal/elements";
import type { HTMLActionArray } from "$lib/hooks/use-actions"; import type { HTMLActionArray } from "$lib/hooks/use-actions";
import Render, { Features } from "$lib/utils/Render.svelte"; import Render, { Features } from "$lib/utils/Render.svelte";
import { writable, Writable } from "svelte/store";
const forwardEvents = forwardEventsBuilder(get_current_component()); const forwardEvents = forwardEventsBuilder(get_current_component());
export let as: SupportedAs = "div"; export let as: SupportedAs = "div";
export let use: HTMLActionArray = []; export let use: HTMLActionArray = [];
let elementRef: Writable<HTMLElement | null> = writable(null);
let api = useTabsContext("TabPanel"); let api = useTabsContext("TabPanel");
let id = `headlessui-tabs-panel-${useId()}`; let id = `headlessui-tabs-panel-${useId()}`;
$: panelData = { id, ref: elementRef };
onMount(() => { onMount(() => {
$api.registerPanel(id); $api.registerPanel(panelData);
return () => $api.unregisterPanel(id); return () => $api.unregisterPanel(panelData);
}); });
$: myIndex = $api.panels.indexOf(id); $: myIndex = $api.panels.indexOf(panelData);
$: selected = myIndex === $api.selectedIndex; $: selected = myIndex === $api.selectedIndex;
$: propsWeControl = { $: propsWeControl = {
@@ -36,6 +40,7 @@
{as} {as}
use={[...use, forwardEvents]} use={[...use, forwardEvents]}
name={"TabPanel"} name={"TabPanel"}
bind:el={$elementRef}
visible={selected} visible={selected}
features={Features.RenderStrategy | Features.Static} features={Features.RenderStrategy | Features.Static}
> >