UNPKG

@hitachivantara/uikit-react-core

Version:
326 lines (325 loc) 10.5 kB
import { HvTypography } from "../../Typography/Typography.js"; import { useForkRef } from "../../hooks/useForkRef.js"; import { HvIcon } from "../../icons.js"; import { HvTooltip } from "../../Tooltip/Tooltip.js"; import { HvOverflowTooltip } from "../../OverflowTooltip/OverflowTooltip.js"; import { HvAvatar } from "../../Avatar/Avatar.js"; import { DescendantProvider, useDescendant } from "../../TreeView/internals/DescendantProvider.js"; import { VerticalNavigationContext } from "../VerticalNavigationContext.js"; import { TreeViewControlContext, TreeViewStateContext } from "./TreeViewContext.js"; import { useClasses } from "./TreeViewItem.styles.js"; import { mergeStyles, useDefaultProps } from "@hitachivantara/uikit-react-utils"; import { forwardRef, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react"; import { jsx, jsxs } from "react/jsx-runtime"; //#region src/VerticalNavigation/TreeView/TreeViewItem.tsx var preventSelection = (event, disabled) => { if (event.shiftKey || event.ctrlKey || event.metaKey || disabled) event.preventDefault(); }; var HvVerticalNavigationTreeViewItem = forwardRef(function HvVerticalNavigationTreeViewItem(props, ref) { const { id: idProp, className, classes: classesProp, disabled: disabledProp = false, selectable: selectableProp, nodeId, icon = null, label, href, target, payload, onClick, onMouseDown, onFocus, children, disableTooltip, ...others } = useDefaultProps("HvVerticalNavigationTreeViewItem", props); const { classes, cx } = useClasses(classesProp); const treeViewControlContext = useContext(TreeViewControlContext); const { isExpanded, isSelected, isFocused, isDisabled, isChildSelected } = useContext(TreeViewStateContext); const { treeId, mode, collapsible, toggleExpansion, multiSelect, selectNode, selectRange, disabledItemsFocusable, registerNode, unregisterNode, mapFirstChar, unMapFirstChar, focus } = treeViewControlContext; const treeviewMode = mode === "treeview"; let id = null; const groupId = useId(); if (idProp != null) id = idProp; else if (treeId && nodeId) id = `${treeId}-${nodeId}`; const [treeitemElement, setTreeitemElement] = useState(); const contentRef = useRef(null); const handleRef = useForkRef(setTreeitemElement, ref); const descendant = useMemo(() => ({ element: treeitemElement, id: nodeId }), [nodeId, treeitemElement]); const { isOpen, useIcons, hasAnyChildWithData } = useContext(VerticalNavigationContext); const { index, parentId, level } = useDescendant(descendant); const expandable = collapsible && Array.isArray(children); const expanded = isExpanded ? isExpanded(nodeId) : false; const focused = isFocused ? isFocused(nodeId) : false; const selected = isSelected ? isSelected(nodeId) : false; const disabled = isDisabled ? isDisabled(nodeId) : false; const selectable = selectableProp != null ? selectableProp : !collapsible || !expandable || !isOpen; useEffect(() => { if (registerNode && unregisterNode && index !== -1) { registerNode({ id: nodeId, idAttribute: id, index, parentId, selectable, expandable, disabled: disabledProp, onFocus, payload }); return () => { unregisterNode(nodeId); }; } }, [ registerNode, unregisterNode, parentId, index, nodeId, expandable, disabledProp, id, selectable, onFocus, payload ]); useEffect(() => { if (mapFirstChar && unMapFirstChar && label && contentRef.current?.textContent) { mapFirstChar(nodeId, contentRef.current?.textContent.slice(0, 1).toLowerCase()); return () => { unMapFirstChar(nodeId); }; } }, [ mapFirstChar, unMapFirstChar, nodeId, label ]); let ariaSelected; if (multiSelect) ariaSelected = selected; else if (selected) ariaSelected = true; const handleFocus = useCallback((event) => { if (event.target === event.currentTarget) (event.target.ownerDocument || document).getElementById(treeId).focus({ preventScroll: true }); const unfocusable = !disabledItemsFocusable && disabled; if (!focused && event.currentTarget === event.target && !unfocusable && focus) focus(event, nodeId); }, [ disabled, disabledItemsFocusable, focus, focused, nodeId, treeId ]); const handleExpansion = useCallback((event) => { if (!disabled) { if (treeviewMode && !focused && focus) focus(event, nodeId); const multiple = multiSelect && (event.shiftKey || event.ctrlKey || event.metaKey); if (expandable && isOpen && !(multiple && isExpanded && isExpanded(nodeId))) { if (toggleExpansion) toggleExpansion(event, nodeId); } } }, [ disabled, expandable, focus, focused, isExpanded, multiSelect, nodeId, toggleExpansion, treeviewMode, isOpen ]); const handleSelection = useCallback((event) => { if (selectable && !disabled) { if (treeviewMode && !focused && focus) focus(event, nodeId); if (multiSelect && (event.shiftKey || event.ctrlKey || event.metaKey)) { if (event.shiftKey) { if (selectRange) return selectRange(event, { end: nodeId }); } else if (selectNode) return selectNode(event, nodeId, true); } else if (selectNode) return selectNode(event, nodeId); } else return false; }, [ disabled, focus, focused, multiSelect, nodeId, selectNode, selectRange, selectable, treeviewMode ]); const handleMouseDown = useCallback((event) => { preventSelection(event, disabled); if (onMouseDown) onMouseDown(event); }, [disabled, onMouseDown]); const handleClick = useCallback((event) => { if (!disabled) { if (expandable && isOpen) handleExpansion(event); if (selectable) handleSelection(event); } if (onClick) onClick(event); }, [ disabled, expandable, handleExpansion, handleSelection, onClick, selectable, isOpen ]); const handleKeyDown = useCallback((event) => { let isEventHandled = false; const { key } = event; if (event.altKey || event.ctrlKey || event.metaKey || event.currentTarget !== event.target) return; if (contentRef.current === event.currentTarget) { if (key === "Enter" || key === " ") { if (expandable && isOpen) isEventHandled = handleExpansion(event); if (selectable) isEventHandled = handleSelection(event); } if (isEventHandled) { event.preventDefault(); event.stopPropagation(); } } }, [ expandable, handleExpansion, handleSelection, selectable, isOpen ]); const renderedContent = useMemo(() => { const buttonLinkProps = { href, target }; const hasChildren = !!children; const showTooltip = !hasChildren && !isOpen && !disableTooltip; const isLink = href !== void 0 && !disabled; return /* @__PURE__ */ jsx(HvTooltip, { placement: "right", title: showTooltip && label, children: /* @__PURE__ */ jsxs(HvTypography, { component: isLink ? "a" : "div", ...isLink ? buttonLinkProps : null, ref: contentRef, classes: { root: cx(classes.content, { [classes.link]: isLink, [classes.minimized]: !isOpen }) }, variant: "body", disabled, onClick: handleClick, onMouseDown: handleMouseDown, style: { paddingLeft: (useIcons || !isOpen ? 0 : 10) + level * (collapsible ? 16 : 10) }, role: isLink ? void 0 : "button", ...treeviewMode ? { tabIndex: -1, onFocus: handleFocus } : { tabIndex: selectable || expandable ? 0 : -1, onKeyDown: handleKeyDown, "aria-current": selectable && selected || !isOpen && isChildSelected?.(nodeId) ? href ? "page" : true : void 0, "aria-expanded": expandable ? expanded : void 0, "aria-controls": isOpen && expandable ? groupId : void 0, "aria-label": payload?.label }, children: [ /* @__PURE__ */ jsxs("div", { className: classes.icon, style: mergeStyles(void 0, { "--icon-margin-left": hasAnyChildWithData ? "auto" : "unset" }), children: [!icon && useIcons ? /* @__PURE__ */ jsx(HvAvatar, { variant: "square", size: "xs", backgroundColor: "textSubtle", children: payload?.label?.slice(0, 1) }) : useIcons && icon, hasChildren && !isOpen ? /* @__PURE__ */ jsx(HvIcon, { name: "Forwards", size: "xs", compact: true }) : hasAnyChildWithData && !isOpen && /* @__PURE__ */ jsx("div", {})] }), isOpen && /* @__PURE__ */ jsx("div", { className: cx(classes.label, { [classes.labelIcon]: useIcons, [classes.labelExpandable]: !!expandable }), children: /* @__PURE__ */ jsx(HvOverflowTooltip, { data: label }) }), isOpen && expandable && /* @__PURE__ */ jsx(HvIcon, { name: "CaretDown", size: "xs", rotate: expanded }) ] }) }); }, [ href, target, children, isOpen, disableTooltip, disabled, label, groupId, cx, classes.content, classes.link, classes.minimized, classes.icon, classes.label, classes.labelIcon, classes.labelExpandable, handleClick, handleMouseDown, useIcons, level, collapsible, treeviewMode, handleFocus, selectable, expandable, handleKeyDown, selected, isChildSelected, nodeId, expanded, payload?.label, icon, hasAnyChildWithData ]); const renderedChildren = useMemo(() => children && /* @__PURE__ */ jsx("ul", { id: groupId, className: classes.group, role: treeviewMode ? "group" : void 0, children }), [ children, classes?.group, groupId, treeviewMode ]); return /* @__PURE__ */ jsxs("li", { ref: handleRef, id: id ?? void 0, className: cx(classes.node, { [classes.expandable]: expandable, [classes.collapsed]: expandable && !expanded, [classes.expanded]: expandable && expanded, [classes.selectable]: selectable && !disabled, [classes.unselectable]: !disabled && !selectable, [classes.selected]: !disabled && selectable && selected || !isOpen && useIcons && isChildSelected && isChildSelected(nodeId), [classes.unselected]: !disabled && selectable && !selected, [classes.focused]: focused, [classes.hide]: !isOpen && !useIcons, [classes.disabled]: disabled }, className), "data-hasicon": icon != null ? true : void 0, ...mode === "treeview" && { role: "treeitem", "aria-selected": ariaSelected, "aria-expanded": expandable ? expanded : void 0, "aria-disabled": disabled ? true : void 0 }, ...others, children: [renderedContent, isOpen && /* @__PURE__ */ jsx(DescendantProvider, { id: nodeId, level: level + 1, children: renderedChildren })] }); }); //#endregion export { HvVerticalNavigationTreeViewItem };