@material-ui/lab
Version:
Material-UI Lab - Incubator for Material-UI React components.
765 lines (626 loc) • 21.9 kB
JavaScript
"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;