@wavemaker-ai/react-runtime 1.0.0-rc.647565 → 1.0.0-rc.647582

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.
Files changed (63) hide show
  1. package/actions/navigation-action.d.ts +1 -0
  2. package/actions/navigation-action.js +34 -9
  3. package/components/auth/RoleAuthGuard.d.ts +6 -0
  4. package/components/auth/RoleAuthGuard.js +65 -0
  5. package/components/basic/search/index.js +2 -1
  6. package/components/basic/search/providers.js +2 -2
  7. package/components/basic/tree/Components/TreeNodeComponent.d.ts +1 -1
  8. package/components/basic/tree/Components/TreeNodeComponent.js +14 -8
  9. package/components/basic/tree/hooks/useTreePersistedState.d.ts +3 -0
  10. package/components/basic/tree/hooks/useTreePersistedState.js +38 -0
  11. package/components/basic/tree/index.d.ts +1 -0
  12. package/components/basic/tree/index.js +105 -30
  13. package/components/basic/tree/props.d.ts +18 -1
  14. package/components/basic/tree/utils.d.ts +16 -1
  15. package/components/basic/tree/utils.js +111 -0
  16. package/components/chart/src/dataUtils.js +1 -1
  17. package/components/common/customTemplate/useCustomTemplate.js +1 -1
  18. package/components/container/layout-grid/grid-column/index.js +2 -1
  19. package/components/data/form/base-form/index.js +3 -1
  20. package/components/data/form/form-controller/withFormController.js +3 -7
  21. package/components/data/pagination/hooks/usePagination.js +0 -1
  22. package/components/data/table/index.js +5 -2
  23. package/components/data/table/utils/columnBuilder.d.ts +1 -1
  24. package/components/data/table/utils/columnBuilder.js +7 -4
  25. package/components/data/table/utils/crud-handlers.js +10 -10
  26. package/components/data/table/utils/index.d.ts +9 -8
  27. package/components/data/table/utils/index.js +73 -9
  28. package/components/layout/leftnav/index.js +21 -2
  29. package/components/layout/leftnav/props.d.ts +9 -0
  30. package/components/layout/leftnav/utils/page-class-util.d.ts +9 -0
  31. package/components/layout/leftnav/utils/page-class-util.js +28 -0
  32. package/components/navigation/popover/index.js +1 -0
  33. package/components/page/index.js +22 -6
  34. package/components/page/page-context.d.ts +4 -0
  35. package/components/page/page-context.js +5 -0
  36. package/components/page/partial-container/index.js +1 -4
  37. package/higherOrder/BaseApp.js +4 -3
  38. package/higherOrder/BaseAppProps.d.ts +1 -0
  39. package/higherOrder/BasePage.js +0 -10
  40. package/hooks/useDataSourceSubscription.js +1 -1
  41. package/hooks/useHttp.js +0 -4
  42. package/package-lock.json +196 -185
  43. package/package.json +3 -3
  44. package/runtime-dynamic/app-initializer.js +1 -1
  45. package/runtime-dynamic/components/use-dynamic-component.js +17 -31
  46. package/runtime-dynamic/factories/build-base-page-like-component.d.ts +1 -1
  47. package/runtime-dynamic/factories/dynamic-component.js +9 -1
  48. package/runtime-dynamic/factories/prefab-factory.d.ts +1 -1
  49. package/runtime-dynamic/factories/utils.d.ts +1 -1
  50. package/runtime-dynamic/services/component-ref-provider.d.ts +18 -3
  51. package/runtime-dynamic/services/component-ref-provider.js +15 -38
  52. package/runtime-dynamic/services/index.d.ts +2 -3
  53. package/runtime-dynamic/services/index.js +1 -18
  54. package/runtime-dynamic/services/resource-manager.d.ts +2 -3
  55. package/runtime-dynamic/services/resource-manager.js +5 -15
  56. package/runtime-dynamic/services/script-executor.js +1 -7
  57. package/runtime-dynamic/services/variable-registry.js +0 -4
  58. package/utils/attr.js +2 -9
  59. package/utils/custom-expression/index.js +4 -1
  60. package/utils/custom-expression/parser.js +4 -2
  61. package/utils/state-persistance.d.ts +1 -1
  62. package/runtime-dynamic/services/cache.d.ts +0 -29
  63. package/runtime-dynamic/services/cache.js +0 -51
@@ -31,5 +31,6 @@ export declare class NavigationAction extends BaseAction<NavigationActionConfig>
31
31
  * When true, we skip router.push.
32
32
  */
33
33
  private isSamePageAsCurrent;
34
+ private notifyAccessDenied;
34
35
  private handleGotoPage;
35
36
  }
@@ -20,6 +20,7 @@ import { merge } from "lodash-es";
20
20
  import { getRouterInstance } from "../store/middleware/navigationMiddleware";
