UNPKG

@itwin/presentation-hierarchies

Version:

A package for creating hierarchies based on data in iTwin.js iModels.

282 lines 13.8 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); exports.HierarchyFilteringPath = void 0; exports.extractFilteringProps = extractFilteringProps; exports.createHierarchyFilteringHelper = createHierarchyFilteringHelper; const HierarchyNodeIdentifier_js_1 = require("./HierarchyNodeIdentifier.js"); const HierarchyNodeKey_js_1 = require("./HierarchyNodeKey.js"); var HierarchyFilteringPathOptions; (function (HierarchyFilteringPathOptions) { function mergeAutoExpandOptions(lhs, rhs) { if (rhs === true || lhs === true) { return true; } if (!rhs || !lhs) { return !!rhs ? rhs : lhs; } // eslint-disable-next-line @typescript-eslint/no-deprecated const lhsDepth = "depth" in lhs ? lhs.depth : "depthInPath" in lhs ? lhs.depthInPath : lhs.depthInHierarchy; // eslint-disable-next-line @typescript-eslint/no-deprecated const rhsDepth = "depth" in rhs ? rhs.depth : "depthInPath" in rhs ? rhs.depthInPath : rhs.depthInHierarchy; const isLhsDepthBasedOnPath = // eslint-disable-next-line @typescript-eslint/no-deprecated "depthInPath" in lhs ? true : !("key" in lhs) && !("depthInHierarchy" in lhs) && !lhs.includeGroupingNodes; const isRhsDepthBasedOnPath = // eslint-disable-next-line @typescript-eslint/no-deprecated "depthInPath" in rhs ? true : !("key" in rhs) && !("depthInHierarchy" in rhs) && !rhs.includeGroupingNodes; if (isLhsDepthBasedOnPath) { if (isRhsDepthBasedOnPath) { return lhsDepth > rhsDepth ? lhs : rhs; } return lhs; } if (isRhsDepthBasedOnPath) { return rhs; } return lhsDepth > rhsDepth ? lhs : rhs; } HierarchyFilteringPathOptions.mergeAutoExpandOptions = mergeAutoExpandOptions; })(HierarchyFilteringPathOptions || (HierarchyFilteringPathOptions = {})); /** @public */ // eslint-disable-next-line @typescript-eslint/no-redeclare var HierarchyFilteringPath; (function (HierarchyFilteringPath) { /** * Normalizes the hierarchy filtering path to the object form. * @public */ function normalize(source) { if (Array.isArray(source)) { return { path: source }; } return source; } HierarchyFilteringPath.normalize = normalize; /** * Merges two given `HierarchyFilteringPathOptions` objects. * - if both inputs are `undefined`, `undefined` is returned, * - else if one of the inputs is `undefined`, the other one is returned. * - else, merge each option individually. * * For the `autoExpand` attribute, the merge chooses to auto-expand as deep as the deepest input: * - if any one of the inputs is `true`, return `true`, * - else if only one of the inputs is an object, return it, * - else if both inputs are falsy, return `false` or `undefined`, * - else: * - if only one of the inputs has `includeGroupingNodes` set to `true` or `key` defined or `depthInHierarchy` set, return the other one, * - else return the one with greater `depth`, `depthInPath` or `depthInHierarchy`. * * @public */ function mergeOptions(lhs, rhs) { if (!lhs || !rhs) { return lhs ?? rhs; } return { autoExpand: HierarchyFilteringPathOptions.mergeAutoExpandOptions(lhs.autoExpand, rhs.autoExpand) }; } HierarchyFilteringPath.mergeOptions = mergeOptions; })(HierarchyFilteringPath || (exports.HierarchyFilteringPath = HierarchyFilteringPath = {})); /** * An utility that extracts filtering properties from given root level filtering props or * the parent node. Returns `undefined` if filtering props are not present. * @public * @deprecated in 1.3. Use `createHierarchyFilteringHelper` instead. */ /* v8 ignore start */ function extractFilteringProps(rootLevelFilteringProps, parentNode) { return extractFilteringPropsInternal(rootLevelFilteringProps, parentNode); } /* v8 ignore stop */ function extractFilteringPropsInternal(rootLevelFilteringProps, parentNode) { if (!parentNode) { return rootLevelFilteringProps ? { filteredNodePaths: rootLevelFilteringProps, hasFilterTargetAncestor: false } : /** v8 ignore next */ undefined; } return parentNode.filtering?.filteredChildrenIdentifierPaths ? { filteredNodePaths: parentNode.filtering.filteredChildrenIdentifierPaths, hasFilterTargetAncestor: !!parentNode.filtering.hasFilterTargetAncestor || !!parentNode.filtering.isFilterTarget, } : undefined; } /** * Creates a set of utilities for making it easier to filter the given hierarchy * level. * * @public */ function createHierarchyFilteringHelper(rootLevelFilteringProps, parentNode) { const filteringProps = extractFilteringPropsInternal(rootLevelFilteringProps, parentNode); const hasFilter = !!filteringProps; return { /** * Returns a flag indicating if the hierarchy level is filtered. */ hasFilter, /** * Returns a flag indicating whether this hierarchy level has an ancestor node * that is a filter target. That generally means that this and all downstream hierarchy * levels should be displayed without filter being applied to them, even if filter paths * say otherwise. */ hasFilterTargetAncestor: filteringProps?.hasFilterTargetAncestor ?? false, /** * Returns a list of hierarchy node identifiers that apply specifically for this * hierarchy level. Returns `undefined` if filtering is not applied to this level. */ getChildNodeFilteringIdentifiers: () => { if (!hasFilter) { return undefined; } return filteringProps.filteredNodePaths .map(HierarchyFilteringPath.normalize) .filter(({ path }) => path.length > 0) .map(({ path }) => path[0]); }, /** * When a hierarchy node is created for a filtered hierarchy level, it needs some attributes (e.g. `filtering` * and `autoExpand`) to be set based on the filter paths and filtering options. This function calculates * these props for a child node based on its key or path matcher. * * When using `pathMatcher` prop, callers have more flexibility to decide whether the given `HierarchyNodeIdentifier` applies * to their node. For example, only some parts of the identifier can be checked for improved performance. Otherwise, the * `nodeKey` prop can be used to check the whole identifier. */ createChildNodeProps: (props) => { // TODO: MISSING_COVERAGE /* v8 ignore if -- @preserve */ if (!hasFilter) { return undefined; } const reducer = new MatchingFilteringPathsReducer(filteringProps?.hasFilterTargetAncestor); filteringProps.filteredNodePaths.forEach((filteredPath) => { const normalizedPath = HierarchyFilteringPath.normalize(filteredPath); // TODO: MISSING_COVERAGE /* v8 ignore start */ if ("nodeKey" in props && ((HierarchyNodeKey_js_1.HierarchyNodeKey.isGeneric(props.nodeKey) && HierarchyNodeIdentifier_js_1.HierarchyNodeIdentifier.equal(normalizedPath.path[0], props.nodeKey)) || (HierarchyNodeKey_js_1.HierarchyNodeKey.isInstances(props.nodeKey) && props.nodeKey.instanceKeys.some((ik) => HierarchyNodeIdentifier_js_1.HierarchyNodeIdentifier.equal(normalizedPath.path[0], ik))))) { reducer.accept(normalizedPath); /* v8 ignore stop */ } else if ("pathMatcher" in props && props.pathMatcher(normalizedPath.path[0])) { reducer.accept(normalizedPath); } }); return reducer.getNodeProps(parentNode); }, /** * Similar to `createChildNodeProps`, but takes an async `pathMatcher` prop. */ createChildNodePropsAsync: (props) => { // TODO: MISSING_COVERAGE /* v8 ignore if -- @preserve */ if (!hasFilter) { return undefined; } const reducer = new MatchingFilteringPathsReducer(filteringProps?.hasFilterTargetAncestor); const matchedPathPromises = new Array(); for (const filteredChildrenNodeIdentifierPath of filteringProps.filteredNodePaths) { const normalizedPath = HierarchyFilteringPath.normalize(filteredChildrenNodeIdentifierPath); // TODO: MISSING_COVERAGE /* v8 ignore if -- @preserve */ if (normalizedPath.path.length === 0) { continue; } const matchesPossiblyPromise = props.pathMatcher(normalizedPath.path[0]); if (matchesPossiblyPromise instanceof Promise) { matchedPathPromises.push(matchesPossiblyPromise.then((matches) => (matches ? normalizedPath : undefined))); continue; } if (!matchesPossiblyPromise) { continue; } reducer.accept(normalizedPath); } if (matchedPathPromises.length === 0) { return reducer.getNodeProps(parentNode); } return Promise.all(matchedPathPromises) .then((matchedPath) => matchedPath.forEach((normalizedPath) => normalizedPath && reducer.accept(normalizedPath))) .then(() => reducer.getNodeProps(parentNode)); }, }; } class MatchingFilteringPathsReducer { _hasFilterTargetAncestor; _filteredChildrenIdentifierPaths = new Array(); _isFilterTarget = false; _filterTargetOptions = undefined; _autoExpandOption = false; constructor(_hasFilterTargetAncestor) { this._hasFilterTargetAncestor = _hasFilterTargetAncestor; } accept(normalizedPath) { const { path, options } = normalizedPath; // TODO: MISSING_COVERAGE /* v8 ignore else -- @preserve */ if (path.length === 1) { this._isFilterTarget = true; this._filterTargetOptions = HierarchyFilteringPath.mergeOptions(this._filterTargetOptions, options); } else if (path.length > 1) { this._filteredChildrenIdentifierPaths.push({ path: path.slice(1), options }); this._autoExpandOption = HierarchyFilteringPathOptions.mergeAutoExpandOptions(options?.autoExpand, this._autoExpandOption); } } getNeedsAutoExpand(parentNode) { if (this._autoExpandOption === true) { return true; } if (typeof this._autoExpandOption === "object") { const parentLength = !parentNode ? 0 : "key" in this._autoExpandOption || "depthInHierarchy" in this._autoExpandOption || // TODO: MISSING_COVERAGE /* v8 ignore start */ // eslint-disable-next-line @typescript-eslint/no-deprecated ("includeGroupingNodes" in this._autoExpandOption && this._autoExpandOption.includeGroupingNodes) ? // TODO: MISSING_COVERAGE /* v8 ignore stop */ 1 + parentNode.parentKeys.length : 1 + parentNode.parentKeys.filter((key) => !HierarchyNodeKey_js_1.HierarchyNodeKey.isGrouping(key)).length; const depth = "depthInHierarchy" in this._autoExpandOption ? this._autoExpandOption.depthInHierarchy : "depth" in this._autoExpandOption ? // eslint-disable-next-line @typescript-eslint/no-deprecated this._autoExpandOption.depth : // With `depthInPath` option we don't want to expand node that is at the `depthInPath` position // TODO: MISSING_COVERAGE /* v8 ignore next */ this._autoExpandOption.depthInPath - 1; return parentLength < depth; } return false; } getNodeProps(parentNode) { return { ...(this._hasFilterTargetAncestor || this._isFilterTarget || this._filteredChildrenIdentifierPaths.length > 0 ? { filtering: { ...(this._hasFilterTargetAncestor ? { hasFilterTargetAncestor: true } : undefined), ...(this._isFilterTarget ? { isFilterTarget: true, filterTargetOptions: this._filterTargetOptions } : undefined), ...(this._filteredChildrenIdentifierPaths.length > 0 ? { filteredChildrenIdentifierPaths: this._filteredChildrenIdentifierPaths } : undefined), }, } : undefined), ...(this.getNeedsAutoExpand(parentNode) ? { autoExpand: true } : undefined), }; } } //# sourceMappingURL=HierarchyFiltering.js.map