UNPKG

@workday/canvas-kit-react

Version:

The parent module that contains all Workday Canvas Kit React components

63 lines (54 loc) 2.28 kB
import React from 'react'; import { createElemPropsHook, getFirstFocusableElement, getLastFocusableElement, } from '@workday/canvas-kit-react/common'; import {usePopupModel} from './usePopupModel'; /** * Manages focus around a popup, treating the popup as if it was part of the DOM where it appears. * Popups are typically "portalled" (inserted at the end of `document.body`) to ensure proper * rendering. This violates [WCAG Focus * Order](https://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-focus-order.html). This * hook helps redirect focus as if the popup element appeared in the DOM. `aria-owns` might also be * used to ensure assistive technology places the popup after the button for virtual cursors. This * hook does no provide `aria-owns` and this must be provided yourself. Requires `useReturnFocus` to * work properly. Works well with `useInitialFocus`. * * This should be used with non-modal dialogs. */ export const useFocusRedirect = createElemPropsHook(usePopupModel)(model => { const onKeyDown = React.useCallback( (event: KeyboardEvent): void => { if (model.state.stackRef.current && event.key === 'Tab') { const firstFocusableElement = getFirstFocusableElement(model.state.stackRef.current); const lastFocusableElement = getLastFocusableElement(model.state.stackRef.current); if (event.getModifierState('Shift') && document.activeElement === firstFocusableElement) { event.preventDefault(); model.events.hide(event); } else if ( !event.getModifierState('Shift') && ((Array.isArray(lastFocusableElement) && document.activeElement && lastFocusableElement.includes(document.activeElement)) || document.activeElement === lastFocusableElement) ) { model.events.hide(event); } } }, [model.events, model.state.stackRef] ); const visible = model.state.visibility !== 'hidden'; // `useLayoutEffect` for automation React.useLayoutEffect(() => { if (!visible) { return; } document.addEventListener('keydown', onKeyDown); return () => { document.removeEventListener('keydown', onKeyDown); }; }, [visible, onKeyDown]); return {}; });