UNPKG

@gechiui/components

Version:
255 lines (198 loc) 8.58 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "TreeGridCell", { enumerable: true, get: function () { return _cell.default; } }); Object.defineProperty(exports, "TreeGridItem", { enumerable: true, get: function () { return _item.default; } }); Object.defineProperty(exports, "TreeGridRow", { enumerable: true, get: function () { return _row.default; } }); exports.default = void 0; var _element = require("@gechiui/element"); var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _lodash = require("lodash"); var _dom = require("@gechiui/dom"); var _keycodes = require("@gechiui/keycodes"); var _rovingTabIndex = _interopRequireDefault(require("./roving-tab-index")); var _row = _interopRequireDefault(require("./row")); var _cell = _interopRequireDefault(require("./cell")); var _item = _interopRequireDefault(require("./item")); /** * External dependencies */ /** * GeChiUI dependencies */ /** * Internal dependencies */ /** * Return focusables in a row element, excluding those from other branches * nested within the row. * * @param {Element} rowElement The DOM element representing the row. * * @return {?Array} The array of focusables in the row. */ function getRowFocusables(rowElement) { const focusablesInRow = _dom.focus.focusable.find(rowElement, { sequential: true }); if (!focusablesInRow || !focusablesInRow.length) { return; } return focusablesInRow.filter(focusable => { return focusable.closest('[role="row"]') === rowElement; }); } /** * Renders both a table and tbody element, used to create a tree hierarchy. * * @see https://github.com/GeChiUI/gutenberg/blob/HEAD/packages/components/src/tree-grid/README.md * @param {Object} props Component props. * @param {GCElement} props.children Children to be rendered. * @param {Function} props.onExpandRow Callback to fire when row is expanded. * @param {Function} props.onCollapseRow Callback to fire when row is collapsed. * @param {Object} ref A ref to the underlying DOM table element. */ function TreeGrid(_ref, ref) { let { children, onExpandRow = () => {}, onCollapseRow = () => {}, ...props } = _ref; const onKeyDown = (0, _element.useCallback)(event => { const { keyCode, metaKey, ctrlKey, altKey, shiftKey } = event; const hasModifierKeyPressed = metaKey || ctrlKey || altKey || shiftKey; if (hasModifierKeyPressed || !(0, _lodash.includes)([_keycodes.UP, _keycodes.DOWN, _keycodes.LEFT, _keycodes.RIGHT], keyCode)) { return; } // The event will be handled, stop propagation. event.stopPropagation(); const { activeElement } = document; const { currentTarget: treeGridElement } = event; if (!treeGridElement.contains(activeElement)) { return; } // Calculate the columnIndex of the active element. const activeRow = activeElement.closest('[role="row"]'); const focusablesInRow = getRowFocusables(activeRow); const currentColumnIndex = focusablesInRow.indexOf(activeElement); if ((0, _lodash.includes)([_keycodes.LEFT, _keycodes.RIGHT], keyCode)) { // Calculate to the next element. let nextIndex; if (keyCode === _keycodes.LEFT) { nextIndex = Math.max(0, currentColumnIndex - 1); } else { nextIndex = Math.min(currentColumnIndex + 1, focusablesInRow.length - 1); } // Focus is either at the left or right edge of the grid. if (nextIndex === currentColumnIndex) { if (keyCode === _keycodes.LEFT) { var _activeRow$ariaLevel, _getRowFocusables, _getRowFocusables$; // Left: // If a row is focused, and it is expanded, collapses the current row. if (activeRow.getAttribute('aria-expanded') === 'true') { onCollapseRow(activeRow); event.preventDefault(); return; } // If a row is focused, and it is collapsed, moves to the parent row (if there is one). const level = Math.max(parseInt((_activeRow$ariaLevel = activeRow === null || activeRow === void 0 ? void 0 : activeRow.ariaLevel) !== null && _activeRow$ariaLevel !== void 0 ? _activeRow$ariaLevel : 1, 10) - 1, 1); const rows = Array.from(treeGridElement.querySelectorAll('[role="row"]')); let parentRow = activeRow; const currentRowIndex = rows.indexOf(activeRow); for (let i = currentRowIndex; i >= 0; i--) { if (parseInt(rows[i].ariaLevel, 10) === level) { parentRow = rows[i]; break; } } (_getRowFocusables = getRowFocusables(parentRow)) === null || _getRowFocusables === void 0 ? void 0 : (_getRowFocusables$ = _getRowFocusables[0]) === null || _getRowFocusables$ === void 0 ? void 0 : _getRowFocusables$.focus(); } if (keyCode === _keycodes.RIGHT) { // Right: // If a row is focused, and it is collapsed, expands the current row. if (activeRow.getAttribute('aria-expanded') === 'false') { onExpandRow(activeRow); event.preventDefault(); return; } // If a row is focused, and it is expanded, focuses the rightmost cell in the row. const focusableItems = getRowFocusables(activeRow); if (focusableItems.length > 0) { var _focusableItems; (_focusableItems = focusableItems[focusableItems.length - 1]) === null || _focusableItems === void 0 ? void 0 : _focusableItems.focus(); } } // Prevent key use for anything else. For example, Voiceover // will start reading text on continued use of left/right arrow // keys. event.preventDefault(); return; } // Focus the next element. focusablesInRow[nextIndex].focus(); // Prevent key use for anything else. This ensures Voiceover // doesn't try to handle key navigation. event.preventDefault(); } else if ((0, _lodash.includes)([_keycodes.UP, _keycodes.DOWN], keyCode)) { // Calculate the rowIndex of the next row. const rows = Array.from(treeGridElement.querySelectorAll('[role="row"]')); const currentRowIndex = rows.indexOf(activeRow); let nextRowIndex; if (keyCode === _keycodes.UP) { nextRowIndex = Math.max(0, currentRowIndex - 1); } else { nextRowIndex = Math.min(currentRowIndex + 1, rows.length - 1); } // Focus is either at the top or bottom edge of the grid. Do nothing. if (nextRowIndex === currentRowIndex) { // Prevent key use for anything else. For example, Voiceover // will start navigating horizontally when reaching the vertical // bounds of a table. event.preventDefault(); return; } // Get the focusables in the next row. const focusablesInNextRow = getRowFocusables(rows[nextRowIndex]); // If for some reason there are no focusables in the next row, do nothing. if (!focusablesInNextRow || !focusablesInNextRow.length) { // Prevent key use for anything else. For example, Voiceover // will still focus text when using arrow keys, while this // component should limit navigation to focusables. event.preventDefault(); return; } // Try to focus the element in the next row that's at a similar column to the activeElement. const nextIndex = Math.min(currentColumnIndex, focusablesInNextRow.length - 1); focusablesInNextRow[nextIndex].focus(); // Prevent key use for anything else. This ensures Voiceover // doesn't try to handle key navigation. event.preventDefault(); } }, [onExpandRow, onCollapseRow]); /* Disable reason: A treegrid is implemented using a table element. */ /* eslint-disable jsx-a11y/no-noninteractive-element-to-interactive-role */ return (0, _element.createElement)(_rovingTabIndex.default, null, (0, _element.createElement)("table", (0, _extends2.default)({}, props, { role: "treegrid", onKeyDown: onKeyDown, ref: ref }), (0, _element.createElement)("tbody", null, children))); /* eslint-enable jsx-a11y/no-noninteractive-element-to-interactive-role */ } var _default = (0, _element.forwardRef)(TreeGrid); exports.default = _default; //# sourceMappingURL=index.js.map