@mskcc/carbon-react
Version:
Carbon react components for the MSKCC DSM
241 lines (233 loc) • 8.35 kB
JavaScript
/**
* MSKCC 2021, 2024
*/
;
Object.defineProperty(exports, '__esModule', { value: true });
var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js');
var React = require('react');
var PropTypes = require('prop-types');
var cx = require('classnames');
var uniqueId = require('../../tools/uniqueId.js');
var usePrefix = require('../../internal/usePrefix.js');
var match = require('../../internal/keyboard/match.js');
var keys = require('../../internal/keyboard/keys.js');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes);
var cx__default = /*#__PURE__*/_interopDefaultLegacy(cx);
function TreeView(_ref) {
let {
active: prespecifiedActive,
children,
className,
hideLabel = false,
label,
multiselect = false,
onSelect,
selected: preselected = [],
size = 'sm',
...rest
} = _ref;
const {
current: treeId
} = React.useRef(rest.id || uniqueId["default"]());
const prefix = usePrefix.usePrefix();
const treeClasses = cx__default["default"](className, `${prefix}--tree`, {
[`${prefix}--tree--${size}`]: size !== 'default'
});
const treeRootRef = React.useRef(null);
const treeWalker = React.useRef(treeRootRef?.current);
const [selected, setSelected] = React.useState(preselected);
const [active, setActive] = React.useState(prespecifiedActive);
function resetNodeTabIndices() {
Array.prototype.forEach.call(treeRootRef?.current?.querySelectorAll('[tabIndex="0"]') ?? [], item => {
item.tabIndex = -1;
});
}
function handleTreeSelect(event) {
let node = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const {
id: nodeId
} = node;
if (multiselect && (event.metaKey || event.ctrlKey)) {
if (!selected.includes(nodeId)) {
setSelected(selected.concat(nodeId));
} else {
setSelected(selected.filter(selectedId => selectedId !== nodeId));
}
onSelect?.(event, node);
} else {
setSelected([nodeId]);
setActive(nodeId);
onSelect?.(event, {
activeNodeId: nodeId,
...node
});
}
}
function handleFocusEvent(event) {
if (event.type === 'blur') {
const {
relatedTarget: currentFocusedNode,
target: prevFocusedNode
} = event;
if (treeRootRef?.current?.contains(currentFocusedNode)) {
prevFocusedNode.tabIndex = -1;
}
}
if (event.type === 'focus') {
resetNodeTabIndices();
const {
relatedTarget: prevFocusedNode,
target: currentFocusedNode
} = event;
if (treeRootRef?.current?.contains(prevFocusedNode)) {
prevFocusedNode.tabIndex = -1;
}
currentFocusedNode.tabIndex = 0;
}
}
let focusTarget = false;
const nodesWithProps = React__default["default"].Children.map(children, node => {
const sharedNodeProps = {
active,
depth: 0,
onNodeFocusEvent: handleFocusEvent,
onTreeSelect: handleTreeSelect,
selected,
tabIndex: !node.props.disabled && -1 || null
};
if (!focusTarget && !node.props.disabled) {
sharedNodeProps.tabIndex = 0;
focusTarget = true;
}
if ( /*#__PURE__*/React__default["default"].isValidElement(node)) {
return /*#__PURE__*/React__default["default"].cloneElement(node, sharedNodeProps);
}
});
function handleKeyDown(event) {
event.stopPropagation();
if (match.matches(event, [keys.ArrowUp, keys.ArrowDown, keys.Home, keys.End, {
code: 'KeyA'
}])) {
event.preventDefault();
}
treeWalker.current.currentNode = event.target;
let nextFocusNode;
if (match.match(event, keys.ArrowUp)) {
nextFocusNode = treeWalker.current.previousNode();
}
if (match.match(event, keys.ArrowDown)) {
nextFocusNode = treeWalker.current.nextNode();
}
if (match.matches(event, [keys.Home, keys.End, {
code: 'KeyA'
}])) {
const nodeIds = [];
if (match.matches(event, [keys.Home, keys.End])) {
if (multiselect && event.shiftKey && event.ctrlKey && !treeWalker.current.currentNode.getAttribute('aria-disabled')) {
nodeIds.push(treeWalker.current.currentNode?.id);
}
while (match.match(event, keys.Home) ? treeWalker.current.previousNode() : treeWalker.current.nextNode()) {
nextFocusNode = treeWalker.current.currentNode;
if (multiselect && event.shiftKey && event.ctrlKey && !nextFocusNode.getAttribute('aria-disabled')) {
nodeIds.push(nextFocusNode?.id);
}
}
}
if (match.match(event, {
code: 'KeyA'
}) && event.ctrlKey) {
treeWalker.current.currentNode = treeWalker.current.root;
while (treeWalker.current.nextNode()) {
if (!treeWalker.current.currentNode.getAttribute('aria-disabled')) {
nodeIds.push(treeWalker.current.currentNode?.id);
}
}
}
setSelected(selected.concat(nodeIds));
}
if (nextFocusNode && nextFocusNode !== event.target) {
resetNodeTabIndices();
nextFocusNode.tabIndex = 0;
nextFocusNode.focus();
}
rest?.onKeyDown?.(event);
}
React.useEffect(() => {
treeWalker.current = treeWalker.current ?? document.createTreeWalker(treeRootRef?.current, NodeFilter.SHOW_ELEMENT, {
acceptNode: function (node) {
if (node.classList.contains(`${prefix}--tree-node--disabled`)) {
return NodeFilter.FILTER_REJECT;
}
if (node.matches(`li.${prefix}--tree-node`)) {
return NodeFilter.FILTER_ACCEPT;
}
return NodeFilter.FILTER_SKIP;
}
});
}, [prefix]);
const useActiveAndSelectedOnMount = () => React.useEffect(() => {
if (preselected.length) {
setSelected(preselected);
}
if (prespecifiedActive) {
setActive(prespecifiedActive);
}
}, []);
useActiveAndSelectedOnMount();
const labelId = `${treeId}__label`;
const TreeLabel = () => !hideLabel && /*#__PURE__*/React__default["default"].createElement("label", {
id: labelId,
className: `${prefix}--label`
}, label);
return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement(TreeLabel, null), /*#__PURE__*/React__default["default"].createElement("ul", _rollupPluginBabelHelpers["extends"]({}, rest, {
"aria-label": hideLabel ? label : null,
"aria-labelledby": !hideLabel ? labelId : null,
"aria-multiselectable": multiselect || null,
className: treeClasses,
onKeyDown: handleKeyDown,
ref: treeRootRef,
role: "tree"
}), nodesWithProps));
}
TreeView.propTypes = {
/**
* Mark the active node in the tree, represented by its value
*/
active: PropTypes__default["default"].oneOfType([PropTypes__default["default"].string, PropTypes__default["default"].number]),
/**
* Specify the children of the TreeView
*/
children: PropTypes__default["default"].node,
/**
* Specify an optional className to be applied to the TreeView
*/
className: PropTypes__default["default"].string,
/**
* Specify whether or not the label should be hidden
*/
hideLabel: PropTypes__default["default"].bool,
/**
* Provide the label text that will be read by a screen reader
*/
label: PropTypes__default["default"].string.isRequired,
/**
* **[Experimental]** Specify the selection mode of the tree.
* If `multiselect` is `false` then only one node can be selected at a time
*/
multiselect: PropTypes__default["default"].bool,
/**
* Callback function that is called when any node is selected
*/
onSelect: PropTypes__default["default"].func,
/**
* Array representing all selected node IDs in the tree
*/
selected: PropTypes__default["default"].arrayOf(PropTypes__default["default"].oneOfType([PropTypes__default["default"].string, PropTypes__default["default"].number])),
/**
* Specify the size of the tree from a list of available sizes.
*/
size: PropTypes__default["default"].oneOf(['xs', 'sm'])
};
exports["default"] = TreeView;