diff --git a/packages/react/src/internals/composite/list/CompositeList.tsx b/packages/react/src/internals/composite/list/CompositeList.tsx index d1f47b79180..911a68c2bf3 100644 --- a/packages/react/src/internals/composite/list/CompositeList.tsx +++ b/packages/react/src/internals/composite/list/CompositeList.tsx @@ -297,6 +297,15 @@ function hasMovedNode(entries: MutationRecord[]) { } function sortByDocumentPosition(a: Element, b: Element) { + // Adjacent siblings are the common case for lists that are already in order, and + // `compareDocumentPosition` scans siblings from the parent's first child, so sorting + // a long flat list would otherwise be quadratic. + if (a.nextElementSibling === b) { + return -1; + } + if (b.nextElementSibling === a) { + return 1; + } // `DOCUMENT_POSITION_CONTAINED_BY` is always reported alongside `FOLLOWING`, and `CONTAINS` // alongside `PRECEDING`, so testing `FOLLOWING` alone orders siblings and nested items alike. return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1; diff --git a/packages/react/src/menu/checkbox-item/MenuCheckboxItem.tsx b/packages/react/src/menu/checkbox-item/MenuCheckboxItem.tsx index ce077dd01df..0b40e2787f0 100644 --- a/packages/react/src/menu/checkbox-item/MenuCheckboxItem.tsx +++ b/packages/react/src/menu/checkbox-item/MenuCheckboxItem.tsx @@ -10,7 +10,6 @@ import { useRenderElement } from '../../internals/useRenderElement'; import { useBaseUiId } from '../../internals/useBaseUiId'; import type { BaseUIComponentProps, NonNativeButtonProps } from '../../internals/types'; import { itemMapping } from '../utils/stateAttributesMapping'; -import { useMenuPositionerContext } from '../positioner/MenuPositionerContext'; import { createChangeEventDetails } from '../../internals/createBaseUIEventDetails'; import { REASONS } from '../../internals/reasons'; import type { MenuRoot } from '../root/MenuRoot'; @@ -41,13 +40,13 @@ export const MenuCheckboxItem = React.forwardRef(function MenuCheckboxItem( } = componentProps; const listItem = useCompositeListItem({ guess: true, label }); - const menuPositionerContext = useMenuPositionerContext(true); const id = useBaseUiId(idProp); const { store } = useMenuRootContext(); const rootDisabled = store.useState('disabled'); const disabled = disabledProp || rootDisabled; const highlighted = store.useState('isActive', listItem.index); + const nodeId = store.useState('floatingNodeId'); const itemProps = store.useState('itemProps'); const [checked, setChecked] = useControlled({ @@ -64,7 +63,7 @@ export const MenuCheckboxItem = React.forwardRef(function MenuCheckboxItem( id, store, nativeButton, - nodeId: menuPositionerContext?.context.nodeId, + nodeId, itemMetadata: REGULAR_ITEM, }); diff --git a/packages/react/src/menu/item/MenuItem.tsx b/packages/react/src/menu/item/MenuItem.tsx index 55f12663bd5..7d153a50072 100644 --- a/packages/react/src/menu/item/MenuItem.tsx +++ b/packages/react/src/menu/item/MenuItem.tsx @@ -6,7 +6,6 @@ import { useRenderElement } from '../../internals/useRenderElement'; import { useBaseUiId } from '../../internals/useBaseUiId'; import type { BaseUIComponentProps, NonNativeButtonProps } from '../../internals/types'; import { useCompositeListItem } from '../../internals/composite/list/useCompositeListItem'; -import { useMenuPositionerContext } from '../positioner/MenuPositionerContext'; /** * An individual interactive item in the menu. @@ -31,13 +30,13 @@ export const MenuItem = React.forwardRef(function MenuItem( } = componentProps; const listItem = useCompositeListItem({ guess: true, label }); - const menuPositionerContext = useMenuPositionerContext(true); const id = useBaseUiId(idProp); const { store } = useMenuRootContext(); const rootDisabled = store.useState('disabled'); const disabled = disabledProp || rootDisabled; const highlighted = store.useState('isActive', listItem.index); + const nodeId = store.useState('floatingNodeId'); const itemProps = store.useState('itemProps'); const { getItemProps, itemRef } = useMenuItem({ @@ -47,7 +46,7 @@ export const MenuItem = React.forwardRef(function MenuItem( id, store, nativeButton, - nodeId: menuPositionerContext?.context.nodeId, + nodeId, itemMetadata: REGULAR_ITEM, }); diff --git a/packages/react/src/menu/link-item/MenuLinkItem.tsx b/packages/react/src/menu/link-item/MenuLinkItem.tsx index 3e426c3ac6a..4b4ea2c5587 100644 --- a/packages/react/src/menu/link-item/MenuLinkItem.tsx +++ b/packages/react/src/menu/link-item/MenuLinkItem.tsx @@ -5,7 +5,6 @@ import { useRenderElement } from '../../internals/useRenderElement'; import { useBaseUiId } from '../../internals/useBaseUiId'; import type { BaseUIComponentProps, HTMLProps } from '../../internals/types'; import { useCompositeListItem } from '../../internals/composite/list/useCompositeListItem'; -import { useMenuPositionerContext } from '../positioner/MenuPositionerContext'; import { useMenuItemCommonProps } from '../item/useMenuItemCommonProps'; import { REGULAR_ITEM } from '../item/useMenuItem'; import { useButton } from '../../internals/use-button'; @@ -34,13 +33,11 @@ export const MenuLinkItem = React.forwardRef(function MenuLinkItem( const linkRef = React.useRef(null); const listItem = useCompositeListItem({ guess: true, label }); - const menuPositionerContext = useMenuPositionerContext(true); - const nodeId = menuPositionerContext?.context.nodeId; - const id = useBaseUiId(idProp); const { store } = useMenuRootContext(); const highlighted = store.useState('isActive', listItem.index); + const nodeId = store.useState('floatingNodeId'); const itemProps = store.useState('itemProps'); const typingRef = store.context.typingRef; diff --git a/packages/react/src/menu/radio-item/MenuRadioItem.tsx b/packages/react/src/menu/radio-item/MenuRadioItem.tsx index 044ead95926..724928410b8 100644 --- a/packages/react/src/menu/radio-item/MenuRadioItem.tsx +++ b/packages/react/src/menu/radio-item/MenuRadioItem.tsx @@ -10,7 +10,6 @@ import { MenuRadioItemContext } from './MenuRadioItemContext'; import { itemMapping } from '../utils/stateAttributesMapping'; import { useCompositeListItem } from '../../internals/composite/list/useCompositeListItem'; import { REGULAR_ITEM, useMenuItem } from '../item/useMenuItem'; -import { useMenuPositionerContext } from '../positioner/MenuPositionerContext'; import { createChangeEventDetails } from '../../internals/createBaseUIEventDetails'; import { REASONS } from '../../internals/reasons'; @@ -38,11 +37,11 @@ export const MenuRadioItem = React.forwardRef(function MenuRadioItem( } = componentProps; const listItem = useCompositeListItem({ guess: true, label }); - const menuPositionerContext = useMenuPositionerContext(true); const id = useBaseUiId(idProp); const { store } = useMenuRootContext(); const highlighted = store.useState('isActive', listItem.index); + const nodeId = store.useState('floatingNodeId'); const itemProps = store.useState('itemProps'); const { @@ -62,7 +61,7 @@ export const MenuRadioItem = React.forwardRef(function MenuRadioItem( id, store, nativeButton, - nodeId: menuPositionerContext?.context.nodeId, + nodeId, itemMetadata: REGULAR_ITEM, });