@momentum-ui/react-collaboration
Version:
Cisco Momentum UI Framework for React Collaboration Applications
105 lines • 5.23 kB
JavaScript
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