UNPKG

@momentum-ui/react-collaboration

Version:

Cisco Momentum UI Framework for React Collaboration Applications

105 lines 5.23 kB
import { useEffect } from 'react'; import { STYLE, TREE_NAVIGATION_KEYS } from './Tree.constants'; import './Tree.style.scss'; import { getKeyboardFocusableElements } from '../../utils/navigation'; import { NODE_ID_ATTRIBUTE_NAME, NODE_ID_DATA_NAME } from '../TreeNodeBase/TreeNodeBase.constants'; /** * Handle DOM changes for virtual tree * * This hook manage 2 use cases: * 1) When Virtual Tree removes active node, the hook adds a cloned node to not lose focus. * 2) When the active node added back to the DOM, the hook invoke focus on the element and trigger * key event if it happened on the cloned element. * * @param props * @internal */ export var useVirtualTreeNavigation = function (_a) { var virtualTreeConnector = _a.virtualTreeConnector, treeRef = _a.treeRef, activeNodeIdRef = _a.activeNodeIdRef; // Handle DOM changes for virtual tree useEffect(function () { var _a; if (!virtualTreeConnector) return; // target = parent of the first tree node to avoid mutation change detection in the subtree var targetNode = (_a = treeRef.current.querySelector("[".concat(NODE_ID_ATTRIBUTE_NAME, "]"))) === null || _a === void 0 ? void 0 : _a.parentElement; if (!targetNode) return; // To re-dispatch arrow keydown events var keyDownEvent; // Remove all cloned nodes var cleanUp = function () { return Array.from(targetNode.querySelectorAll(".".concat(STYLE.clonedVirtualTreeNode))).forEach(function (e) { return e.remove(); }); }; // Filter element by active node id var getByNodeId = function (node) { return node.dataset[NODE_ID_DATA_NAME] === activeNodeIdRef.current; }; // Mutation observer to handle the focus change var mutationHandler = function (mutationList) { var _a, _b; for (var _i = 0, mutationList_1 = mutationList; _i < mutationList_1.length; _i++) { var mutation = mutationList_1[_i]; // Active node moved out of view and removed var removed = Array.from(mutation.removedNodes).find(getByNodeId); if (removed) { // Clone the active node and make some modifications var clonedNode = removed.cloneNode(true); // remove nodeid attribute clonedNode.removeAttribute(NODE_ID_ATTRIBUTE_NAME); clonedNode.classList.add(STYLE.clonedVirtualTreeNode); // make all focusable elements un-focusable getKeyboardFocusableElements(clonedNode).forEach(function (el) { return el.setAttribute('tabindex', '-1'); }); targetNode.appendChild(clonedNode); clonedNode.focus({ preventScroll: true }); // add focus and keydown event listeners clonedNode.addEventListener('focus', function () { virtualTreeConnector.scrollToNode(activeNodeIdRef.current); }); clonedNode.addEventListener('keydown', function (evt) { if (TREE_NAVIGATION_KEYS.includes(evt.key) || (evt.key === 'Tab' && !evt.shiftKey)) { virtualTreeConnector.scrollToNode(activeNodeIdRef.current); keyDownEvent = new KeyboardEvent('keydown', evt); evt.stopPropagation(); evt.preventDefault(); } }); return; } // Active node moved back into the view and it added back to the DOM var added = Array.from(mutation.addedNodes).find(getByNodeId); if (added) { cleanUp(); added.focus({ preventScroll: true }); // Handle keydown event originated from the cloned node if (keyDownEvent) { // re-dispatch Tab key event does not change focus in every browser, so // manually focus on the first element if (keyDownEvent.key === 'Tab') { (_b = (_a = getKeyboardFocusableElements(added)[0]) === null || _a === void 0 ? void 0 : _a.focus) === null || _b === void 0 ? void 0 : _b.call(_a); } else { // re-dispatch the arrow key events added.dispatchEvent(keyDownEvent); } keyDownEvent = null; } return; } } }; // Start observing the target node var observer = new MutationObserver(mutationHandler); observer.observe(targetNode, { childList: true }); // Clean up return function () { observer.disconnect(); cleanUp(); }; }, [virtualTreeConnector, treeRef.current]); }; //# sourceMappingURL=Tree.hooks.js.map