21
21
  import { PREVIEW_ROUTING_BASEPATH, getActiveRoutingBasePath } from "../core/constants";
22
22
  import { store } from "../store";
23
+ import { checkAccess } from "../store/slices/authSlice";
23
24
  import {
24
25
  setPendingAccordionExpansion,
25
26
  setPendingTabSelection
@@ -57,19 +58,19 @@ class NavigationAction extends BaseAction {
57
58
  }
58
59
  params = (params == null ? void 0 : params.data) ? merge(this.config.paramProvider(), this.dataBinding, params == null ? void 0 : params.data) : merge(this.config.paramProvider(), this.dataBinding, this.dataSet);
59
60
  this.notify(VariableEvents.BEFORE_INVOKE, [this, this.dataSet]);
60
- return super.invoke(params, onSuccess, onError).then(() => {
61
+ return super.invoke(params, onSuccess, onError).then(async () => {
61
62
  switch (config.operation) {
62
63
  case "goToPreviousPage":
63
64
  this.handleGoToPreviousPage();
64
65
  break;
65
66
  case "gotoTab":
66
- this.handleGotoTab(params || {});
67
+ await this.handleGotoTab(params || {});
67
68
  break;
68
69
  case "gotoAccordion":
69
- this.handleGotoAccordion(params || {});
70
+ await this.handleGotoAccordion(params || {});
70
71
  break;
71
72
  case "gotoPage":
72
- this.handleGotoPage(params || {});
73
+ await this.handleGotoPage(params || {});
73
74
  }
74
75
  }).then(
75
76
  () => {
@@ -108,7 +109,7 @@ class NavigationAction extends BaseAction {
108
109
  }
109
110
  }
110
111
  }
111
- handleGotoTab(params) {
112
+ async handleGotoTab(params) {
112
113
  const tabName = params == null ? void 0 : params.tabName;
113
114
  const pageName = params == null ? void 0 : params.pageName;
114
115
  if (!tabName) {
@@ -122,7 +123,7 @@ class NavigationAction extends BaseAction {
122
123
  }
123
124
  if (pageName) {
124
125
  store.dispatch(setPendingTabSelection(tabName));
125
- this.handleGotoPage(params);
126
+ await this.handleGotoPage(params);
126
127
  return;
127
128
  }
128
129
  console.warn(
@@ -137,7 +138,7 @@ class NavigationAction extends BaseAction {
137
138
  }
138
139
  return null;
139
140
  }
140
- handleGotoAccordion(params) {
141
+ async handleGotoAccordion(params) {
141
142
  var _a, _b, _c, _d;
142
143
  const accordionName = params == null ? void 0 : params.accordionName;
143
144
  const pageName = params == null ? void 0 : params.pageName;
@@ -145,7 +146,7 @@ class NavigationAction extends BaseAction {
145
146
  (_d = (_c = this.config._context) == null ? void 0 : _c.Widgets[accordionName]) == null ? void 0 : _d.expand();
146
147
  } else if (pageName && accordionName) {
147
148
  store.dispatch(setPendingAccordionExpansion(accordionName));
148
- this.handleGotoPage(params);
149
+ await this.handleGotoPage(params);
149
150
  } else {
150
151
  console.warn(`Unable to expand accordion: ${accordionName}`);
151
152
  }
@@ -253,10 +254,34 @@ class NavigationAction extends BaseAction {
253
254
  }
254
255
  return true;
255
256
  }
256
- handleGotoPage(params) {
257
+ notifyAccessDenied() {
258
+ var _a, _b, _c, _d;
259
+ const state = store.getState();
260
+ const isAuthenticated = (_b = (_a = state.auth) == null ? void 0 : _a.loggedInUser) == null ? void 0 : _b.isAuthenticated;
261
+ if (!isAuthenticated) {
262
+ return;
263
+ }
264
+ const context = this.config._context;
265
+ const message = ((_d = (_c = context == null ? void 0 : context.appLocale) == null ? void 0 : _c.LABELS) == null ? void 0 : _d.ACCESS_DENIED) || "Access Denied";
266
+ const notifyApp = context == null ? void 0 : context.notifyApp;
267
+ notifyApp == null ? void 0 : notifyApp(message, "Error");
268
+ }
269
+ async handleGotoPage(params) {
257
270
  const router = getRouterInstance();
258
271
  const pageName = params == null ? void 0 : params.pageName;
259
272
  if (pageName) {
273
+ try {
274
+ const hasAccess = await store.dispatch(checkAccess({ name: pageName, type: "PAGE" })).unwrap();
275
+ if (!hasAccess) {
276
+ clearPageNavigationPending();
277
+ this.notifyAccessDenied();
278
+ return;
279
+ }
280
+ } catch (e) {
281
+ clearPageNavigationPending();
282
+ this.notifyAccessDenied();
283
+ return;
284
+ }
260
285
  const currentPageInfo = this.getCurrentPageInfo();
261
286
  if (currentPageInfo) {
262
287
  this.rememberPageQueryParams(currentPageInfo.pageName, currentPageInfo.queryParams);
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ type RoleAuthGuardProps = {
3
+ children: React.ReactNode;
4
+ };
5
+ export default function RoleAuthGuard({ children }: RoleAuthGuardProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
@@ -0,0 +1,65 @@
1
+ "use client";
2
+ import { Fragment, jsx } from "react/jsx-runtime";
3
+ import { useEffect, useState } from "react";
4
+ import { usePathname } from "next/navigation";
5
+ import { useAppSelector } from "../../store";
6
+ import { useAuth } from "../../hooks/useAuth";
7
+ import { getActiveRoutingBasePath } from "../../core/constants";
8
+ import { useAppContext } from "../../context/AppContext";
9
+ function getPageNameFromPathname(pathname) {
10
+ const segments = pathname.split("/").filter(Boolean);
11
+ const routingBase = getActiveRoutingBasePath();
12
+ const baseIndex = segments.indexOf(routingBase);
13
+ if (baseIndex >= 0) {
14
+ return segments[baseIndex + 1] || "Main";
15
+ }
16
+ return segments[segments.length - 1] || "Main";
17
+ }
18
+ function RoleAuthGuard({ children }) {
19
+ const pathname = usePathname();
20
+ const { loggedInUser } = useAuth();
21
+ const isSecurityEnabled = useAppSelector(
22
+ (state) => {
23
+ var _a;
24
+ return (_a = state.auth.securityConfig) == null ? void 0 : _a.isSecurityEnabled;
25
+ }
26
+ );
27
+ const pages = useAppSelector((state) => {
28
+ var _a, _b;
29
+ return ((_b = (_a = state.info) == null ? void 0 : _a.appConfig) == null ? void 0 : _b.pages) || [];
30
+ });
31
+ const i18n = useAppSelector((state) => state.i18n);
32
+ const appContext = useAppContext();
33
+ const [hasAccess, setHasAccess] = useState(false);
34
+ useEffect(() => {
35
+ var _a, _b, _c;
36
+ if (isSecurityEnabled === void 0) {
37
+ return;
38
+ }
39
+ if (!isSecurityEnabled) {
40
+ setHasAccess(true);
41
+ return;
42
+ }
43
+ const pageName = getPageNameFromPathname(pathname || "");
44
+ const userRoles = (loggedInUser == null ? void 0 : loggedInUser.roles) || [];
45
+ const allowedRoles = ((_a = pages.find((page) => page.name === pageName)) == null ? void 0 : _a.allowedRoles) || [];
46
+ if (allowedRoles.length === 0) {
47
+ setHasAccess(true);
48
+ return;
49
+ }
50
+ const pageAccess = userRoles.some((role) => allowedRoles.includes(role));
51
+ if (!pageAccess) {
52
+ appContext.notifyApp(((_c = (_b = i18n == null ? void 0 : i18n.appLocale) == null ? void 0 : _b.LABELS) == null ? void 0 : _c.ACCESS_DENIED) || "Access Denied", "Error");
53
+ setHasAccess(false);
54
+ return;
55
+ }
56
+ setHasAccess(true);
57
+ }, [pathname, pages, isSecurityEnabled, loggedInUser == null ? void 0 : loggedInUser.roles, appContext.notifyApp]);
58
+ if (!hasAccess) {
59
+ return null;
60
+ }
61
+ return /* @__PURE__ */ jsx(Fragment, { children });
62
+ }
63
+ export {
64
+ RoleAuthGuard as default
65
+ };
@@ -1075,7 +1075,8 @@ const Search = React.forwardRef((props, ref) => {
1075
1075
  "groupedData",
1076
1076
  "handleHeaderClick",
1077
1077
  "toggleAllHeaders",
1078
- "isDestroyed"
1078
+ "isDestroyed",
1079
+ "setTemplate"
1079
1080
  ];
1080
1081
  if (!invalidProps.includes(key)) {
1081
1082
  acc[key] = value2;
@@ -196,7 +196,7 @@ class DataProvider {
196
196
  init(component) {
197
197
  var _a, _b, _c;
198
198
  let datasource = component == null ? void 0 : component.datasource;
199
- if (datasource) {
199
+ if (datasource && datasource.execute) {
200
200
  return datasource.execute(DataSource.Operation.IS_PAGEABLE) || ((_c = (_b = (_a = datasource == null ? void 0 : datasource.serviceInfo) == null ? void 0 : _a.parameters) == null ? void 0 : _b.length) != null ? _c : 0) > 0;
201
201
  }
202
202
  return false;
@@ -224,7 +224,7 @@ class DataProvider {
224
224
  }
225
225
  return new Promise(async (resolve, reject) => {
226
226
  try {
227
- const result = await dataSource.execute(DataSource.Operation.SEARCH_RECORDS, component);
227
+ const result = await (dataSource == null ? void 0 : dataSource.execute(DataSource.Operation.SEARCH_RECORDS, component));
228
228
  resolve((result == null ? void 0 : result.data) || []);
229
229
  } catch (err) {
230
230
  resolve([]);
@@ -1,4 +1,4 @@
1
1
  import React from "react";
2
2
  import { TreeNodeComponentProps } from "../props";
3
- declare const TreeNodeComponent: React.MemoExoticComponent<({ node, treeicons, isCheckboxTree, isRadioTree, selectedItem, handleNodeClick, toggleNodeExpansion, toggleNodeChecked, handleRadioSelect, depth, treeName, template, listener, }: TreeNodeComponentProps) => import("react/jsx-runtime").JSX.Element>;
3
+ declare const TreeNodeComponent: React.MemoExoticComponent<({ node, treeicons, isCheckboxTree, isRadioTree, selectedItem, handleNodeClick, toggleNodeExpansion, toggleNodeChecked, handleRadioSelect, depth, treeName, template, listener, urlExpansionOverlay, urlSelectedId, }: TreeNodeComponentProps) => import("react/jsx-runtime").JSX.Element>;
4
4
  export default TreeNodeComponent;
@@ -39,8 +39,12 @@ const TreeNodeComponent = React.memo(
39
39
  depth = 0,
40
40
  treeName,
41
41
  template,
42
- listener
42
+ listener,
43
+ urlExpansionOverlay,
44
+ urlSelectedId
43
45
  }) => {
46
+ const isNodeOpen = node.open || !!(urlExpansionOverlay == null ? void 0 : urlExpansionOverlay.has(node.nodeId));
47
+ const isSelected = (selectedItem == null ? void 0 : selectedItem.nodeId) === node.nodeId || urlSelectedId === node.nodeId;
44
48
  const hasChildren = node.children && node.children.length > 0;
45
49
  const disabledClassName = useMemo(() => {
46
50
  return isTreeNodeDisabled(node.data) ? "wm-tree-node-disabled" : "";
@@ -79,7 +83,7 @@ const TreeNodeComponent = React.memo(
79
83
  "button",
80
84
  `level${depth}`,
81
85
  "switch",
82
- depth === 0 ? node.open ? "roots_open" : "roots_close" : node.open ? "bottom_open" : "bottom_close",
86
+ depth === 0 ? isNodeOpen ? "roots_open" : "roots_close" : isNodeOpen ? "bottom_open" : "bottom_close",
83
87
  treeicons,
84
88
  "treenode_switch",
85
89
  disabledClassName
@@ -156,7 +160,7 @@ const TreeNodeComponent = React.memo(
156
160
  `level${depth}`,
157
161
  !template && "glyphicon",
158
162
  !template && (node.icon || "glyphicon-music"),
159
- { curSelectedNode: (selectedItem == null ? void 0 : selectedItem.nodeId) === node.nodeId },
163
+ { curSelectedNode: isSelected },
160
164
  "treenode_a",
161
165
  disabledClassName
162
166
  ),
@@ -181,7 +185,7 @@ const TreeNodeComponent = React.memo(
181
185
  };
182
186
  const renderChildNodes = () => {
183
187
  if (!hasChildren) return null;
184
- return /* @__PURE__ */ jsx(Collapse, { in: !!node.open, timeout: "auto", unmountOnExit: false, children: /* @__PURE__ */ jsx(
188
+ return /* @__PURE__ */ jsx(Collapse, { in: isNodeOpen, timeout: "auto", unmountOnExit: false, children: /* @__PURE__ */ jsx(
185
189
  Box,
186
190
  {
187
191
  component: "ul",
@@ -189,7 +193,7 @@ const TreeNodeComponent = React.memo(
189
193
  `level${depth}`,
190
194
  "line",
191
195
  "wm-tree-children-list",
192
- node.open ? "wm-tree-children-open" : "wm-tree-children-closed"
196
+ isNodeOpen ? "wm-tree-children-open" : "wm-tree-children-closed"
193
197
  ),
194
198
  id: `${node.tId}_ul`,
195
199
  children: node.children && node.children.map((childNode) => /* @__PURE__ */ jsx(
@@ -207,7 +211,9 @@ const TreeNodeComponent = React.memo(
207
211
  depth: depth + 1,
208
212
  treeName,
209
213
  template,
210
- listener
214
+ listener,
215
+ urlExpansionOverlay,
216
+ urlSelectedId
211
217
  },
212
218
  childNode.nodeId
213
219
  ))
@@ -219,10 +225,10 @@ const TreeNodeComponent = React.memo(
219
225
  __spreadProps(__spreadValues({
220
226
  component: "li",
221
227
  className: clsx(`level${depth}`, {
222
- selected: (selectedItem == null ? void 0 : selectedItem.nodeId) === node.nodeId
228
+ selected: isSelected
223
229
  }),
224
230
  id: node.tId,
225
- tabIndex: (selectedItem == null ? void 0 : selectedItem.nodeId) === node.nodeId ? 0 : -1
231
+ tabIndex: isSelected ? 0 : -1
226
232
  }, { treenode: "true" }), {
227
233
  children: [
228
234
  !template && renderExpandCollapseIcon(),
@@ -0,0 +1,3 @@
1
+ import { type Dispatch, type SetStateAction } from "react";
2
+ import type { TreeUrlState } from "../props";
3
+ export declare function useTreePersistedState(name: string, statehandler: string | undefined): readonly [TreeUrlState | null, Dispatch<SetStateAction<TreeUrlState | null>>];
@@ -0,0 +1,38 @@
1
+ import { useCallback, useEffect, useState } from "react";
2
+ import { getTreeState, saveTreeState } from "../utils";
3
+ const storageType = (statehandler) => {
4
+ if (statehandler === "localStorage" || statehandler === "sessionStorage" || statehandler === "URL") {
5
+ return statehandler;
6
+ }
7
+ return null;
8
+ };
9
+ function useTreePersistedState(name, statehandler) {
10
+ const storage = storageType(statehandler);
11
+ const [persisted, setPersisted] = useState(null);
12
+ useEffect(() => {
13
+ if (!storage || typeof window === "undefined") {
14
+ setPersisted(null);
15
+ return;
16
+ }
17
+ try {
18
+ setPersisted(getTreeState(name, storage));
19
+ } catch (e) {
20
+ setPersisted(null);
21
+ }
22
+ }, [name, storage]);
23
+ const setPersistedState = useCallback(
24
+ (update) => {
25
+ if (!storage) return;
26
+ setPersisted((prev) => {
27
+ const next = typeof update === "function" ? update(prev) : update;
28
+ saveTreeState(name, storage, next);
29
+ return next;
30
+ });
31
+ },
32
+ [name, storage]
33
+ );
34
+ return [persisted, setPersistedState];
35
+ }
36
+ export {
37
+ useTreePersistedState
38
+ };
@@ -30,6 +30,7 @@ declare const _default: React.ComponentType<{
30
30
  onSelect?: (event: React.MouseEvent, widget: any, item: any, path: string) => void;
31
31
  onCheck?: (event: React.MouseEvent, widget: any, item: any, checked: boolean) => void;
32
32
  template?: string;
33
+ statehandler?: string;
33
34
  standalone?: boolean;
34
35
  ref?: React.RefObject<HTMLElement> | React.RefObject<null>;
35
36
  animation?: string;
@@ -17,6 +17,18 @@ var __spreadValues = (a, b) => {
17
17
  return a;
18
18
  };
19
19
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+ var __objRest = (source, exclude) => {
21
+ var target = {};
22
+ for (var prop in source)
23
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
24
+ target[prop] = source[prop];
25
+ if (source != null && __getOwnPropSymbols)
26
+ for (var prop of __getOwnPropSymbols(source)) {
27
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
28
+ target[prop] = source[prop];
29
+ }
30
+ return target;
31
+ };
20
32
  import { jsx } from "react/jsx-runtime";
21
33
  import { useState, useEffect, useMemo, useCallback, useRef } from "react";
22
34
  import clsx from "clsx";
@@ -34,11 +46,16 @@ import {
34
46
  generateUniqueNodeId,
35
47
  findNodeById,
36
48
  toggleExpansionInTree,
37
- isTreeNodeDisabled
49
+ isTreeNodeDisabled,
50
+ migrateLegacyTreeUrlState,
51
+ normalizeExpandedIds,
52
+ collectExpandedNodeIdsForUrl
38
53
  } from "./utils";
39
54
  import { useCustomTemplate } from "../../common/customTemplate";
55
+ import { useTreePersistedState } from "./hooks/useTreePersistedState";
40
56
  const DEFAULT_CLASS = "app-tree";
41
57
  const DEFAULT_TREE_ICON_CLASS = "plus-minus";
58
+ const persistenceEnabledFor = (statehandler) => statehandler === "URL" || statehandler === "localStorage" || statehandler === "sessionStorage";
42
59
  const WmTree = (props) => {
43
60
  var _a, _b;
44
61
  const {
@@ -64,8 +81,11 @@ const WmTree = (props) => {
64
81
  listener,
65
82
  width,
66
83
  height,
67
- template
84
+ template,
85
+ statehandler
68
86
  } = props;
87
+ const [persistedState, setPersistedState] = useTreePersistedState(name, statehandler);
88
+ const persistenceEnabled = persistenceEnabledFor(statehandler);
69
89
  const isCheckboxTree = (_a = className == null ? void 0 : className.includes("Checkbox")) != null ? _a : false;
70
90
  const isRadioTree = (_b = className == null ? void 0 : className.includes("Radio")) != null ? _b : false;
71
91
  const [treeNodes, setTreeNodes] = useState([]);
@@ -74,6 +94,7 @@ const WmTree = (props) => {
74
94
  const { templateName, setTemplate } = useCustomTemplate({ content: template, name });
75
95
  const treeNodesRef = useRef([]);
76
96
  treeNodesRef.current = treeNodes;
97
+ const skipNextPersistRef = useRef(false);
77
98
  const usedNodeIds = useRef(/* @__PURE__ */ new Set());
78
99
  const getNodesFromString = useCallback(
79
100
  (value) => {
@@ -128,7 +149,7 @@ const WmTree = (props) => {
128
149
  }, []);
129
150
  const toggleNodeExpansion = useCallback(
130
151
  (event, node) => {
131
- var _a2;
152
+ var _a2, _b2, _c;
132
153
  const ev = event != null ? event : {
133
154
  preventDefault: () => {
134
155
  },
@@ -144,34 +165,53 @@ const WmTree = (props) => {
144
165
  const after_nodeUpdate = findNodeById(updatedNodes, node.nodeId);
145
166
  treeNodesRef.current = updatedNodes;
146
167
  setTreeNodes(updatedNodes);
168
+ if (persistenceEnabled) {
169
+ setPersistedState((prevPersist) => {
170
+ const base = prevPersist && typeof prevPersist === "object" ? prevPersist : {};
171
+ return __spreadProps(__spreadValues({}, base), {
172
+ expanded: collectExpandedNodeIdsForUrl(updatedNodes)
173
+ });
174
+ });
175
+ }
147
176
  if (after_nodeUpdate && ev && name) {
148
177
  const path = getPath(after_nodeUpdate);
149
178
  if (after_nodeUpdate.open) {
150
179
  if (onExpand) {
151
- onExpand(ev, listener == null ? void 0 : listener.Widgets[name], after_nodeUpdate.data, path);
180
+ onExpand(ev, (_b2 = listener == null ? void 0 : listener.Widgets) == null ? void 0 : _b2[name], after_nodeUpdate.data, path);
152
181
  }
153
182
  } else if (onCollapse) {
154
- onCollapse(ev, listener == null ? void 0 : listener.Widgets[name], after_nodeUpdate.data, path);
183
+ onCollapse(ev, (_c = listener == null ? void 0 : listener.Widgets) == null ? void 0 : _c[name], after_nodeUpdate.data, path);
155
184
  }
156
185
  }
157
186
  },
158
- [getPath, name, onCollapse, onExpand]
187
+ [getPath, name, onCollapse, onExpand, persistenceEnabled]
159
188
  );
160
189
  const selectNode = useCallback(
161
190
  (event, node) => {
162
- var _a2, _b2;
191
+ var _a2, _b2, _c;
163
192
  if (!node) return;
164
193
  const resolvedTreeNode = (_a2 = findNodeById(treeNodesRef.current, node.nodeId)) != null ? _a2 : node;
165
194
  if (isTreeNodeDisabled(resolvedTreeNode.data)) return;
166
195
  setSelectedItem(resolvedTreeNode);
196
+ if (persistenceEnabled && !skipNextPersistRef.current) {
197
+ setPersistedState((prevPersist) => {
198
+ const base = prevPersist && typeof prevPersist === "object" ? prevPersist : {};
199
+ return __spreadProps(__spreadValues({}, base), { selected: resolvedTreeNode.nodeId });
200
+ });
201
+ }
167
202
  if (event && nodeclick === "expand" && ((_b2 = resolvedTreeNode.children) == null ? void 0 : _b2.length)) {
168
203
  toggleNodeExpansion(event, resolvedTreeNode);
169
204
  }
170
205
  if (onSelect && event && name) {
171
- onSelect(event, listener == null ? void 0 : listener.Widgets[name], resolvedTreeNode.data, getPath(resolvedTreeNode));
206
+ onSelect(
207
+ event,
208
+ (_c = listener == null ? void 0 : listener.Widgets) == null ? void 0 : _c[name],
209
+ resolvedTreeNode.data,
210
+ getPath(resolvedTreeNode)
211
+ );
172
212
  }
173
213
  },
174
- [name, nodeclick, onSelect, toggleNodeExpansion]
214
+ [name, nodeclick, onSelect, toggleNodeExpansion, persistenceEnabled]
175
215
  );
176
216
  const handleNodeClick = useCallback(
177
217
  (event, node) => {
@@ -274,32 +314,41 @@ const WmTree = (props) => {
274
314
  (nodeId) => {
275
315
  if (!nodeId) {
276
316
  setSelectedItem(null);
317
+ if (persistenceEnabled) {
318
+ setPersistedState((prevPersist) => {
319
+ if (!prevPersist || typeof prevPersist !== "object") return prevPersist;
320
+ const _a2 = prevPersist, { selected: _s } = _a2, rest = __objRest(_a2, ["selected"]);
321
+ return rest;
322
+ });
323
+ }
277
324
  return;
278
325
  }
279
326
  const node = findNodeById(treeNodes, nodeId);
280
327
  if (node) {
281
328
  if (isTreeNodeDisabled(node.data)) return;
329
+ skipNextPersistRef.current = true;
282
330
  selectNode(void 0, node);
331
+ skipNextPersistRef.current = false;
332
+ let working = treeNodesRef.current;
283
333
  if (isRadioTree) {
284
334
  setRadioSelectedNodeId(node.nodeId);
285
335
  const updateRadioState = (nodes, selectedNodeId) => {
286
- return nodes.map((node2) => {
336
+ return nodes.map((n) => {
287
337
  var _a2;
288
- const isChecked = node2.nodeId === selectedNodeId;
289
- const updatedNode = __spreadProps(__spreadValues({}, node2), {
338
+ const isChecked = n.nodeId === selectedNodeId;
339
+ const updatedNode = __spreadProps(__spreadValues({}, n), {
290
340
  checked: isChecked
291
341
  });
292
342
  if (updatedNode.data) {
293
343
  updatedNode.data = __spreadProps(__spreadValues({}, updatedNode.data), { checked: isChecked });
294
344
  }
295
- if ((_a2 = node2.children) == null ? void 0 : _a2.length) {
296
- updatedNode.children = updateRadioState(node2.children, selectedNodeId);
345
+ if ((_a2 = n.children) == null ? void 0 : _a2.length) {
346
+ updatedNode.children = updateRadioState(n.children, selectedNodeId);
297
347
  }
298
348
  return updatedNode;
299
349
  });
300
350
  };
301
- const updatedNodes = updateRadioState(treeNodes, node.nodeId);
302
- setTreeNodes(updatedNodes);
351
+ working = updateRadioState(working, node.nodeId);
303
352
  }
304
353
  let currentNode = node.parent;
305
354
  const nodesToExpand = [];
@@ -308,29 +357,46 @@ const WmTree = (props) => {
308
357
  currentNode = currentNode.parent;
309
358
  }
310
359
  if (nodesToExpand.length) {
311
- const updateNodeOpenState = (nodes, nodesToExpand2) => {
312
- return nodes.map((node2) => {
360
+ const updateNodeOpenState = (nodes, toExpand) => {
361
+ return nodes.map((n) => {
313
362
  var _a2;
314
- if (nodesToExpand2.some((n) => n.nodeId === node2.nodeId)) {
315
- return __spreadProps(__spreadValues({}, node2), {
363
+ if (toExpand.some((x) => x.nodeId === n.nodeId)) {
364
+ return __spreadProps(__spreadValues({}, n), {
316
365
  open: true,
317
- children: node2.children ? updateNodeOpenState(node2.children, nodesToExpand2) : void 0
366
+ children: n.children ? updateNodeOpenState(n.children, toExpand) : void 0
318
367
  });
319
- } else if ((_a2 = node2.children) == null ? void 0 : _a2.length) {
320
- return __spreadProps(__spreadValues({}, node2), { children: updateNodeOpenState(node2.children, nodesToExpand2) });
368
+ } else if ((_a2 = n.children) == null ? void 0 : _a2.length) {
369
+ return __spreadProps(__spreadValues({}, n), { children: updateNodeOpenState(n.children, toExpand) });
321
370
  }
322
- return node2;
371
+ return n;
323
372
  });
324
373
  };
325
- const updatedNodes = updateNodeOpenState(treeNodes, nodesToExpand);
326
- setTreeNodes(updatedNodes);
374
+ working = updateNodeOpenState(working, nodesToExpand);
375
+ }
376
+ treeNodesRef.current = working;
377
+ setTreeNodes(working);
378
+ if (persistenceEnabled) {
379
+ setPersistedState((prevPersist) => {
380
+ const base = prevPersist && typeof prevPersist === "object" ? prevPersist : {};
381
+ return __spreadProps(__spreadValues({}, base), {
382
+ selected: node.nodeId,
383
+ expanded: collectExpandedNodeIdsForUrl(working)
384
+ });
385
+ });
327
386
  }
328
387
  }
329
388
  },
330
- [isRadioTree, selectNode, treeNodes]
389
+ [isRadioTree, selectNode, persistenceEnabled, treeNodes]
331
390
  );
332
391
  const deselectById = useCallback(() => {
333
392
  setSelectedItem(null);
393
+ if (persistenceEnabled) {
394
+ setPersistedState((prevPersist) => {
395
+ if (!prevPersist || typeof prevPersist !== "object") return prevPersist;
396
+ const _a2 = prevPersist, { selected: _s } = _a2, rest = __objRest(_a2, ["selected"]);
397
+ return rest;
398
+ });
399
+ }
334
400
  if (isRadioTree) {
335
401
  setRadioSelectedNodeId(null);
336
402
  const clearCheckedState = (nodes) => {
@@ -348,8 +414,15 @@ const WmTree = (props) => {
348
414
  };
349
415
  setTreeNodes(clearCheckedState(treeNodes));
350
416
  }
351
- }, [isRadioTree]);
417
+ }, [isRadioTree, persistenceEnabled, treeNodes]);
352
418
  const sortedTreeNodes = useMemo(() => treeNodes, [treeNodes]);
419
+ const urlExpansionOverlay = useMemo(() => {
420
+ if (!persistenceEnabled || !sortedTreeNodes.length || !persistedState) return void 0;
421
+ const raw = Array.isArray(persistedState.expanded) && persistedState.expanded.length > 0 ? persistedState.expanded.map(String) : migrateLegacyTreeUrlState(persistedState);
422
+ if (!raw.length) return void 0;
423
+ return normalizeExpandedIds(sortedTreeNodes, raw);
424
+ }, [persistenceEnabled, sortedTreeNodes, persistedState]);
425
+ const urlSelectedId = persistenceEnabled ? persistedState == null ? void 0 : persistedState.selected : void 0;
353
426
  const prevDatasetRef = useRef(void 0);
354
427
  const prevGetNodesRef = useRef(null);
355
428
  useEffect(() => {
@@ -409,7 +482,7 @@ const WmTree = (props) => {
409
482
  setTemplate
410
483
  });
411
484
  }
412
- }, [selectById, deselectById]);
485
+ }, [name]);
413
486
  return /* @__PURE__ */ jsx(
414
487
  Box,
415
488
  {
@@ -438,7 +511,9 @@ const WmTree = (props) => {
438
511
  handleRadioSelect,
439
512
  treeName: name,
440
513
  template: template || templateName,
441
- listener
514
+ listener,
515
+ urlExpansionOverlay,
516
+ urlSelectedId
442
517
  },
443
518
  node.nodeId
444
519
  )) : /* @__PURE__ */ jsx("li", { className: "wm-tree-empty-message", children: "No tree data available" })
@@ -29,6 +29,17 @@ export interface TreeItem {
29
29
  tId?: string;
30
30
  isParent?: boolean;
31
31
  }
32
+ /** Persisted tree widget state shape (URL / localStorage / sessionStorage via wm_state). */
33
+ export type TreeUrlState = {
34
+ /** Node ids that are expanded (nested shells open). Ancestors need not be listed; they are implied. */
35
+ expanded?: string[];
36
+ /** Last selected node id (optional). */
37
+ selected?: string;
38
+ /** @deprecated Legacy shape — migrated on read */
39
+ id?: string;
40
+ open?: boolean;
41
+ parentNode?: string[];
42
+ };
32
43
  /**
33
44
  * Base props specific to the WmTree component.
34
45
  */
@@ -90,6 +101,8 @@ export interface TreeProps {
90
101
  **template**: partial name for custom node rendering .
91
102
  */
92
103
  template?: string;
104
+ /** Persist expansion/selection via `URL`, `localStorage`, or `sessionStorage` (wm_state). */
105
+ statehandler?: string;
93
106
  }
94
107
  /**
95
108
  * Combined props for the WmTree component, including base WaveMaker props.
@@ -113,7 +126,11 @@ export interface TreeNodeComponentProps {
113
126
  /** Partial name from {@link TreeProps.template}; replaces default row with partial UI when set */
114
127
  template?: string;
115
128
  /** Page / fragment listener for partial container + widget registry */
116
- listener?: Record<string, unknown>;
129
+ listener?: Record<string, unknown> | undefined;
130
+ /** Normalized expanded ids from URL (overlay until tree state hydrates) */
131
+ urlExpansionOverlay?: Set<string> | undefined;
132
+ /** Selected node id from URL persistence */
133
+ urlSelectedId?: string | undefined;
117
134
  }
118
135
  export type TreePartialNodeApi = {
119
136
  select: () => void;