@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.
- package/actions/navigation-action.d.ts +1 -0
- package/actions/navigation-action.js +34 -9
- package/components/auth/RoleAuthGuard.d.ts +6 -0
- package/components/auth/RoleAuthGuard.js +65 -0
- package/components/basic/search/index.js +2 -1
- package/components/basic/search/providers.js +2 -2
- package/components/basic/tree/Components/TreeNodeComponent.d.ts +1 -1
- package/components/basic/tree/Components/TreeNodeComponent.js +14 -8
- package/components/basic/tree/hooks/useTreePersistedState.d.ts +3 -0
- package/components/basic/tree/hooks/useTreePersistedState.js +38 -0
- package/components/basic/tree/index.d.ts +1 -0
- package/components/basic/tree/index.js +105 -30
- package/components/basic/tree/props.d.ts +18 -1
- package/components/basic/tree/utils.d.ts +16 -1
- package/components/basic/tree/utils.js +111 -0
- package/components/chart/src/dataUtils.js +1 -1
- package/components/common/customTemplate/useCustomTemplate.js +1 -1
- package/components/container/layout-grid/grid-column/index.js +2 -1
- package/components/data/form/base-form/index.js +3 -1
- package/components/data/form/form-controller/withFormController.js +3 -7
- package/components/data/pagination/hooks/usePagination.js +0 -1
- package/components/data/table/index.js +5 -2
- package/components/data/table/utils/columnBuilder.d.ts +1 -1
- package/components/data/table/utils/columnBuilder.js +7 -4
- package/components/data/table/utils/crud-handlers.js +10 -10
- package/components/data/table/utils/index.d.ts +9 -8
- package/components/data/table/utils/index.js +73 -9
- package/components/layout/leftnav/index.js +21 -2
- package/components/layout/leftnav/props.d.ts +9 -0
- package/components/layout/leftnav/utils/page-class-util.d.ts +9 -0
- package/components/layout/leftnav/utils/page-class-util.js +28 -0
- package/components/navigation/popover/index.js +1 -0
- package/components/page/index.js +22 -6
- package/components/page/page-context.d.ts +4 -0
- package/components/page/page-context.js +5 -0
- package/components/page/partial-container/index.js +1 -4
- package/higherOrder/BaseApp.js +4 -3
- package/higherOrder/BaseAppProps.d.ts +1 -0
- package/higherOrder/BasePage.js +0 -10
- package/hooks/useDataSourceSubscription.js +1 -1
- package/hooks/useHttp.js +0 -4
- package/package-lock.json +196 -185
- package/package.json +3 -3
- package/runtime-dynamic/app-initializer.js +1 -1
- package/runtime-dynamic/components/use-dynamic-component.js +17 -31
- package/runtime-dynamic/factories/build-base-page-like-component.d.ts +1 -1
- package/runtime-dynamic/factories/dynamic-component.js +9 -1
- package/runtime-dynamic/factories/prefab-factory.d.ts +1 -1
- package/runtime-dynamic/factories/utils.d.ts +1 -1
- package/runtime-dynamic/services/component-ref-provider.d.ts +18 -3
- package/runtime-dynamic/services/component-ref-provider.js +15 -38
- package/runtime-dynamic/services/index.d.ts +2 -3
- package/runtime-dynamic/services/index.js +1 -18
- package/runtime-dynamic/services/resource-manager.d.ts +2 -3
- package/runtime-dynamic/services/resource-manager.js +5 -15
- package/runtime-dynamic/services/script-executor.js +1 -7
- package/runtime-dynamic/services/variable-registry.js +0 -4
- package/utils/attr.js +2 -9
- package/utils/custom-expression/index.js +4 -1
- package/utils/custom-expression/parser.js +4 -2
- package/utils/state-persistance.d.ts +1 -1
- package/runtime-dynamic/services/cache.d.ts +0 -29
- package/runtime-dynamic/services/cache.js +0 -51
|
@@ -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
|
-
|
|
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,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 ?
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
228
|
+
selected: isSelected
|
|
223
229
|
}),
|
|
224
230
|
id: node.tId,
|
|
225
|
-
tabIndex:
|
|
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(
|
|
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((
|
|
336
|
+
return nodes.map((n) => {
|
|
287
337
|
var _a2;
|
|
288
|
-
const isChecked =
|
|
289
|
-
const updatedNode = __spreadProps(__spreadValues({},
|
|
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 =
|
|
296
|
-
updatedNode.children = updateRadioState(
|
|
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
|
-
|
|
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,
|
|
312
|
-
return nodes.map((
|
|
360
|
+
const updateNodeOpenState = (nodes, toExpand) => {
|
|
361
|
+
return nodes.map((n) => {
|
|
313
362
|
var _a2;
|
|
314
|
-
if (
|
|
315
|
-
return __spreadProps(__spreadValues({},
|
|
363
|
+
if (toExpand.some((x) => x.nodeId === n.nodeId)) {
|
|
364
|
+
return __spreadProps(__spreadValues({}, n), {
|
|
316
365
|
open: true,
|
|
317
|
-
children:
|
|
366
|
+
children: n.children ? updateNodeOpenState(n.children, toExpand) : void 0
|
|
318
367
|
});
|
|
319
|
-
} else if ((_a2 =
|
|
320
|
-
return __spreadProps(__spreadValues({},
|
|
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
|
|
371
|
+
return n;
|
|
323
372
|
});
|
|
324
373
|
};
|
|
325
|
-
|
|
326
|
-
|
|
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
|
-
}, [
|
|
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;
|