Skip to content

Commit

Permalink
Fix binding aria-labelledby
Browse files Browse the repository at this point in the history
  • Loading branch information
mj12albert committed Nov 19, 2024
1 parent 7fada3f commit 68b5598
Show file tree
Hide file tree
Showing 14 changed files with 99 additions and 135 deletions.
2 changes: 1 addition & 1 deletion packages/mui-base/src/Tabs/Root/TabsRoot.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ describe('<Tabs.Root />', () => {
].forEach((entry) => {
const [orientation, direction, previousItemKey, nextItemKey] = entry;

describe(`when focus is on a tab element in a ${orientation} ${direction} tablist`, () => {
describe(`when focus is on a tab element in a ${orientation} ${direction ?? ''} tablist`, () => {
describe(previousItemKey ?? '', () => {
describe('with `activateOnFocus = false`', () => {
it('moves focus to the last tab without activating it if focus is on the first tab', async () => {
Expand Down
3 changes: 3 additions & 0 deletions packages/mui-base/src/Tabs/Root/TabsRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const TabsRoot = React.forwardRef(function TabsRoot(
const {
getRootProps,
direction,
getTabElementBySelectedValue,
getTabIdByPanelValueOrIndex,
getTabPanelIdByTabValueOrIndex,
onValueChange,
Expand All @@ -55,6 +56,7 @@ const TabsRoot = React.forwardRef(function TabsRoot(
const tabsContextValue = React.useMemo(
() => ({
direction,
getTabElementBySelectedValue,
getTabIdByPanelValueOrIndex,
getTabPanelIdByTabValueOrIndex,
onValueChange,
Expand All @@ -66,6 +68,7 @@ const TabsRoot = React.forwardRef(function TabsRoot(
}),
[
direction,
getTabElementBySelectedValue,
getTabIdByPanelValueOrIndex,
getTabPanelIdByTabValueOrIndex,
onValueChange,
Expand Down
1 change: 1 addition & 0 deletions packages/mui-base/src/Tabs/Root/TabsRootContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface TabsRootContext {
* Registers a function that returns the id of the tab with the given value.
*/
// registerGetTabIdByPanelValueOrIndexFn?: (lookupFunction: (id: any) => string | undefined) => void;
getTabElementBySelectedValue: (selectedValue: any | undefined) => HTMLElement | null;
/**
* Gets the id of the tab with the given value.
* @param {any | undefined} value Value to find the tab for.
Expand Down
61 changes: 45 additions & 16 deletions packages/mui-base/src/Tabs/Root/useTabsRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,24 +76,49 @@ function useTabsRoot(parameters: useTabsRoot.Parameters): useTabsRoot.ReturnValu
// get the `id` attribute of <Tabs.Tab> to set as the value of `aria-labelledby` on <Tabs.Panel>
const getTabIdByPanelValueOrIndex = React.useCallback(
(tabPanelValue: any | undefined, index: number) => {
// return getTabIdByPanelValueOrIndexRef.current(tabPanelValue);
if (tabPanelValue === undefined && index < 0) {
return undefined;
}

for (const tabMetadata of tabMap.values()) {
console.log(tabPanelValue, index, tabMetadata);
// console.log(tabPanelValue, index, tabMetadata);

// find by tabPanelValue
if (
tabPanelValue !== undefined &&
index > -1 &&
tabPanelValue === (tabMetadata?.value ?? tabMetadata?.index ?? undefined)
) {
// console.log('found by tabPanelValue', tabMetadata?.id);
return tabMetadata?.id;
}

// find by index
if (
tabPanelValue === undefined &&
index > -1 &&
index === (tabMetadata?.value ?? tabMetadata?.index ?? undefined)
) {
// console.log('found by tabPanel index', tabMetadata?.id);
return tabMetadata?.id;
}
}

return undefined;
},
[tabMap],
);

// FIXME
// used as a param for `useActivationDirectionDetector`
const getTabElementByPanelValueOrIndex = React.useCallback(
(tabPanelValue: any | undefined, index: number) => {
// return getTabIdByPanelValueOrIndexRef.current(tabPanelValue);
return null;
},
[],
);

// put this into TabsRootContext
// use it in useTabsList
const getTabElementBySelectedValue = React.useCallback((selectedValue: any | undefined) => {
console.log('getTabElementBySelectedValue', selectedValue);
return null;
}, []);

// TODO: no need to put this in a ref anymore?
const registerGetTabIdByPanelValueOrIndexFn = React.useCallback(
(lookupFunction: IdLookupFunction) => {
getTabIdByPanelValueOrIndexRef.current = lookupFunction;
Expand All @@ -112,14 +137,15 @@ function useTabsRoot(parameters: useTabsRoot.Parameters): useTabsRoot.ReturnValu
return {
getRootProps,
direction,
getTabElementByPanelValueOrIndex,
getTabElementBySelectedValue,
getTabIdByPanelValueOrIndex,
getTabPanelIdByTabValueOrIndex,
onValueChange,
registerGetTabIdByPanelValueOrIndexFn,
setTabMap,
setTabPanelMap,
tabActivationDirection,
tabMap,
tabPanelRefs,
value,
};
Expand Down Expand Up @@ -155,14 +181,16 @@ namespace useTabsRoot {
* The direction of the text.
*/
direction: 'ltr' | 'rtl';
getTabElementByPanelValueOrIndex: (
panelValue: any | undefined,
index: number,
) => HTMLElement | null;
/**
* Gets the id of the tab with the given value.
* Gets the underlying element of the Tab with the given value.
* @param value Value to find the tab for.
*/
getTabElementBySelectedValue: (panelValue: any | undefined) => HTMLElement | null;
/**
* Gets the `id` attribute of the Tab with the given value.
* @param (any | undefined) panelValue Value to find the Tab for.
* @param (number) index The index of the Tab to look for.
*/
getTabIdByPanelValueOrIndex: (panelValue: any | undefined, index: number) => string | undefined;
/**
* Gets the id of the tab panel with the given value.
Expand Down Expand Up @@ -192,6 +220,7 @@ namespace useTabsRoot {
* The position of the active tab relative to the previously active tab.
*/
tabActivationDirection: TabActivationDirection;
tabMap: Map<Node, (TabMetadata & { index?: number | null }) | null>;
tabPanelRefs: React.RefObject<(HTMLElement | null)[]>;
/**
* The currently selected tab's value.
Expand Down
2 changes: 1 addition & 1 deletion packages/mui-base/src/Tabs/Tab/Tab.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ describe('<Tabs.Tab />', () => {

const testTabsListContext: TabsListContext = {
activateOnFocus: true,
getTabElement: () => null,
highlightedTabIndex: 0,
onTabActivation: NOOP,
setHighlightedTabIndex: NOOP,
Expand All @@ -29,6 +28,7 @@ describe('<Tabs.Tab />', () => {
value: 0,
onValueChange() {},
setTabMap() {},
getTabElementBySelectedValue: () => null,
getTabIdByPanelValueOrIndex: () => '',
getTabPanelIdByTabValueOrIndex: () => '',
orientation: 'horizontal',
Expand Down
3 changes: 2 additions & 1 deletion packages/mui-base/src/Tabs/Tab/useTab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { TabsList } from '../TabsList/TabsList';
export interface TabMetadata {
disabled: boolean;
id: string | undefined;
value: any | undefined;
}

function useTab(parameters: useTab.Parameters): useTab.ReturnValue {
Expand Down Expand Up @@ -45,7 +46,7 @@ function useTab(parameters: useTab.Parameters): useTab.ReturnValue {
const tabValue = valueParam ?? index;

// the `selected` state isn't set on the server (it relies on effects to be calculated),
// so we fall back to checking the `value` param with the selectedValue from the TabsContext
// so we fall back to checking the `value` param with the selectedTabValue from the TabsContext
const selected = React.useMemo(() => {
if (valueParam === undefined) {
return index < 0 ? false : index === selectedTabValue;
Expand Down
18 changes: 10 additions & 8 deletions packages/mui-base/src/Tabs/TabIndicator/TabIndicator.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
'use client';
import * as React from 'react';
import { ActiveTabPosition, useTabIndicator } from './useTabIndicator';
import { script as prehydrationScript } from './prehydrationScript.min';
import { useComponentRenderer } from '../../utils/useComponentRenderer';
import { useOnMount } from '../../utils/useOnMount';
import type { BaseUIComponentProps } from '../../utils/types';
import type { TabsDirection, TabsOrientation, TabsRoot } from '../Root/TabsRoot';
import { useTabsRootContext } from '../Root/TabsRootContext';
import { tabsStyleHookMapping } from '../Root/styleHooks';
import { useTabsListContext } from '../TabsList/TabsListContext';
import { useComponentRenderer } from '../../utils/useComponentRenderer';
import { useOnMount } from '../../utils/useOnMount';
import type { BaseUIComponentProps } from '../../utils/types';
import { ActiveTabPosition, useTabIndicator } from './useTabIndicator';
import { script as prehydrationScript } from './prehydrationScript.min';

const noop = () => null;

Expand All @@ -26,8 +26,10 @@ const TabIndicator = React.forwardRef<HTMLSpanElement, TabIndicator.Props>(
function TabIndicator(props, forwardedRef) {
const { className, render, renderBeforeHydration = false, ...other } = props;

const { orientation, direction, value, tabActivationDirection } = useTabsRootContext();
const { tabsListRef, getTabElement } = useTabsListContext();
const { direction, getTabElementBySelectedValue, orientation, tabActivationDirection, value } =
useTabsRootContext();

const { tabsListRef } = useTabsListContext();

const [instanceId] = React.useState(() => Math.random().toString(36).slice(2));
const [isMounted, setIsMounted] = React.useState(false);
Expand All @@ -36,7 +38,7 @@ const TabIndicator = React.forwardRef<HTMLSpanElement, TabIndicator.Props>(
useOnMount(() => setIsMounted(true));

const { getRootProps, activeTabPosition: selectedTabPosition } = useTabIndicator({
getTabElement,
getTabElementBySelectedValue,
tabsListRef,
value,
});
Expand Down
10 changes: 5 additions & 5 deletions packages/mui-base/src/Tabs/TabIndicator/useTabIndicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function round(value: number) {
export function useTabIndicator(
parameters: useTabIndicator.Parameters,
): useTabIndicator.ReturnValue {
const { value, tabsListRef, getTabElement } = parameters;
const { value, tabsListRef, getTabElementBySelectedValue } = parameters;

const rerender = useForcedRerendering();
React.useEffect(() => {
Expand Down Expand Up @@ -41,7 +41,7 @@ export function useTabIndicator(
let isTabSelected = false;

if (value != null && tabsListRef.current != null) {
const selectedTabElement = null; /*getTabElement(value)*/
const selectedTabElement = getTabElementBySelectedValue(value);
isTabSelected = true;

if (selectedTabElement != null) {
Expand Down Expand Up @@ -120,9 +120,9 @@ export interface ActiveTabPosition {
}

export namespace useTabIndicator {
export interface Parameters extends Pick<TabsListContext, 'getTabElement' | 'tabsListRef'> {
value: TabsRootContext['value'];
}
export interface Parameters
extends Pick<TabsRootContext, 'getTabElementBySelectedValue' | 'value'>,
Pick<TabsListContext, 'tabsListRef'> {}

export interface ReturnValue {
getRootProps: (
Expand Down
14 changes: 2 additions & 12 deletions packages/mui-base/src/Tabs/TabPanel/TabPanel.test.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
import * as React from 'react';
import { Tabs } from '@base_ui/react/Tabs';
import { createRenderer, describeConformance } from '#test-utils';
import { CompoundComponentContext } from '../../useCompound';
import { TabsRootContext } from '../Root/TabsRootContext';

describe('<Tabs.Panel />', () => {
const { render } = createRenderer();

const compoundContextValue = {
getItemIndex: () => 0,
registerItem: () => ({ id: 0, deregister: () => {} }),
totalSubitemCount: 1,
};

const tabsContextDefaultValue: TabsRootContext = {
value: '1',
onValueChange: () => {},
setTabMap() {},
getTabElementBySelectedValue: () => null,
getTabIdByPanelValueOrIndex: () => '',
getTabPanelIdByTabValueOrIndex: () => '',
direction: 'ltr',
Expand All @@ -27,11 +21,7 @@ describe('<Tabs.Panel />', () => {
describeConformance(<Tabs.Panel value="1" />, () => ({
render: (node) => {
return render(
<CompoundComponentContext.Provider value={compoundContextValue}>
<TabsRootContext.Provider value={tabsContextDefaultValue}>
{node}
</TabsRootContext.Provider>
</CompoundComponentContext.Provider>,
<TabsRootContext.Provider value={tabsContextDefaultValue}>{node}</TabsRootContext.Provider>,
);
},
refInstanceof: window.HTMLDivElement,
Expand Down
34 changes: 11 additions & 23 deletions packages/mui-base/src/Tabs/TabPanel/useTabPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type { TabsRootContext } from '../Root/TabsRootContext';
import { useId } from '../../utils/useId';
import { useForkRef } from '../../utils/useForkRef';
import { mergeReactProps } from '../../utils/mergeReactProps';
import { useCompositeListContext } from '../../Composite/List/CompositeListContext';
import { useCompositeListItem } from '../../Composite/List/useCompositeListItem';

export interface TabPanelMetadata {
Expand All @@ -13,7 +12,12 @@ export interface TabPanelMetadata {
}

function useTabPanel(parameters: useTabPanel.Parameters): useTabPanel.ReturnValue {
const { rootRef: externalRef, selectedValue, value: valueParam } = parameters;
const {
getTabIdByPanelValueOrIndex,
rootRef: externalRef,
selectedValue,
value: valueParam,
} = parameters;

const id = useId();

Expand All @@ -25,9 +29,6 @@ function useTabPanel(parameters: useTabPanel.Parameters): useTabPanel.ReturnValu
[id, valueParam],
);

// tabPanelCompositeMap
const { map: tabPanelCompositeMap } = useCompositeListContext();

const { ref: listItemRef, index } = useCompositeListItem<TabPanelMetadata>({
metadata,
});
Expand All @@ -39,29 +40,16 @@ function useTabPanel(parameters: useTabPanel.Parameters): useTabPanel.ReturnValu

const hidden = tabPanelValue !== selectedValue;

// const correspondingTabId = tabPanelValue !== undefined ? getTabId(tabPanelValue) : undefined;

const tabId = React.useMemo(() => {
if (index < 0) {
return undefined;
}

// TODO: this is incorrect, should be tabButtonMap
for (const value of tabPanelCompositeMap.values()) {
if (value.index > -1 && value.index === index) {
return value.id;
}
}

return undefined;
}, [index, tabPanelCompositeMap]);
const correspondingTabId = React.useMemo(() => {
return getTabIdByPanelValueOrIndex(valueParam, index);
}, [getTabIdByPanelValueOrIndex, index, valueParam]);

const getRootProps = React.useCallback(
(
externalProps: React.ComponentPropsWithoutRef<'div'> = {},
): React.ComponentPropsWithRef<'div'> => {
return mergeReactProps(externalProps, {
'aria-labelledby': undefined,
'aria-labelledby': correspondingTabId,
hidden,
id: id ?? undefined,
role: 'tabpanel',
Expand All @@ -70,7 +58,7 @@ function useTabPanel(parameters: useTabPanel.Parameters): useTabPanel.ReturnValu
'data-index': index,
});
},
[handleRef, hidden, id, index],
[correspondingTabId, handleRef, hidden, id, index],
);

return {
Expand Down
1 change: 1 addition & 0 deletions packages/mui-base/src/Tabs/TabsList/TabsList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe('<Tabs.List />', () => {
value: '1',
onValueChange: () => {},
setTabMap() {},
getTabElementBySelectedValue: () => null,
getTabIdByPanelValueOrIndex: () => '',
getTabPanelIdByTabValueOrIndex: () => '',
orientation: 'horizontal',
Expand Down
2 changes: 2 additions & 0 deletions packages/mui-base/src/Tabs/TabsList/TabsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const TabsList = React.forwardRef(function TabsList(

const {
direction = 'ltr',
getTabElementBySelectedValue,
onValueChange,
orientation = 'horizontal',
value,
Expand All @@ -39,6 +40,7 @@ const TabsList = React.forwardRef(function TabsList(
const tabsListRef = React.useRef<HTMLElement>(null);

const { getRootProps, onTabActivation } = useTabsList({
getTabElementBySelectedValue,
onValueChange,
orientation,
rootRef: forwardedRef,
Expand Down
1 change: 0 additions & 1 deletion packages/mui-base/src/Tabs/TabsList/TabsListContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import * as React from 'react';

export interface TabsListContext {
activateOnFocus: boolean;
getTabElement: (value: any) => HTMLElement | null;
highlightedTabIndex: number;
onTabActivation: (newValue: any, event: Event) => void;
setHighlightedTabIndex: (index: number) => void;
Expand Down
Loading

0 comments on commit 68b5598

Please sign in to comment.