UNPKG

@itwin/presentation-hierarchies-react

Version:

React components based on `@itwin/presentation-hierarchies`

143 lines 11.8 kB
"use strict"; 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