mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
fix(packages): scope menu data attributes (#1628)
This commit is contained in:
@@ -33,7 +33,6 @@ export const MenuBack = forwardRef<HTMLButtonElement, MenuBackProps>(function Me
|
||||
{ render, className, style },
|
||||
{
|
||||
state: parentMenu?.state ?? ({} as MenuState),
|
||||
stateAttrMap: parentMenu?.stateAttrMap,
|
||||
ref: forwardedRef,
|
||||
props: [
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ export const MenuCheckboxItem = forwardRef<HTMLDivElement, MenuCheckboxItemProps
|
||||
{ render, className, style, checked, onCheckedChange, disabled, onClick, ...elementProps },
|
||||
forwardedRef
|
||||
) {
|
||||
const { menu, state, stateAttrMap } = useMenuContext();
|
||||
const { menu, state } = useMenuContext();
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -50,7 +50,6 @@ export const MenuCheckboxItem = forwardRef<HTMLDivElement, MenuCheckboxItemProps
|
||||
{ render, className, style },
|
||||
{
|
||||
state,
|
||||
stateAttrMap,
|
||||
ref: [forwardedRef, elementRef],
|
||||
props: [
|
||||
{
|
||||
|
||||
@@ -332,7 +332,6 @@ export const MenuContent = forwardRef<HTMLDivElement, MenuContentProps>(function
|
||||
{ render, className, style },
|
||||
{
|
||||
state,
|
||||
stateAttrMap,
|
||||
ref: menuViewComposedRef,
|
||||
props: [
|
||||
{
|
||||
|
||||
@@ -17,14 +17,13 @@ export const MenuGroup = forwardRef<HTMLDivElement, MenuGroupProps>(function Men
|
||||
{ render, className, style, label, ...elementProps },
|
||||
forwardedRef
|
||||
) {
|
||||
const { state, stateAttrMap } = useMenuContext();
|
||||
const { state } = useMenuContext();
|
||||
|
||||
return renderElement(
|
||||
'div',
|
||||
{ render, className, style },
|
||||
{
|
||||
state,
|
||||
stateAttrMap,
|
||||
ref: [forwardedRef],
|
||||
props: [{ role: 'group' as const, 'aria-label': label }, elementProps],
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export const MenuItemIndicator = forwardRef<HTMLSpanElement, MenuItemIndicatorPr
|
||||
{ render, className, style, checked, forceMount = false, ...elementProps },
|
||||
forwardedRef
|
||||
) {
|
||||
const { state, stateAttrMap } = useMenuContext();
|
||||
const { state } = useMenuContext();
|
||||
|
||||
if (!checked && !forceMount) return null;
|
||||
|
||||
@@ -28,7 +28,6 @@ export const MenuItemIndicator = forwardRef<HTMLSpanElement, MenuItemIndicatorPr
|
||||
{ render, className, style },
|
||||
{
|
||||
state,
|
||||
stateAttrMap,
|
||||
ref: [forwardedRef],
|
||||
props: [{ 'aria-hidden': true }, elementProps],
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export const MenuItem = forwardRef<HTMLDivElement, MenuItemProps>(function MenuI
|
||||
{ render, className, style, onSelect, disabled, onClick, ...elementProps },
|
||||
forwardedRef
|
||||
) {
|
||||
const { menu, state, stateAttrMap } = useMenuContext();
|
||||
const { menu, state } = useMenuContext();
|
||||
const subMenuCtx = useSubMenuContext();
|
||||
const parentMenu = subMenuCtx?.parentMenu.menu ?? null;
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
@@ -52,7 +52,6 @@ export const MenuItem = forwardRef<HTMLDivElement, MenuItemProps>(function MenuI
|
||||
{ render, className, style },
|
||||
{
|
||||
state,
|
||||
stateAttrMap,
|
||||
ref: [forwardedRef, elementRef],
|
||||
props: [
|
||||
{
|
||||
|
||||
@@ -14,14 +14,13 @@ export const MenuLabel = forwardRef<HTMLDivElement, MenuLabelProps>(function Men
|
||||
{ render, className, style, ...elementProps },
|
||||
forwardedRef
|
||||
) {
|
||||
const { state, stateAttrMap } = useMenuContext();
|
||||
const { state } = useMenuContext();
|
||||
|
||||
return renderElement(
|
||||
'div',
|
||||
{ render, className, style },
|
||||
{
|
||||
state,
|
||||
stateAttrMap,
|
||||
ref: [forwardedRef],
|
||||
props: [elementProps],
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export const MenuRadioGroup = forwardRef<HTMLDivElement, MenuRadioGroupProps>(fu
|
||||
{ render, className, style, value, onValueChange, label, ...elementProps },
|
||||
forwardedRef
|
||||
) {
|
||||
const { state, stateAttrMap } = useMenuContext();
|
||||
const { state } = useMenuContext();
|
||||
|
||||
return (
|
||||
<MenuRadioGroupContextProvider value={{ value, onValueChange }}>
|
||||
@@ -30,7 +30,6 @@ export const MenuRadioGroup = forwardRef<HTMLDivElement, MenuRadioGroupProps>(fu
|
||||
{ render, className, style },
|
||||
{
|
||||
state,
|
||||
stateAttrMap,
|
||||
ref: [forwardedRef],
|
||||
props: [{ role: 'group' as const, 'aria-label': label }, elementProps],
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export const MenuRadioItem = forwardRef<HTMLDivElement, MenuRadioItemProps>(func
|
||||
{ render, className, style, value, disabled, onClick, ...elementProps },
|
||||
forwardedRef
|
||||
) {
|
||||
const { menu, state, stateAttrMap } = useMenuContext();
|
||||
const { menu, state } = useMenuContext();
|
||||
const { value: groupValue, onValueChange } = useMenuRadioGroupContext();
|
||||
const subMenuCtx = useSubMenuContext();
|
||||
const parentMenu = subMenuCtx?.parentMenu.menu ?? null;
|
||||
@@ -54,7 +54,6 @@ export const MenuRadioItem = forwardRef<HTMLDivElement, MenuRadioItemProps>(func
|
||||
{ render, className, style },
|
||||
{
|
||||
state,
|
||||
stateAttrMap,
|
||||
ref: [forwardedRef, elementRef],
|
||||
props: [
|
||||
{
|
||||
|
||||
@@ -14,14 +14,13 @@ export const MenuSeparator = forwardRef<HTMLDivElement, MenuSeparatorProps>(func
|
||||
{ render, className, style, ...elementProps },
|
||||
forwardedRef
|
||||
) {
|
||||
const { state, stateAttrMap } = useMenuContext();
|
||||
const { state } = useMenuContext();
|
||||
|
||||
return renderElement(
|
||||
'div',
|
||||
{ render, className, style },
|
||||
{
|
||||
state,
|
||||
stateAttrMap,
|
||||
ref: [forwardedRef],
|
||||
props: [{ role: 'separator' as const }, elementProps],
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ export const MenuTrigger = forwardRef<HTMLButtonElement | HTMLDivElement, MenuTr
|
||||
{ render, className, style, disabled, onClick, onKeyDown, ...elementProps },
|
||||
forwardedRef
|
||||
) {
|
||||
const { core, menu, state, stateAttrMap, anchorName, contentId } = useMenuContext();
|
||||
const { core, menu, state, anchorName, contentId } = useMenuContext();
|
||||
const subMenuCtx = useSubMenuContext();
|
||||
const isSubMenuTrigger = subMenuCtx !== null;
|
||||
|
||||
@@ -119,7 +119,6 @@ export const MenuTrigger = forwardRef<HTMLButtonElement | HTMLDivElement, MenuTr
|
||||
{ render, className, style },
|
||||
{
|
||||
state: parentState,
|
||||
stateAttrMap,
|
||||
ref: [forwardedRef as React.Ref<HTMLDivElement>, elementRef as React.Ref<HTMLDivElement>],
|
||||
props: [
|
||||
{
|
||||
@@ -145,7 +144,6 @@ export const MenuTrigger = forwardRef<HTMLButtonElement | HTMLDivElement, MenuTr
|
||||
{ render, className, style },
|
||||
{
|
||||
state,
|
||||
stateAttrMap,
|
||||
ref: [forwardedRef as React.Ref<HTMLButtonElement>, triggerRef],
|
||||
props: [
|
||||
{ type: 'button' as const, ...core.getTriggerAttrs(state, contentId) },
|
||||
|
||||
@@ -6,8 +6,14 @@ import { ControlsContextProvider } from '../../controls/context';
|
||||
import { MenuBack } from '../menu-back';
|
||||
import { MenuCheckboxItem } from '../menu-checkbox-item';
|
||||
import { MenuContent } from '../menu-content';
|
||||
import { MenuGroup } from '../menu-group';
|
||||
import { MenuItem } from '../menu-item';
|
||||
import { MenuItemIndicator } from '../menu-item-indicator';
|
||||
import { MenuLabel } from '../menu-label';
|
||||
import { MenuRadioGroup } from '../menu-radio-group';
|
||||
import { MenuRadioItem } from '../menu-radio-item';
|
||||
import { MenuRoot } from '../menu-root';
|
||||
import { MenuSeparator } from '../menu-separator';
|
||||
import { MenuTrigger } from '../menu-trigger';
|
||||
import { MenuView } from '../menu-view';
|
||||
|
||||
@@ -263,7 +269,93 @@ function FocusOutFixture({ onRootOpenChange }: { onRootOpenChange: NonNullable<M
|
||||
);
|
||||
}
|
||||
|
||||
const menuStateAttrs = ['data-open', 'data-side', 'data-align', 'data-starting-style', 'data-ending-style'] as const;
|
||||
|
||||
function expectNoMenuStateAttrs(element: HTMLElement): void {
|
||||
for (const attr of menuStateAttrs) {
|
||||
expect(element.hasAttribute(attr), `${element.dataset.testid ?? element.tagName} should not have ${attr}`).toBe(
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
describe('MenuContent', () => {
|
||||
it('scopes menu state data attributes to content elements', async () => {
|
||||
render(
|
||||
<MenuRoot defaultOpen side="top" align="end">
|
||||
<MenuTrigger data-testid="trigger">Settings</MenuTrigger>
|
||||
<MenuContent data-testid="root-content">
|
||||
<MenuLabel data-testid="label">Playback</MenuLabel>
|
||||
<MenuGroup data-testid="group" label="Playback">
|
||||
<MenuItem data-testid="item">Copy link</MenuItem>
|
||||
<MenuCheckboxItem data-testid="checkbox-item" checked={false} onCheckedChange={vi.fn()}>
|
||||
Autoplay
|
||||
</MenuCheckboxItem>
|
||||
<MenuRadioGroup data-testid="radio-group" value="auto" onValueChange={vi.fn()} label="Quality">
|
||||
<MenuRadioItem data-testid="radio-item" value="auto">
|
||||
Auto
|
||||
<MenuItemIndicator data-testid="indicator" checked>
|
||||
Checked
|
||||
</MenuItemIndicator>
|
||||
</MenuRadioItem>
|
||||
</MenuRadioGroup>
|
||||
</MenuGroup>
|
||||
<MenuSeparator data-testid="separator" />
|
||||
<MenuView data-testid="root-view">
|
||||
<MenuRoot>
|
||||
<MenuTrigger data-testid="submenu-trigger">Quality</MenuTrigger>
|
||||
<MenuContent data-testid="submenu-content">
|
||||
<MenuBack data-testid="back">Back</MenuBack>
|
||||
<MenuItem data-testid="submenu-item">Auto</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuRoot>
|
||||
</MenuView>
|
||||
</MenuContent>
|
||||
</MenuRoot>
|
||||
);
|
||||
|
||||
const rootContent = screen.getByTestId('root-content');
|
||||
|
||||
expect(rootContent.hasAttribute('data-open')).toBe(true);
|
||||
expect(rootContent.getAttribute('data-side')).toBe('top');
|
||||
expect(rootContent.getAttribute('data-align')).toBe('end');
|
||||
|
||||
for (const testId of [
|
||||
'trigger',
|
||||
'label',
|
||||
'group',
|
||||
'separator',
|
||||
'item',
|
||||
'checkbox-item',
|
||||
'radio-group',
|
||||
'radio-item',
|
||||
'indicator',
|
||||
'submenu-trigger',
|
||||
]) {
|
||||
expectNoMenuStateAttrs(screen.getByTestId(testId));
|
||||
}
|
||||
|
||||
expect(screen.getByTestId('item').hasAttribute('data-item')).toBe(true);
|
||||
expect(screen.getByTestId('radio-item').hasAttribute('data-item')).toBe(true);
|
||||
expect(screen.getByTestId('checkbox-item').hasAttribute('data-item')).toBe(true);
|
||||
expect(screen.getByTestId('submenu-trigger').hasAttribute('data-item')).toBe(true);
|
||||
|
||||
fireEvent.click(screen.getByTestId('submenu-trigger'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('submenu-content').getAttribute('data-menu-view-state')).toBe('active');
|
||||
});
|
||||
|
||||
const submenuContent = screen.getByTestId('submenu-content');
|
||||
|
||||
expect(submenuContent.hasAttribute('data-submenu')).toBe(true);
|
||||
expect(submenuContent.hasAttribute('data-menu-view')).toBe(true);
|
||||
expect(submenuContent.hasAttribute('data-open')).toBe(true);
|
||||
expect(submenuContent.hasAttribute('data-side')).toBe(false);
|
||||
expect(submenuContent.hasAttribute('data-align')).toBe(false);
|
||||
expectNoMenuStateAttrs(screen.getByTestId('back'));
|
||||
});
|
||||
|
||||
it('marks the root view inactive while a submenu view is active', async () => {
|
||||
render(<SubmenuFixture />);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user