diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 44e453668955a..049863f7bee05 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -28,6 +28,10 @@
- `CheckboxControl`: Remove ability for label prop to be false ([#58339](https://github.com/WordPress/gutenberg/pull/58339)).
+### Internal
+
+- `Composite`: Removing Reakit `Composite` implementation ([#58620](https://github.com/WordPress/gutenberg/pull/58620)).
+
## 25.16.0 (2024-01-24)
### Enhancements
diff --git a/packages/components/src/composite/index.ts b/packages/components/src/composite/index.ts
index f0dd4ef8995b5..aa06a6adf36ef 100644
--- a/packages/components/src/composite/index.ts
+++ b/packages/components/src/composite/index.ts
@@ -1,4 +1,7 @@
-// Until we migrate away from Reakit, the 'unstable'
-// implementation remains the default.
+// Originally this pointed at a Reakit implementation of
+// `Composite`, but we are removing Reakit entirely from the
+// codebase. We will continue to support the Reakit API
+// through the 'legacy' version, which uses Ariakit under
+// the hood.
-export * from './unstable';
+export * from './legacy';
diff --git a/packages/components/src/composite/legacy/index.tsx b/packages/components/src/composite/legacy/index.tsx
index 06a82461be9a1..5c5c674b5086b 100644
--- a/packages/components/src/composite/legacy/index.tsx
+++ b/packages/components/src/composite/legacy/index.tsx
@@ -12,7 +12,6 @@
* WordPress dependencies
*/
import { forwardRef } from '@wordpress/element';
-import deprecated from '@wordpress/deprecated';
/**
* Internal dependencies
@@ -99,14 +98,6 @@ type CompositeComponentProps = CompositeState &
| ComponentProps< typeof Current.CompositeRow >
);
-function showDeprecationMessage( previous?: string, next?: string ) {
- if ( previous ) {
- deprecated( `wp.components.__unstable${ previous }`, {
- alternative: `wp.components.${ next || previous }`,
- } );
- }
-}
-
function mapLegacyStatePropsToComponentProps(
legacyProps: CompositeStateProps
): CompositeComponentProps {
@@ -128,8 +119,6 @@ function proxyComposite< C extends Component >(
): CompositeComponent< C > {
const displayName = ProxiedComponent.displayName;
const Component = ( legacyProps: CompositeStateProps ) => {
- showDeprecationMessage( displayName );
-
const { store, ...rest } =
mapLegacyStatePropsToComponentProps( legacyProps );
const props = rest as ComponentProps< C >;
@@ -175,8 +164,6 @@ export const CompositeItem = proxyComposite( Current.CompositeItem, {
export function useCompositeState(
legacyStateOptions: LegacyStateOptions = {}
): CompositeState {
- showDeprecationMessage( 'UseCompositeState', 'useCompositeStore' );
-
const {
baseId,
currentId: defaultActiveId,
diff --git a/packages/components/src/composite/legacy/stories/index.story.tsx b/packages/components/src/composite/legacy/stories/index.story.tsx
index a11e4838125d9..e46d656a16810 100644
--- a/packages/components/src/composite/legacy/stories/index.story.tsx
+++ b/packages/components/src/composite/legacy/stories/index.story.tsx
@@ -15,7 +15,7 @@ import {
import { UseCompositeStatePlaceholder, transform } from './utils';
const meta: Meta< typeof UseCompositeStatePlaceholder > = {
- title: 'Components/Composite (Legacy)',
+ title: 'Components/Composite',
component: UseCompositeStatePlaceholder,
subcomponents: {
Composite,
diff --git a/packages/components/src/composite/legacy/test/index.tsx b/packages/components/src/composite/legacy/test/index.tsx
index dd57d6373b2bf..ee6654f9fd369 100644
--- a/packages/components/src/composite/legacy/test/index.tsx
+++ b/packages/components/src/composite/legacy/test/index.tsx
@@ -41,8 +41,6 @@ type InitialState = Parameters< typeof useCompositeState >[ 0 ];
type CompositeState = ReturnType< typeof useCompositeState >;
type CompositeStateProps = CompositeState | { state: CompositeState };
-const warningsIssued = new Map();
-
async function renderAndValidate( ...args: Parameters< typeof render > ) {
const view = render( ...args );
await waitFor( () => {
@@ -180,20 +178,6 @@ describe.each( [
);
renderAndValidate( );
- // Using the legacy composite components issues a deprecation
- // warning, but only on the first usage. As such, we only
- // expect `console` to warn once; any further rendering
- // should not warn.
- const warningKey = 'single tab stop';
- if ( warningsIssued.get( warningKey ) ) {
- // eslint-disable-next-line jest/no-conditional-expect
- expect( console ).not.toHaveWarned();
- } else {
- // eslint-disable-next-line jest/no-conditional-expect
- expect( console ).toHaveWarned();
- warningsIssued.set( warningKey, true );
- }
-
await press.Tab();
expect( screen.getByText( 'Before' ) ).toHaveFocus();
await press.Tab();
@@ -450,20 +434,6 @@ describe.each( [
const { itemA1, itemA2, itemA3, itemB1, itemB2, itemC1, itemC3 } =
useTwoDimensionalTest();
- // Using the legacy composite components issues a deprecation
- // warning, but only on the first usage. As such, we only
- // expect `console` to warn once; any further rendering
- // should not warn.
- const warningKey = 'directions';
- if ( warningsIssued.get( warningKey ) ) {
- // eslint-disable-next-line jest/no-conditional-expect
- expect( console ).not.toHaveWarned();
- } else {
- // eslint-disable-next-line jest/no-conditional-expect
- expect( console ).toHaveWarned();
- warningsIssued.set( warningKey, true );
- }
-
await press.Tab();
expect( itemA1 ).toHaveFocus();
await press.ArrowUp();
diff --git a/packages/components/src/composite/unstable/index.ts b/packages/components/src/composite/unstable/index.ts
deleted file mode 100644
index f2a195091dff9..0000000000000
--- a/packages/components/src/composite/unstable/index.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * Composite is a component that may contain navigable items represented by
- * CompositeItem. It's inspired by the WAI-ARIA Composite Role and implements
- * all the keyboard navigation mechanisms to ensure that there's only one
- * tab stop for the whole Composite element. This means that it can behave as
- * a roving tabindex or aria-activedescendant container.
- *
- * @see https://reakit.io/docs/composite/
- *
- * This is now entirely deprecated in favor of [current](../current), which is
- * based on Ariakit. A [legacy](../legacy) implementation of this API has been
- * created for backwards-compatibility, which will eventually replace this.
- */
-/* eslint-disable-next-line no-restricted-imports */
-export {
- Composite,
- CompositeGroup,
- CompositeItem,
- useCompositeState,
-} from 'reakit';
-
-/* eslint-disable-next-line no-restricted-imports */
-export type { CompositeStateReturn as CompositeState } from 'reakit';
diff --git a/packages/components/src/composite/unstable/stories/index.story.tsx b/packages/components/src/composite/unstable/stories/index.story.tsx
deleted file mode 100644
index 54543e80fc480..0000000000000
--- a/packages/components/src/composite/unstable/stories/index.story.tsx
+++ /dev/null
@@ -1,151 +0,0 @@
-/**
- * External dependencies
- */
-import type { Meta, StoryFn } from '@storybook/react';
-
-/**
- * Internal dependencies
- */
-import {
- Composite,
- CompositeGroup,
- CompositeItem,
- useCompositeState,
-} from '..';
-
-import Legacy from '../../legacy/stories/index.story';
-import { UseCompositeStatePlaceholder, transform } from './utils';
-
-Composite.displayName = 'Composite';
-CompositeGroup.displayName = 'CompositeGroup';
-CompositeItem.displayName = 'CompositeItem';
-
-const meta: Meta< typeof UseCompositeStatePlaceholder > = {
- title: 'Components/Composite',
- component: UseCompositeStatePlaceholder,
- subcomponents: {
- // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
- Composite,
- // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
- CompositeGroup,
- // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
- CompositeItem,
- },
- argTypes: { ...Legacy.argTypes },
- parameters: {
- docs: {
- canvas: { sourceState: 'shown' },
- source: { transform },
- },
- },
- decorators: [
- // Because of the way Reakit caches state, this is a hack to make sure
- // stories update when config is changed.
- ( Story ) => (
-
-
-
- ),
- ],
-};
-export default meta;
-
-export const TwoDimensionsWithStateProp: StoryFn<
- typeof UseCompositeStatePlaceholder
-> = ( initialState ) => {
- const state = useCompositeState( initialState );
-
- return (
-
-
- Item A1
- Item A2
- Item A3
-
-
- Item B1
- Item B2
- Item B3
-
-
- Item C1
- Item C2
- Item C3
-
-
- );
-};
-TwoDimensionsWithStateProp.args = {};
-
-export const TwoDimensionsWithSpreadProps: StoryFn<
- typeof UseCompositeStatePlaceholder
-> = ( initialState ) => {
- const state = useCompositeState( initialState );
-
- return (
-
-
- Item A1
- Item A2
- Item A3
-
-
- Item B1
- Item B2
- Item B3
-
-
- Item C1
- Item C2
- Item C3
-
-
- );
-};
-TwoDimensionsWithSpreadProps.args = {};
-
-export const OneDimensionWithStateProp: StoryFn<
- typeof UseCompositeStatePlaceholder
-> = ( initialState ) => {
- const state = useCompositeState( initialState );
-
- return (
-
- Item 1
- Item 2
- Item 3
- Item 4
- Item 5
-
- );
-};
-OneDimensionWithStateProp.args = {};
-
-export const OneDimensionWithSpreadProps: StoryFn<
- typeof UseCompositeStatePlaceholder
-> = ( initialState ) => {
- const state = useCompositeState( initialState );
-
- return (
-
- Item 1
- Item 2
- Item 3
- Item 4
- Item 5
-
- );
-};
-OneDimensionWithSpreadProps.args = {};
diff --git a/packages/components/src/composite/unstable/stories/utils.tsx b/packages/components/src/composite/unstable/stories/utils.tsx
deleted file mode 100644
index ed79e5ae85044..0000000000000
--- a/packages/components/src/composite/unstable/stories/utils.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-/**
- * External dependencies
- */
-import type { StoryContext } from '@storybook/react';
-
-/**
- * Internal dependencies
- */
-import type { CompositeState as FullCompositeState } from '..';
-
-type CompositeState = Pick<
- FullCompositeState,
- 'baseId' | 'rtl' | 'orientation' | 'currentId' | 'loop' | 'wrap' | 'shift'
->;
-
-export function UseCompositeStatePlaceholder( props: CompositeState ) {
- return (
-
- { Object.entries( props ).map( ( [ name, value ] ) => (
- <>
- - { name }
- - { JSON.stringify( value ) }
- >
- ) ) }
-
- );
-}
-UseCompositeStatePlaceholder.displayName = 'useCompositeState';
-
-export function transform( code: string, context: StoryContext ) {
- // The output generated by Storybook for these components is
- // messy, so we apply this transform to make it more useful
- // for anyone reading the docs.
- const config = ` ${ JSON.stringify( context.args, null, 2 ) } `;
- const state = config.replace( ' {} ', '' );
- return [
- // Include a setup line, showing how to make use of
- // `useCompositeState` to convert state options into
- // a composite state option.
- `const state = useCompositeState(${ state });`,
- '',
- 'return (',
- ' ' +
- code
- // The generated output includes a full dump of everything
- // in the state; the reader probably isn't interested in
- // what that looks like, so instead we drop all of that
- // in favor of the state generated above.
- .replaceAll( /state=\{\{[\s\S]*?\}\}/g, 'state={ state }' )
- // The previous line only works for `state={ state }`, and
- // doesn't replace spread props, so we do that separately.
- .replaceAll( '=>', '' )
- .replaceAll( /baseId=[^>]+?(\s*>)/g, ( _, close ) => {
- return `{ ...state }${ close }`;
- } )
- // Now we tidy the output by removing any unnecessary
- // whitespace...
- .replaceAll( //g, ( match ) =>
- match.replaceAll( /\s+\s/g, ' ' )
- )
- // ...including around children...
- .replaceAll(
- / >\s+([\w\s]*?)\s+<\//g,
- ( _, value ) => `>${ value }`
- )
- // ...and inside JSX definitions.
- .replaceAll( '} >', '}>' )
- // Finally we indent everything to make it more readable.
- .replaceAll( /\n/g, '\n ' ),
- ');',
- ].join( '\n' );
-}
diff --git a/packages/components/src/composite/unstable/test/index.tsx b/packages/components/src/composite/unstable/test/index.tsx
deleted file mode 100644
index bb565f63f0cda..0000000000000
--- a/packages/components/src/composite/unstable/test/index.tsx
+++ /dev/null
@@ -1,543 +0,0 @@
-/**
- * External dependencies
- */
-import { render, screen } from '@testing-library/react';
-import { press } from '@ariakit/test';
-
-/**
- * Internal dependencies
- */
-import {
- Composite,
- CompositeGroup,
- CompositeItem,
- useCompositeState,
-} from '..';
-
-type InitialState = Parameters< typeof useCompositeState >[ 0 ];
-type CompositeState = ReturnType< typeof useCompositeState >;
-type CompositeStateProps = CompositeState | { state: CompositeState };
-
-function getKeys( rtl: boolean ) {
- return {
- previous: rtl ? 'ArrowRight' : 'ArrowLeft',
- next: rtl ? 'ArrowLeft' : 'ArrowRight',
- first: 'Home',
- last: 'End',
- };
-}
-
-function OneDimensionalTest( props: CompositeStateProps ) {
- return (
-
- Item 1
- Item 2
- Item 3
-
- );
-}
-
-function getOneDimensionalItems() {
- return {
- item1: screen.getByText( 'Item 1' ),
- item2: screen.getByText( 'Item 2' ),
- item3: screen.getByText( 'Item 3' ),
- };
-}
-
-function TwoDimensionalTest( props: CompositeStateProps ) {
- return (
-
-
- Item A1
- Item A2
- Item A3
-
-
- Item B1
- Item B2
- Item B3
-
-
- Item C1
- Item C2
- Item C3
-
-
- );
-}
-
-function getTwoDimensionalItems() {
- return {
- itemA1: screen.getByText( 'Item A1' ),
- itemA2: screen.getByText( 'Item A2' ),
- itemA3: screen.getByText( 'Item A3' ),
- itemB1: screen.getByText( 'Item B1' ),
- itemB2: screen.getByText( 'Item B2' ),
- itemB3: screen.getByText( 'Item B3' ),
- itemC1: screen.getByText( 'Item C1' ),
- itemC2: screen.getByText( 'Item C2' ),
- itemC3: screen.getByText( 'Item C3' ),
- };
-}
-
-function ShiftTest( props: CompositeStateProps ) {
- return (
-
-
- Item A1
-
-
- Item B1
- Item B2
-
-
- Item C1
-
- Item C2
-
-
-
- );
-}
-
-function getShiftTestItems() {
- return {
- itemA1: screen.getByText( 'Item A1' ),
- itemB1: screen.getByText( 'Item B1' ),
- itemB2: screen.getByText( 'Item B2' ),
- itemC1: screen.getByText( 'Item C1' ),
- itemC2: screen.getByText( 'Item C2' ),
- };
-}
-
-describe.each( [
- [
- 'With "spread" state',
- ( initialState?: InitialState ) => useCompositeState( initialState ),
- ],
- [
- 'With `state` prop',
- ( initialState?: InitialState ) => ( {
- state: useCompositeState( initialState ),
- } ),
- ],
-] )( '%s', ( __, useProps ) => {
- test( 'Renders as a single tab stop', async () => {
- const Test = () => (
- <>
-
-
-
- >
- );
- render( );
-
- await press.Tab();
- expect( screen.getByText( 'Before' ) ).toHaveFocus();
- await press.Tab();
- expect( screen.getByText( 'Item 1' ) ).toHaveFocus();
- await press.Tab();
- expect( screen.getByText( 'After' ) ).toHaveFocus();
- await press.ShiftTab();
- expect( screen.getByText( 'Item 1' ) ).toHaveFocus();
- } );
-
- test( 'Excludes disabled items', async () => {
- const Test = () => {
- const props = useProps();
- return (
-
- Item 1
-
- Item 2
-
- Item 3
-
- );
- };
- render( );
-
- const { item1, item2, item3 } = getOneDimensionalItems();
-
- expect( item2 ).toBeDisabled();
-
- await press.Tab();
- expect( item1 ).toHaveFocus();
- await press.ArrowDown();
- expect( item2 ).not.toHaveFocus();
- expect( item3 ).toHaveFocus();
- } );
-
- test( 'Includes focusable disabled items', async () => {
- const Test = () => {
- const props = useProps();
- return (
-
- Item 1
-
- Item 2
-
- Item 3
-
- );
- };
- render( );
- const { item1, item2, item3 } = getOneDimensionalItems();
-
- expect( item2 ).toBeEnabled();
- expect( item2 ).toHaveAttribute( 'aria-disabled', 'true' );
-
- await press.Tab();
- expect( item1 ).toHaveFocus();
- await press.ArrowDown();
- expect( item2 ).toHaveFocus();
- expect( item3 ).not.toHaveFocus();
- } );
-
- test( 'Supports `baseId`', async () => {
- const Test = () => (
-
- );
- render( );
- const { item1, item2, item3 } = getOneDimensionalItems();
-
- expect( item1.id ).toMatch( 'test-id-1' );
- expect( item2.id ).toMatch( 'test-id-2' );
- expect( item3.id ).toMatch( 'test-id-3' );
- } );
-
- test( 'Supports `currentId`', async () => {
- const Test = () => (
-
- );
- render( );
- const { item2 } = getOneDimensionalItems();
-
- await press.Tab();
- expect( item2 ).toHaveFocus();
- } );
-} );
-
-describe.each( [
- [ 'When LTR', false ],
- [ 'When RTL', true ],
-] )( '%s', ( _when, rtl ) => {
- const { previous, next, first, last } = getKeys( rtl );
-
- function useOneDimensionalTest( initialState?: InitialState ) {
- const Test = () => (
-
- );
- render( );
- return getOneDimensionalItems();
- }
-
- function useTwoDimensionalTest( initialState?: InitialState ) {
- const Test = () => (
-
- );
- render( );
- return getTwoDimensionalItems();
- }
-
- function useShiftTest( shift: boolean ) {
- const Test = () => (
-
- );
- render( );
- return getShiftTestItems();
- }
-
- describe( 'In one dimension', () => {
- test( 'All directions work with no orientation', async () => {
- const { item1, item2, item3 } = useOneDimensionalTest();
-
- await press.Tab();
- expect( item1 ).toHaveFocus();
- await press.ArrowDown();
- expect( item2 ).toHaveFocus();
- await press.ArrowDown();
- expect( item3 ).toHaveFocus();
- await press.ArrowDown();
- expect( item3 ).toHaveFocus();
- await press.ArrowUp();
- expect( item2 ).toHaveFocus();
- await press.ArrowUp();
- expect( item1 ).toHaveFocus();
- await press.ArrowUp();
- expect( item1 ).toHaveFocus();
- await press( next );
- expect( item2 ).toHaveFocus();
- await press( next );
- expect( item3 ).toHaveFocus();
- await press( previous );
- expect( item2 ).toHaveFocus();
- await press( previous );
- expect( item1 ).toHaveFocus();
- await press.End();
- expect( item3 ).toHaveFocus();
- await press.Home();
- expect( item1 ).toHaveFocus();
- await press.PageDown();
- expect( item3 ).toHaveFocus();
- await press.PageUp();
- expect( item1 ).toHaveFocus();
- } );
-
- test( 'Only left/right work with horizontal orientation', async () => {
- const { item1, item2, item3 } = useOneDimensionalTest( {
- orientation: 'horizontal',
- } );
-
- await press.Tab();
- expect( item1 ).toHaveFocus();
- await press.ArrowDown();
- expect( item1 ).toHaveFocus();
- await press( next );
- expect( item2 ).toHaveFocus();
- await press( next );
- expect( item3 ).toHaveFocus();
- await press.ArrowUp();
- expect( item3 ).toHaveFocus();
- await press( previous );
- expect( item2 ).toHaveFocus();
- await press( previous );
- expect( item1 ).toHaveFocus();
- await press.End();
- expect( item3 ).toHaveFocus();
- await press.Home();
- expect( item1 ).toHaveFocus();
- await press.PageDown();
- expect( item3 ).toHaveFocus();
- await press.PageUp();
- expect( item1 ).toHaveFocus();
- } );
-
- test( 'Only up/down work with vertical orientation', async () => {
- const { item1, item2, item3 } = useOneDimensionalTest( {
- orientation: 'vertical',
- } );
-
- await press.Tab();
- expect( item1 ).toHaveFocus();
- await press( next );
- expect( item1 ).toHaveFocus();
- await press.ArrowDown();
- expect( item2 ).toHaveFocus();
- await press.ArrowDown();
- expect( item3 ).toHaveFocus();
- await press( previous );
- expect( item3 ).toHaveFocus();
- await press.ArrowUp();
- expect( item2 ).toHaveFocus();
- await press.ArrowUp();
- expect( item1 ).toHaveFocus();
- await press.End();
- expect( item3 ).toHaveFocus();
- await press.Home();
- expect( item1 ).toHaveFocus();
- await press.PageDown();
- expect( item3 ).toHaveFocus();
- await press.PageUp();
- expect( item1 ).toHaveFocus();
- } );
-
- test( 'Focus wraps with loop enabled', async () => {
- const { item1, item2, item3 } = useOneDimensionalTest( {
- loop: true,
- } );
-
- await press.Tab();
- expect( item1 ).toHaveFocus();
- await press.ArrowDown();
- expect( item2 ).toHaveFocus();
- await press.ArrowDown();
- expect( item3 ).toHaveFocus();
- await press.ArrowDown();
- expect( item1 ).toHaveFocus();
- await press.ArrowUp();
- expect( item3 ).toHaveFocus();
- await press( next );
- expect( item1 ).toHaveFocus();
- await press( previous );
- expect( item3 ).toHaveFocus();
- } );
- } );
-
- describe( 'In two dimensions', () => {
- test( 'All directions work as standard', async () => {
- const { itemA1, itemA2, itemA3, itemB1, itemB2, itemC1, itemC3 } =
- useTwoDimensionalTest();
-
- await press.Tab();
- expect( itemA1 ).toHaveFocus();
- await press.ArrowUp();
- expect( itemA1 ).toHaveFocus();
- await press( previous );
- expect( itemA1 ).toHaveFocus();
- await press.ArrowDown();
- expect( itemB1 ).toHaveFocus();
- await press( next );
- expect( itemB2 ).toHaveFocus();
- await press.ArrowUp();
- expect( itemA2 ).toHaveFocus();
- await press( previous );
- expect( itemA1 ).toHaveFocus();
- await press( last );
- expect( itemA3 ).toHaveFocus();
- await press.PageDown();
- expect( itemC3 ).toHaveFocus();
- await press( next );
- expect( itemC3 ).toHaveFocus();
- await press.ArrowDown();
- expect( itemC3 ).toHaveFocus();
- await press( first );
- expect( itemC1 ).toHaveFocus();
- await press.PageUp();
- expect( itemA1 ).toHaveFocus();
- await press.End( null, { ctrlKey: true } );
- expect( itemC3 ).toHaveFocus();
- await press.Home( null, { ctrlKey: true } );
- expect( itemA1 ).toHaveFocus();
- } );
-
- test( 'Focus wraps around rows/columns with loop enabled', async () => {
- const { itemA1, itemA2, itemA3, itemB1, itemC1, itemC3 } =
- useTwoDimensionalTest( { loop: true } );
-
- await press.Tab();
- expect( itemA1 ).toHaveFocus();
- await press( next );
- expect( itemA2 ).toHaveFocus();
- await press( next );
- expect( itemA3 ).toHaveFocus();
- await press( next );
- expect( itemA1 ).toHaveFocus();
- await press.ArrowDown();
- expect( itemB1 ).toHaveFocus();
- await press.ArrowDown();
- expect( itemC1 ).toHaveFocus();
- await press.ArrowDown();
- expect( itemA1 ).toHaveFocus();
- await press( previous );
- expect( itemA3 ).toHaveFocus();
- await press.ArrowUp();
- expect( itemC3 ).toHaveFocus();
- } );
-
- test( 'Focus moves between rows/columns with wrap enabled', async () => {
- const { itemA1, itemA2, itemA3, itemB1, itemC1, itemC3 } =
- useTwoDimensionalTest( { wrap: true } );
-
- await press.Tab();
- expect( itemA1 ).toHaveFocus();
- await press( next );
- expect( itemA2 ).toHaveFocus();
- await press( next );
- expect( itemA3 ).toHaveFocus();
- await press( next );
- expect( itemB1 ).toHaveFocus();
- await press.ArrowDown();
- expect( itemC1 ).toHaveFocus();
- await press.ArrowDown();
- expect( itemA2 ).toHaveFocus();
- await press( previous );
- expect( itemA1 ).toHaveFocus();
- await press( previous );
- expect( itemA1 ).toHaveFocus();
- await press.ArrowUp();
- expect( itemA1 ).toHaveFocus();
- await press.End( itemA1, { ctrlKey: true } );
- expect( itemC3 ).toHaveFocus();
- await press( next );
- expect( itemC3 ).toHaveFocus();
- await press.ArrowDown();
- expect( itemC3 ).toHaveFocus();
- } );
-
- test( 'Focus wraps around start/end with loop and wrap enabled', async () => {
- const { itemA1, itemC3 } = useTwoDimensionalTest( {
- loop: true,
- wrap: true,
- } );
-
- await press.Tab();
- expect( itemA1 ).toHaveFocus();
- await press( previous );
- expect( itemC3 ).toHaveFocus();
- await press.ArrowDown();
- expect( itemA1 ).toHaveFocus();
- await press.ArrowUp();
- expect( itemC3 ).toHaveFocus();
- await press( next );
- expect( itemA1 ).toHaveFocus();
- } );
-
- test( 'Focus shifts if vertical neighbour unavailable when shift enabled', async () => {
- const { itemA1, itemB1, itemB2, itemC1 } = useShiftTest( true );
-
- await press.Tab();
- expect( itemA1 ).toHaveFocus();
- await press.ArrowDown();
- expect( itemB1 ).toHaveFocus();
- await press( next );
- expect( itemB2 ).toHaveFocus();
- await press.ArrowUp();
- // A2 doesn't exist
- expect( itemA1 ).toHaveFocus();
- await press.ArrowDown();
- expect( itemB1 ).toHaveFocus();
- await press( next );
- expect( itemB2 ).toHaveFocus();
- await press.ArrowDown();
- // C2 is disabled
- expect( itemC1 ).toHaveFocus();
- } );
-
- test( 'Focus does not shift if vertical neighbour unavailable when shift not enabled', async () => {
- const { itemA1, itemB1, itemB2 } = useShiftTest( false );
-
- await press.Tab();
- expect( itemA1 ).toHaveFocus();
- await press.ArrowDown();
- expect( itemB1 ).toHaveFocus();
- await press( next );
- expect( itemB2 ).toHaveFocus();
- await press.ArrowUp();
- // A2 doesn't exist
- expect( itemB2 ).toHaveFocus();
- await press.ArrowDown();
- // C2 is disabled
- expect( itemB2 ).toHaveFocus();
- } );
- } );
-} );