@itwin/presentation-hierarchies-react
Version:
React components based on `@itwin/presentation-hierarchies`
143 lines • 11.8 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TreeNodeRenderer = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
require("./TreeNodeRenderer.css");
const classnames_1 = __importDefault(require("classnames"));
const react_1 = require("react");
const itwinui_icons_react_1 = require("@itwin/itwinui-icons-react");
const itwinui_react_1 = require("@itwin/itwinui-react");
const presentation_hierarchies_1 = require("@itwin/presentation-hierarchies");
const Utils_js_1 = require("../internal/Utils.js");
const TreeNode_js_1 = require("../TreeNode.js");
const LocalizationContext_js_1 = require("./LocalizationContext.js");
/**
* A component that renders `RenderedTreeNode` using the `TreeNode` component from `@itwin/itwinui-react`.
*
* @see `TreeRenderer`
* @see https://itwinui.bentley.com/docs/tree
* @public
*/
exports.TreeNodeRenderer = (0, react_1.forwardRef)(({ node, expandNode, getIcon, getLabel, getSublabel, onFilterClick, onNodeClick, onNodeKeyDown, isSelected, isDisabled, actionButtonsClassName, getHierarchyLevelDetails, reloadTree, size, filterButtonsVisibility, ...treeNodeProps }, forwardedRef) => {
const { localizedStrings } = (0, LocalizationContext_js_1.useLocalizationContext)();
if ("type" in node && node.type === "ChildrenPlaceholder") {
return (0, jsx_runtime_1.jsx)(PlaceholderNode, { ...treeNodeProps, ref: forwardedRef, size: size });
}
if ((0, TreeNode_js_1.isPresentationHierarchyNode)(node)) {
return ((0, jsx_runtime_1.jsx)(HierarchyNodeRenderer, { ...treeNodeProps, ref: forwardedRef, node: node, expandNode: expandNode, getIcon: getIcon, getLabel: getLabel, getSublabel: getSublabel, onFilterClick: onFilterClick, onNodeClick: onNodeClick, onNodeKeyDown: onNodeKeyDown, isSelected: isSelected, isDisabled: isDisabled, actionButtonsClassName: actionButtonsClassName, getHierarchyLevelDetails: getHierarchyLevelDetails, filterButtonsVisibility: filterButtonsVisibility }));
}
if (node.type === "ResultSetTooLarge") {
const hierarchyLevelDetails = getHierarchyLevelDetails?.(node.parentNodeId);
const isFilterable = hierarchyLevelDetails?.hierarchyNode &&
!presentation_hierarchies_1.HierarchyNode.isGroupingNode(hierarchyLevelDetails.hierarchyNode) &&
hierarchyLevelDetails.hierarchyNode.supportsFiltering;
return ((0, jsx_runtime_1.jsx)(ResultSetTooLargeNode, { ...treeNodeProps, ref: forwardedRef, limit: node.resultSetSizeLimit, onOverrideLimit: hierarchyLevelDetails ? (limit) => hierarchyLevelDetails.setSizeLimit(limit) : undefined, onFilterClick: onFilterClick && hierarchyLevelDetails && isFilterable
? () => {
onFilterClick(hierarchyLevelDetails);
}
: undefined }));
}
if (node.type === "NoFilterMatches") {
return ((0, jsx_runtime_1.jsx)(itwinui_react_1.TreeNode, { ...treeNodeProps, ref: forwardedRef, label: localizedStrings.noFilteredChildren, isDisabled: true, onExpanded: /* c8 ignore next */ () => { } }));
}
const onRetry = reloadTree ? () => reloadTree({ parentNodeId: node.parentNodeId, state: "reset" }) : undefined;
return ((0, jsx_runtime_1.jsx)(itwinui_react_1.TreeNode, { ...treeNodeProps, ref: forwardedRef, label: (0, jsx_runtime_1.jsx)(ErrorNodeLabel, { message: node.message, onRetry: onRetry }), isDisabled: true, onExpanded: /* c8 ignore next */ () => { } }));
});
exports.TreeNodeRenderer.displayName = "TreeNodeRenderer";
const HierarchyNodeRenderer = (0, react_1.forwardRef)(({ node, expandNode, getIcon, getLabel, getSublabel, onFilterClick, onNodeClick, onNodeKeyDown, isSelected, isDisabled, actionButtonsClassName, getHierarchyLevelDetails, filterButtonsVisibility, ...treeNodeProps }, forwardedRef) => {
const { localizedStrings } = (0, LocalizationContext_js_1.useLocalizationContext)();
const applyFilterButtonRef = (0, react_1.useRef)(null);
const nodeRef = (0, react_1.useRef)(null);
const ref = useMergedRefs(forwardedRef, nodeRef);
const prevIsFiltered = (0, react_1.useRef)(node.isFiltered);
(0, react_1.useEffect)(() => {
// If the node is filtered, focus the apply filter button
if (node.isFiltered && !prevIsFiltered.current) {
applyFilterButtonRef.current?.focus();
}
prevIsFiltered.current = node.isFiltered;
}, [node.isFiltered]);
return ((0, jsx_runtime_1.jsx)(itwinui_react_1.TreeNode, { ...treeNodeProps, ref: ref, isSelected: isSelected, isDisabled: isDisabled, className: (0, classnames_1.default)(treeNodeProps.className, "stateless-tree-node", { filtered: node.isFiltered }), onClick: (event) => !isDisabled && onNodeClick?.(node, !isSelected, event), onKeyDown: (event) => {
// Ignore if it is called on the element inside, e.g. checkbox or expander
if (!isDisabled && event.target === nodeRef.current) {
onNodeKeyDown?.(node, !isSelected, event);
}
}, onExpanded: (_, isExpanded) => {
expandNode(node.id, isExpanded);
}, icon: getIcon ? getIcon(node) : undefined, label: getLabel ? getLabel(node) : node.label, sublabel: getSublabel ? getSublabel(node) : undefined, children: (0, jsx_runtime_1.jsxs)(itwinui_react_1.ButtonGroup, { className: (0, classnames_1.default)("action-buttons", actionButtonsClassName), children: [getHierarchyLevelDetails && node.isFiltered ? ((0, jsx_runtime_1.jsx)(itwinui_react_1.IconButton, { className: "filtering-action-button", styleType: "borderless", size: "small", label: localizedStrings.clearHierarchyLevelFilter, onClick: (e) => {
e.stopPropagation();
getHierarchyLevelDetails(node.id)?.setInstanceFilter(undefined);
applyFilterButtonRef.current?.focus();
}, children: (0, jsx_runtime_1.jsx)(itwinui_icons_react_1.SvgRemove, {}) })) : null, onFilterClick && node.isFilterable && (filterButtonsVisibility !== "hide" || node.isFiltered) ? ((0, jsx_runtime_1.jsx)(itwinui_react_1.IconButton, { ref: applyFilterButtonRef, className: "filtering-action-button", styleType: "borderless", size: "small", label: localizedStrings.filterHierarchyLevel, onClick: (e) => {
e.stopPropagation();
const hierarchyLevelDetails = getHierarchyLevelDetails?.(node.id);
hierarchyLevelDetails && onFilterClick(hierarchyLevelDetails);
}, children: node.isFiltered ? (0, jsx_runtime_1.jsx)(itwinui_icons_react_1.SvgFilter, {}) : (0, jsx_runtime_1.jsx)(itwinui_icons_react_1.SvgFilterHollow, {}) })) : null] }) }));
});
HierarchyNodeRenderer.displayName = "HierarchyNodeRenderer";
const PlaceholderNode = (0, react_1.forwardRef)(({ size, ...props }, forwardedRef) => {
const { localizedStrings } = (0, LocalizationContext_js_1.useLocalizationContext)();
return ((0, jsx_runtime_1.jsx)(itwinui_react_1.TreeNode, { ...props, ref: forwardedRef, label: localizedStrings.loading, icon: (0, jsx_runtime_1.jsx)(itwinui_react_1.ProgressRadial, { size: "x-small", indeterminate: true, title: localizedStrings.loading, className: (0, classnames_1.default)(props.className, { "stateless-tree-node-small-spinner": size === "small" }) }), onExpanded: /* c8 ignore next */ () => { } }));
});
PlaceholderNode.displayName = "PlaceholderNode";
const ResultSetTooLargeNode = (0, react_1.forwardRef)(({ onFilterClick, onOverrideLimit, limit, ...props }, forwardedRef) => {
return ((0, jsx_runtime_1.jsx)(itwinui_react_1.TreeNode, { ...props, ref: forwardedRef, className: "stateless-tree-node", label: (0, jsx_runtime_1.jsx)(ResultSetTooLargeNodeLabel, { limit: limit, onFilterClick: onFilterClick, onOverrideLimit: onOverrideLimit }), onExpanded: /* c8 ignore next */ () => { }, isDisabled: true }));
});
ResultSetTooLargeNode.displayName = "ResultSetTooLargeNode";
function ErrorNodeLabel({ message, onRetry }) {
const { localizedStrings } = (0, LocalizationContext_js_1.useLocalizationContext)();
return ((0, jsx_runtime_1.jsxs)(itwinui_react_1.Flex, { flexDirection: "row", gap: "xs", title: message, alignItems: "start", children: [(0, jsx_runtime_1.jsx)(itwinui_react_1.Text, { children: message }), onRetry ? (0, jsx_runtime_1.jsx)(itwinui_react_1.Anchor, { onClick: onRetry, children: localizedStrings?.retry }) : null] }));
}
function ResultSetTooLargeNodeLabel({ onFilterClick, onOverrideLimit, limit }) {
const { localizedStrings } = (0, LocalizationContext_js_1.useLocalizationContext)();
const supportsFiltering = !!onFilterClick;
const supportsLimitOverride = !!onOverrideLimit && limit < Utils_js_1.MAX_LIMIT_OVERRIDE;
const limitExceededMessage = createLocalizedMessage(supportsFiltering ? localizedStrings.resultLimitExceededWithFiltering : localizedStrings.resultLimitExceeded, limit, onFilterClick);
const increaseLimitMessage = supportsLimitOverride
? createLocalizedMessage(supportsFiltering ? localizedStrings.increaseHierarchyLimitWithFiltering : localizedStrings.increaseHierarchyLimit, Utils_js_1.MAX_LIMIT_OVERRIDE, () => onOverrideLimit(Utils_js_1.MAX_LIMIT_OVERRIDE))
: { title: "", element: null };
const title = `${limitExceededMessage.title} ${increaseLimitMessage.title}`;
return ((0, jsx_runtime_1.jsxs)(itwinui_react_1.Flex, { flexDirection: "column", gap: "3xs", title: title, alignItems: "start", children: [limitExceededMessage.element, increaseLimitMessage.element] }));
}
function createLocalizedMessage(message, limit, onClick) {
const limitStr = limit.toLocaleString(undefined, { useGrouping: true });
const messageWithLimit = message.replace("{{limit}}", limitStr);
const exp = new RegExp("<link>(.*)</link>");
const match = messageWithLimit.match(exp);
if (!match) {
return {
title: messageWithLimit,
element: ((0, jsx_runtime_1.jsx)(itwinui_react_1.Text, { as: "span", style: { whiteSpace: "normal" }, children: messageWithLimit })),
};
}
const [fullText, innerText] = match;
const [textBefore, textAfter] = messageWithLimit.split(fullText);
return {
title: messageWithLimit.replace(fullText, innerText),
element: ((0, jsx_runtime_1.jsxs)("div", { children: [textBefore ? ((0, jsx_runtime_1.jsx)(itwinui_react_1.Text, { as: "span", style: { whiteSpace: "normal" }, children: textBefore })) : null, (0, jsx_runtime_1.jsx)(itwinui_react_1.Anchor, { style: { whiteSpace: "normal" }, underline: true, onClick: (e) => {
e.stopPropagation();
onClick?.();
}, children: innerText }), textAfter ? ((0, jsx_runtime_1.jsx)(itwinui_react_1.Text, { as: "span", style: { whiteSpace: "normal" }, children: textAfter })) : null] })),
};
}
function useMergedRefs(...refs) {
return (0, react_1.useCallback)((instance) => {
refs.forEach((ref) => {
if (typeof ref === "function") {
ref(instance);
}
else if (ref) {
ref.current = instance;
}
});
}, // eslint-disable-next-line react-hooks/exhaustive-deps
[...refs]);
}
//# sourceMappingURL=TreeNodeRenderer.js.map