UNPKG

@carbon/react

Version:

React components for the Carbon Design System

180 lines (178 loc) 8.21 kB
/** * Copyright IBM Corp. 2016, 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ const require_runtime = require("../../_virtual/_rolldown/runtime.js"); const require_usePrefix = require("../../internal/usePrefix.js"); const require_keys = require("../../internal/keyboard/keys.js"); const require_match = require("../../internal/keyboard/match.js"); const require_useId = require("../../internal/useId.js"); const require_index = require("../FeatureFlags/index.js"); const require_useControllableState = require("../../internal/useControllableState.js"); const require_TreeContext = require("./TreeContext.js"); const require_TreeNode = require("./TreeNode.js"); let classnames = require("classnames"); classnames = require_runtime.__toESM(classnames); let react = require("react"); react = require_runtime.__toESM(react); let prop_types = require("prop-types"); prop_types = require_runtime.__toESM(prop_types); let react_jsx_runtime = require("react/jsx-runtime"); //#region src/components/TreeView/TreeView.tsx /** * Copyright IBM Corp. 2016, 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ const TreeView = ({ active: prespecifiedActive, children, className, hideLabel = false, label, multiselect = false, onActivate, onSelect, selected: preselected, size = "sm", ...rest }) => { const enableTreeviewControllable = require_index.useFeatureFlag("enable-treeview-controllable"); const generatedId = require_useId.useId(); const { current: treeId } = (0, react.useRef)(rest.id ?? generatedId); const prefix = require_usePrefix.usePrefix(); const treeClasses = (0, classnames.default)(className, `${prefix}--tree`, { [`${prefix}--tree--${size}`]: size !== "default" }); const treeRootRef = (0, react.useRef)(null); const treeWalker = (0, react.useRef)(null); const controllableSelectionState = require_useControllableState.useControllableState({ value: preselected, onChange: onSelect, defaultValue: [] }); const uncontrollableSelectionState = (0, react.useState)(preselected ?? []); const [selected, setSelected] = enableTreeviewControllable ? controllableSelectionState : uncontrollableSelectionState; const controllableActiveState = require_useControllableState.useControllableState({ value: prespecifiedActive, onChange: onActivate, defaultValue: void 0 }); const uncontrollableActiveState = (0, react.useState)(prespecifiedActive); const [active, setActive] = enableTreeviewControllable ? controllableActiveState : uncontrollableActiveState; function resetNodeTabIndices() { Array.prototype.forEach.call(treeRootRef?.current?.querySelectorAll("[tabIndex=\"0\"]") ?? [], (item) => { item.tabIndex = -1; }); } function handleTreeSelect(event, node) { const nodeId = node.id; if (nodeId) if (multiselect && (event.metaKey || event.ctrlKey)) { if (!selected.includes(nodeId)) setSelected(selected.concat(nodeId)); else setSelected(selected.filter((selectedId) => selectedId !== nodeId)); if (!enableTreeviewControllable) onSelect?.(event, node); } else { setSelected([nodeId]); setActive(nodeId); if (!enableTreeviewControllable) onSelect?.(event, { activeNodeId: nodeId, ...node }); } } (0, react.useEffect)(() => { const firstNode = treeRootRef.current?.querySelector(`.${prefix}--tree-node:not(.${prefix}--tree-node--disabled)`); if (firstNode instanceof HTMLElement) firstNode.tabIndex = 0; }, [children, prefix]); function handleKeyDown(event) { event.stopPropagation(); if (require_match.matches(event, [ require_keys.ArrowUp, require_keys.ArrowDown, require_keys.Home, require_keys.End ])) event.preventDefault(); if (!treeWalker.current) return; treeWalker.current.currentNode = event.target; let nextFocusNode = null; if (require_match.match(event, require_keys.ArrowUp)) nextFocusNode = treeWalker.current.previousNode(); if (require_match.match(event, require_keys.ArrowDown)) nextFocusNode = treeWalker.current.nextNode(); if (require_match.matches(event, [ require_keys.Home, require_keys.End, { code: "KeyA" } ])) { const nodeIds = []; if (require_match.matches(event, [require_keys.Home, require_keys.End])) { if (multiselect && event.shiftKey && event.ctrlKey && treeWalker.current.currentNode instanceof Element && !treeWalker.current.currentNode.getAttribute("aria-disabled") && !treeWalker.current.currentNode.classList.contains(`${prefix}--tree-node--hidden`)) nodeIds.push(treeWalker.current.currentNode.id); while (require_match.match(event, require_keys.Home) ? treeWalker.current.previousNode() : treeWalker.current.nextNode()) { nextFocusNode = treeWalker.current.currentNode; if (multiselect && event.shiftKey && event.ctrlKey && nextFocusNode instanceof Element && !nextFocusNode.getAttribute("aria-disabled") && !nextFocusNode.classList.contains(`${prefix}--tree-node--hidden`)) nodeIds.push(nextFocusNode.id); } } if (require_match.match(event, { code: "KeyA" }) && event.ctrlKey) { treeWalker.current.currentNode = treeWalker.current.root; while (treeWalker.current.nextNode()) if (treeWalker.current.currentNode instanceof Element && !treeWalker.current.currentNode.getAttribute("aria-disabled") && !treeWalker.current.currentNode.classList.contains(`${prefix}--tree-node--hidden`)) nodeIds.push(treeWalker.current.currentNode.id); } setSelected(selected.concat(nodeIds)); } if (nextFocusNode && nextFocusNode !== event.target) { resetNodeTabIndices(); if (nextFocusNode instanceof HTMLElement) { nextFocusNode.tabIndex = 0; nextFocusNode.focus(); } } rest?.onKeyDown?.(event); } (0, react.useEffect)(() => { if (treeRootRef.current && !treeWalker.current) treeWalker.current = document.createTreeWalker(treeRootRef.current, NodeFilter.SHOW_ELEMENT, { acceptNode: function(node) { if (!(node instanceof Element)) return NodeFilter.FILTER_SKIP; if (node.classList.contains(`${prefix}--tree-node--disabled`) || node.classList.contains(`${prefix}--tree-node--hidden`)) return NodeFilter.FILTER_REJECT; if (node.matches(`.${prefix}--tree-node`)) return NodeFilter.FILTER_ACCEPT; return NodeFilter.FILTER_SKIP; } }); }, [prefix]); const labelId = `${treeId}__label`; const TreeLabel = () => !hideLabel ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { id: labelId, className: `${prefix}--label`, children: label }) : null; const treeContextValue = (0, react.useMemo)(() => ({ active, multiselect, onActivate: setActive, onTreeSelect: handleTreeSelect, selected, size }), [ active, multiselect, setActive, handleTreeSelect, selected, size ]); return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TreeLabel, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_TreeContext.TreeContext.Provider, { value: treeContextValue, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_TreeContext.DepthContext.Provider, { value: 0, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { ...rest, "aria-label": hideLabel ? label : void 0, "aria-labelledby": !hideLabel ? labelId : void 0, "aria-multiselectable": multiselect || void 0, className: treeClasses, onKeyDown: handleKeyDown, ref: treeRootRef, role: "tree", children }) }) })] }); }; TreeView.propTypes = { active: prop_types.default.oneOfType([prop_types.default.string, prop_types.default.number]), children: prop_types.default.node, className: prop_types.default.string, hideLabel: prop_types.default.bool, label: prop_types.default.string.isRequired, multiselect: prop_types.default.bool, onActivate: prop_types.default.func, onSelect: prop_types.default.func, selected: prop_types.default.arrayOf(prop_types.default.oneOfType([prop_types.default.string, prop_types.default.number])), size: prop_types.default.oneOf(["xs", "sm"]) }; TreeView.TreeNode = require_TreeNode.default; //#endregion exports.default = TreeView;