From 45e038eacfb84b4e461f8b07205b50f7ebffada4 Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Fri, 3 Mar 2023 19:00:18 +0900 Subject: [PATCH] navigateRegions: Convert to TypeScript (#48632) * navigateRegions: Convert to TypeScript * Add changelog * Accept both native and synthetic events * Fixup changelog --- packages/components/CHANGELOG.md | 1 + .../navigate-regions/{index.js => index.tsx} | 58 +++++++++++++++---- packages/components/tsconfig.json | 1 - packages/keycodes/src/index.js | 4 +- 4 files changed, 50 insertions(+), 14 deletions(-) rename packages/components/src/higher-order/navigate-regions/{index.js => index.tsx} (53%) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 249ae1cfa617a..fcea596e15385 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -6,6 +6,7 @@ - `Guide`: Convert to TypeScript ([#47493](https://github.com/WordPress/gutenberg/pull/47493)). - `PanelBody`: Convert to TypeScript ([#47702](https://github.com/WordPress/gutenberg/pull/47702)). +- `navigateRegions` HOC: Convert to TypeScript ([#48632](https://github.com/WordPress/gutenberg/pull/48632)). ## 23.5.0 (2023-03-01) diff --git a/packages/components/src/higher-order/navigate-regions/index.js b/packages/components/src/higher-order/navigate-regions/index.tsx similarity index 53% rename from packages/components/src/higher-order/navigate-regions/index.js rename to packages/components/src/higher-order/navigate-regions/index.tsx index 5b5a773789731..f2b9c31bfbe95 100644 --- a/packages/components/src/higher-order/navigate-regions/index.js +++ b/packages/components/src/higher-order/navigate-regions/index.tsx @@ -8,6 +8,7 @@ import { useMergeRefs, } from '@wordpress/compose'; import { isKeyboardEvent } from '@wordpress/keycodes'; +import type { WPKeycodeModifier } from '@wordpress/keycodes'; const defaultShortcuts = { previous: [ @@ -23,7 +24,7 @@ const defaultShortcuts = { modifier: 'access', character: 'p', }, - ], + ] as const, next: [ { modifier: 'ctrl', @@ -33,27 +34,36 @@ const defaultShortcuts = { modifier: 'access', character: 'n', }, - ], + ] as const, }; -export function useNavigateRegions( shortcuts = defaultShortcuts ) { - const ref = useRef(); +type Shortcuts = { + previous: readonly { modifier: WPKeycodeModifier; character: string }[]; + next: readonly { modifier: WPKeycodeModifier; character: string }[]; +}; + +export function useNavigateRegions( shortcuts: Shortcuts = defaultShortcuts ) { + const ref = useRef< HTMLDivElement >( null ); const [ isFocusingRegions, setIsFocusingRegions ] = useState( false ); - function focusRegion( offset ) { + function focusRegion( offset: number ) { const regions = Array.from( - ref.current.querySelectorAll( '[role="region"][tabindex="-1"]' ) + ref.current?.querySelectorAll< HTMLElement >( + '[role="region"][tabindex="-1"]' + ) ?? [] ); if ( ! regions.length ) { return; } let nextRegion = regions[ 0 ]; // Based off the current element, use closest to determine the wrapping region since this operates up the DOM. Also, match tabindex to avoid edge cases with regions we do not want. - const selectedIndex = regions.indexOf( - ref.current.ownerDocument.activeElement.closest( + const wrappingRegion = + ref.current?.ownerDocument?.activeElement?.closest< HTMLElement >( '[role="region"][tabindex="-1"]' - ) - ); + ); + const selectedIndex = wrappingRegion + ? regions.indexOf( wrappingRegion ) + : -1; if ( selectedIndex !== -1 ) { let nextIndex = selectedIndex + offset; nextIndex = nextIndex === -1 ? regions.length - 1 : nextIndex; @@ -83,7 +93,7 @@ export function useNavigateRegions( shortcuts = defaultShortcuts ) { return { ref: useMergeRefs( [ ref, clickRef ] ), className: isFocusingRegions ? 'is-focusing-regions' : '', - onKeyDown( event ) { + onKeyDown( event: React.KeyboardEvent< HTMLDivElement > ) { if ( shortcuts.previous.some( ( { modifier, character } ) => { return isKeyboardEvent[ modifier ]( event, character ); @@ -101,6 +111,32 @@ export function useNavigateRegions( shortcuts = defaultShortcuts ) { }; } +/** + * `navigateRegions` is a React [higher-order component](https://facebook.github.io/react/docs/higher-order-components.html) + * adding keyboard navigation to switch between the different DOM elements marked as "regions" (role="region"). + * These regions should be focusable (By adding a tabIndex attribute for example). For better accessibility, + * these elements must be properly labelled to briefly describe the purpose of the content in the region. + * For more details, see "Landmark Roles" in the [WAI-ARIA specification](https://www.w3.org/TR/wai-aria/) + * and "Landmark Regions" in the [ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/). + * + * ```jsx + * import { navigateRegions } from '@wordpress/components'; + * + * const MyComponentWithNavigateRegions = navigateRegions( () => ( + *
+ *
+ * Header + *
+ *
+ * Content + *
+ *
+ * Sidebar + *
+ *
+ * ) ); + * ``` + */ export default createHigherOrderComponent( ( Component ) => ( { shortcuts, ...props } ) => diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json index b2ef337c4221e..9b80c6ecfe334 100644 --- a/packages/components/tsconfig.json +++ b/packages/components/tsconfig.json @@ -49,7 +49,6 @@ "src/dimension-control", "src/duotone-picker", "src/gradient-picker", - "src/higher-order/navigate-regions", "src/higher-order/with-fallback-styles", "src/higher-order/with-filters", "src/higher-order/with-focus-return", diff --git a/packages/keycodes/src/index.js b/packages/keycodes/src/index.js index 0871e75a0875d..a9efb210496c3 100644 --- a/packages/keycodes/src/index.js +++ b/packages/keycodes/src/index.js @@ -42,7 +42,7 @@ import { isAppleOS } from './platform'; * * @typedef {(character: string, isApple?: () => boolean) => T} WPKeyHandler */ -/** @typedef {(event: KeyboardEvent, character: string, isApple?: () => boolean) => boolean} WPEventKeyHandler */ +/** @typedef {(event: import('react').KeyboardEvent | KeyboardEvent, character: string, isApple?: () => boolean) => boolean} WPEventKeyHandler */ /** @typedef {( isApple: () => boolean ) => WPModifierPart[]} WPModifier */ @@ -346,7 +346,7 @@ export const shortcutAriaLabel = mapValues( * From a given KeyboardEvent, returns an array of active modifier constants for * the event. * - * @param {KeyboardEvent} event Keyboard event. + * @param {import('react').KeyboardEvent | KeyboardEvent} event Keyboard event. * * @return {Array} Active modifier constants. */