UNPKG

@wordpress/components

Version:
178 lines (141 loc) 4.77 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _element = require("@wordpress/element"); var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _lodash = require("lodash"); var _dom = require("@wordpress/dom"); /** * External dependencies */ /** * WordPress dependencies */ const MENU_ITEM_ROLES = ['menuitem', 'menuitemradio', 'menuitemcheckbox']; function cycleValue(value, total, offset) { const nextValue = value + offset; if (nextValue < 0) { return total + nextValue; } else if (nextValue >= total) { return nextValue - total; } return nextValue; } class NavigableContainer extends _element.Component { constructor() { super(...arguments); this.onKeyDown = this.onKeyDown.bind(this); this.bindContainer = this.bindContainer.bind(this); this.getFocusableContext = this.getFocusableContext.bind(this); this.getFocusableIndex = this.getFocusableIndex.bind(this); } componentDidMount() { // We use DOM event listeners instead of React event listeners // because we want to catch events from the underlying DOM tree // The React Tree can be different from the DOM tree when using // portals. Block Toolbars for instance are rendered in a separate // React Trees. this.container.addEventListener('keydown', this.onKeyDown); this.container.addEventListener('focus', this.onFocus); } componentWillUnmount() { this.container.removeEventListener('keydown', this.onKeyDown); this.container.removeEventListener('focus', this.onFocus); } bindContainer(ref) { const { forwardedRef } = this.props; this.container = ref; if ((0, _lodash.isFunction)(forwardedRef)) { forwardedRef(ref); } else if (forwardedRef && 'current' in forwardedRef) { forwardedRef.current = ref; } } getFocusableContext(target) { const { onlyBrowserTabstops } = this.props; const finder = onlyBrowserTabstops ? _dom.focus.tabbable : _dom.focus.focusable; const focusables = finder.find(this.container); const index = this.getFocusableIndex(focusables, target); if (index > -1 && target) { return { index, target, focusables }; } return null; } getFocusableIndex(focusables, target) { const directIndex = focusables.indexOf(target); if (directIndex !== -1) { return directIndex; } } onKeyDown(event) { if (this.props.onKeyDown) { this.props.onKeyDown(event); } const { getFocusableContext } = this; const { cycle = true, eventToOffset, onNavigate = _lodash.noop, stopNavigationEvents } = this.props; const offset = eventToOffset(event); // eventToOffset returns undefined if the event is not handled by the component if (offset !== undefined && stopNavigationEvents) { // Prevents arrow key handlers bound to the document directly interfering event.stopImmediatePropagation(); // When navigating a collection of items, prevent scroll containers // from scrolling. The preventDefault also prevents Voiceover from // 'handling' the event, as voiceover will try to use arrow keys // for highlighting text. const targetRole = event.target.getAttribute('role'); if (MENU_ITEM_ROLES.includes(targetRole)) { event.preventDefault(); } } if (!offset) { return; } const context = getFocusableContext(event.target.ownerDocument.activeElement); if (!context) { return; } const { index, focusables } = context; const nextIndex = cycle ? cycleValue(index, focusables.length, offset) : index + offset; if (nextIndex >= 0 && nextIndex < focusables.length) { focusables[nextIndex].focus(); onNavigate(nextIndex, focusables[nextIndex]); } } render() { const { children, ...props } = this.props; return (0, _element.createElement)("div", (0, _extends2.default)({ ref: this.bindContainer }, (0, _lodash.omit)(props, ['stopNavigationEvents', 'eventToOffset', 'onNavigate', 'onKeyDown', 'cycle', 'onlyBrowserTabstops', 'forwardedRef'])), children); } } const forwardedNavigableContainer = (props, ref) => { return (0, _element.createElement)(NavigableContainer, (0, _extends2.default)({}, props, { forwardedRef: ref })); }; forwardedNavigableContainer.displayName = 'NavigableContainer'; var _default = (0, _element.forwardRef)(forwardedNavigableContainer); exports.default = _default; //# sourceMappingURL=container.js.map