UNPKG

@momentum-ui/react-collaboration

Version:

Cisco Momentum UI Framework for React Collaboration Applications

280 lines 15 kB
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