Skip to content

Commit

Permalink
chore(utils): Update keyboard movement
Browse files Browse the repository at this point in the history
These changes are to help with the Tab movement.

Add a new `horizontal` state to the movement config which updates the
default keyboard config to use `ArrowRight` and `ArrowLeft` instead of
`ArrowDown` and `ArrowUp`.

Add a `nextFocusIndex` param to the `onFocusChange` handler.
  • Loading branch information
mlaursen committed Apr 2, 2022
1 parent aa0d3cd commit 71d1343
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 13 deletions.
34 changes: 27 additions & 7 deletions packages/utils/src/keyboardMovement/KeyboardMovementProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import type { ReactElement, ReactNode } from "react";
import { useMemo, useRef } from "react";

import { useDir } from "../Dir";
import {
DEFAULT_KEYBOARD_MOVEMENT,
DEFAULT_LTR_KEYBOARD_MOVEMENT,
DEFAULT_RTL_KEYBOARD_MOVEMENT,
KeyboardMovementContextProvider,
} from "./movementContext";
import type {
Expand Down Expand Up @@ -36,9 +39,9 @@ export interface KeyboardMovementProviderProps
* }
*
* function CustomKeyboardFocusWidget() {
* const { onKeyDown } = useKeyboardFocus();
* const { focusIndex: _focusIndex, ...eventHandlers } = useKeyboardFocus();
* return (
* <div onKeyDown={onKeyDown}>
* <div {...eventHandlers}>
* <FocusableChild />
* <FocusableChild />
* <FocusableChild />
Expand All @@ -60,12 +63,28 @@ export function KeyboardMovementProvider({
children,
loopable = false,
searchable = false,
horizontal = false,
includeDisabled = false,
incrementKeys = DEFAULT_KEYBOARD_MOVEMENT.incrementKeys,
decrementKeys = DEFAULT_KEYBOARD_MOVEMENT.decrementKeys,
jumpToFirstKeys = DEFAULT_KEYBOARD_MOVEMENT.jumpToFirstKeys,
jumpToLastKeys = DEFAULT_KEYBOARD_MOVEMENT.jumpToLastKeys,
incrementKeys: propIncrementKeys,
decrementKeys: propDecrementKeys,
jumpToFirstKeys: propJumpToFirstKeys,
jumpToLastKeys: propJumpToLastKeys,
}: KeyboardMovementProviderProps): ReactElement {
const isRTL = useDir().dir === "rtl";
let defaults: Readonly<Required<KeyboardMovementConfiguration>>;
if (horizontal) {
defaults = isRTL
? DEFAULT_RTL_KEYBOARD_MOVEMENT
: DEFAULT_LTR_KEYBOARD_MOVEMENT;
} else {
defaults = DEFAULT_KEYBOARD_MOVEMENT;
}

const incrementKeys = propIncrementKeys || defaults.incrementKeys;
const decrementKeys = propDecrementKeys || defaults.decrementKeys;
const jumpToFirstKeys = propJumpToFirstKeys || defaults.jumpToFirstKeys;
const jumpToLastKeys = propJumpToLastKeys || defaults.jumpToLastKeys;

const watching = useRef<KeyboardFocusElementData[]>([]);
const configuration: KeyboardMovementConfig = {
incrementKeys,
Expand Down Expand Up @@ -93,9 +112,10 @@ export function KeyboardMovementProvider({
config,
loopable,
searchable,
horizontal,
includeDisabled: includeDisabled,
}),
[includeDisabled, loopable, searchable]
[horizontal, includeDisabled, loopable, searchable]
);

return (
Expand Down
23 changes: 23 additions & 0 deletions packages/utils/src/keyboardMovement/movementContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,28 @@ export const DEFAULT_KEYBOARD_MOVEMENT: Readonly<KeyboardMovementConfig> = {
jumpToLastKeys: ["End"],
};

/**
* @remarks \@since 5.1.2
* @internal
*/
export const DEFAULT_LTR_KEYBOARD_MOVEMENT: Readonly<KeyboardMovementConfig> = {
incrementKeys: ["ArrowRight"],
decrementKeys: ["ArrowLeft"],
jumpToFirstKeys: ["Home"],
jumpToLastKeys: ["End"],
};

/**
* @remarks \@since 5.1.2
* @internal
*/
export const DEFAULT_RTL_KEYBOARD_MOVEMENT: Readonly<KeyboardMovementConfig> = {
incrementKeys: ["ArrowLeft"],
decrementKeys: ["ArrowRight"],
jumpToFirstKeys: ["Home"],
jumpToLastKeys: ["End"],
};

/**
* @remarks \@since 5.0.0
* @internal
Expand All @@ -35,6 +57,7 @@ const context = createContext<KeyboardFocusContext>({
watching: { current: [] },
loopable: false,
searchable: false,
horizontal: false,
includeDisabled: false,
config: { current: DEFAULT_KEYBOARD_MOVEMENT },
});
Expand Down
10 changes: 10 additions & 0 deletions packages/utils/src/keyboardMovement/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ export interface KeyboardMovementBehavior {
* @defaultValue `false`
*/
includeDisabled?: boolean;

/**
* Boolean if the keyboard movement is horizontal instead of vertical. This
* updates the default keyboard config to use `ArrowRight` and `ArrowLeft`
* isntead of `ArrowDown` and `ArrowUp`,
*
* @remarks \@since 5.1.2
* @defaultValue `false`
*/
horizontal?: boolean;
}

/**
Expand Down
12 changes: 6 additions & 6 deletions packages/utils/src/keyboardMovement/useKeyboardFocus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,13 @@ export interface KeyboardFocusHookOptions<E extends HTMLElement>
* The default value is just to call `element.focus()`.
*
* @param element - The element that should gain custom focus
* @param nextFocusIndex - The next focus index which can be used for
* additional movement behavior.
*/
onFocusChange?(element: HTMLElement): void;
onFocusChange?(element: HTMLElement, nextFocusIndex: number): void;
}

/**
* @remarks \@since 5.0.0
*/
/** @remarks \@since 5.0.0 */
export interface KeyboardFocusHookReturnValue<E extends HTMLElement> {
onFocus: FocusEventHandler<E>;
onKeyDown: KeyboardEventHandler<E>;
Expand Down Expand Up @@ -184,7 +184,7 @@ export function useKeyboardFocus<E extends HTMLElement>(

focusIndex.current = defaultFocusIndex;
const element = watching.current[focusIndex.current]?.element;
element && onFocusChange(element);
element && onFocusChange(element, focusIndex.current);
},
onKeyDown(event) {
onKeyDown(event);
Expand All @@ -206,7 +206,7 @@ export function useKeyboardFocus<E extends HTMLElement>(
focusIndex.current = index;

const element = watching.current[index]?.element;
element && onFocusChange(element);
element && onFocusChange(element, focusIndex.current);
};

if (
Expand Down

0 comments on commit 71d1343

Please sign in to comment.