UNPKG

@zendeskgarden/container-focusjail

Version:

Containers relating to focusjail in the Garden Design System

142 lines (135 loc) 4.1 kB
/** * Copyright Zendesk, Inc. * * Use of this source code is governed under the Apache License, Version 2.0 * found at http://www.apache.org/licenses/LICENSE-2.0. */ import React, { createRef, useRef, useState, useEffect, useCallback } from 'react'; import { composeEventHandlers, KEYS } from '@zendeskgarden/container-utilities'; import { tabbable } from 'tabbable'; import PropTypes from 'prop-types'; function ownerDocument(node) { return node && node.ownerDocument || document; } function activeElement(doc) { if (doc === void 0) { doc = ownerDocument(); } try { var active = doc.activeElement; if (!active || !active.nodeName) return null; return active; } catch (e) { return doc.body; } } const useFocusJail = function (_temp) { let { focusOnMount = true, restoreFocus = true, environment, focusElem, containerRef } = _temp === void 0 ? { containerRef: createRef() } : _temp; const restoreFocusElement = useRef(null); const [currentRef, setCurrentRef] = useState(containerRef.current); useEffect(() => { if (containerRef.current !== currentRef) { setCurrentRef(containerRef.current); } }); const focusElement = useCallback(element => { const htmlElement = element; if (focusElem) { focusElem(htmlElement); } else { htmlElement && htmlElement.focus(); } }, [focusElem]); const validateContainerRef = () => { if (!currentRef) { throw new Error('Accessibility Error: You must apply the ref prop to your containing element.'); } }; const getInitialFocusNode = () => { const doc = environment ? environment : document; const activeElem = activeElement(doc); const containerElem = currentRef; return containerElem.contains(activeElem) ? activeElem : containerElem; }; const getTabbableNodes = () => { const elements = tabbable(currentRef); return { firstItem: elements[0] || getInitialFocusNode(), lastItem: elements[elements.length - 1] || getInitialFocusNode() }; }; const getContainerProps = function (_temp2) { let { onKeyDown, ...other } = _temp2 === void 0 ? {} : _temp2; return { onKeyDown: composeEventHandlers(onKeyDown, event => { if (event.key !== KEYS.TAB) { return; } validateContainerRef(); const tabbableNodes = getTabbableNodes(); if (event.shiftKey && (event.target === tabbableNodes.firstItem || event.target === currentRef)) { focusElement(tabbableNodes.lastItem); event.preventDefault(); } if (!event.shiftKey && event.target === tabbableNodes.lastItem) { focusElement(tabbableNodes.firstItem); event.preventDefault(); } }), 'data-garden-container-id': 'containers.focusjail', 'data-garden-container-version': '2.0.19', ...other }; }; useEffect(() => { const doc = environment || document; restoreFocusElement.current = activeElement(doc); if (focusOnMount) { focusElement(currentRef); } return () => { const isBodyInactive = restoreFocusElement.current !== doc.body; const hasActiveElement = restoreFocusElement.current !== null; if (isBodyInactive && hasActiveElement && restoreFocus) { focusElement(restoreFocusElement.current); } }; }, [focusOnMount, restoreFocus, environment, focusElement, currentRef]); return { getContainerProps, focusElement }; }; const FocusJailContainer = _ref => { let { children, render = children, ...options } = _ref; return React.createElement(React.Fragment, null, render(useFocusJail(options))); }; FocusJailContainer.propTypes = { children: PropTypes.func, render: PropTypes.func, focusOnMount: PropTypes.bool, restoreFocus: PropTypes.bool, environment: PropTypes.any, containerRef: PropTypes.any.isRequired, focusElem: PropTypes.func }; FocusJailContainer.defaultProps = { focusOnMount: true, restoreFocus: true }; export { FocusJailContainer, useFocusJail };