@vc-shell/framework 1.0.191 → 1.0.193

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 (25) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/core/plugins/modularity/index.ts +9 -2
  3. package/dist/core/plugins/modularity/index.d.ts.map +1 -1
  4. package/dist/framework.js +14460 -14304
  5. package/dist/shared/components/blade-navigation/composables/useBladeNavigation/index.d.ts +7 -2
  6. package/dist/shared/components/blade-navigation/composables/useBladeNavigation/index.d.ts.map +1 -1
  7. package/dist/shared/components/blade-navigation/types/index.d.ts +0 -1
  8. package/dist/shared/components/blade-navigation/types/index.d.ts.map +1 -1
  9. package/dist/shared/modules/dynamic/index.d.ts.map +1 -1
  10. package/dist/shared/modules/dynamic/pages/dynamic-blade-form.vue.d.ts +8 -9
  11. package/dist/shared/modules/dynamic/pages/dynamic-blade-form.vue.d.ts.map +1 -1
  12. package/dist/shared/modules/dynamic/pages/dynamic-blade-list.vue.d.ts.map +1 -1
  13. package/dist/tsconfig.tsbuildinfo +1 -1
  14. package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/_internal/vc-app-menu-link.vue.d.ts.map +1 -1
  15. package/dist/ui/components/organisms/vc-app/vc-app.vue.d.ts.map +1 -1
  16. package/package.json +4 -4
  17. package/shared/components/blade-navigation/composables/useBladeNavigation/index.ts +303 -192
  18. package/shared/components/blade-navigation/plugin.ts +1 -1
  19. package/shared/components/blade-navigation/types/index.ts +0 -1
  20. package/shared/modules/dynamic/index.ts +5 -0
  21. package/shared/modules/dynamic/pages/dynamic-blade-form.vue +4 -1
  22. package/shared/modules/dynamic/pages/dynamic-blade-list.vue +12 -5
  23. package/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/_internal/vc-app-menu-link.vue +14 -11
  24. package/ui/components/organisms/vc-app/vc-app.vue +8 -1
  25. package/ui/components/organisms/vc-table/vc-table.vue +2 -2
@@ -5,16 +5,24 @@ import {
5
5
  inject,
6
6
  warn,
7
7
  Component,
8
- watch,
9
8
  isVNode,
10
9
  h,
11
10
  shallowRef,
12
11
  ComputedRef,
12
+ mergeProps,
13
13
  } from "vue";
14
14
  /* eslint-disable @typescript-eslint/no-explicit-any */
15
- import { createSharedComposable, reactiveComputed } from "@vueuse/core";
15
+ import { createSharedComposable, reactiveComputed, watchDebounced } from "@vueuse/core";
16
16
  import * as _ from "lodash-es";
17
- import { RouteLocationNormalized, useRoute, NavigationFailure } from "vue-router";
17
+ import {
18
+ RouteLocationNormalized,
19
+ useRoute,
20
+ NavigationFailure,
21
+ RouteRecordName,
22
+ RouteParams,
23
+ Router,
24
+ LocationQuery,
25
+ } from "vue-router";
18
26
  import { bladeNavigationInstance } from "../../plugin";
