1 | import _extends from "@babel/runtime/helpers/esm/extends";
|
2 | import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
|
3 |
|
4 |
|
5 | import * as React from 'react';
|
6 | import clsx from 'clsx';
|
7 | import PropTypes from 'prop-types';
|
8 | import Typography from '@material-ui/core/Typography';
|
9 | import Collapse from '@material-ui/core/Collapse';
|
10 | import { alpha, withStyles, useTheme } from '@material-ui/core/styles';
|
11 | import { useForkRef } from '@material-ui/core/utils';
|
12 | import TreeViewContext from '../TreeView/TreeViewContext';
|
13 | export const styles = theme => ({
|
14 |
|
15 | root: {
|
16 | listStyle: 'none',
|
17 | margin: 0,
|
18 | padding: 0,
|
19 | outline: 0,
|
20 | WebkitTapHighlightColor: 'transparent',
|
21 | '&:focus > $content $label': {
|
22 | backgroundColor: theme.palette.action.hover
|
23 | },
|
24 | '&$selected > $content $label': {
|
25 | backgroundColor: alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity)
|
26 | },
|
27 | '&$selected > $content $label:hover, &$selected:focus > $content $label': {
|
28 | backgroundColor: alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity + theme.palette.action.hoverOpacity),
|
29 |
|
30 | '@media (hover: none)': {
|
31 | backgroundColor: 'transparent'
|
32 | }
|
33 | }
|
34 | },
|
35 |
|
36 |
|
37 | expanded: {},
|
38 |
|
39 |
|
40 | selected: {},
|
41 |
|
42 |
|
43 | group: {
|
44 | margin: 0,
|
45 | padding: 0,
|
46 | marginLeft: 17
|
47 | },
|
48 |
|
49 |
|
50 | content: {
|
51 | width: '100%',
|
52 | display: 'flex',
|
53 | alignItems: 'center',
|
54 | cursor: 'pointer'
|
55 | },
|
56 |
|
57 |
|
58 | iconContainer: {
|
59 | marginRight: 4,
|
60 | width: 15,
|
61 | display: 'flex',
|
62 | flexShrink: 0,
|
63 | justifyContent: 'center',
|
64 | '& svg': {
|
65 | fontSize: 18
|
66 | }
|
67 | },
|
68 |
|
69 |
|
70 | label: {
|
71 | width: '100%',
|
72 | paddingLeft: 4,
|
73 | position: 'relative',
|
74 | '&:hover': {
|
75 | backgroundColor: theme.palette.action.hover,
|
76 |
|
77 | '@media (hover: none)': {
|
78 | backgroundColor: 'transparent'
|
79 | }
|
80 | }
|
81 | }
|
82 | });
|
83 |
|
84 | const isPrintableCharacter = str => {
|
85 | return str && str.length === 1 && str.match(/\S/);
|
86 | };
|
87 |
|
88 | const TreeItem = React.forwardRef(function TreeItem(props, ref) {
|
89 | const {
|
90 | children,
|
91 | classes,
|
92 | className,
|
93 | collapseIcon,
|
94 | endIcon,
|
95 | expandIcon,
|
96 | icon: iconProp,
|
97 | label,
|
98 | nodeId,
|
99 | onClick,
|
100 | onLabelClick,
|
101 | onIconClick,
|
102 | onFocus,
|
103 | onKeyDown,
|
104 | onMouseDown,
|
105 | TransitionComponent = Collapse,
|
106 | TransitionProps
|
107 | } = props,
|
108 | other = _objectWithoutPropertiesLoose(props, ["children", "classes", "className", "collapseIcon", "endIcon", "expandIcon", "icon", "label", "nodeId", "onClick", "onLabelClick", "onIconClick", "onFocus", "onKeyDown", "onMouseDown", "TransitionComponent", "TransitionProps"]);
|
109 |
|
110 | const {
|
111 | icons: contextIcons,
|
112 | focus,
|
113 | focusFirstNode,
|
114 | focusLastNode,
|
115 | focusNextNode,
|
116 | focusPreviousNode,
|
117 | focusByFirstCharacter,
|
118 | selectNode,
|
119 | selectRange,
|
120 | selectNextNode,
|
121 | selectPreviousNode,
|
122 | rangeSelectToFirst,
|
123 | rangeSelectToLast,
|
124 | selectAllNodes,
|
125 | expandAllSiblings,
|
126 | toggleExpansion,
|
127 | isExpanded,
|
128 | isFocused,
|
129 | isSelected,
|
130 | isTabbable,
|
131 | multiSelect,
|
132 | getParent,
|
133 | mapFirstChar,
|
134 | addNodeToNodeMap,
|
135 | removeNodeFromNodeMap
|
136 | } = React.useContext(TreeViewContext);
|
137 | const nodeRef = React.useRef(null);
|
138 | const contentRef = React.useRef(null);
|
139 | const handleRef = useForkRef(nodeRef, ref);
|
140 | let icon = iconProp;
|
141 | const expandable = Boolean(Array.isArray(children) ? children.length : children);
|
142 | const expanded = isExpanded ? isExpanded(nodeId) : false;
|
143 | const focused = isFocused ? isFocused(nodeId) : false;
|
144 | const tabbable = isTabbable ? isTabbable(nodeId) : false;
|
145 | const selected = isSelected ? isSelected(nodeId) : false;
|
146 | const icons = contextIcons || {};
|
147 | const theme = useTheme();
|
148 |
|
149 | if (!icon) {
|
150 | if (expandable) {
|
151 | if (!expanded) {
|
152 | icon = expandIcon || icons.defaultExpandIcon;
|
153 | } else {
|
154 | icon = collapseIcon || icons.defaultCollapseIcon;
|
155 | }
|
156 |
|
157 | if (!icon) {
|
158 | icon = icons.defaultParentIcon;
|
159 | }
|
160 | } else {
|
161 | icon = endIcon || icons.defaultEndIcon;
|
162 | }
|
163 | }
|
164 |
|
165 | const handleClick = event => {
|
166 | if (!focused) {
|
167 | focus(nodeId);
|
168 | }
|
169 |
|
170 | const multiple = multiSelect && (event.shiftKey || event.ctrlKey || event.metaKey);
|
171 |
|
172 | if (expandable && !event.defaultPrevented && !(multiple && isExpanded(nodeId))) {
|
173 | toggleExpansion(event, nodeId);
|
174 | }
|
175 |
|
176 | if (multiple) {
|
177 | if (event.shiftKey) {
|
178 | selectRange(event, {
|
179 | end: nodeId
|
180 | });
|
181 | } else {
|
182 | selectNode(event, nodeId, true);
|
183 | }
|
184 | } else {
|
185 | selectNode(event, nodeId);
|
186 | }
|
187 |
|
188 | if (onClick) {
|
189 | onClick(event);
|
190 | }
|
191 | };
|
192 |
|
193 | const handleMouseDown = event => {
|
194 | if (event.shiftKey || event.ctrlKey || event.metaKey) {
|
195 | event.preventDefault();
|
196 | }
|
197 |
|
198 | if (onMouseDown) {
|
199 | onMouseDown(event);
|
200 | }
|
201 | };
|
202 |
|
203 | const handleNextArrow = event => {
|
204 | if (expandable) {
|
205 | if (expanded) {
|
206 | focusNextNode(nodeId);
|
207 | } else {
|
208 | toggleExpansion(event);
|
209 | }
|
210 | }
|
211 |
|
212 | return true;
|
213 | };
|
214 |
|
215 | const handlePreviousArrow = event => {
|
216 | if (expanded) {
|
217 | toggleExpansion(event, nodeId);
|
218 | return true;
|
219 | }
|
220 |
|
221 | const parent = getParent(nodeId);
|
222 |
|
223 | if (parent) {
|
224 | focus(parent);
|
225 | return true;
|
226 | }
|
227 |
|
228 | return false;
|
229 | };
|
230 |
|
231 | const handleKeyDown = event => {
|
232 | let flag = false;
|
233 | const key = event.key;
|
234 |
|
235 | if (event.altKey || event.currentTarget !== event.target) {
|
236 | return;
|
237 | }
|
238 |
|
239 | const ctrlPressed = event.ctrlKey || event.metaKey;
|
240 |
|
241 | switch (key) {
|
242 | case ' ':
|
243 | if (nodeRef.current === event.currentTarget) {
|
244 | if (multiSelect && event.shiftKey) {
|
245 | flag = selectRange(event, {
|
246 | end: nodeId
|
247 | });
|
248 | } else if (multiSelect) {
|
249 | flag = selectNode(event, nodeId, true);
|
250 | } else {
|
251 | flag = selectNode(event, nodeId);
|
252 | }
|
253 | }
|
254 |
|
255 | event.stopPropagation();
|
256 | break;
|
257 |
|
258 | case 'Enter':
|
259 | if (nodeRef.current === event.currentTarget && expandable) {
|
260 | toggleExpansion(event);
|
261 | flag = true;
|
262 | }
|
263 |
|
264 | event.stopPropagation();
|
265 | break;
|
266 |
|
267 | case 'ArrowDown':
|
268 | if (multiSelect && event.shiftKey) {
|
269 | selectNextNode(event, nodeId);
|
270 | }
|
271 |
|
272 | focusNextNode(nodeId);
|
273 | flag = true;
|
274 | break;
|
275 |
|
276 | case 'ArrowUp':
|
277 | if (multiSelect && event.shiftKey) {
|
278 | selectPreviousNode(event, nodeId);
|
279 | }
|
280 |
|
281 | focusPreviousNode(nodeId);
|
282 | flag = true;
|
283 | break;
|
284 |
|
285 | case 'ArrowRight':
|
286 | if (theme.direction === 'rtl') {
|
287 | flag = handlePreviousArrow(event);
|
288 | } else {
|
289 | flag = handleNextArrow(event);
|
290 | }
|
291 |
|
292 | break;
|
293 |
|
294 | case 'ArrowLeft':
|
295 | if (theme.direction === 'rtl') {
|
296 | flag = handleNextArrow(event);
|
297 | } else {
|
298 | flag = handlePreviousArrow(event);
|
299 | }
|
300 |
|
301 | break;
|
302 |
|
303 | case 'Home':
|
304 | if (multiSelect && ctrlPressed && event.shiftKey) {
|
305 | rangeSelectToFirst(event, nodeId);
|
306 | }
|
307 |
|
308 | focusFirstNode();
|
309 | flag = true;
|
310 | break;
|
311 |
|
312 | case 'End':
|
313 | if (multiSelect && ctrlPressed && event.shiftKey) {
|
314 | rangeSelectToLast(event, nodeId);
|
315 | }
|
316 |
|
317 | focusLastNode();
|
318 | flag = true;
|
319 | break;
|
320 |
|
321 | default:
|
322 | if (key === '*') {
|
323 | expandAllSiblings(event, nodeId);
|
324 | flag = true;
|
325 | } else if (multiSelect && ctrlPressed && key.toLowerCase() === 'a') {
|
326 | flag = selectAllNodes(event);
|
327 | } else if (!ctrlPressed && !event.shiftKey && isPrintableCharacter(key)) {
|
328 | focusByFirstCharacter(nodeId, key);
|
329 | flag = true;
|
330 | }
|
331 |
|
332 | }
|
333 |
|
334 | if (flag) {
|
335 | event.preventDefault();
|
336 | event.stopPropagation();
|
337 | }
|
338 |
|
339 | if (onKeyDown) {
|
340 | onKeyDown(event);
|
341 | }
|
342 | };
|
343 |
|
344 | const handleFocus = event => {
|
345 | if (!focused && event.currentTarget === event.target) {
|
346 | focus(nodeId);
|
347 | }
|
348 |
|
349 | if (onFocus) {
|
350 | onFocus(event);
|
351 | }
|
352 | };
|
353 |
|
354 | React.useEffect(() => {
|
355 | if (addNodeToNodeMap) {
|
356 | const childIds = [];
|
357 | React.Children.forEach(children, child => {
|
358 | if ( React.isValidElement(child) && child.props.nodeId) {
|
359 | childIds.push(child.props.nodeId);
|
360 | }
|
361 | });
|
362 | addNodeToNodeMap(nodeId, childIds);
|
363 | }
|
364 | }, [children, nodeId, addNodeToNodeMap]);
|
365 | React.useEffect(() => {
|
366 | if (removeNodeFromNodeMap) {
|
367 | return () => {
|
368 | removeNodeFromNodeMap(nodeId);
|
369 | };
|
370 | }
|
371 |
|
372 | return undefined;
|
373 | }, [nodeId, removeNodeFromNodeMap]);
|
374 | React.useEffect(() => {
|
375 | if (mapFirstChar && label) {
|
376 | mapFirstChar(nodeId, contentRef.current.textContent.substring(0, 1).toLowerCase());
|
377 | }
|
378 | }, [mapFirstChar, nodeId, label]);
|
379 | React.useEffect(() => {
|
380 | if (focused) {
|
381 | nodeRef.current.focus();
|
382 | }
|
383 | }, [focused]);
|
384 | let ariaSelected;
|
385 |
|
386 | if (multiSelect) {
|
387 | ariaSelected = selected;
|
388 | } else if (selected) {
|
389 |
|
390 | ariaSelected = true;
|
391 | }
|
392 |
|
393 | return React.createElement("li", _extends({
|
394 | className: clsx(classes.root, className, expanded && classes.expanded, selected && classes.selected),
|
395 | role: "treeitem",
|
396 | onKeyDown: handleKeyDown,
|
397 | onFocus: handleFocus,
|
398 | "aria-expanded": expandable ? expanded : null,
|
399 | "aria-selected": ariaSelected,
|
400 | ref: handleRef,
|
401 | tabIndex: tabbable ? 0 : -1
|
402 | }, other), React.createElement("div", {
|
403 | className: classes.content,
|
404 | onClick: handleClick,
|
405 | onMouseDown: handleMouseDown,
|
406 | ref: contentRef
|
407 | }, React.createElement("div", {
|
408 | onClick: onIconClick,
|
409 | className: classes.iconContainer
|
410 | }, icon), React.createElement(Typography, {
|
411 | onClick: onLabelClick,
|
412 | component: "div",
|
413 | className: classes.label
|
414 | }, label)), children && React.createElement(TransitionComponent, _extends({
|
415 | unmountOnExit: true,
|
416 | className: classes.group,
|
417 | in: expanded,
|
418 | component: "ul",
|
419 | role: "group"
|
420 | }, TransitionProps), children));
|
421 | });
|
422 | process.env.NODE_ENV !== "production" ? TreeItem.propTypes = {
|
423 |
|
424 |
|
425 |
|
426 |
|
427 |
|
428 | |
429 |
|
430 |
|
431 | children: PropTypes.node,
|
432 |
|
433 | |
434 |
|
435 |
|
436 |
|
437 | classes: PropTypes.object,
|
438 |
|
439 | |
440 |
|
441 |
|
442 | className: PropTypes.string,
|
443 |
|
444 | |
445 |
|
446 |
|
447 | collapseIcon: PropTypes.node,
|
448 |
|
449 | |
450 |
|
451 |
|
452 | endIcon: PropTypes.node,
|
453 |
|
454 | |
455 |
|
456 |
|
457 | expandIcon: PropTypes.node,
|
458 |
|
459 | |
460 |
|
461 |
|
462 | icon: PropTypes.node,
|
463 |
|
464 | |
465 |
|
466 |
|
467 | label: PropTypes.node,
|
468 |
|
469 | |
470 |
|
471 |
|
472 | nodeId: PropTypes.string.isRequired,
|
473 |
|
474 | |
475 |
|
476 |
|
477 | onClick: PropTypes.func,
|
478 |
|
479 | |
480 |
|
481 |
|
482 | onFocus: PropTypes.func,
|
483 |
|
484 | |
485 |
|
486 |
|
487 | onIconClick: PropTypes.func,
|
488 |
|
489 | |
490 |
|
491 |
|
492 | onKeyDown: PropTypes.func,
|
493 |
|
494 | |
495 |
|
496 |
|
497 | onLabelClick: PropTypes.func,
|
498 |
|
499 | |
500 |
|
501 |
|
502 | onMouseDown: PropTypes.func,
|
503 |
|
504 | |
505 |
|
506 |
|
507 |
|
508 | TransitionComponent: PropTypes.elementType,
|
509 |
|
510 | |
511 |
|
512 |
|
513 | TransitionProps: PropTypes.object
|
514 | } : void 0;
|
515 | export default withStyles(styles, {
|
516 | name: 'MuiTreeItem'
|
517 | })(TreeItem); |
\ | No newline at end of file |