@evoke-platform/ui-components 1.10.0 → 1.10.1-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,4 @@
1
1
  import { AddRounded, UnfoldMore } from '@mui/icons-material';
2
- import { Typography } from '@mui/material';
3
2
  import { QueryBuilderMaterial } from '@react-querybuilder/material';
4
3
  import { isArray, isEmpty, startCase } from 'lodash';
5
4
  import React, { useEffect, useMemo, useState } from 'react';
@@ -7,7 +6,7 @@ import { QueryBuilder, RuleGroupBodyComponents, RuleGroupHeaderComponents, TestI
7
6
  import 'react-querybuilder/dist/query-builder.css';
8
7
  import escape from 'string-escape-regex';
9
8
  import { TrashCan } from '../../../icons/custom';
10
- import { Autocomplete, Button, IconButton } from '../../core';
9
+ import { Autocomplete, Button, IconButton, Typography } from '../../core';
11
10
  import { Box } from '../../layout';
12
11
  import { OverflowTextField } from '../OverflowTextField';
13
12
  import { difference } from '../util';
@@ -181,7 +180,7 @@ const customSelector = (props) => {
181
180
  });
182
181
  handleOnChange(propertyId);
183
182
  };
184
- return (React.createElement(React.Fragment, null, isTreeViewEnabled ? (React.createElement(PropertyTree, { value: val ?? value, rootObject: object, fetchObject: fetchObject, handleTreePropertySelect: handleTreePropertySelect })) : (React.createElement(Autocomplete, { options: opts, value: val ?? null, getOptionLabel: (option) => {
183
+ return (React.createElement(React.Fragment, null, isTreeViewEnabled ? (React.createElement(PropertyTree, { value: val ?? value, rootObject: object, fetchObject: fetchObject, propertyTreeMap: context.propertyTreeMap ?? {}, handleTreePropertySelect: handleTreePropertySelect })) : (React.createElement(Autocomplete, { options: opts, value: val ?? null, getOptionLabel: (option) => {
185
184
  if (typeof option === 'string') {
186
185
  return opts.find((o) => option === o.name)?.label || option;
187
186
  }
@@ -264,6 +263,18 @@ export const valueEditor = (props) => {
264
263
  }
265
264
  return ValueEditor(props);
266
265
  };
266
+ const getAllRuleIds = (rules) => {
267
+ const ids = [];
268
+ rules.forEach((rule) => {
269
+ if ('rules' in rule) {
270
+ ids.push(...getAllRuleIds(rule.rules));
271
+ }
272
+ else {
273
+ ids.push(rule.field);
274
+ }
275
+ });
276
+ return ids;
277
+ };
267
278
  const CriteriaBuilder = (props) => {
268
279
  const { properties, criteria, setCriteria, originalCriteria, enablePresetValues, presetValues, operators, disabled, disabledCriteria, hideBorder, presetGroupLabel, customValueEditor, treeViewOpts, disableRegexEscapeChars, } = props;
269
280
  const [propertyTreeMap, setPropertyTreeMap] = useState();
@@ -299,18 +310,7 @@ const CriteriaBuilder = (props) => {
299
310
  // they are then used in the custom query builder components to determine the input type etc
300
311
  const updatePropertyTreeMap = async () => {
301
312
  const newQuery = parseMongoDB(criteria || originalCriteria || {});
302
- const ids = [];
303
- const traverseRulesForIds = (rules) => {
304
- rules.forEach((rule) => {
305
- if ('rules' in rule) {
306
- traverseRulesForIds(rule.rules);
307
- }
308
- else {
309
- ids.push(rule.field);
310
- }
311
- });
312
- };
313
- traverseRulesForIds(newQuery.rules);
313
+ const ids = getAllRuleIds(newQuery.rules);
314
314
  let newPropertyTreeMap = {};
315
315
  const newPropertyTreeMapPromises = [];
316
316
  for (const id of ids) {
@@ -1,10 +1,12 @@
1
+ import { Property } from '@evoke-platform/context';
1
2
  import React from 'react';
2
3
  import { EvokeObject } from '../../../types';
3
4
  type PropertyTreeProps = {
4
5
  fetchObject: (id: string) => Promise<EvokeObject | undefined>;
5
6
  rootObject: EvokeObject;
6
- handleTreePropertySelect: (propertyId: string) => void;
7
+ handleTreePropertySelect: (propertyId: string) => Promise<void>;
7
8
  value: string | undefined;
9
+ propertyTreeMap: Record<string, Property>;
8
10
  };
9
- declare const PropertyTree: ({ fetchObject, handleTreePropertySelect, rootObject, value }: PropertyTreeProps) => React.JSX.Element;
11
+ declare const PropertyTree: (props: PropertyTreeProps) => React.JSX.Element;
10
12
  export default PropertyTree;
@@ -1,160 +1,83 @@
1
- import { ChevronRight, ExpandMore } from '@mui/icons-material';
1
+ import { ClickAwayListener } from '@mui/material';
2
2
  import React, { useEffect, useState } from 'react';
3
- import { Autocomplete, MenuItem, TreeView } from '../../core';
3
+ import { Autocomplete, RichTreeView } from '../../core';
4
+ import { Box } from '../../layout';
4
5
  import { OverflowTextField } from '../OverflowTextField';
5
- import PropertyTreeItem from './PropertyTreeItem';
6
- import { fetchDisplayNamePath, findPropertyById, setIdPaths, truncateNamePath, updateTreeNode } from './utils';
7
- const PropertyTree = ({ fetchObject, handleTreePropertySelect, rootObject, value }) => {
8
- const [expandedNodes, setExpandedNodes] = useState([]);
9
- const [objectPropertyNamePathMap, setObjectPropertyNamePathMap] = useState({});
10
- const [objectProperties, setObjectProperties] = useState(rootObject.properties ?? []);
11
- const [propertyOptions, setPropertyOptions] = useState([]);
12
- const NAME_PATH_LIMIT = 35;
13
- const originalValue = value ?? '';
14
- const flattenObjectProperties = (properties) => {
15
- let result = [];
16
- properties.forEach((property) => {
17
- const { children, ...rest } = property;
18
- const fullId = property.id;
19
- const fullName = property.name;
20
- const newProperty = {
21
- ...rest,
22
- id: fullId,
23
- name: fullName,
24
- };
25
- result.push(newProperty);
26
- // If the property has children, recursively process them
27
- if (children && children.length > 0) {
28
- const childProperties = flattenObjectProperties(children);
29
- result = result.concat(childProperties);
30
- }
31
- });
32
- return result;
33
- };
6
+ import { PropertyTreeItem } from './PropertyTreeItem';
7
+ import { findTreeItemById, truncateNamePath, updateTreeNode } from './utils';
8
+ const NAME_PATH_LIMIT = 35;
9
+ const PropertyTree = (props) => {
10
+ const { fetchObject, handleTreePropertySelect, rootObject, value, propertyTreeMap } = props;
11
+ const [options, setOptions] = useState([]);
12
+ const [expandedItems, setExpandedItems] = useState([]);
13
+ const [openDropdown, setOpenDropdown] = useState(false);
34
14
  useEffect(() => {
35
- const flattendProperties = flattenObjectProperties(objectProperties);
36
- setPropertyOptions(flattendProperties);
37
- if (objectProperties) {
38
- const fetchDisplayNames = async () => {
39
- const results = await Promise.all(flattendProperties.map(async (property) => {
40
- const objectPropertyNamePath = await fetchDisplayNamePath(property.id, rootObject, fetchObject);
41
- return { [property.id]: objectPropertyNamePath };
42
- }));
43
- let newObjectPropertyNamePathMap = {};
44
- results.forEach((result) => {
45
- newObjectPropertyNamePathMap = { ...newObjectPropertyNamePathMap, ...result };
46
- });
47
- setObjectPropertyNamePathMap(newObjectPropertyNamePathMap);
48
- //Fetch the display name path for the original value
49
- if (originalValue) {
50
- if (originalValue.includes('.')) {
51
- const parts = originalValue.split('.');
52
- if (parts.length >= 2) {
53
- if (!newObjectPropertyNamePathMap[originalValue]) {
54
- const fieldNamePath = await fetchDisplayNamePath(originalValue, rootObject, fetchObject);
55
- newObjectPropertyNamePathMap = {
56
- ...newObjectPropertyNamePathMap,
57
- [originalValue]: fieldNamePath,
58
- };
59
- setObjectPropertyNamePathMap(newObjectPropertyNamePathMap);
60
- }
61
- }
62
- }
63
- }
64
- };
65
- fetchDisplayNames();
66
- }
67
- }, [originalValue, rootObject, objectProperties, fetchObject]);
68
- const handleNodeToggle = (event, nodeIds) => {
69
- const newlyExpandedNodes = nodeIds.filter((id) => !expandedNodes.includes(id));
70
- newlyExpandedNodes.forEach((nodeId) => {
71
- handleLoadData(nodeId);
72
- });
73
- setExpandedNodes(nodeIds);
15
+ // Transform rootObject properties to TreeItem format
16
+ setOptions(rootObject.properties.map((property) => ({
17
+ id: property.id,
18
+ label: property.name,
19
+ value: property.id,
20
+ type: property.type,
21
+ objectId: property.objectId,
22
+ children: property.children
23
+ ? property.children.map((child) => ({
24
+ id: child.id,
25
+ label: child.name,
26
+ value: child.id,
27
+ type: child.type,
28
+ objectId: child.objectId,
29
+ }))
30
+ : undefined,
31
+ })));
32
+ setExpandedItems([]);
33
+ }, [rootObject.properties]);
34
+ const handleExpandedItemsChange = (e, itemIds) => {
35
+ setExpandedItems(itemIds);
74
36
  };
75
- const updateObjectProperties = (nodeId, newChildren) => {
76
- setObjectProperties((prevProperties) => updateTreeNode(prevProperties, nodeId, (node) => ({ ...node, children: newChildren })));
37
+ const handleUpdateNodeChildren = (nodeId, children) => {
38
+ setOptions((prevOptions) => updateTreeNode(prevOptions, nodeId, (node) => ({ ...node, children })));
77
39
  };
78
- const handleLoadData = async (nodeId) => {
79
- const availableProperty = findPropertyById(objectProperties, nodeId);
80
- if (!availableProperty || !availableProperty.objectId)
81
- return;
82
- const object = await fetchObject(availableProperty.objectId);
83
- if (object?.properties) {
84
- const properties = object.properties.filter((prop) => !['collection', 'image', 'documents'].includes(prop.type));
85
- // this step adds path specific ids to the user and address fields
86
- const newChildren = setIdPaths(properties, nodeId);
87
- updateObjectProperties(availableProperty.id, newChildren);
88
- }
89
- };
90
- const handleNodeSelect = (event, nodeId) => {
91
- const node = findPropertyById(objectProperties, nodeId);
92
- if (node?.children) {
93
- event.preventDefault();
94
- event.stopPropagation();
95
- }
96
- };
97
- const handleTreeItemClick = (propertyId) => {
98
- const property = findPropertyById(objectProperties, propertyId);
99
- // this prevents the selection of a parent node
100
- if (property && !property.children) {
101
- handleTreePropertySelect(property.id);
102
- }
103
- };
104
- return (React.createElement(Autocomplete, { "aria-label": "Property Selector", value: value, fullWidth: true, sx: {
105
- width: '37%',
106
- }, disableClearable: true, options: propertyOptions.map((property) => {
107
- return {
108
- label: objectPropertyNamePathMap[property.id],
109
- value: property.id,
110
- };
111
- }), componentsProps: {
112
- paper: {
113
- sx: {
114
- '& .MuiAutocomplete-option': {
115
- '&.Mui-focused': {
116
- '&:has(.MuiTreeItem-group)': {
117
- backgroundColor: 'transparent', // Remove background color if child of TreeView
118
- },
119
- },
120
- },
121
- },
122
- },
123
- }, getOptionLabel: (option) => {
40
+ return (React.createElement(Autocomplete, { "aria-label": "Property Selector", value: value, fullWidth: true, sx: { width: '37%' }, size: "small", disableClearable: true, options: options, open: openDropdown, onBlur: (e) => {
41
+ const targetComponents = e.relatedTarget?.getAttribute('id')?.split('-');
42
+ // If the user clicks on tree component with children, don't close the menu.
43
+ if (targetComponents?.[targetComponents.length - 1]) {
44
+ const treeItem = findTreeItemById(options, targetComponents[targetComponents.length - 1]);
45
+ // Only keep dropdown open if the clicked item has children AND was not selected
46
+ if (!treeItem?.children) {
47
+ setOpenDropdown(false);
48
+ }
49
+ }
50
+ else {
51
+ setOpenDropdown(false);
52
+ }
53
+ }, onOpen: () => setOpenDropdown(true), onClose: (e, reason) => {
54
+ // Only close if user clicked the caret close icon
55
+ if (reason === 'toggleInput') {
56
+ setOpenDropdown(false);
57
+ }
58
+ }, onFocus: () => setOpenDropdown(true), getOptionLabel: (option) => {
124
59
  // Retrieve the full name path from the map
125
60
  const namePath = typeof option === 'string'
126
- ? (objectPropertyNamePathMap[option] ?? '')
127
- : (objectPropertyNamePathMap[option.value] ?? '');
61
+ ? ((propertyTreeMap[option] ?? '')?.name ?? option)
62
+ : (propertyTreeMap[option.value]?.name ?? option.value);
128
63
  return truncateNamePath(namePath, NAME_PATH_LIMIT);
129
64
  }, renderInput: (params) => {
130
- const fullDisplayName = value && objectPropertyNamePathMap[value];
131
- return (React.createElement(OverflowTextField, { ...params, "aria-label": fullDisplayName, value: fullDisplayName, size: "small", placeholder: "Select a property", variant: "outlined" }));
132
- }, isOptionEqualToValue: (option, val) => {
133
- if (typeof val === 'string') {
134
- return option.value === val;
135
- }
136
- return option.value === val?.value;
137
- }, renderOption: (props, option) => {
138
- // Find the corresponding property in objectProperties
139
- const property = objectProperties.find((prop) => prop.id === option.value);
140
- if (property) {
141
- if (property.type === 'object') {
142
- return (React.createElement("li", { ...props },
143
- React.createElement(TreeView, { role: "menu", "aria-label": "Property Tree", defaultCollapseIcon: React.createElement(ExpandMore, null), defaultExpandIcon: React.createElement(ChevronRight, null), onNodeToggle: handleNodeToggle, expanded: expandedNodes, onNodeSelect: handleNodeSelect, sx: { width: '100%' } },
144
- React.createElement(PropertyTreeItem, { key: option.value, treeData: objectProperties, property: property, fetchObject: fetchObject, onClick: handleTreeItemClick }))));
145
- }
146
- // top level items
147
- return (React.createElement(MenuItem, { sx: {
148
- '&:hover': {
149
- backgroundColor: 'transparent',
150
- },
151
- '.MuiTreeItem-group &': {
152
- '&:hover': {
153
- backgroundColor: 'rgba(0, 0, 0, 0.04)',
154
- },
155
- },
156
- }, onClick: () => handleTreeItemClick(property?.id), key: property.id, value: property.id }, property.name));
157
- }
65
+ const fullDisplayName = value && propertyTreeMap[value]?.name;
66
+ return (React.createElement(OverflowTextField, { ...params, "aria-label": fullDisplayName, value: fullDisplayName, placeholder: "Select a property" }));
67
+ }, ListboxComponent: (props) => {
68
+ return (React.createElement(ClickAwayListener, { onClickAway: (e) => {
69
+ // Close the dropdown when clicking outside but don't close when clicking to the autocomplete input
70
+ if (!e.target.closest('.MuiAutocomplete-root')) {
71
+ setOpenDropdown(false);
72
+ }
73
+ } },
74
+ React.createElement(Box, { ...props },
75
+ React.createElement(RichTreeView, { sx: { width: '100%', paddingLeft: 0 }, role: "menu", "aria-label": "Property Tree", expandedItems: expandedItems, onExpandedItemsChange: handleExpandedItemsChange, items: options, slots: {
76
+ item: (itemProps) => (React.createElement(PropertyTreeItem, { ...itemProps, items: options, expanded: expandedItems, setExpanded: setExpandedItems, fetchObject: fetchObject, updateNodeChildren: handleUpdateNodeChildren, handleTreePropertySelect: async (propertyId) => {
77
+ await handleTreePropertySelect(propertyId);
78
+ setOpenDropdown(false);
79
+ } })),
80
+ } }))));
158
81
  } }));
159
82
  };
160
83
  export default PropertyTree;
@@ -1,10 +1,14 @@
1
+ import { TreeItemProps } from '@mui/x-tree-view';
1
2
  import React from 'react';
2
- import { ExpandedProperty, Obj } from '../../../types';
3
- type PropertyTreeItemProps = {
4
- property: ExpandedProperty | undefined;
3
+ import { Obj } from '../../../types';
4
+ import { TreeItem } from './types';
5
+ type PropertyTreeItemProps = TreeItemProps & {
6
+ items: TreeItem[];
7
+ expanded: string[];
8
+ setExpanded: (expanded: string[]) => void;
9
+ updateNodeChildren: (nodeId: string, children: TreeItem[]) => void;
5
10
  fetchObject: (id: string) => Promise<Obj | undefined>;
6
- treeData: ExpandedProperty[];
7
- onClick: (property: string) => void;
11
+ handleTreePropertySelect: (propertyId: string) => Promise<void>;
8
12
  };
9
- declare const PropertyTreeItem: ({ property, fetchObject, treeData, onClick }: PropertyTreeItemProps) => React.JSX.Element | null;
10
- export default PropertyTreeItem;
13
+ export declare const PropertyTreeItem: (props: PropertyTreeItemProps) => React.JSX.Element;
14
+ export {};
@@ -1,43 +1,45 @@
1
- import { TreeItem } from '@mui/lab';
2
- import { MenuItem } from '@mui/material';
3
1
  import React from 'react';
4
- const PropertyTreeItem = ({ property, fetchObject, treeData, onClick }) => {
5
- if (!property) {
6
- return null;
7
- }
8
- const hasChildren = property.children && property.children.length > 0;
9
- if (hasChildren) {
10
- // Parent node
11
- return (React.createElement(TreeItem, { nodeId: property.id, role: "menu", label: property.name, onClick: () => onClick(property.id), sx: {
12
- paddingTop: '6px',
13
- paddingBottom: '6px',
14
- '.MuiCollapse-root': {
15
- 'MuiTreeItem-group': {
16
- width: '100%',
17
- },
18
- },
19
- '& .MuiTreeItem-content': {
20
- '&:hover': {
21
- backgroundColor: 'transparent',
22
- },
23
- },
24
- '& .MuiTreeItem-group .MuiTreeItem-content:hover': {
25
- backgroundColor: 'rgba(0, 0, 0, 0.04)',
26
- },
27
- } }, property.children?.map((child) => (React.createElement(PropertyTreeItem, { onClick: onClick, treeData: treeData, key: child.id, property: child, fetchObject: fetchObject })))));
28
- }
29
- else {
30
- // Leaf node, render as MenuItem
31
- return (React.createElement(MenuItem, { sx: {
32
- '&:hover': {
33
- backgroundColor: 'transparent',
34
- },
35
- '.MuiTreeItem-group &': {
36
- '&:hover': {
37
- backgroundColor: 'rgba(0, 0, 0, 0.04)',
38
- },
39
- },
40
- }, onClick: () => onClick(property.id), key: property.id, value: property.id }, property.name));
41
- }
2
+ import { RichTreeItem } from '../../core';
3
+ import { findTreeItemById } from './utils';
4
+ export const PropertyTreeItem = (props) => {
5
+ const { itemId, label, items, setExpanded, expanded, children, updateNodeChildren, fetchObject, handleTreePropertySelect, } = props;
6
+ const onClick = async (e) => {
7
+ // If the item is already expanded, collapse it.
8
+ if (expanded.includes(itemId)) {
9
+ e.stopPropagation();
10
+ setExpanded(expanded.filter((id) => id !== itemId));
11
+ return;
12
+ }
13
+ const item = findTreeItemById(items, itemId);
14
+ if (item?.type === 'object') {
15
+ e.stopPropagation();
16
+ // If the item has an associated "objectId", fetch the properties of the object and expand the item.
17
+ if (item.objectId && item.children?.length === 1 && item.children[0].type === 'loading') {
18
+ const object = item.objectId ? await fetchObject(item.objectId) : undefined;
19
+ if (object) {
20
+ updateNodeChildren(itemId, (object.properties ?? []).map((prop) => ({
21
+ id: `${itemId}.${prop.id}`,
22
+ label: prop.name,
23
+ value: `${itemId}.${prop.id}`,
24
+ type: prop.type,
25
+ objectId: prop.objectId,
26
+ children: prop.type === 'object'
27
+ ? [
28
+ {
29
+ id: `${itemId}.${prop.id}-loading`,
30
+ label: 'Loading...',
31
+ value: `${itemId}.${prop.id}-loading`,
32
+ type: 'loading',
33
+ },
34
+ ]
35
+ : undefined,
36
+ })));
37
+ }
38
+ }
39
+ setExpanded([...expanded, itemId]);
40
+ return;
41
+ }
42
+ await handleTreePropertySelect(itemId);
43
+ };
44
+ return React.createElement(RichTreeItem, { itemId: itemId, label: label, children: children, onClick: onClick });
42
45
  };
43
- export default PropertyTreeItem;
@@ -1,7 +1,7 @@
1
1
  /// <reference types="react" />
2
2
  import { BaseSelectorProps } from 'react-querybuilder';
3
3
  import { ExpandedProperty } from '../../../types';
4
- import { AutocompleteOption } from '../../core';
4
+ import { AutocompleteOption, TreeViewBaseItem } from '../../core';
5
5
  export type ObjectProperty = {
6
6
  id: string;
7
7
  name: string;
@@ -51,3 +51,10 @@ export type MongoDBQueryValue = null | string | boolean | {
51
51
  $in?: unknown[];
52
52
  $nin?: unknown[];
53
53
  };
54
+ export type TreeItem = TreeViewBaseItem<{
55
+ id: string;
56
+ label: string;
57
+ value: string;
58
+ type?: string;
59
+ objectId?: string;
60
+ }>;
@@ -1,23 +1,16 @@
1
1
  import { Property } from '@evoke-platform/context';
2
2
  import { RuleGroupType } from 'react-querybuilder';
3
- import { ExpandedProperty, Obj, ObjectProperty } from '../../../types';
3
+ import { Obj, ObjectProperty } from '../../../types';
4
+ import { TreeItem } from './types';
4
5
  /**
5
6
  * Recursively updates a node in a tree structure by applying an updater function to the node with the specified ID.
6
7
  *
7
- * @param {ExpandedProperty[]} tree - The tree structure to update.
8
+ * @param {TreeItem[]} tree - The tree structure to update.
8
9
  * @param {string} nodeId - The ID of the node to update.
9
- * @param {(node: ExpandedProperty) => ExpandedProperty} updater - The function to apply to the node.
10
- * @returns {ExpandedProperty[]} - The updated tree structure.
10
+ * @param {(node: TreeItem) => TreeItem} updater - The function to apply to the node.
11
+ * @returns {TreeItem[]} - The updated tree structure.
11
12
  */
12
- export declare const updateTreeNode: (tree: ExpandedProperty[], nodeId: string, updater: (node: ExpandedProperty) => ExpandedProperty) => ExpandedProperty[];
13
- /**
14
- * Recursively searches for a property in a tree structure by its ID.
15
- *
16
- * @param {ExpandedProperty[]} properties - The tree structure to search.
17
- * @param {string} id - The ID of the property to find.
18
- * @returns {ExpandedProperty | null} - The found property or null if not found.
19
- */
20
- export declare const findPropertyById: (properties: ExpandedProperty[], id: string) => ExpandedProperty | null;
13
+ export declare const updateTreeNode: (tree: TreeItem[], nodeId: string, updater: (node: TreeItem) => TreeItem) => TreeItem[];
21
14
  type FetchObjectFunction = (id: string) => Promise<Obj | undefined>;
22
15
  /**
23
16
  * Fetches the display name path for a given property ID within an object hierarchy.
@@ -74,4 +67,5 @@ export declare const ALL_OPERATORS: {
74
67
  * @returns {string} The resulting query string.
75
68
  */
76
69
  export declare const getReadableQuery: (mongoQuery?: Record<string, unknown>, properties?: Property[]) => string;
70
+ export declare const findTreeItemById: (nodes: TreeItem[], nodeId: string) => TreeItem | null;
77
71
  export {};
@@ -2,10 +2,10 @@ import { isArray, isEmpty, startCase } from 'lodash';
2
2
  /**
3
3
  * Recursively updates a node in a tree structure by applying an updater function to the node with the specified ID.
4
4
  *
5
- * @param {ExpandedProperty[]} tree - The tree structure to update.
5
+ * @param {TreeItem[]} tree - The tree structure to update.
6
6
  * @param {string} nodeId - The ID of the node to update.
7
- * @param {(node: ExpandedProperty) => ExpandedProperty} updater - The function to apply to the node.
8
- * @returns {ExpandedProperty[]} - The updated tree structure.
7
+ * @param {(node: TreeItem) => TreeItem} updater - The function to apply to the node.
8
+ * @returns {TreeItem[]} - The updated tree structure.
9
9
  */
10
10
  export const updateTreeNode = (tree, nodeId, updater) => {
11
11
  return tree.map((node) => {
@@ -20,26 +20,6 @@ export const updateTreeNode = (tree, nodeId, updater) => {
20
20
  }
21
21
  });
22
22
  };
23
- /**
24
- * Recursively searches for a property in a tree structure by its ID.
25
- *
26
- * @param {ExpandedProperty[]} properties - The tree structure to search.
27
- * @param {string} id - The ID of the property to find.
28
- * @returns {ExpandedProperty | null} - The found property or null if not found.
29
- */
30
- export const findPropertyById = (properties, id) => {
31
- for (const prop of properties) {
32
- if (prop.id === id) {
33
- return prop;
34
- }
35
- else if (prop.children) {
36
- const result = findPropertyById(prop.children, id);
37
- if (result)
38
- return result;
39
- }
40
- }
41
- return null;
42
- };
43
23
  /**
44
24
  * Fetches the display name path for a given property ID within an object hierarchy.
45
25
  *
@@ -359,3 +339,13 @@ export const getReadableQuery = (mongoQuery, properties) => {
359
339
  const parsedQuery = parseMongoDB(mongoQuery);
360
340
  return buildQueryString(parsedQuery);
361
341
  };
342
+ export const findTreeItemById = (nodes, nodeId) => {
343
+ for (const node of nodes) {
344
+ if (node.id === nodeId)
345
+ return node;
346
+ const found = node.children && findTreeItemById(node.children, nodeId);
347
+ if (found)
348
+ return found;
349
+ }
350
+ return null;
351
+ };
@@ -73,6 +73,9 @@ const RepeatableField = (props) => {
73
73
  .get(getPrefixedUrl(`/forms/${formId || action?.defaultFormId}`))
74
74
  .then((evokeForm) => {
75
75
  setForm(evokeForm);
76
+ setFetchedOptions({
77
+ [`${fieldDefinition.id}${action?.type === 'delete' ? '-deleteForm' : action?.type === 'create' ? '-createForm' : '-updateForm'}`]: evokeForm,
78
+ });
76
79
  })
77
80
  .catch((error) => {
78
81
  console.error(error);
@@ -22,7 +22,7 @@ const ObjectPropertyInput = (props) => {
22
22
  const [openOptions, setOpenOptions] = useState(false);
23
23
  const [hasFetched, setHasFetched] = useState(fetchedOptions[`${id}OptionsHaveFetched`] || false);
24
24
  const [options, setOptions] = useState(fetchedOptions[`${id}Options`] || []);
25
- const [layout, setLayout] = useState();
25
+ const [layout, setLayout] = useState(fetchedOptions[`${id}ViewLayout`]);
26
26
  const [form, setForm] = useState();
27
27
  const [snackbarError, setSnackbarError] = useState({
28
28
  showAlert: false,
@@ -68,10 +68,17 @@ const ObjectPropertyInput = (props) => {
68
68
  };
69
69
  }
70
70
  if (viewLayout) {
71
- apiServices
72
- .get(getPrefixedUrl(`/objects/${viewLayout.objectId}/${displayOption === 'dropdown' ? 'dropdown' : 'table'}Layouts/${viewLayout.id}`))
73
- .then(setLayout)
74
- .catch((err) => setLayout(defaultViewLayout));
71
+ if (!fetchedOptions[`${id}ViewLayout`]) {
72
+ apiServices
73
+ .get(getPrefixedUrl(`/objects/${viewLayout.objectId}/${displayOption === 'dropdown' ? 'dropdown' : 'table'}Layouts/${viewLayout.id}`))
74
+ .then((layout) => {
75
+ setLayout(layout);
76
+ setFetchedOptions({
77
+ [`${id}ViewLayout`]: layout,
78
+ });
79
+ })
80
+ .catch((err) => setLayout(defaultViewLayout));
81
+ }
75
82
  }
76
83
  else {
77
84
  setLayout(defaultViewLayout);
@@ -255,6 +255,21 @@ CriteriaBuilderRelatedObject.args = {
255
255
  },
256
256
  ],
257
257
  },
258
+ {
259
+ id: 'dynamic.id',
260
+ name: 'Dynamic ID',
261
+ type: 'string',
262
+ },
263
+ {
264
+ id: 'dynamic.name',
265
+ name: 'Dynamic Name',
266
+ type: 'string',
267
+ },
268
+ {
269
+ id: 'dynamic.objectId',
270
+ name: 'Dynamic Object ID',
271
+ type: 'string',
272
+ },
258
273
  {
259
274
  id: 'name',
260
275
  name: 'Name',
@@ -526,6 +541,21 @@ CriteriaBuilderRelatedObject.args = {
526
541
  type: 'string',
527
542
  required: false,
528
543
  },
544
+ {
545
+ id: 'dynamic.id',
546
+ name: 'Dynamic ID',
547
+ type: 'string',
548
+ },
549
+ {
550
+ id: 'dynamic.name',
551
+ name: 'Dynamic Name',
552
+ type: 'string',
553
+ },
554
+ {
555
+ id: 'dynamic.objectId',
556
+ name: 'Dynamic Object Id',
557
+ type: 'string',
558
+ },
529
559
  ],
530
560
  },
531
561
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evoke-platform/ui-components",
3
- "version": "1.10.0",
3
+ "version": "1.10.1-dev.1",
4
4
  "description": "",
5
5
  "main": "dist/published/index.js",
6
6
  "module": "dist/published/index.js",