UNPKG

@material-ui/lab

Version:

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

518 lines (446 loc) 14.5 kB
import _extends from "@babel/runtime/helpers/esm/extends"; import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties"; /* eslint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */ import * as React from 'react'; import clsx from 'clsx'; import PropTypes from 'prop-types'; import Typography from '@material-ui/core/Typography'; import Collapse from '@material-ui/core/Collapse'; import { alpha, withStyles, useTheme } from '@material-ui/core/styles'; import { useForkRef } from '@material-ui/core/utils'; import TreeViewContext from '../TreeView/TreeViewContext'; export var styles = function styles(theme) { return { /* Styles applied to the root element. */ root: { listStyle: 'none', margin: 0, padding: 0, outline: 0, WebkitTapHighlightColor: 'transparent', '&:focus > $content $label': { backgroundColor: theme.palette.action.hover }, '&$selected > $content $label': { backgroundColor: alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity) }, '&$selected > $content $label:hover, &$selected:focus > $content $label': { backgroundColor: alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity + theme.palette.action.hoverOpacity), // Reset on touch devices, it doesn't add specificity '@media (hover: none)': { backgroundColor: 'transparent' } } }, /* Pseudo-class applied to the root element when expanded. */ expanded: {}, /* Pseudo-class applied to the root element when selected. */ selected: {}, /* Styles applied to the `role="group"` element. */ group: { margin: 0, padding: 0, marginLeft: 17 }, /* Styles applied to the tree node content. */ content: { width: '100%', display: 'flex', alignItems: 'center', cursor: 'pointer' }, /* Styles applied to the tree node icon and collapse/expand icon. */ iconContainer: { marginRight: 4, width: 15, display: 'flex', flexShrink: 0, justifyContent: 'center', '& svg': { fontSize: 18 } }, /* Styles applied to the label element. */ label: { width: '100%', paddingLeft: 4, position: 'relative', '&:hover': { backgroundColor: theme.palette.action.hover, // Reset on touch devices, it doesn't add specificity '@media (hover: none)': { backgroundColor: 'transparent' } } } }; }; var isPrintableCharacter = function isPrintableCharacter(str) { return str && str.length === 1 && str.match(/\S/); }; var TreeItem = /*#__PURE__*/React.forwardRef(function TreeItem(props, ref) { var children = props.children, classes = props.classes, className = props.className, collapseIcon = props.collapseIcon, endIcon = props.endIcon, expandIcon = props.expandIcon, iconProp = props.icon, label = props.label, nodeId = props.nodeId, onClick = props.onClick, onLabelClick = props.onLabelClick, onIconClick = props.onIconClick, onFocus = props.onFocus, onKeyDown = props.onKeyDown, onMouseDown = props.onMouseDown, _props$TransitionComp = props.TransitionComponent, TransitionComponent = _props$TransitionComp === void 0 ? Collapse : _props$TransitionComp, TransitionProps = props.TransitionProps, other = _objectWithoutProperties(props, ["children", "classes", "className", "collapseIcon", "endIcon", "expandIcon", "icon", "label", "nodeId", "onClick", "onLabelClick", "onIconClick", "onFocus", "onKeyDown", "onMouseDown", "TransitionComponent", "TransitionProps"]); var _React$useContext = React.useContext(TreeViewContext), contextIcons = _React$useContext.icons, focus = _React$useContext.focus, focusFirstNode = _React$useContext.focusFirstNode, focusLastNode = _React$useContext.focusLastNode, focusNextNode = _React$useContext.focusNextNode, focusPreviousNode = _React$useContext.focusPreviousNode, focusByFirstCharacter = _React$useContext.focusByFirstCharacter, selectNode = _React$useContext.selectNode, selectRange = _React$useContext.selectRange, selectNextNode = _React$useContext.selectNextNode, selectPreviousNode = _React$useContext.selectPreviousNode, rangeSelectToFirst = _React$useContext.rangeSelectToFirst, rangeSelectToLast = _React$useContext.rangeSelectToLast, selectAllNodes = _React$useContext.selectAllNodes, expandAllSiblings = _React$useContext.expandAllSiblings, toggleExpansion = _React$useContext.toggleExpansion, isExpanded = _React$useContext.isExpanded, isFocused = _React$useContext.isFocused, isSelected = _React$useContext.isSelected, isTabbable = _React$useContext.isTabbable, multiSelect = _React$useContext.multiSelect, getParent = _React$useContext.getParent, mapFirstChar = _React$useContext.mapFirstChar, addNodeToNodeMap = _React$useContext.addNodeToNodeMap, removeNodeFromNodeMap = _React$useContext.removeNodeFromNodeMap; var nodeRef = React.useRef(null); var contentRef = React.useRef(null); var handleRef = useForkRef(nodeRef, ref); var icon = iconProp; var expandable = Boolean(Array.isArray(children) ? children.length : children); var expanded = isExpanded ? isExpanded(nodeId) : false; var focused = isFocused ? isFocused(nodeId) : false; var tabbable = isTabbable ? isTabbable(nodeId) : false; var selected = isSelected ? isSelected(nodeId) : false; var icons = contextIcons || {}; var theme = useTheme(); if (!icon) { if (expandable) { if (!expanded) { icon = expandIcon || icons.defaultExpandIcon; } else { icon = collapseIcon || icons.defaultCollapseIcon; } if (!icon) { icon = icons.defaultParentIcon; } } else { icon = endIcon || icons.defaultEndIcon; } } var handleClick = function handleClick(event) { if (!focused) { focus(nodeId); } var multiple = multiSelect && (event.shiftKey || event.ctrlKey || event.metaKey); // If already expanded and trying to toggle selection don't close if (expandable && !event.defaultPrevented && !(multiple && isExpanded(nodeId))) { toggleExpansion(event, nodeId); } if (multiple) { if (event.shiftKey) { selectRange(event, { end: nodeId }); } else { selectNode(event, nodeId, true); } } else { selectNode(event, nodeId); } if (onClick) { onClick(event); } }; var handleMouseDown = function handleMouseDown(event) { if (event.shiftKey || event.ctrlKey || event.metaKey) { event.preventDefault(); } if (onMouseDown) { onMouseDown(event); } }; var handleNextArrow = function handleNextArrow(event) { if (expandable) { if (expanded) { focusNextNode(nodeId); } else { toggleExpansion(event); } } return true; }; var handlePreviousArrow = function handlePreviousArrow(event) { if (expanded) { toggleExpansion(event, nodeId); return true; } var parent = getParent(nodeId); if (parent) { focus(parent); return true; } return false; }; var handleKeyDown = function handleKeyDown(event) { var flag = false; var key = event.key; if (event.altKey || event.currentTarget !== event.target) { return; } var ctrlPressed = event.ctrlKey || event.metaKey; switch (key) { case ' ': if (nodeRef.current === event.currentTarget) { if (multiSelect && event.shiftKey) { flag = selectRange(event, { end: nodeId }); } else if (multiSelect) { flag = selectNode(event, nodeId, true); } else { flag = selectNode(event, nodeId); } } event.stopPropagation(); break; case 'Enter': if (nodeRef.current === event.currentTarget && expandable) { toggleExpansion(event); flag = true; } event.stopPropagation(); break; case 'ArrowDown': if (multiSelect && event.shiftKey) { selectNextNode(event, nodeId); } focusNextNode(nodeId); flag = true; break; case 'ArrowUp': if (multiSelect && event.shiftKey) { selectPreviousNode(event, nodeId); } focusPreviousNode(nodeId); flag = true; break; case 'ArrowRight': if (theme.direction === 'rtl') { flag = handlePreviousArrow(event); } else { flag = handleNextArrow(event); } break; case 'ArrowLeft': if (theme.direction === 'rtl') { flag = handleNextArrow(event); } else { flag = handlePreviousArrow(event); } break; case 'Home': if (multiSelect && ctrlPressed && event.shiftKey) { rangeSelectToFirst(event, nodeId); } focusFirstNode(); flag = true; break; case 'End': if (multiSelect && ctrlPressed && event.shiftKey) { rangeSelectToLast(event, nodeId); } focusLastNode(); flag = true; break; default: if (key === '*') { expandAllSiblings(event, nodeId); flag = true; } else if (multiSelect && ctrlPressed && key.toLowerCase() === 'a') { flag = selectAllNodes(event); } else if (!ctrlPressed && !event.shiftKey && isPrintableCharacter(key)) { focusByFirstCharacter(nodeId, key); flag = true; } } if (flag) { event.preventDefault(); event.stopPropagation(); } if (onKeyDown) { onKeyDown(event); } }; var handleFocus = function handleFocus(event) { if (!focused && event.currentTarget === event.target) { focus(nodeId); } if (onFocus) { onFocus(event); } }; React.useEffect(function () { if (addNodeToNodeMap) { var childIds = []; React.Children.forEach(children, function (child) { if ( /*#__PURE__*/React.isValidElement(child) && child.props.nodeId) { childIds.push(child.props.nodeId); } }); addNodeToNodeMap(nodeId, childIds); } }, [children, nodeId, addNodeToNodeMap]); React.useEffect(function () { if (removeNodeFromNodeMap) { return function () { removeNodeFromNodeMap(nodeId); }; } return undefined; }, [nodeId, removeNodeFromNodeMap]); React.useEffect(function () { if (mapFirstChar && label) { mapFirstChar(nodeId, contentRef.current.textContent.substring(0, 1).toLowerCase()); } }, [mapFirstChar, nodeId, label]); React.useEffect(function () { if (focused) { nodeRef.current.focus(); } }, [focused]); var ariaSelected; if (multiSelect) { ariaSelected = selected; } else if (selected) { // single-selection trees unset aria-selected ariaSelected = true; } return /*#__PURE__*/React.createElement("li", _extends({ className: clsx(classes.root, className, expanded && classes.expanded, selected && classes.selected), role: "treeitem", onKeyDown: handleKeyDown, onFocus: handleFocus, "aria-expanded": expandable ? expanded : null, "aria-selected": ariaSelected, ref: handleRef, tabIndex: tabbable ? 0 : -1 }, other), /*#__PURE__*/React.createElement("div", { className: classes.content, onClick: handleClick, onMouseDown: handleMouseDown, ref: contentRef }, /*#__PURE__*/React.createElement("div", { onClick: onIconClick, className: classes.iconContainer }, icon), /*#__PURE__*/React.createElement(Typography, { onClick: onLabelClick, component: "div", className: classes.label }, label)), children && /*#__PURE__*/React.createElement(TransitionComponent, _extends({ unmountOnExit: true, className: classes.group, in: expanded, component: "ul", role: "group" }, TransitionProps), children)); }); process.env.NODE_ENV !== "production" ? TreeItem.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.node, /** * Override or extend the styles applied to the component. * See [CSS API](#css) below for more details. */ classes: PropTypes.object, /** * @ignore */ className: PropTypes.string, /** * The icon used to collapse the node. */ collapseIcon: PropTypes.node, /** * The icon displayed next to a end node. */ endIcon: PropTypes.node, /** * The icon used to expand the node. */ expandIcon: PropTypes.node, /** * The icon to display next to the tree node's label. */ icon: PropTypes.node, /** * The tree node label. */ label: PropTypes.node, /** * The id of the node. */ nodeId: PropTypes.string.isRequired, /** * @ignore */ onClick: PropTypes.func, /** * @ignore */ onFocus: PropTypes.func, /** * `onClick` handler for the icon container. Call `event.preventDefault()` to prevent `onNodeToggle` from being called. */ onIconClick: PropTypes.func, /** * @ignore */ onKeyDown: PropTypes.func, /** * `onClick` handler for the label container. Call `event.preventDefault()` to prevent `onNodeToggle` from being called. */ onLabelClick: PropTypes.func, /** * @ignore */ onMouseDown: PropTypes.func, /** * The component used for the transition. * [Follow this guide](/components/transitions/#transitioncomponent-prop) to learn more about the requirements for this component. */ TransitionComponent: PropTypes.elementType, /** * Props applied to the [`Transition`](http://reactcommunity.org/react-transition-group/transition#Transition-props) element. */ TransitionProps: PropTypes.object } : void 0; export default withStyles(styles, { name: 'MuiTreeItem' })(TreeItem);