19
27
  import {
20
28
  BladeComponentInternalInstance,
@@ -24,6 +32,7 @@ import {
24
32
  IParentCallArgs,
25
33
  BladeInstanceConstructor,
26
34
  BladeRoutesRecord,
35
+ ExtractedBladeOptions,
27
36
  } from "../../types";
28
37
  import { navigationViewLocation } from "../../injectionKeys";
29
38
  import { usePermissions } from "../../../../../core/composables";
@@ -43,38 +52,54 @@ interface IUseBladeNavigation {
43
52
  onParentCall: (parentExposedMethods: Record<string, any>, args: IParentCallArgs) => void;
44
53
  onBeforeClose: (cb: () => Promise<boolean | undefined>) => void;
45
54
  resolveBladeByName: (name: string) => BladeInstanceConstructor;
46
- routeResolver: (to: RouteLocationNormalized) => Promise<void | NavigationFailure | undefined> | undefined;
55
+ routeResolver: (to: RouteLocationNormalized) =>
56
+ | Promise<
57
+ | {
58
+ name: RouteRecordName | undefined;
59
+ params: RouteParams;
60
+ }
61
+ | undefined
62
+ >
63
+ | undefined;
47
64
  getCurrentBlade: () => BladeVNode;
65
+ setNavigationQuery: (query: Record<string, string | number>) => void;
66
+ getNavigationQuery: () => Record<string, string | number>;
48
67
  }
49
68
 
50
69
  const activeWorkspace = shallowRef<BladeVNode>();
51
70
  const mainRouteBaseParamURL = shallowRef<string>();
52
71
 
53
- function parseUrl(url: string) {
54
- const urlRegex = /^\/([a-zA-Z0-9_-]+)(?:\/([a-zA-Z0-9_-]+))?(?:\/([a-zA-Z0-9_-]+))?$/;
55
- const match = url.match(urlRegex);
72
+ const utils = (router: Router) => {
73
+ const route = useRoute();
56
74
 
57
- if (!match) {
58
- return undefined;
59
- }
75
+ function parseUrl(url: string) {
76
+ // remove parts of url that does not contain workspace, blade or param - everything before workspace
77
+ const parts = url.split("/");
78
+ const workspaceIndex = parts.findIndex((part) => {
79
+ const route = router
80
+ .getRoutes()
81
+ .find((r) => r.path.endsWith("/" + part) && (r.components?.default as BladeVNode).type.isWorkspace);
60
82
 
61
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
62
- const [_, workspace, blade, param] = match;
83
+ return route !== undefined;
84
+ });
85
+ const cleanUrl = "/" + parts.slice(workspaceIndex).join("/");
63
86
 
64
- return {
65
- workspace,
66
- blade: blade || undefined,
67
- param: param || undefined,
68
- };
69
- }
87
+ const urlRegex = /^\/([a-zA-Z0-9_-]+)(?:\/([a-zA-Z0-9_-]+))?(?:\/([a-zA-Z0-9_-]+))?$/;
88
+ const match = cleanUrl.match(urlRegex);
70
89
 
71
- const useBladeNavigationSingleton = createSharedComposable(() => {
72
- const route = useRoute();
90
+ if (!match) {
91
+ return undefined;
92
+ }
73
93
 
74
- const instance: BladeComponentInternalInstance = getCurrentInstance() as BladeComponentInternalInstance;
75
- const navigationInstance =
76
- (instance !== null && inject<BladeNavigationPlugin>("bladeNavigationPlugin")) || bladeNavigationInstance;
77
- const router = navigationInstance?.router;
94
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
95
+ const [_, workspace, blade, param] = match;
96
+
97
+ return {
98
+ workspace,
99
+ blade: blade || undefined,
100
+ param: param || undefined,
101
+ };
102
+ }
78
103
 
79
104
  function parseWorkspaceUrl(path: string): string {
80
105
  // Object.values(route.params)[0] will always be base path of the app
@@ -89,81 +114,127 @@ const useBladeNavigationSingleton = createSharedComposable(() => {
89
114
  return "/" + workspaceUrl;
90
115
  }
91
116
 
92
- watch(
117
+ function getURLQuery() {
118
+ if (route.query && Object.keys(route.query).length) {
119
+ return { params: new URLSearchParams(route.query as Record<string, string>).toString(), obj: route.query };
120
+ }
121
+
122
+ const [, query] = window.location.href.split("#")[1].split("?");
123
+ const params = new URLSearchParams(query).toString();
124
+
125
+ return { params, obj: Object.fromEntries(new URLSearchParams(query)) };
126
+ }
127
+
128
+ return {
129
+ parseUrl,
130
+ parseWorkspaceUrl,
131
+ getURLQuery,
132
+ };
133
+ };
134
+
135
+ const useBladeNavigationSingleton = createSharedComposable(() => {
136
+ const route = useRoute();
137
+
138
+ const instance: BladeComponentInternalInstance = getCurrentInstance() as BladeComponentInternalInstance;
139
+ const navigationInstance =
140
+ (instance !== null && inject<BladeNavigationPlugin>("bladeNavigationPlugin")) || bladeNavigationInstance;
141
+ const router = navigationInstance?.router;
142
+
143
+ const { parseUrl, parseWorkspaceUrl, getURLQuery } = utils(router);
144
+
145
+ watchDebounced(
93
146
  () => route.path,
94
147
  async (newVal, oldVal) => {
95
148
  const workspaceUrl = parseWorkspaceUrl(newVal);
96
149
 
97
- const wsRouteComponent =
98
- (route?.matched?.[1]?.components?.default as BladeVNode) ??
99
- (router.resolve({ path: workspaceUrl })?.matched?.[1]?.components?.default as BladeVNode);
150
+ const wsRouteComponent = getWorkspaceRouteComponent(workspaceUrl);
100
151
 
101
152
  if (wsRouteComponent !== undefined) {
102
153
  if (isVNode(wsRouteComponent) && wsRouteComponent.type.isBlade) {
103
- //&& wsRouteComponent.type.isBlade
104
- navigationInstance.blades.value[0] = markRaw(wsRouteComponent);
105
- activeWorkspace.value = wsRouteComponent;
154
+ updateActiveWorkspace(wsRouteComponent);
106
155
  } else {
107
- const isPrevented = await closeBlade(0);
108
-
109
- if (!isPrevented) {
110
- // add not blade page to blades array to show simple routes as
111
- // we use only one router-view for all routes
112
- navigationInstance.blades.value = [];
113
- } else {
114
- if (oldVal) router.replace({ path: oldVal });
115
- }
156
+ await handleNonBladePage(oldVal);
116
157
  }
117
158
  }
118
159
  },
119
160
  { immediate: true },
120
161
  );
121
162
 
122
- watch(
163
+ async function handleNonBladePage(oldVal?: string) {
164
+ const isPrevented = await closeBlade(0);
165
+ if (!isPrevented) {
166
+ navigationInstance.blades.value = [];
167
+ activeWorkspace.value = undefined;
168
+ } else {
169
+ if (oldVal) router.replace({ path: oldVal });
170
+ }
171
+ }
172
+
173
+ function updateActiveWorkspace(wsRouteComponent: BladeVNode) {
174
+ navigationInstance.blades.value[0] = markRaw(
175
+ Object.assign(wsRouteComponent, {
176
+ props: mergeProps(wsRouteComponent.props, {
177
+ navigation: {
178
+ idx: 0,
179
+ },
180
+ }),
181
+ }),
182
+ );
183
+ activeWorkspace.value = wsRouteComponent;
184
+ }
185
+
186
+ function getWorkspaceRouteComponent(workspaceUrl: string) {
187
+ return (
188
+ (route?.matched?.[1]?.components?.default as BladeVNode) ??
189
+ (router.resolve({ path: workspaceUrl })?.matched?.[1]?.components?.default as BladeVNode)
190
+ );
191
+ }
192
+
193
+ watchDebounced(
123
194
  navigationInstance.blades,
124
- (newVal) => {
195
+ async (newVal) => {
125
196
  const workspace = navigationInstance.blades.value[0];
126
-
127
- // method that checks if last item in newVal array has url and returns item. If it has no url - it returns previous item with url, but not first item from array as it's workspace
128
- const getLastItemWithUrl = () => {
129
- // Find the previous item with a URL
130
- for (let i = newVal.length - 1; i > 0; i--) {
131
- if (newVal[i].type.url) {
132
- return newVal[i]; // return the previous item with a URL
133
- }
134
- }
135
- return;
136
- };
197
+ const lastBlade = getLastItemWithUrl(newVal);
137
198
 
138
199
  if (workspace?.type?.url) {
139
- let url: string;
140
- // when restoring blades we need to use full path of the blade to show last blade from the url as workspace.
141
- // fullPath is provided by generateRoute function
142
- const wsBladeUrl = workspace?.props?.navigation?.fullPath || workspace?.type.url;
143
-
144
- const lastBlade = getLastItemWithUrl();
145
- if (getLastItemWithUrl()) {
146
- url =
147
- "/" +
148
- parseUrl(wsBladeUrl)?.workspace +
149
- lastBlade?.type.url +
150
- (lastBlade?.props?.param ? "/" + lastBlade?.props.param : "");
151
- } else {
152
- url = wsBladeUrl;
153
- }
154
-
200
+ const url = constructUrl(workspace, lastBlade);
155
201
  if (url) {
156
- router.options.history.replace(
157
- (mainRouteBaseParamURL.value && !url.startsWith(mainRouteBaseParamURL.value)
158
- ? mainRouteBaseParamURL.value
159
- : "") + url,
160
- );
202
+ updateRouterHistory(url);
161
203
  }
162
204
  }
163
205
  },
164
- { deep: true },
206
+ { deep: true, debounce: 1 },
165
207
  );
166
208
 
209
+ function getLastItemWithUrl(newVal: BladeVNode[]) {
210
+ for (let i = newVal.length - 1; i > 0; i--) {
211
+ if (newVal[i].type.url) {
212
+ return newVal[i];
213
+ }
214
+ }
215
+ }
216
+
217
+ function constructUrl(workspace: BladeVNode, lastBlade?: BladeVNode) {
218
+ const wsBladeUrl = workspace?.type.url;
219
+ const lastBladeUrl = lastBlade?.type.url;
220
+ const param = lastBlade?.props?.param;
221
+ if (lastBlade && wsBladeUrl) {
222
+ return "/" + parseUrl(wsBladeUrl)?.workspace + lastBladeUrl + (param ? "/" + param : "");
223
+ } else {
224
+ return wsBladeUrl;
225
+ }
226
+ }
227
+
228
+ function updateRouterHistory(url: string) {
229
+ const params = getURLQuery().params;
230
+
231
+ router.options.history.replace(
232
+ (mainRouteBaseParamURL.value && !url.startsWith(mainRouteBaseParamURL.value) ? mainRouteBaseParamURL.value : "") +
233
+ url +
234
+ (params ? "?" + params : ""),
235
+ );
236
+ }
237
+
167
238
  async function closeBlade(index: number) {
168
239
  console.debug(`[@vc-shell/framework#useBladeNavigation] - closeBlade called.`);
169
240
 
@@ -215,10 +286,15 @@ export function useBladeNavigation(): IUseBladeNavigation {
215
286
  const instance: BladeComponentInternalInstance = getCurrentInstance() as BladeComponentInternalInstance;
216
287
 
217
288
  const { router, route, navigationInstance, closeBlade } = useBladeNavigationSingleton();
218
-
219
- const mainRoute = router.getRoutes().find((r) => r.meta?.root)!;
220
-
221
- async function openWorkspace<Blade extends Component>({ blade, param, options }: IBladeEvent<Blade>) {
289
+ const { parseUrl, getURLQuery } = utils(router);
290
+ const routerRoutes = router.getRoutes();
291
+ const mainRoute = routerRoutes.find((r) => r.meta?.root)!;
292
+
293
+ async function openWorkspace<Blade extends Component>(
294
+ { blade, param, options }: IBladeEvent<Blade>,
295
+ query: LocationQuery | undefined = undefined,
296
+ params: RouteParams = {},
297
+ ) {
222
298
  const createdComponent = h(blade, {
223
299
  param,
224
300
  options,
@@ -228,26 +304,25 @@ export function useBladeNavigation(): IUseBladeNavigation {
228
304
  }) as BladeVNode;
229
305
 
230
306
  try {
231
- const isPrevented = await closeBlade(0);
307
+ // Close all blades except the first one cause it will be overwritten
308
+ const isPrevented = await closeBlade(1);
232
309
 
233
310
  if (!isPrevented && createdComponent.type?.url) {
234
311
  if (hasAccess(blade.permissions)) {
235
312
  navigationInstance.blades.value = [createdComponent];
236
-
237
313
  // Find the route with the matching URL and update the components.default property with the new component
238
- const route = router.getRoutes().find((r) => r.path === createdComponent.type?.url);
239
- if (route && route.components) {
240
- route.components.default = createdComponent;
314
+ const wsroute = routerRoutes.find((r) => r.path.endsWith(createdComponent.type?.url as string));
315
+ if (wsroute && wsroute.components) {
316
+ wsroute.components.default = createdComponent;
241
317
  }
242
-
243
- return await router.replace({ path: createdComponent.type?.url as string });
318
+ return await router.replace({ name: wsroute?.name, params: { ...params, ...route.params }, query });
244
319
  } else
245
320
  notification.error(i18n.global.t("PERMISSION_MESSAGES.ACCESS_RESTRICTED"), {
246
321
  timeout: 3000,
247
322
  });
248
323
  }
249
324
  } catch (e) {
250
- console.log(e);
325
+ console.error(e);
251
326
  throw new Error(`Opening workspace '${blade.type.name}' is prevented`);
252
327
  }
253
328
  }
@@ -267,52 +342,64 @@ export function useBladeNavigation(): IUseBladeNavigation {
267
342
  try {
268
343
  const instanceComponent = navigationView || activeWorkspace.value;
269
344
 
270
- if (isVNode(instanceComponent) || activeWorkspace.value) {
271
- const instanceComponentIndex =
272
- navigationInstance.blades.value /* @ts-expect-error - findLastIndex is not parsed correctly by ts */
273
- .findLastIndex((x) => _.isEqual(x.type, instanceComponent.type));
345
+ if (!instanceComponent) {
346
+ throw new Error("No workspace found");
347
+ }
274
348
 
275
- const instanceComponentChild =
276
- instanceComponentIndex >= 0 ? navigationInstance.blades.value[instanceComponentIndex + 1] : undefined;
349
+ const instanceComponentIndex = findInstanceComponentIndex(instanceComponent);
350
+ const instanceComponentChild = navigationInstance.blades.value[instanceComponentIndex + 1];
277
351
 
278
- let isPrevented = false;
352
+ let isPrevented = false;
279
353
 
280
- if (instanceComponentChild) {
281
- isPrevented = await closeBlade(instanceComponentChild.props?.navigation?.idx);
282
- }
354
+ if (instanceComponentChild) {
355
+ isPrevented = await closeBlade(instanceComponentChild.props?.navigation?.idx);
356
+ }
357
+
358
+ const currentBladeIdx = instanceComponent.props?.navigation?.idx ?? 0;
283
359
 
284
- const currentBladeIdx = instanceComponent.props?.navigation?.idx ? instanceComponent.props?.navigation?.idx : 0;
285
-
286
- const bladeNode = h(
287
- blade,
288
- Object.assign(
289
- {},
290
- reactiveComputed(() => ({ options, param })),
291
- {
292
- navigation: {
293
- onClose,
294
- onOpen,
295
- idx: currentBladeIdx + 1,
296
- },
297
- },
298
- ),
299
- ) as BladeVNode;
300
-
301
- if (!isPrevented) {
302
- if (hasAccess(blade.permissions)) navigationInstance.blades.value.push(bladeNode);
303
- else
304
- notification.error(i18n.global.t("PERMISSION_MESSAGES.ACCESS_RESTRICTED"), {
305
- timeout: 3000,
306
- });
360
+ const bladeNode = createBladeNode<Blade>({ blade, currentBladeIdx, options, param, onClose, onOpen });
361
+
362
+ if (!isPrevented) {
363
+ if (hasAccess(blade.permissions)) {
364
+ navigationInstance.blades.value.push(bladeNode);
365
+ } else {
366
+ notification.error(i18n.global.t("PERMISSION_MESSAGES.ACCESS_RESTRICTED"), { timeout: 3000 });
307
367
  }
308
- } else {
309
- throw new Error("No workspace found");
310
368
  }
311
369
  } catch (e) {
312
370
  console.error(e);
313
371
  }
314
372
  }
315
373
 
374
+ function findInstanceComponentIndex(instanceComponent: BladeVNode) {
375
+ return navigationInstance.blades.value.findIndex((x) => _.isEqual(x.type, instanceComponent.type));
376
+ }
377
+
378
+ function createBladeNode<Blade extends Component>(args: {
379
+ blade: BladeInstanceConstructor<Blade>;
380
+ currentBladeIdx: number;
381
+ options: ExtractedBladeOptions<InstanceType<BladeInstanceConstructor<Blade>>["$props"], "options"> | undefined;
382
+ param?: string;
383
+ onClose?: () => void;
384
+ onOpen?: () => void;
385
+ }) {
386
+ const { blade, currentBladeIdx, options, param, onClose, onOpen } = args;
387
+ return h(
388
+ blade,
389
+ Object.assign(
390
+ {},
391
+ reactiveComputed(() => ({ options, param })),
392
+ {
393
+ navigation: {
394
+ onClose,
395
+ onOpen,
396
+ idx: currentBladeIdx + 1,
397
+ },
398
+ },
399
+ ),
400
+ ) as BladeVNode;
401
+ }
402
+
316
403
  async function onParentCall(parentExposedMethods: Record<string, any>, args: IParentCallArgs) {
317
404
  console.debug(`vc-app#onParentCall({ method: ${args.method} }) called.`);
318
405
 
@@ -355,7 +442,7 @@ export function useBladeNavigation(): IUseBladeNavigation {
355
442
  }
356
443
 
357
444
  function hasNecessaryRoute(to: RouteLocationNormalized) {
358
- return router.getRoutes().find((route) => route.path === to.path);
445
+ return routerRoutes.find((route) => route.path === to.path);
359
446
  }
360
447
 
361
448
  /**
@@ -369,92 +456,77 @@ export function useBladeNavigation(): IUseBladeNavigation {
369
456
  */
370
457
  async function generateRoute(to: RouteLocationNormalized, routes: BladeRoutesRecord[]) {
371
458
  // Extract parameters excluding "pathMatch". This is necessary if a variable is used as the App component URL, for example, /:userId?
372
- const params = Object.entries(to.params)
373
- .filter(([key]) => key !== "pathMatch")
374
- .reduce(
375
- (acc, [key, value]) => {
376
- acc[key] = value;
377
- return acc;
378
- },
379
- {} as Record<string, string | string[]>,
380
- );
459
+ const params = Object.fromEntries(Object.entries(to.params).filter(([key]) => key !== "pathMatch"));
381
460
 
382
461
  // Get the raw path of the main route.
383
- const parentRawPath = router.getRoutes().find((route) => route.name === mainRoute.name)!.path!;
462
+ const parentRawPath = routerRoutes.find((route) => route.name === mainRoute.name)?.path;
384
463
 
385
464
  // Determine the parent path based on the parameters.
386
- const parentPath = parentRawPath.includes(Object.keys(params)?.[0] as string)
387
- ? "/" + (Object.values(params)?.[0] ?? "")
388
- : "";
465
+ const parentPath =
466
+ parentRawPath && parentRawPath.includes(Object.keys(params)[0]) ? `/${Object.values(params)[0]}` : "";
389
467
 
390
468
  // Set the base param value.
391
469
  mainRouteBaseParamURL.value = parentPath;
392
470
 
393
- // Check if the current path matches the parent path.
394
- const isCorrectParentPath = parentRawPath !== "/" && to.path.startsWith(parentPath);
395
-
396
- // Check if the current route is a parent route.
397
- const isRouteParent = router.getRoutes().find((x) => x.path.endsWith(parentPath));
398
-
399
471
  // Parse the URL to extract relevant route information.
400
- const parsedRoutes = parseUrl(!isRouteParent ? to.path.slice(parentPath.length) : to.path);
472
+ const parsedRoutes = parseUrl(parentPath !== "/" ? to.path.slice(parentPath.length) : to.path);
473
+
474
+ if (parsedRoutes !== undefined) {
475
+ const { workspace, blade, param } = parsedRoutes;
401
476
 
402
- if (parsedRoutes) {
403
477
  // Find the registered route component.
404
- const registeredRouteComponent = routes.find((route) => route.route === "/" + parsedRoutes?.blade)?.component;
478
+ const registeredWorkspaceComponent = routes.find((route) => route.route === `/${workspace}`)?.component;
479
+ const registeredRouteComponent = routes.find((route) => route.route === `/${blade}`)?.component;
405
480
 
406
- if (registeredRouteComponent) {
407
- if (registeredRouteComponent.type?.isWorkspace) {
408
- // If the route is a workspace, navigate to it directly.
409
- router.replace({ path: registeredRouteComponent.type.url as string });
410
- } else {
411
- // If the route is not a workspace, add it to the router and navigate to it.
412
- const url =
413
- (!isRouteParent ? parentPath : "") +
414
- "/" +
415
- parsedRoutes?.workspace +
416
- "/" +
417
- parsedRoutes.blade +
418
- (parsedRoutes.param ? "/" + parsedRoutes.param : "");
419
-
420
- // If the route is not routable, redirect to the workspace.
421
- if (!parsedRoutes.param && !registeredRouteComponent.type?.routable) {
422
- return router.replace("/" + parsedRoutes?.workspace);
423
- } else {
424
- // Add the route to the router.
425
- router.addRoute(mainRoute.name as string, {
426
- name: url,
427
- path: parentRawPath !== "/" ? parentRawPath + url : "" + url,
428
- components: {
429
- default: _.merge({}, registeredRouteComponent, {
430
- props: {
431
- param: parsedRoutes.param,
432
- navigation: {
433
- fullPath: url,
434
- idx: 0,
435
- },
436
- },
437
- }),
438
- },
439
- meta: {
440
- permissions: registeredRouteComponent.type?.permissions,
441
- },
442
- });
443
-
444
- // Navigate to the newly added route.
445
- return router.replace({ name: url, params: isCorrectParentPath ? params : {} });
446
- }
447
- }
448
- } else {
449
- // If the registered route component is not found, navigate to the main route.
450
- const mainRoute = router.getRoutes().find((route) => route.meta?.root);
451
- const mainRouteAlias = router.getRoutes().find((route) => route.aliasOf?.path === mainRoute?.path) ?? mainRoute;
481
+ if (!registeredWorkspaceComponent) {
482
+ return goToRoot();
483
+ }
452
484
 
453
- router.replace({ name: mainRouteAlias?.name, params: route.params });
485
+ // Open the workspace component or workspace route.
486
+ if (registeredRouteComponent?.type.isWorkspace) {
487
+ await openWorkspace({
488
+ blade: registeredRouteComponent as unknown as BladeInstanceConstructor,
489
+ });
490
+ return { name: registeredRouteComponent?.type.name, params };
454
491
  }
492
+
493
+ // Open the workspace component with param or workspace route.
494
+ if (registeredWorkspaceComponent) {
495
+ await openWorkspace(
496
+ {
497
+ blade: registeredWorkspaceComponent as unknown as BladeInstanceConstructor,
498
+ param:
499
+ registeredRouteComponent?.type.moduleUid === registeredWorkspaceComponent.type.moduleUid
500
+ ? param
501
+ : undefined,
502
+ },
503
+ getURLQuery().obj,
504
+ params,
505
+ );
506
+
507
+ // Open the route if it's not from the workspace module.
508
+ if (
509
+ registeredRouteComponent?.type.moduleUid !== registeredWorkspaceComponent.type.moduleUid &&
510
+ registeredRouteComponent?.type.routable
511
+ ) {
512
+ await openBlade({
513
+ blade: registeredRouteComponent as unknown as BladeInstanceConstructor,
514
+ param: param,
515
+ });
516
+ }
517
+ return { name: registeredWorkspaceComponent?.type.name, params, query: to.query };
518
+ }
519
+ } else {
520
+ return goToRoot();
455
521
  }
456
522
  }
457
523
 
524
+ function goToRoot() {
525
+ const mainRoute = routerRoutes.find((route) => route.meta?.root);
526
+ const mainRouteAlias = routerRoutes.find((route) => route.aliasOf?.path === mainRoute?.path) || mainRoute;
527
+ return { name: mainRouteAlias?.name, params: route.params };
528
+ }
529
+
458
530
  /**
459
531
  * The function getCurrentBlade returns the current BladeVNode instance's vnode.
460
532
  * @returns the `vnode` property of the `instance` object, which is of type `BladeVNode`.
@@ -475,6 +547,43 @@ export function useBladeNavigation(): IUseBladeNavigation {
475
547
  }
476
548
  }
477
549
 
550
+ function setNavigationQuery(query: Record<string, string | number>) {
551
+ // add blade name to query keys
552
+ const namedQuery = _.mapKeys(
553
+ _.mapValues(query, (value) => value?.toString()),
554
+ (value, key) => instance.vnode.type.name.toLowerCase() + "_" + key,
555
+ );
556
+ const cleanQuery = _.omitBy(namedQuery, _.isNil);
557
+
558
+ router.options.history.replace(
559
+ decodeURIComponent(
560
+ `${window.location.hash.substring(1).split("?")[0]}?${new URLSearchParams(cleanQuery).toString()}`,
561
+ ),
562
+ );
563
+ }
564
+
565
+ function getNavigationQuery() {
566
+ const queryKeys = Array.from(Object.keys(route.query));
567
+ const bladeQueryKeys = queryKeys.filter((key) => key.startsWith(instance.vnode.type.name.toLowerCase()));
568
+
569
+ const namedQuery = _.mapKeys(_.pick(route.query, bladeQueryKeys), (value, key) =>
570
+ key.replace(instance.vnode.type.name.toLowerCase() + "_", ""),
571
+ ) as Record<string, string | number>;
572
+
573
+ const obj: typeof namedQuery = {};
574
+ for (const [key, value] of Object.entries(namedQuery)) {
575
+ const numValue = Number(value);
576
+
577
+ if (!isNaN(numValue)) {
578
+ obj[key] = numValue;
579
+ } else {
580
+ obj[key] = value;
581
+ }
582
+ }
583
+
584
+ return obj;
585
+ }
586
+
478
587
  return {
479
588
  blades: computed(() => navigationInstance.blades.value),
480
589
  openBlade,
@@ -485,5 +594,7 @@ export function useBladeNavigation(): IUseBladeNavigation {
485
594
  getCurrentBlade,
486
595
  currentBladeNavigationData,
487
596
  onBeforeClose,
597
+ setNavigationQuery,
598
+ getNavigationQuery,
488
599
  };
489
600
  }
@@ -29,7 +29,7 @@ export const VcBladeNavigationComponent = {
29
29
  };
30
30
 
31
31
  app.config.globalProperties.$bladeNavigationPlugin = bladeNavigationPlugin;
32
- app.provide("bladeNavigationPlugin", bladeNavigationPlugin);
33
32
  bladeNavigationInstance = bladeNavigationPlugin;
33
+ app.provide("bladeNavigationPlugin", bladeNavigationPlugin);
34
34
  },
35
35
  };
@@ -95,7 +95,6 @@ export interface BladeVNode extends VNode {
95
95
  onClose?: () => void;
96
96
  onBeforeClose?: () => Promise<boolean | undefined>;
97
97
  instance: Ref<CoreBladeExposed | undefined | null>;
98
- fullPath: string;
99
98
  idx: number;
100
99
  };
101
100
  onVnodeUnmounted?: VNodeMountHook | VNodeMountHook[];