@hitachivantara/uikit-react-core
Version:
UI Kit Core React components.
326 lines (325 loc) • 10.5 kB
JavaScript
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 };