UNPKG

@material-ui/lab

Version:

Material-UI Lab - Incubator for Material-UI React components.

765 lines (626 loc) 21.9 kB
"use strict"; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.styles = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties")); var React = _interopRequireWildcard(require("react")); var _clsx = _interopRequireDefault(require("clsx")); var _propTypes = _interopRequireDefault(require("prop-types")); var _styles = require("@material-ui/core/styles"); var _utils = require("@material-ui/core/utils"); var _TreeViewContext = _interopRequireDefault(require("./TreeViewContext")); var styles = { /* Styles applied to the root element. */ root: { padding: 0, margin: 0, listStyle: 'none' } }; exports.styles = styles; function arrayDiff(arr1, arr2) { if (arr1.length !== arr2.length) return true; for (var i = 0; i < arr1.length; i += 1) { if (arr1[i] !== arr2[i]) return true; } return false; } var findNextFirstChar = function findNextFirstChar(firstChars, startIndex, char) { for (var i = startIndex; i < firstChars.length; i += 1) { if (char === firstChars[i]) { return i; } } return -1; }; var defaultExpandedDefault = []; var defaultSelectedDefault = []; var TreeView = /*#__PURE__*/React.forwardRef(function TreeView(props, ref) { var children = props.children, classes = props.classes, className = props.className, defaultCollapseIcon = props.defaultCollapseIcon, defaultEndIcon = props.defaultEndIcon, _props$defaultExpande = props.defaultExpanded, defaultExpanded = _props$defaultExpande === void 0 ? defaultExpandedDefault : _props$defaultExpande, defaultExpandIcon = props.defaultExpandIcon, defaultParentIcon = props.defaultParentIcon, _props$defaultSelecte = props.defaultSelected, defaultSelected = _props$defaultSelecte === void 0 ? defaultSelectedDefault : _props$defaultSelecte, _props$disableSelecti = props.disableSelection, disableSelection = _props$disableSelecti === void 0 ? false : _props$disableSelecti, _props$multiSelect = props.multiSelect, multiSelect = _props$multiSelect === void 0 ? false : _props$multiSelect, expandedProp = props.expanded, onNodeSelect = props.onNodeSelect, onNodeToggle = props.onNodeToggle, selectedProp = props.selected, other = (0, _objectWithoutProperties2.default)(props, ["children", "classes", "className", "defaultCollapseIcon", "defaultEndIcon", "defaultExpanded", "defaultExpandIcon", "defaultParentIcon", "defaultSelected", "disableSelection", "multiSelect", "expanded", "onNodeSelect", "onNodeToggle", "selected"]); var _React$useState = React.useState(null), tabbable = _React$useState[0], setTabbable = _React$useState[1]; var _React$useState2 = React.useState(null), focusedNodeId = _React$useState2[0], setFocusedNodeId = _React$useState2[1]; var nodeMap = React.useRef({}); var firstCharMap = React.useRef({}); var visibleNodes = React.useRef([]); var _useControlled = (0, _utils.useControlled)({ controlled: expandedProp, default: defaultExpanded, name: 'TreeView', state: 'expanded' }), _useControlled2 = (0, _slicedToArray2.default)(_useControlled, 2), expanded = _useControlled2[0], setExpandedState = _useControlled2[1]; var _useControlled3 = (0, _utils.useControlled)({ controlled: selectedProp, default: defaultSelected, name: 'TreeView', state: 'selected' }), _useControlled4 = (0, _slicedToArray2.default)(_useControlled3, 2), selected = _useControlled4[0], setSelectedState = _useControlled4[1]; /* * Status Helpers */ var isExpanded = React.useCallback(function (id) { return Array.isArray(expanded) ? expanded.indexOf(id) !== -1 : false; }, [expanded]); var isSelected = React.useCallback(function (id) { return Array.isArray(selected) ? selected.indexOf(id) !== -1 : selected === id; }, [selected]); var isTabbable = function isTabbable(id) { return tabbable === id; }; var isFocused = function isFocused(id) { return focusedNodeId === id; }; /* * Node Helpers */ var getNextNode = function getNextNode(id) { var nodeIndex = visibleNodes.current.indexOf(id); if (nodeIndex !== -1 && nodeIndex + 1 < visibleNodes.current.length) { return visibleNodes.current[nodeIndex + 1]; } return null; }; var getPreviousNode = function getPreviousNode(id) { var nodeIndex = visibleNodes.current.indexOf(id); if (nodeIndex !== -1 && nodeIndex - 1 >= 0) { return visibleNodes.current[nodeIndex - 1]; } return null; }; var getLastNode = function getLastNode() { return visibleNodes.current[visibleNodes.current.length - 1]; }; var getFirstNode = function getFirstNode() { return visibleNodes.current[0]; }; var getParent = function getParent(id) { return nodeMap.current[id].parent; }; var getNodesInRange = function getNodesInRange(a, b) { var aIndex = visibleNodes.current.indexOf(a); var bIndex = visibleNodes.current.indexOf(b); var start = Math.min(aIndex, bIndex); var end = Math.max(aIndex, bIndex); return visibleNodes.current.slice(start, end + 1); }; /* * Focus Helpers */ var focus = function focus(id) { if (id) { setTabbable(id); setFocusedNodeId(id); } }; var focusNextNode = function focusNextNode(id) { return focus(getNextNode(id)); }; var focusPreviousNode = function focusPreviousNode(id) { return focus(getPreviousNode(id)); }; var focusFirstNode = function focusFirstNode() { return focus(getFirstNode()); }; var focusLastNode = function focusLastNode() { return focus(getLastNode()); }; var focusByFirstCharacter = function focusByFirstCharacter(id, char) { var start; var index; var lowercaseChar = char.toLowerCase(); var firstCharIds = []; var firstChars = []; // This really only works since the ids are strings Object.keys(firstCharMap.current).forEach(function (nodeId) { var firstChar = firstCharMap.current[nodeId]; var map = nodeMap.current[nodeId]; var visible = map.parent ? isExpanded(map.parent) : true; if (visible) { firstCharIds.push(nodeId); firstChars.push(firstChar); } }); // Get start index for search based on position of currentItem start = firstCharIds.indexOf(id) + 1; if (start === nodeMap.current.length) { start = 0; } // Check remaining slots in the menu index = findNextFirstChar(firstChars, start, lowercaseChar); // If not found in remaining slots, check from beginning if (index === -1) { index = findNextFirstChar(firstChars, 0, lowercaseChar); } // If match was found... if (index > -1) { focus(firstCharIds[index]); } }; /* * Expansion Helpers */ var toggleExpansion = function toggleExpansion(event) { var value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : focusedNodeId; var newExpanded; if (expanded.indexOf(value) !== -1) { newExpanded = expanded.filter(function (id) { return id !== value; }); setTabbable(function (oldTabbable) { var map = nodeMap.current[oldTabbable]; if (oldTabbable && (map && map.parent ? map.parent.id : null) === value) { return value; } return oldTabbable; }); } else { newExpanded = [value].concat(expanded); } if (onNodeToggle) { onNodeToggle(event, newExpanded); } setExpandedState(newExpanded); }; var expandAllSiblings = function expandAllSiblings(event, id) { var map = nodeMap.current[id]; var parent = nodeMap.current[map.parent]; var diff; if (parent) { diff = parent.children.filter(function (child) { return !isExpanded(child); }); } else { var topLevelNodes = nodeMap.current[-1].children; diff = topLevelNodes.filter(function (node) { return !isExpanded(node); }); } var newExpanded = expanded.concat(diff); if (diff.length > 0) { setExpandedState(newExpanded); if (onNodeToggle) { onNodeToggle(event, newExpanded); } } }; /* * Selection Helpers */ var lastSelectedNode = React.useRef(null); var lastSelectionWasRange = React.useRef(false); var currentRangeSelection = React.useRef([]); var handleRangeArrowSelect = function handleRangeArrowSelect(event, nodes) { var base = selected; var start = nodes.start, next = nodes.next, current = nodes.current; if (!next || !current) { return; } if (currentRangeSelection.current.indexOf(current) === -1) { currentRangeSelection.current = []; } if (lastSelectionWasRange.current) { if (currentRangeSelection.current.indexOf(next) !== -1) { base = base.filter(function (id) { return id === start || id !== current; }); currentRangeSelection.current = currentRangeSelection.current.filter(function (id) { return id === start || id !== current; }); } else { base.push(next); currentRangeSelection.current.push(next); } } else { base.push(next); currentRangeSelection.current.push(current, next); } if (onNodeSelect) { onNodeSelect(event, base); } setSelectedState(base); }; var handleRangeSelect = function handleRangeSelect(event, nodes) { var base = selected; var start = nodes.start, end = nodes.end; // If last selection was a range selection ignore nodes that were selected. if (lastSelectionWasRange.current) { base = selected.filter(function (id) { return currentRangeSelection.current.indexOf(id) === -1; }); } var range = getNodesInRange(start, end); currentRangeSelection.current = range; var newSelected = base.concat(range); newSelected = newSelected.filter(function (id, i) { return newSelected.indexOf(id) === i; }); if (onNodeSelect) { onNodeSelect(event, newSelected); } setSelectedState(newSelected); }; var handleMultipleSelect = function handleMultipleSelect(event, value) { var newSelected = []; if (selected.indexOf(value) !== -1) { newSelected = selected.filter(function (id) { return id !== value; }); } else { newSelected = [value].concat(selected); } if (onNodeSelect) { onNodeSelect(event, newSelected); } setSelectedState(newSelected); }; var handleSingleSelect = function handleSingleSelect(event, value) { var newSelected = multiSelect ? [value] : value; if (onNodeSelect) { onNodeSelect(event, newSelected); } setSelectedState(newSelected); }; var selectNode = function selectNode(event, id) { var multiple = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; if (id) { if (multiple) { handleMultipleSelect(event, id); } else { handleSingleSelect(event, id); } lastSelectedNode.current = id; lastSelectionWasRange.current = false; currentRangeSelection.current = []; return true; } return false; }; var selectRange = function selectRange(event, nodes) { var stacked = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; var _nodes$start = nodes.start, start = _nodes$start === void 0 ? lastSelectedNode.current : _nodes$start, end = nodes.end, current = nodes.current; if (stacked) { handleRangeArrowSelect(event, { start: start, next: end, current: current }); } else { handleRangeSelect(event, { start: start, end: end }); } lastSelectionWasRange.current = true; return true; }; var rangeSelectToFirst = function rangeSelectToFirst(event, id) { if (!lastSelectedNode.current) { lastSelectedNode.current = id; } var start = lastSelectionWasRange.current ? lastSelectedNode.current : id; return selectRange(event, { start: start, end: getFirstNode() }); }; var rangeSelectToLast = function rangeSelectToLast(event, id) { if (!lastSelectedNode.current) { lastSelectedNode.current = id; } var start = lastSelectionWasRange.current ? lastSelectedNode.current : id; return selectRange(event, { start: start, end: getLastNode() }); }; var selectNextNode = function selectNextNode(event, id) { return selectRange(event, { end: getNextNode(id), current: id }, true); }; var selectPreviousNode = function selectPreviousNode(event, id) { return selectRange(event, { end: getPreviousNode(id), current: id }, true); }; var selectAllNodes = function selectAllNodes(event) { return selectRange(event, { start: getFirstNode(), end: getLastNode() }); }; /* * Mapping Helpers */ var addNodeToNodeMap = function addNodeToNodeMap(id, childrenIds) { var currentMap = nodeMap.current[id]; nodeMap.current[id] = (0, _extends2.default)({}, currentMap, { children: childrenIds, id: id }); childrenIds.forEach(function (childId) { var currentChildMap = nodeMap.current[childId]; nodeMap.current[childId] = (0, _extends2.default)({}, currentChildMap, { parent: id, id: childId }); }); }; var getNodesToRemove = React.useCallback(function (id) { var map = nodeMap.current[id]; var nodes = []; if (map) { nodes.push(id); if (map.children) { nodes.concat(map.children); map.children.forEach(function (node) { nodes.concat(getNodesToRemove(node)); }); } } return nodes; }, []); var cleanUpFirstCharMap = React.useCallback(function (nodes) { var newMap = (0, _extends2.default)({}, firstCharMap.current); nodes.forEach(function (node) { if (newMap[node]) { delete newMap[node]; } }); firstCharMap.current = newMap; }, []); var removeNodeFromNodeMap = React.useCallback(function (id) { var nodes = getNodesToRemove(id); cleanUpFirstCharMap(nodes); var newMap = (0, _extends2.default)({}, nodeMap.current); nodes.forEach(function (node) { var map = newMap[node]; if (map) { if (map.parent) { var parentMap = newMap[map.parent]; if (parentMap && parentMap.children) { var parentChildren = parentMap.children.filter(function (c) { return c !== node; }); newMap[map.parent] = (0, _extends2.default)({}, parentMap, { children: parentChildren }); } } delete newMap[node]; } }); nodeMap.current = newMap; setFocusedNodeId(function (oldFocusedNodeId) { if (oldFocusedNodeId === id) { return null; } return oldFocusedNodeId; }); }, [getNodesToRemove, cleanUpFirstCharMap]); var mapFirstChar = function mapFirstChar(id, firstChar) { firstCharMap.current[id] = firstChar; }; var prevChildIds = React.useRef([]); var _React$useState3 = React.useState(false), childrenCalculated = _React$useState3[0], setChildrenCalculated = _React$useState3[1]; React.useEffect(function () { var childIds = []; React.Children.forEach(children, function (child) { if ( /*#__PURE__*/React.isValidElement(child) && child.props.nodeId) { childIds.push(child.props.nodeId); } }); if (arrayDiff(prevChildIds.current, childIds)) { nodeMap.current[-1] = { parent: null, children: childIds }; childIds.forEach(function (id, index) { if (index === 0) { setTabbable(id); } }); visibleNodes.current = nodeMap.current[-1].children; prevChildIds.current = childIds; setChildrenCalculated(true); } }, [children]); React.useEffect(function () { var buildVisible = function buildVisible(nodes) { var list = []; for (var i = 0; i < nodes.length; i += 1) { var item = nodes[i]; list.push(item); var childs = nodeMap.current[item].children; if (isExpanded(item) && childs) { list = list.concat(buildVisible(childs)); } } return list; }; if (childrenCalculated) { visibleNodes.current = buildVisible(nodeMap.current[-1].children); } }, [expanded, childrenCalculated, isExpanded, children]); var noopSelection = function noopSelection() { return false; }; return /*#__PURE__*/React.createElement(_TreeViewContext.default.Provider, { value: { icons: { defaultCollapseIcon: defaultCollapseIcon, defaultExpandIcon: defaultExpandIcon, defaultParentIcon: defaultParentIcon, defaultEndIcon: defaultEndIcon }, focus: focus, focusFirstNode: focusFirstNode, focusLastNode: focusLastNode, focusNextNode: focusNextNode, focusPreviousNode: focusPreviousNode, focusByFirstCharacter: focusByFirstCharacter, expandAllSiblings: expandAllSiblings, toggleExpansion: toggleExpansion, isExpanded: isExpanded, isFocused: isFocused, isSelected: isSelected, selectNode: disableSelection ? noopSelection : selectNode, selectRange: disableSelection ? noopSelection : selectRange, selectNextNode: disableSelection ? noopSelection : selectNextNode, selectPreviousNode: disableSelection ? noopSelection : selectPreviousNode, rangeSelectToFirst: disableSelection ? noopSelection : rangeSelectToFirst, rangeSelectToLast: disableSelection ? noopSelection : rangeSelectToLast, selectAllNodes: disableSelection ? noopSelection : selectAllNodes, isTabbable: isTabbable, multiSelect: multiSelect, getParent: getParent, mapFirstChar: mapFirstChar, addNodeToNodeMap: addNodeToNodeMap, removeNodeFromNodeMap: removeNodeFromNodeMap } }, /*#__PURE__*/React.createElement("ul", (0, _extends2.default)({ role: "tree", "aria-multiselectable": multiSelect, className: (0, _clsx.default)(classes.root, className), ref: ref }, other), children)); }); process.env.NODE_ENV !== "production" ? TreeView.propTypes = { // ----------------------------- Warning -------------------------------- // | These PropTypes are generated from the TypeScript type definitions | // | To update them edit the d.ts file and run "yarn proptypes" | // ---------------------------------------------------------------------- /** * The content of the component. */ children: _propTypes.default.node, /** * Override or extend the styles applied to the component. * See [CSS API](#css) below for more details. */ classes: _propTypes.default.object, /** * @ignore */ className: _propTypes.default.string, /** * The default icon used to collapse the node. */ defaultCollapseIcon: _propTypes.default.node, /** * The default icon displayed next to a end node. This is applied to all * tree nodes and can be overridden by the TreeItem `icon` prop. */ defaultEndIcon: _propTypes.default.node, /** * Expanded node ids. (Uncontrolled) */ defaultExpanded: _propTypes.default.arrayOf(_propTypes.default.string), /** * The default icon used to expand the node. */ defaultExpandIcon: _propTypes.default.node, /** * The default icon displayed next to a parent node. This is applied to all * parent nodes and can be overridden by the TreeItem `icon` prop. */ defaultParentIcon: _propTypes.default.node, /** * Selected node ids. (Uncontrolled) * When `multiSelect` is true this takes an array of strings; when false (default) a string. */ defaultSelected: _propTypes.default.oneOfType([_propTypes.default.arrayOf(_propTypes.default.string), _propTypes.default.string]), /** * If `true` selection is disabled. */ disableSelection: _propTypes.default.bool, /** * Expanded node ids. (Controlled) */ expanded: _propTypes.default.arrayOf(_propTypes.default.string), /** * If true `ctrl` and `shift` will trigger multiselect. */ multiSelect: _propTypes.default.bool, /** * Callback fired when tree items are selected/unselected. * * @param {object} event The event source of the callback * @param {(array|string)} value of the selected nodes. When `multiSelect` is true * this is an array of strings; when false (default) a string. */ onNodeSelect: _propTypes.default.func, /** * Callback fired when tree items are expanded/collapsed. * * @param {object} event The event source of the callback. * @param {array} nodeIds The ids of the expanded nodes. */ onNodeToggle: _propTypes.default.func, /** * Selected node ids. (Controlled) * When `multiSelect` is true this takes an array of strings; when false (default) a string. */ selected: _propTypes.default.oneOfType([_propTypes.default.arrayOf(_propTypes.default.string), _propTypes.default.string]) } : void 0; var _default = (0, _styles.withStyles)(styles, { name: 'MuiTreeView' })(TreeView); exports.default = _default;