@momentum-ui/react-collaboration
Version:
Cisco Momentum UI Framework for React Collaboration Applications
280 lines • 15 kB
JavaScript
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import React, { useRef, useState, useCallback, useMemo, useImperativeHandle, forwardRef, } from 'react';
import classnames from 'classnames';
import { STYLE, DEFAULTS } from './Tree.constants';
import './Tree.style.scss';
import { convertNestedTree2MappedTree, isActiveNodeInDOM, getInitialActiveNode, getNextActiveNode, getNodeDOMId, getTreeRootId, migrateTreeState, toggleTreeNodeRecord, TreeContext, } from './Tree.utils';
import { useFocusWithin, useKeyboard } from '@react-aria/interactions';
import { useVirtualTreeNavigation } from './Tree.hooks';
import { useDidUpdateEffect } from '../../hooks/useDidUpdateEffect';
import { usePrevious } from '../../hooks/usePrevious';
import { useItemSelected } from '../../hooks/useItemSelected';
import { useSpatialNavigationContext } from '../SpatialNavigationProvider/SpatialNavigationProvider.utils';
/**
* @deprecated Use the equivalent from momentum.design (NPM: `@momentum-design/components/dist/react`)
*/
var Tree = forwardRef(function (props, ref) {
var className = props.className, id = props.id, style = props.style, children = props.children, _a = props.shouldNodeFocusBeInset, shouldNodeFocusBeInset = _a === void 0 ? DEFAULTS.SHOULD_NODE_FOCUS_BE_INSET : _a, treeStructure = props.treeStructure, _b = props.isRenderedFlat, isRenderedFlat = _b === void 0 ? DEFAULTS.IS_RENDERED_FLAT : _b, _c = props.excludeTreeRoot, excludeTreeRoot = _c === void 0 ? DEFAULTS.EXCLUDE_TREE_ROOT : _c, virtualTreeConnector = props.virtualTreeConnector, _d = props.selectionMode, selectionMode = _d === void 0 ? DEFAULTS.SELECTION_MODE : _d, _e = props.selectableNodes, selectableNodes = _e === void 0 ? DEFAULTS.SELECTABLE_NODES : _e, selectedByDefault = props.selectedByDefault, onSelectionChange = props.onSelectionChange, selectedItems = props.selectedItems, _f = props.isRequired, isRequired = _f === void 0 ? DEFAULTS.IS_REQUIRED : _f, onToggleNode = props.onToggleNode, rest = __rest(props, ["className", "id", "style", "children", "shouldNodeFocusBeInset", "treeStructure", "isRenderedFlat", "excludeTreeRoot", "virtualTreeConnector", "selectionMode", "selectableNodes", "selectedByDefault", "onSelectionChange", "selectedItems", "isRequired", "onToggleNode"]);
// aria-label or aria-labelledby is required on the Props type, but union type enforcement doesn't work properly with forwardRef
if (!rest['aria-label'] && !rest['aria-labelledby']) {
console.warn('MRV2: Tree must have aria-label or aria-labelledby');
}
var treeRef = useRef();
var spatialNav = useSpatialNavigationContext();
var itemSelection = useItemSelected({
selectionMode: selectionMode,
selectedByDefault: selectedByDefault,
selectedItems: selectedItems,
onSelectionChange: onSelectionChange,
isRequired: isRequired,
});
var _g = useState(convertNestedTree2MappedTree(treeStructure)), tree = _g[0], setTree = _g[1];
var _h = useState(getInitialActiveNode(tree, excludeTreeRoot, itemSelection)), activeNode = _h[0], setActiveNode = _h[1];
var _j = useState(false), isFocusWithin = _j[0], setIsFocusWithin = _j[1];
var activeNodeIdRef = useRef(activeNode);
var previousTree = usePrevious(tree);
useDidUpdateEffect(function () {
var _a;
var newTree = convertNestedTree2MappedTree(treeStructure);
migrateTreeState(previousTree, newTree);
setTree(newTree);
// Find the closest node to the last active node in the new tree
var newActiveNodeId = activeNode;
while (newActiveNodeId) {
if (newTree.has(newActiveNodeId) && !newTree.get(newActiveNodeId).isHidden)
break;
newActiveNodeId = (_a = previousTree.get(newActiveNodeId)) === null || _a === void 0 ? void 0 : _a.parent;
}
// Fallback to the first node
if (!newActiveNodeId || newActiveNodeId === getTreeRootId(newTree)) {
newActiveNodeId = getInitialActiveNode(newTree, excludeTreeRoot, itemSelection);
}
setActiveNodeId(newActiveNodeId);
}, [treeStructure]);
var isVirtualTree = virtualTreeConnector !== undefined;
var scrollToVTreeNode = useCallback(function (nodeId) {
var _a;
if (isVirtualTree && virtualTreeConnector && !isActiveNodeInDOM(treeRef, nodeId)) {
(_a = virtualTreeConnector.scrollToNode) === null || _a === void 0 ? void 0 : _a.call(virtualTreeConnector, nodeId);
}
}, [isVirtualTree, virtualTreeConnector]);
// Handle DOM changes for virtual tree
useVirtualTreeNavigation({ virtualTreeConnector: virtualTreeConnector, treeRef: treeRef, activeNodeIdRef: activeNodeIdRef });
var setActiveNodeId = useCallback(function (newNodeId) {
if (activeNodeIdRef.current !== newNodeId) {
activeNodeIdRef.current = newNodeId;
setActiveNode(newNodeId);
scrollToVTreeNode(newNodeId);
}
}, [scrollToVTreeNode]);
var toggleTreeNode = useCallback(function (id, isOpen) { return __awaiter(void 0, void 0, void 0, function () {
var newOpenState;
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
newOpenState = isOpen !== undefined ? isOpen : !tree.get(id).isOpen;
if (!isVirtualTree) return [3 /*break*/, 2];
scrollToVTreeNode(id);
return [4 /*yield*/, ((_a = virtualTreeConnector.setNodeOpen) === null || _a === void 0 ? void 0 : _a.call(virtualTreeConnector, id, newOpenState))];
case 1:
_b.sent();
_b.label = 2;
case 2:
onToggleNode === null || onToggleNode === void 0 ? void 0 : onToggleNode(id, newOpenState);
setTree(function (prevTree) { return toggleTreeNodeRecord(id, prevTree, newOpenState); });
return [2 /*return*/];
}
});
}); },
// eslint-disable-next-line react-hooks/exhaustive-deps
[tree, isVirtualTree]);
var getNodeDetails = useCallback(function (id) { return tree.get(id); }, [tree]);
var getNodeAriaProps = useCallback(function (id) {
var node = tree.get(id);
if (!node)
return {};
var parent = node.parent && tree.get(node.parent);
var isRoot = parent === undefined;
// tabindex depends on the treeNodeBase params as well
var nodeProps = {
id: getNodeDOMId(node.id),
'aria-setsize': isRoot ? 1 : parent.children.length,
'aria-level': node.level + (excludeTreeRoot ? 0 : 1),
'aria-posinset': node.index + 1,
role: 'treeitem',
};
// Root level node(s) should not have aria-level attribute and
if (node.level > (excludeTreeRoot ? 1 : 0)) {
// level should start with 1 under the root level nodes
nodeProps['aria-level'] = node.level - (excludeTreeRoot ? 1 : 0);
}
if (!node.isLeaf) {
nodeProps['aria-expanded'] = (!!node.isOpen).toString();
}
var contentId = "".concat(getNodeDOMId(node.id), "-content");
var nodeContentProps = { id: contentId };
var groupProps = {
role: 'group',
'aria-owns': node.children.map(getNodeDOMId).join(' '),
'aria-labelledby': contentId,
};
return { nodeProps: nodeProps, nodeContentProps: nodeContentProps, groupProps: groupProps };
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[tree]);
var context = useMemo(function () { return ({
getNodeAriaProps: getNodeAriaProps,
getNodeDetails: getNodeDetails,
isRenderedFlat: isRenderedFlat,
shouldNodeFocusBeInset: shouldNodeFocusBeInset,
activeNodeId: activeNode,
setActiveNodeId: setActiveNodeId,
toggleTreeNode: toggleTreeNode,
selectableNodes: selectableNodes,
itemSelection: itemSelection,
isFocusWithin: isFocusWithin,
}); }, [
getNodeAriaProps,
getNodeDetails,
isRenderedFlat,
shouldNodeFocusBeInset,
activeNode,
setActiveNodeId,
toggleTreeNode,
selectableNodes,
itemSelection,
isFocusWithin,
]);
useImperativeHandle(ref, function () { return ({
treeRef: treeRef,
setActiveNodeId: setActiveNodeId,
toggleTreeNode: toggleTreeNode,
clearSelection: itemSelection.clear,
updateSelection: itemSelection.update,
toggleSelection: itemSelection.toggle,
}); }, [setActiveNodeId, toggleTreeNode, itemSelection]);
var focusWithinProps = useFocusWithin({
onFocusWithin: function (e) {
setIsFocusWithin(true);
var targetNode = e.target.closest('[data-nodeid]');
if (!!targetNode && targetNode.getAttribute('data-nodeid') !== activeNode) {
// if the user's focus is on a different tree node, then don't scroll to the current active node
return;
}
scrollToVTreeNode(activeNode);
},
onBlurWithin: function () {
setIsFocusWithin(false);
},
}).focusWithinProps;
var keyboardProps = useKeyboard({
onKeyDown: function (evt) {
var _a;
var key = evt.key;
switch (key) {
case 'ArrowUp':
case 'ArrowDown':
case 'ArrowRight':
case 'ArrowLeft': {
evt.preventDefault();
if (activeNode) {
var next = getNextActiveNode(tree, activeNode, key, excludeTreeRoot);
if (next.action !== 'noop') {
if (spatialNav) {
// skip spatial navigation
evt.nativeEvent.stopImmediatePropagation();
}
if (next.action === 'move') {
setActiveNodeId(next.nextNodeId);
}
else if (next.action === 'open' || next.action === 'close') {
toggleTreeNode(next.nodeId);
}
}
}
break;
}
case 'Space': // Space
if (selectionMode !== 'none' &&
activeNode &&
(selectableNodes === 'any' || ((_a = tree.get(activeNode)) === null || _a === void 0 ? void 0 : _a.isLeaf))) {
evt.preventDefault();
itemSelection.toggle(activeNode);
}
break;
default:
evt.continuePropagation();
break;
}
},
}).keyboardProps;
var ariaProps = {
role: 'tree',
};
if (selectionMode !== 'none') {
ariaProps['aria-multiselectable'] = selectionMode === 'multiple' ? 'true' : 'false';
}
return (React.createElement(TreeContext.Provider, { value: context },
React.createElement("div", __assign({ className: classnames(className, STYLE.wrapper), ref: treeRef, style: style, id: id }, ariaProps, keyboardProps, rest, focusWithinProps), children)));
});
Tree.displayName = 'Tree';
export default Tree;
//# sourceMappingURL=Tree.js.map