UNPKG

21.9 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
4
5var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
6
7Object.defineProperty(exports, "__esModule", {
8 value: true
9});
10exports.default = exports.styles = void 0;
11
12var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
13
14var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
15
16var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
17
18var React = _interopRequireWildcard(require("react"));
19
20var _clsx = _interopRequireDefault(require("clsx"));
21
22var _propTypes = _interopRequireDefault(require("prop-types"));
23
24var _styles = require("@material-ui/core/styles");
25
26var _utils = require("@material-ui/core/utils");
27
28var _TreeViewContext = _interopRequireDefault(require("./TreeViewContext"));
29
30var styles = {
31 /* Styles applied to the root element. */
32 root: {
33 padding: 0,
34 margin: 0,
35 listStyle: 'none'
36 }
37};
38exports.styles = styles;
39
40function arrayDiff(arr1, arr2) {
41 if (arr1.length !== arr2.length) return true;
42
43 for (var i = 0; i < arr1.length; i += 1) {
44 if (arr1[i] !== arr2[i]) return true;
45 }
46
47 return false;
48}
49
50var findNextFirstChar = function findNextFirstChar(firstChars, startIndex, char) {
51 for (var i = startIndex; i < firstChars.length; i += 1) {
52 if (char === firstChars[i]) {
53 return i;
54 }
55 }
56
57 return -1;
58};
59
60var defaultExpandedDefault = [];
61var defaultSelectedDefault = [];
62var TreeView = /*#__PURE__*/React.forwardRef(function TreeView(props, ref) {
63 var children = props.children,
64 classes = props.classes,
65 className = props.className,
66 defaultCollapseIcon = props.defaultCollapseIcon,
67 defaultEndIcon = props.defaultEndIcon,
68 _props$defaultExpande = props.defaultExpanded,
69 defaultExpanded = _props$defaultExpande === void 0 ? defaultExpandedDefault : _props$defaultExpande,
70 defaultExpandIcon = props.defaultExpandIcon,
71 defaultParentIcon = props.defaultParentIcon,
72 _props$defaultSelecte = props.defaultSelected,
73 defaultSelected = _props$defaultSelecte === void 0 ? defaultSelectedDefault : _props$defaultSelecte,
74 _props$disableSelecti = props.disableSelection,
75 disableSelection = _props$disableSelecti === void 0 ? false : _props$disableSelecti,
76 _props$multiSelect = props.multiSelect,
77 multiSelect = _props$multiSelect === void 0 ? false : _props$multiSelect,
78 expandedProp = props.expanded,
79 onNodeSelect = props.onNodeSelect,
80 onNodeToggle = props.onNodeToggle,
81 selectedProp = props.selected,
82 other = (0, _objectWithoutProperties2.default)(props, ["children", "classes", "className", "defaultCollapseIcon", "defaultEndIcon", "defaultExpanded", "defaultExpandIcon", "defaultParentIcon", "defaultSelected", "disableSelection", "multiSelect", "expanded", "onNodeSelect", "onNodeToggle", "selected"]);
83
84 var _React$useState = React.useState(null),
85 tabbable = _React$useState[0],
86 setTabbable = _React$useState[1];
87
88 var _React$useState2 = React.useState(null),
89 focusedNodeId = _React$useState2[0],
90 setFocusedNodeId = _React$useState2[1];
91
92 var nodeMap = React.useRef({});
93 var firstCharMap = React.useRef({});
94 var visibleNodes = React.useRef([]);
95
96 var _useControlled = (0, _utils.useControlled)({
97 controlled: expandedProp,
98 default: defaultExpanded,
99 name: 'TreeView',
100 state: 'expanded'
101 }),
102 _useControlled2 = (0, _slicedToArray2.default)(_useControlled, 2),
103 expanded = _useControlled2[0],
104 setExpandedState = _useControlled2[1];
105
106 var _useControlled3 = (0, _utils.useControlled)({
107 controlled: selectedProp,
108 default: defaultSelected,
109 name: 'TreeView',
110 state: 'selected'
111 }),
112 _useControlled4 = (0, _slicedToArray2.default)(_useControlled3, 2),
113 selected = _useControlled4[0],
114 setSelectedState = _useControlled4[1];
115 /*
116 * Status Helpers
117 */
118
119
120 var isExpanded = React.useCallback(function (id) {
121 return Array.isArray(expanded) ? expanded.indexOf(id) !== -1 : false;
122 }, [expanded]);
123 var isSelected = React.useCallback(function (id) {
124 return Array.isArray(selected) ? selected.indexOf(id) !== -1 : selected === id;
125 }, [selected]);
126
127 var isTabbable = function isTabbable(id) {
128 return tabbable === id;
129 };
130
131 var isFocused = function isFocused(id) {
132 return focusedNodeId === id;
133 };
134 /*
135 * Node Helpers
136 */
137
138
139 var getNextNode = function getNextNode(id) {
140 var nodeIndex = visibleNodes.current.indexOf(id);
141
142 if (nodeIndex !== -1 && nodeIndex + 1 < visibleNodes.current.length) {
143 return visibleNodes.current[nodeIndex + 1];
144 }
145
146 return null;
147 };
148
149 var getPreviousNode = function getPreviousNode(id) {
150 var nodeIndex = visibleNodes.current.indexOf(id);
151
152 if (nodeIndex !== -1 && nodeIndex - 1 >= 0) {
153 return visibleNodes.current[nodeIndex - 1];
154 }
155
156 return null;
157 };
158
159 var getLastNode = function getLastNode() {
160 return visibleNodes.current[visibleNodes.current.length - 1];
161 };
162
163 var getFirstNode = function getFirstNode() {
164 return visibleNodes.current[0];
165 };
166
167 var getParent = function getParent(id) {
168 return nodeMap.current[id].parent;
169 };
170
171 var getNodesInRange = function getNodesInRange(a, b) {
172 var aIndex = visibleNodes.current.indexOf(a);
173 var bIndex = visibleNodes.current.indexOf(b);
174 var start = Math.min(aIndex, bIndex);
175 var end = Math.max(aIndex, bIndex);
176 return visibleNodes.current.slice(start, end + 1);
177 };
178 /*
179 * Focus Helpers
180 */
181
182
183 var focus = function focus(id) {
184 if (id) {
185 setTabbable(id);
186 setFocusedNodeId(id);
187 }
188 };
189
190 var focusNextNode = function focusNextNode(id) {
191 return focus(getNextNode(id));
192 };
193
194 var focusPreviousNode = function focusPreviousNode(id) {
195 return focus(getPreviousNode(id));
196 };
197
198 var focusFirstNode = function focusFirstNode() {
199 return focus(getFirstNode());
200 };
201
202 var focusLastNode = function focusLastNode() {
203 return focus(getLastNode());
204 };
205
206 var focusByFirstCharacter = function focusByFirstCharacter(id, char) {
207 var start;
208 var index;
209 var lowercaseChar = char.toLowerCase();
210 var firstCharIds = [];
211 var firstChars = []; // This really only works since the ids are strings
212
213 Object.keys(firstCharMap.current).forEach(function (nodeId) {
214 var firstChar = firstCharMap.current[nodeId];
215 var map = nodeMap.current[nodeId];
216 var visible = map.parent ? isExpanded(map.parent) : true;
217
218 if (visible) {
219 firstCharIds.push(nodeId);
220 firstChars.push(firstChar);
221 }
222 }); // Get start index for search based on position of currentItem
223
224 start = firstCharIds.indexOf(id) + 1;
225
226 if (start === nodeMap.current.length) {
227 start = 0;
228 } // Check remaining slots in the menu
229
230
231 index = findNextFirstChar(firstChars, start, lowercaseChar); // If not found in remaining slots, check from beginning
232
233 if (index === -1) {
234 index = findNextFirstChar(firstChars, 0, lowercaseChar);
235 } // If match was found...
236
237
238 if (index > -1) {
239 focus(firstCharIds[index]);
240 }
241 };
242 /*
243 * Expansion Helpers
244 */
245
246
247 var toggleExpansion = function toggleExpansion(event) {
248 var value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : focusedNodeId;
249 var newExpanded;
250
251 if (expanded.indexOf(value) !== -1) {
252 newExpanded = expanded.filter(function (id) {
253 return id !== value;
254 });
255 setTabbable(function (oldTabbable) {
256 var map = nodeMap.current[oldTabbable];
257
258 if (oldTabbable && (map && map.parent ? map.parent.id : null) === value) {
259 return value;
260 }
261
262 return oldTabbable;
263 });
264 } else {
265 newExpanded = [value].concat(expanded);
266 }
267
268 if (onNodeToggle) {
269 onNodeToggle(event, newExpanded);
270 }
271
272 setExpandedState(newExpanded);
273 };
274
275 var expandAllSiblings = function expandAllSiblings(event, id) {
276 var map = nodeMap.current[id];
277 var parent = nodeMap.current[map.parent];
278 var diff;
279
280 if (parent) {
281 diff = parent.children.filter(function (child) {
282 return !isExpanded(child);
283 });
284 } else {
285 var topLevelNodes = nodeMap.current[-1].children;
286 diff = topLevelNodes.filter(function (node) {
287 return !isExpanded(node);
288 });
289 }
290
291 var newExpanded = expanded.concat(diff);
292
293 if (diff.length > 0) {
294 setExpandedState(newExpanded);
295
296 if (onNodeToggle) {
297 onNodeToggle(event, newExpanded);
298 }
299 }
300 };
301 /*
302 * Selection Helpers
303 */
304
305
306 var lastSelectedNode = React.useRef(null);
307 var lastSelectionWasRange = React.useRef(false);
308 var currentRangeSelection = React.useRef([]);
309
310 var handleRangeArrowSelect = function handleRangeArrowSelect(event, nodes) {
311 var base = selected;
312 var start = nodes.start,
313 next = nodes.next,
314 current = nodes.current;
315
316 if (!next || !current) {
317 return;
318 }
319
320 if (currentRangeSelection.current.indexOf(current) === -1) {
321 currentRangeSelection.current = [];
322 }
323
324 if (lastSelectionWasRange.current) {
325 if (currentRangeSelection.current.indexOf(next) !== -1) {
326 base = base.filter(function (id) {
327 return id === start || id !== current;
328 });
329 currentRangeSelection.current = currentRangeSelection.current.filter(function (id) {
330 return id === start || id !== current;
331 });
332 } else {
333 base.push(next);
334 currentRangeSelection.current.push(next);
335 }
336 } else {
337 base.push(next);
338 currentRangeSelection.current.push(current, next);
339 }
340
341 if (onNodeSelect) {
342 onNodeSelect(event, base);
343 }
344
345 setSelectedState(base);
346 };
347
348 var handleRangeSelect = function handleRangeSelect(event, nodes) {
349 var base = selected;
350 var start = nodes.start,
351 end = nodes.end; // If last selection was a range selection ignore nodes that were selected.
352
353 if (lastSelectionWasRange.current) {
354 base = selected.filter(function (id) {
355 return currentRangeSelection.current.indexOf(id) === -1;
356 });
357 }
358
359 var range = getNodesInRange(start, end);
360 currentRangeSelection.current = range;
361 var newSelected = base.concat(range);
362 newSelected = newSelected.filter(function (id, i) {
363 return newSelected.indexOf(id) === i;
364 });
365
366 if (onNodeSelect) {
367 onNodeSelect(event, newSelected);
368 }
369
370 setSelectedState(newSelected);
371 };
372
373 var handleMultipleSelect = function handleMultipleSelect(event, value) {
374 var newSelected = [];
375
376 if (selected.indexOf(value) !== -1) {
377 newSelected = selected.filter(function (id) {
378 return id !== value;
379 });
380 } else {
381 newSelected = [value].concat(selected);
382 }
383
384 if (onNodeSelect) {
385 onNodeSelect(event, newSelected);
386 }
387
388 setSelectedState(newSelected);
389 };
390
391 var handleSingleSelect = function handleSingleSelect(event, value) {
392 var newSelected = multiSelect ? [value] : value;
393
394 if (onNodeSelect) {
395 onNodeSelect(event, newSelected);
396 }
397
398 setSelectedState(newSelected);
399 };
400
401 var selectNode = function selectNode(event, id) {
402 var multiple = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
403
404 if (id) {
405 if (multiple) {
406 handleMultipleSelect(event, id);
407 } else {
408 handleSingleSelect(event, id);
409 }
410
411 lastSelectedNode.current = id;
412 lastSelectionWasRange.current = false;
413 currentRangeSelection.current = [];
414 return true;
415 }
416
417 return false;
418 };
419
420 var selectRange = function selectRange(event, nodes) {
421 var stacked = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
422 var _nodes$start = nodes.start,
423 start = _nodes$start === void 0 ? lastSelectedNode.current : _nodes$start,
424 end = nodes.end,
425 current = nodes.current;
426
427 if (stacked) {
428 handleRangeArrowSelect(event, {
429 start: start,
430 next: end,
431 current: current
432 });
433 } else {
434 handleRangeSelect(event, {
435 start: start,
436 end: end
437 });
438 }
439
440 lastSelectionWasRange.current = true;
441 return true;
442 };
443
444 var rangeSelectToFirst = function rangeSelectToFirst(event, id) {
445 if (!lastSelectedNode.current) {
446 lastSelectedNode.current = id;
447 }
448
449 var start = lastSelectionWasRange.current ? lastSelectedNode.current : id;
450 return selectRange(event, {
451 start: start,
452 end: getFirstNode()
453 });
454 };
455
456 var rangeSelectToLast = function rangeSelectToLast(event, id) {
457 if (!lastSelectedNode.current) {
458 lastSelectedNode.current = id;
459 }
460
461 var start = lastSelectionWasRange.current ? lastSelectedNode.current : id;
462 return selectRange(event, {
463 start: start,
464 end: getLastNode()
465 });
466 };
467
468 var selectNextNode = function selectNextNode(event, id) {
469 return selectRange(event, {
470 end: getNextNode(id),
471 current: id
472 }, true);
473 };
474
475 var selectPreviousNode = function selectPreviousNode(event, id) {
476 return selectRange(event, {
477 end: getPreviousNode(id),
478 current: id
479 }, true);
480 };
481
482 var selectAllNodes = function selectAllNodes(event) {
483 return selectRange(event, {
484 start: getFirstNode(),
485 end: getLastNode()
486 });
487 };
488 /*
489 * Mapping Helpers
490 */
491
492
493 var addNodeToNodeMap = function addNodeToNodeMap(id, childrenIds) {
494 var currentMap = nodeMap.current[id];
495 nodeMap.current[id] = (0, _extends2.default)({}, currentMap, {
496 children: childrenIds,
497 id: id
498 });
499 childrenIds.forEach(function (childId) {
500 var currentChildMap = nodeMap.current[childId];
501 nodeMap.current[childId] = (0, _extends2.default)({}, currentChildMap, {
502 parent: id,
503 id: childId
504 });
505 });
506 };
507
508 var getNodesToRemove = React.useCallback(function (id) {
509 var map = nodeMap.current[id];
510 var nodes = [];
511
512 if (map) {
513 nodes.push(id);
514
515 if (map.children) {
516 nodes.concat(map.children);
517 map.children.forEach(function (node) {
518 nodes.concat(getNodesToRemove(node));
519 });
520 }
521 }
522
523 return nodes;
524 }, []);
525 var cleanUpFirstCharMap = React.useCallback(function (nodes) {
526 var newMap = (0, _extends2.default)({}, firstCharMap.current);
527 nodes.forEach(function (node) {
528 if (newMap[node]) {
529 delete newMap[node];
530 }
531 });
532 firstCharMap.current = newMap;
533 }, []);
534 var removeNodeFromNodeMap = React.useCallback(function (id) {
535 var nodes = getNodesToRemove(id);
536 cleanUpFirstCharMap(nodes);
537 var newMap = (0, _extends2.default)({}, nodeMap.current);
538 nodes.forEach(function (node) {
539 var map = newMap[node];
540
541 if (map) {
542 if (map.parent) {
543 var parentMap = newMap[map.parent];
544
545 if (parentMap && parentMap.children) {
546 var parentChildren = parentMap.children.filter(function (c) {
547 return c !== node;
548 });
549 newMap[map.parent] = (0, _extends2.default)({}, parentMap, {
550 children: parentChildren
551 });
552 }
553 }
554
555 delete newMap[node];
556 }
557 });
558 nodeMap.current = newMap;
559 setFocusedNodeId(function (oldFocusedNodeId) {
560 if (oldFocusedNodeId === id) {
561 return null;
562 }
563
564 return oldFocusedNodeId;
565 });
566 }, [getNodesToRemove, cleanUpFirstCharMap]);
567
568 var mapFirstChar = function mapFirstChar(id, firstChar) {
569 firstCharMap.current[id] = firstChar;
570 };
571
572 var prevChildIds = React.useRef([]);
573
574 var _React$useState3 = React.useState(false),
575 childrenCalculated = _React$useState3[0],
576 setChildrenCalculated = _React$useState3[1];
577
578 React.useEffect(function () {
579 var childIds = [];
580 React.Children.forEach(children, function (child) {
581 if ( /*#__PURE__*/React.isValidElement(child) && child.props.nodeId) {
582 childIds.push(child.props.nodeId);
583 }
584 });
585
586 if (arrayDiff(prevChildIds.current, childIds)) {
587 nodeMap.current[-1] = {
588 parent: null,
589 children: childIds
590 };
591 childIds.forEach(function (id, index) {
592 if (index === 0) {
593 setTabbable(id);
594 }
595 });
596 visibleNodes.current = nodeMap.current[-1].children;
597 prevChildIds.current = childIds;
598 setChildrenCalculated(true);
599 }
600 }, [children]);
601 React.useEffect(function () {
602 var buildVisible = function buildVisible(nodes) {
603 var list = [];
604
605 for (var i = 0; i < nodes.length; i += 1) {
606 var item = nodes[i];
607 list.push(item);
608 var childs = nodeMap.current[item].children;
609
610 if (isExpanded(item) && childs) {
611 list = list.concat(buildVisible(childs));
612 }
613 }
614
615 return list;
616 };
617
618 if (childrenCalculated) {
619 visibleNodes.current = buildVisible(nodeMap.current[-1].children);
620 }
621 }, [expanded, childrenCalculated, isExpanded, children]);
622
623 var noopSelection = function noopSelection() {
624 return false;
625 };
626
627 return /*#__PURE__*/React.createElement(_TreeViewContext.default.Provider, {
628 value: {
629 icons: {
630 defaultCollapseIcon: defaultCollapseIcon,
631 defaultExpandIcon: defaultExpandIcon,
632 defaultParentIcon: defaultParentIcon,
633 defaultEndIcon: defaultEndIcon
634 },
635 focus: focus,
636 focusFirstNode: focusFirstNode,
637 focusLastNode: focusLastNode,
638 focusNextNode: focusNextNode,
639 focusPreviousNode: focusPreviousNode,
640 focusByFirstCharacter: focusByFirstCharacter,
641 expandAllSiblings: expandAllSiblings,
642 toggleExpansion: toggleExpansion,
643 isExpanded: isExpanded,
644 isFocused: isFocused,
645 isSelected: isSelected,
646 selectNode: disableSelection ? noopSelection : selectNode,
647 selectRange: disableSelection ? noopSelection : selectRange,
648 selectNextNode: disableSelection ? noopSelection : selectNextNode,
649 selectPreviousNode: disableSelection ? noopSelection : selectPreviousNode,
650 rangeSelectToFirst: disableSelection ? noopSelection : rangeSelectToFirst,
651 rangeSelectToLast: disableSelection ? noopSelection : rangeSelectToLast,
652 selectAllNodes: disableSelection ? noopSelection : selectAllNodes,
653 isTabbable: isTabbable,
654 multiSelect: multiSelect,
655 getParent: getParent,
656 mapFirstChar: mapFirstChar,
657 addNodeToNodeMap: addNodeToNodeMap,
658 removeNodeFromNodeMap: removeNodeFromNodeMap
659 }
660 }, /*#__PURE__*/React.createElement("ul", (0, _extends2.default)({
661 role: "tree",
662 "aria-multiselectable": multiSelect,
663 className: (0, _clsx.default)(classes.root, className),
664 ref: ref
665 }, other), children));
666});
667process.env.NODE_ENV !== "production" ? TreeView.propTypes = {
668 // ----------------------------- Warning --------------------------------
669 // | These PropTypes are generated from the TypeScript type definitions |
670 // | To update them edit the d.ts file and run "yarn proptypes" |
671 // ----------------------------------------------------------------------
672
673 /**
674 * The content of the component.
675 */
676 children: _propTypes.default.node,
677
678 /**
679 * Override or extend the styles applied to the component.
680 * See [CSS API](#css) below for more details.
681 */
682 classes: _propTypes.default.object,
683
684 /**
685 * @ignore
686 */
687 className: _propTypes.default.string,
688
689 /**
690 * The default icon used to collapse the node.
691 */
692 defaultCollapseIcon: _propTypes.default.node,
693
694 /**
695 * The default icon displayed next to a end node. This is applied to all
696 * tree nodes and can be overridden by the TreeItem `icon` prop.
697 */
698 defaultEndIcon: _propTypes.default.node,
699
700 /**
701 * Expanded node ids. (Uncontrolled)
702 */
703 defaultExpanded: _propTypes.default.arrayOf(_propTypes.default.string),
704
705 /**
706 * The default icon used to expand the node.
707 */
708 defaultExpandIcon: _propTypes.default.node,
709
710 /**
711 * The default icon displayed next to a parent node. This is applied to all
712 * parent nodes and can be overridden by the TreeItem `icon` prop.
713 */
714 defaultParentIcon: _propTypes.default.node,
715
716 /**
717 * Selected node ids. (Uncontrolled)
718 * When `multiSelect` is true this takes an array of strings; when false (default) a string.
719 */
720 defaultSelected: _propTypes.default.oneOfType([_propTypes.default.arrayOf(_propTypes.default.string), _propTypes.default.string]),
721
722 /**
723 * If `true` selection is disabled.
724 */
725 disableSelection: _propTypes.default.bool,
726
727 /**
728 * Expanded node ids. (Controlled)
729 */
730 expanded: _propTypes.default.arrayOf(_propTypes.default.string),
731
732 /**
733 * If true `ctrl` and `shift` will trigger multiselect.
734 */
735 multiSelect: _propTypes.default.bool,
736
737 /**
738 * Callback fired when tree items are selected/unselected.
739 *
740 * @param {object} event The event source of the callback
741 * @param {(array|string)} value of the selected nodes. When `multiSelect` is true
742 * this is an array of strings; when false (default) a string.
743 */
744 onNodeSelect: _propTypes.default.func,
745
746 /**
747 * Callback fired when tree items are expanded/collapsed.
748 *
749 * @param {object} event The event source of the callback.
750 * @param {array} nodeIds The ids of the expanded nodes.
751 */
752 onNodeToggle: _propTypes.default.func,
753
754 /**
755 * Selected node ids. (Controlled)
756 * When `multiSelect` is true this takes an array of strings; when false (default) a string.
757 */
758 selected: _propTypes.default.oneOfType([_propTypes.default.arrayOf(_propTypes.default.string), _propTypes.default.string])
759} : void 0;
760
761var _default = (0, _styles.withStyles)(styles, {
762 name: 'MuiTreeView'
763})(TreeView);
764
765exports.default = _default;
\No newline at end of file