@nocobase/flow-engine 2.1.0-beta.37 → 2.1.0-beta.38

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 (38) hide show
  1. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +8 -1
  2. package/lib/components/subModel/LazyDropdown.js +200 -16
  3. package/lib/flowContext.js +3 -0
  4. package/lib/flowEngine.js +3 -3
  5. package/lib/models/flowModel.js +3 -3
  6. package/lib/utils/parsePathnameToViewParams.d.ts +5 -1
  7. package/lib/utils/parsePathnameToViewParams.js +28 -4
  8. package/lib/views/ViewNavigation.d.ts +12 -2
  9. package/lib/views/ViewNavigation.js +22 -7
  10. package/lib/views/createViewMeta.js +114 -50
  11. package/lib/views/inheritLayoutContext.d.ts +10 -0
  12. package/lib/views/inheritLayoutContext.js +50 -0
  13. package/lib/views/useDialog.js +2 -0
  14. package/lib/views/useDrawer.js +2 -0
  15. package/lib/views/usePage.js +2 -0
  16. package/package.json +4 -4
  17. package/src/__tests__/createViewMeta.popup.test.ts +115 -1
  18. package/src/__tests__/flowEngine.removeModel.test.ts +47 -3
  19. package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +11 -1
  20. package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +5 -2
  21. package/src/components/subModel/LazyDropdown.tsx +228 -16
  22. package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +203 -1
  23. package/src/executor/__tests__/flowExecutor.test.ts +28 -0
  24. package/src/flowContext.ts +3 -0
  25. package/src/flowEngine.ts +4 -3
  26. package/src/models/__tests__/flowEngine.resolveUse.test.ts +0 -15
  27. package/src/models/__tests__/flowModel.test.ts +33 -34
  28. package/src/models/flowModel.tsx +3 -3
  29. package/src/utils/__tests__/parsePathnameToViewParams.test.ts +21 -0
  30. package/src/utils/parsePathnameToViewParams.ts +45 -5
  31. package/src/views/ViewNavigation.ts +40 -7
  32. package/src/views/__tests__/ViewNavigation.test.ts +52 -0
  33. package/src/views/__tests__/inheritLayoutContext.test.ts +53 -0
  34. package/src/views/createViewMeta.ts +106 -34
  35. package/src/views/inheritLayoutContext.ts +26 -0
  36. package/src/views/useDialog.tsx +2 -0
  37. package/src/views/useDrawer.tsx +2 -0
  38. package/src/views/usePage.tsx +2 -0
@@ -183,6 +183,13 @@ const getToolbarPopupContainer = /* @__PURE__ */ __name((triggerNode) => {
183
183
  }
184
184
  return triggerNode.closest(TOOLBAR_ICONS_SELECTOR) || triggerNode.closest(TOOLBAR_CONTAINER_SELECTOR);
185
185
  }, "getToolbarPopupContainer");
186
+ const removeExtraMenuItemClickHandlers = /* @__PURE__ */ __name((item) => {
187
+ const { onClick: _onClick, children, ...rest } = item;
188
+ return {
189
+ ...rest,
190
+ children: (children == null ? void 0 : children.length) ? children.map(removeExtraMenuItemClickHandlers) : void 0
191
+ };
192
+ }, "removeExtraMenuItemClickHandlers");
186
193
  const DefaultSettingsIcon = /* @__PURE__ */ __name(({
187
194
  model,
188
195
  showDeleteButton = true,
@@ -698,7 +705,7 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
698
705
  type: "divider"
699
706
  });
700
707
  if (commonExtras.length > 0) {
701
- items.push(...commonExtras);
708
+ items.push(...commonExtras.map(removeExtraMenuItemClickHandlers));
702
709
  }
703
710
  if (showCopyUidButton && model.uid) {
704
711
  items.push({
@@ -169,16 +169,66 @@ const useKeepDropdownOpen = /* @__PURE__ */ __name(() => {
169
169
  }, "useKeepDropdownOpen");
170
170
  const useMenuSearch = /* @__PURE__ */ __name(() => {
171
171
  const [searchValues, setSearchValues] = (0, import_react.useState)({});
172
+ const [inputValues, setInputValues] = (0, import_react.useState)({});
172
173
  const [isSearching, setIsSearching] = (0, import_react.useState)(false);
174
+ const [composingCount, setComposingCount] = (0, import_react.useState)(0);
175
+ const composingKeysRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
173
176
  const searchTimeoutRef = (0, import_react.useRef)(null);
174
- const updateSearchValue = /* @__PURE__ */ __name((key, value) => {
177
+ const updateSearchValue = (0, import_react.useCallback)((key, value) => {
175
178
  setIsSearching(true);
179
+ setInputValues((prev) => ({ ...prev, [key]: value }));
176
180
  setSearchValues((prev) => ({ ...prev, [key]: value }));
177
181
  if (searchTimeoutRef.current) {
178
182
  clearTimeout(searchTimeoutRef.current);
179
183
  }
180
184
  searchTimeoutRef.current = setTimeout(() => setIsSearching(false), 300);
181
- }, "updateSearchValue");
185
+ }, []);
186
+ const startComposition = (0, import_react.useCallback)((key) => {
187
+ composingKeysRef.current.add(key);
188
+ setIsSearching(true);
189
+ setComposingCount(composingKeysRef.current.size);
190
+ if (searchTimeoutRef.current) {
191
+ clearTimeout(searchTimeoutRef.current);
192
+ searchTimeoutRef.current = null;
193
+ }
194
+ }, []);
195
+ const endComposition = (0, import_react.useCallback)(
196
+ (key, value) => {
197
+ composingKeysRef.current.delete(key);
198
+ setComposingCount(composingKeysRef.current.size);
199
+ updateSearchValue(key, value);
200
+ },
201
+ [updateSearchValue]
202
+ );
203
+ const updateInputValue = (0, import_react.useCallback)((key, value) => {
204
+ setInputValues((prev) => ({ ...prev, [key]: value }));
205
+ }, []);
206
+ const clearSearchValue = (0, import_react.useCallback)((key) => {
207
+ composingKeysRef.current.delete(key);
208
+ setComposingCount(composingKeysRef.current.size);
209
+ setInputValues((prev) => {
210
+ if (!(key in prev)) return prev;
211
+ const next = { ...prev };
212
+ delete next[key];
213
+ return next;
214
+ });
215
+ setSearchValues((prev) => {
216
+ if (!(key in prev)) return prev;
217
+ const next = { ...prev };
218
+ delete next[key];
219
+ return next;
220
+ });
221
+ }, []);
222
+ const clearAllSearchValues = (0, import_react.useCallback)(() => {
223
+ composingKeysRef.current.clear();
224
+ setComposingCount(0);
225
+ setInputValues({});
226
+ setSearchValues({});
227
+ setIsSearching(false);
228
+ }, []);
229
+ const isComposing = (0, import_react.useCallback)((key) => {
230
+ return key ? composingKeysRef.current.has(key) : composingKeysRef.current.size > 0;
231
+ }, []);
182
232
  (0, import_react.useEffect)(() => {
183
233
  return () => {
184
234
  if (searchTimeoutRef.current) {
@@ -188,8 +238,15 @@ const useMenuSearch = /* @__PURE__ */ __name(() => {
188
238
  }, []);
189
239
  return {
190
240
  searchValues,
191
- isSearching,
192
- updateSearchValue
241
+ inputValues,
242
+ isSearching: isSearching || composingCount > 0,
243
+ updateSearchValue,
244
+ updateInputValue,
245
+ startComposition,
246
+ endComposition,
247
+ clearSearchValue,
248
+ clearAllSearchValues,
249
+ isComposing
193
250
  };
194
251
  }, "useMenuSearch");
195
252
  const useSubmenuStyles = /* @__PURE__ */ __name((menuVisible, dropdownMaxHeight) => {
@@ -275,10 +332,10 @@ const getLabelSearchText = /* @__PURE__ */ __name((label) => {
275
332
  }
276
333
  return "";
277
334
  }, "getLabelSearchText");
278
- const createSearchItem = /* @__PURE__ */ __name((item, searchKey, currentSearchValue, menuVisible, t, updateSearchValue) => ({
335
+ const createSearchItem = /* @__PURE__ */ __name((item, searchKey, currentSearchValue, menuVisible, t, searchHandlers, activateSearchSubmenu, deactivateSearchSubmenu) => ({
279
336
  key: `${searchKey}-search`,
280
337
  type: "group",
281
- label: /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement(
338
+ label: /* @__PURE__ */ import_react.default.createElement("div", { onMouseDown: (e) => e.stopPropagation(), onClick: (e) => e.stopPropagation() }, /* @__PURE__ */ import_react.default.createElement(
282
339
  SearchInputWithAutoFocus,
283
340
  {
284
341
  visible: menuVisible,
@@ -286,11 +343,44 @@ const createSearchItem = /* @__PURE__ */ __name((item, searchKey, currentSearchV
286
343
  allowClear: true,
287
344
  placeholder: t(item.searchPlaceholder || "Search"),
288
345
  value: currentSearchValue,
346
+ onFocus: (e) => {
347
+ e.stopPropagation();
348
+ },
289
349
  onChange: (e) => {
350
+ var _a;
351
+ e.stopPropagation();
352
+ const value = e.target.value;
353
+ activateSearchSubmenu(searchKey);
354
+ if (((_a = e.nativeEvent) == null ? void 0 : _a.isComposing) || searchHandlers.isComposing(searchKey)) {
355
+ searchHandlers.updateInputValue(searchKey, value);
356
+ return;
357
+ }
358
+ if (!value) {
359
+ deactivateSearchSubmenu(searchKey);
360
+ }
361
+ searchHandlers.updateSearchValue(searchKey, value);
362
+ },
363
+ onCompositionStart: (e) => {
364
+ e.stopPropagation();
365
+ activateSearchSubmenu(searchKey);
366
+ searchHandlers.startComposition(searchKey);
367
+ },
368
+ onCompositionEnd: (e) => {
369
+ e.stopPropagation();
370
+ const value = e.currentTarget.value;
371
+ if (value) {
372
+ activateSearchSubmenu(searchKey);
373
+ } else {
374
+ deactivateSearchSubmenu(searchKey);
375
+ }
376
+ searchHandlers.endComposition(searchKey, value);
377
+ },
378
+ onClick: (e) => {
379
+ e.stopPropagation();
380
+ },
381
+ onKeyDown: (e) => {
290
382
  e.stopPropagation();
291
- updateSearchValue(searchKey, e.target.value);
292
383
  },
293
- onClick: (e) => e.stopPropagation(),
294
384
  onMouseDown: (e) => {
295
385
  e.stopPropagation();
296
386
  },
@@ -313,13 +403,21 @@ const KEEP_OPEN_LABEL_STYLE = {
313
403
  width: "100%"
314
404
  };
315
405
  const DROPDOWN_PERSIST_TTL_MS = 350;
406
+ const SUBMENU_CLOSE_DELAY = 0.05;
407
+ const SUBMENU_MOTION_DISABLED = {
408
+ motionEnter: false,
409
+ motionLeave: false
410
+ };
316
411
  const dropdownPersistRegistry = /* @__PURE__ */ new Map();
317
412
  const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
318
413
  const engine = (0, import_provider.useFlowEngine)();
319
414
  const [menuVisible, setMenuVisible] = (0, import_react.useState)(false);
320
415
  const [openKeys, setOpenKeys] = (0, import_react.useState)(/* @__PURE__ */ new Set());
416
+ const [activeSearchKey, setActiveSearchKey] = (0, import_react.useState)(null);
321
417
  const [rootItems, setRootItems] = (0, import_react.useState)([]);
322
418
  const [rootLoading, setRootLoading] = (0, import_react.useState)(false);
419
+ const closeByOutsideClickRef = (0, import_react.useRef)(false);
420
+ const skipPreserveActiveSearchRef = (0, import_react.useRef)(false);
323
421
  const dropdownMaxHeight = useNiceDropdownMaxHeight();
324
422
  const t = engine.translate.bind(engine);
325
423
  const { items: menuItems, keepDropdownOpen, persistKey, stateVersion, refreshKeys, ...dropdownMenuProps } = menu;
@@ -330,22 +428,89 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
330
428
  openKeys,
331
429
  refreshKeys
332
430
  );
333
- const { searchValues, isSearching, updateSearchValue } = useMenuSearch();
431
+ const searchHandlers = useMenuSearch();
432
+ const { searchValues, inputValues, clearSearchValue, clearAllSearchValues } = searchHandlers;
334
433
  const { requestKeepOpen, shouldPreventClose } = useKeepDropdownOpen();
335
434
  useSubmenuStyles(menuVisible, dropdownMaxHeight);
435
+ const closeMenu = (0, import_react.useCallback)(() => {
436
+ setMenuVisible(false);
437
+ setActiveSearchKey(null);
438
+ setOpenKeys(/* @__PURE__ */ new Set());
439
+ clearAllSearchValues();
440
+ }, [clearAllSearchValues]);
441
+ const activateSearchSubmenu = (0, import_react.useCallback)((key) => {
442
+ setActiveSearchKey(key);
443
+ setOpenKeys((prev) => {
444
+ if (prev.has(key)) return prev;
445
+ const next = new Set(prev);
446
+ next.add(key);
447
+ return next;
448
+ });
449
+ }, []);
450
+ const deactivateSearchSubmenu = (0, import_react.useCallback)((key) => {
451
+ setActiveSearchKey((prev) => prev === key ? null : prev);
452
+ }, []);
453
+ const closeActiveSearchForPath = (0, import_react.useCallback)(
454
+ (keyPath) => {
455
+ if (!activeSearchKey || keyPath === activeSearchKey || keyPath.startsWith(`${activeSearchKey}/`) || activeSearchKey.startsWith(`${keyPath}/`)) {
456
+ return;
457
+ }
458
+ skipPreserveActiveSearchRef.current = true;
459
+ clearSearchValue(activeSearchKey);
460
+ setActiveSearchKey(null);
461
+ setOpenKeys((prev) => {
462
+ const next = new Set(prev);
463
+ next.delete(activeSearchKey);
464
+ return next;
465
+ });
466
+ },
467
+ [activeSearchKey, clearSearchValue]
468
+ );
336
469
  const handleMenuOpenChange = (0, import_react.useCallback)(
337
470
  (nextOpenKeys) => {
338
471
  var _a, _b;
339
- if (!nextOpenKeys.length && shouldPreventClose()) {
472
+ let normalized = normalizeOpenKeys(nextOpenKeys);
473
+ if (activeSearchKey && openKeys.has(activeSearchKey) && !normalized.includes(activeSearchKey)) {
474
+ if (normalized.length || skipPreserveActiveSearchRef.current) {
475
+ clearSearchValue(activeSearchKey);
476
+ setActiveSearchKey(null);
477
+ } else {
478
+ normalized = [activeSearchKey];
479
+ }
480
+ }
481
+ if (!normalized.length && shouldPreventClose()) {
340
482
  (_a = dropdownMenuProps.onOpenChange) == null ? void 0 : _a.call(dropdownMenuProps, Array.from(openKeys));
483
+ skipPreserveActiveSearchRef.current = false;
341
484
  return;
342
485
  }
343
- const normalized = normalizeOpenKeys(nextOpenKeys);
486
+ Array.from(openKeys).forEach((key) => {
487
+ if (!normalized.includes(key)) {
488
+ clearSearchValue(key);
489
+ }
490
+ });
344
491
  setOpenKeys(new Set(normalized));
345
492
  (_b = dropdownMenuProps.onOpenChange) == null ? void 0 : _b.call(dropdownMenuProps, normalized);
493
+ skipPreserveActiveSearchRef.current = false;
346
494
  },
347
- [dropdownMenuProps, openKeys, shouldPreventClose]
495
+ [activeSearchKey, clearSearchValue, dropdownMenuProps, openKeys, shouldPreventClose]
348
496
  );
497
+ (0, import_react.useEffect)(() => {
498
+ if (!menuVisible) return;
499
+ const markOutsideClick = /* @__PURE__ */ __name((event) => {
500
+ const target = event.target;
501
+ const isOutside = !(target == null ? void 0 : target.closest(".ant-dropdown, .ant-dropdown-menu, .ant-dropdown-menu-submenu-popup"));
502
+ closeByOutsideClickRef.current = isOutside;
503
+ if (isOutside) {
504
+ closeMenu();
505
+ }
506
+ }, "markOutsideClick");
507
+ document.addEventListener("pointerdown", markOutsideClick, true);
508
+ document.addEventListener("mousedown", markOutsideClick, true);
509
+ return () => {
510
+ document.removeEventListener("pointerdown", markOutsideClick, true);
511
+ document.removeEventListener("mousedown", markOutsideClick, true);
512
+ };
513
+ }, [closeMenu, menuVisible]);
349
514
  (0, import_react.useEffect)(() => {
350
515
  if (!persistKey) return;
351
516
  const until = dropdownPersistRegistry.get(persistKey) || 0;
@@ -391,6 +556,7 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
391
556
  function buildSearchChildren(children, item, keyPath, path, menuVisible2, resolve) {
392
557
  const searchKey = keyPath;
393
558
  const currentSearchValue = searchValues[searchKey] || "";
559
+ const currentInputValue = inputValues[searchKey] ?? currentSearchValue;
394
560
  const filteredChildren = currentSearchValue ? (/* @__PURE__ */ __name(function deepFilter(items2) {
395
561
  const searchText = currentSearchValue.toLowerCase();
396
562
  return items2.map((child) => {
@@ -407,7 +573,16 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
407
573
  }).filter(Boolean);
408
574
  }, "deepFilter"))(children) : children;
409
575
  const resolvedFiltered = resolve(filteredChildren, [...path, item.key]);
410
- const searchItem = createSearchItem(item, searchKey, currentSearchValue, menuVisible2, t, updateSearchValue);
576
+ const searchItem = createSearchItem(
577
+ item,
578
+ searchKey,
579
+ currentInputValue,
580
+ menuVisible2,
581
+ t,
582
+ searchHandlers,
583
+ activateSearchSubmenu,
584
+ deactivateSearchSubmenu
585
+ );
411
586
  const dividerItem = { key: `${keyPath}-search-divider`, type: "divider" };
412
587
  if (currentSearchValue && resolvedFiltered.length === 0) {
413
588
  return [searchItem, dividerItem, createEmptyItem(keyPath, t)];
@@ -466,6 +641,7 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
466
641
  label,
467
642
  onClick: /* @__PURE__ */ __name((info) => {
468
643
  }, "onClick"),
644
+ onMouseEnter: /* @__PURE__ */ __name(() => closeActiveSearchForPath(keyPath), "onMouseEnter"),
469
645
  children: buildSearchChildren(children, item, keyPath, path, menuVisible, resolveItems)
470
646
  };
471
647
  }
@@ -513,6 +689,7 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
513
689
  onClick: /* @__PURE__ */ __name((info) => {
514
690
  if (!itemShouldKeepOpen) handleLeafClick(info);
515
691
  }, "onClick"),
692
+ onMouseEnter: /* @__PURE__ */ __name(() => closeActiveSearchForPath(keyPath), "onMouseEnter"),
516
693
  onMouseDown: /* @__PURE__ */ __name(() => {
517
694
  if (!itemShouldKeepOpen) {
518
695
  return;
@@ -555,6 +732,8 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
555
732
  ...dropdownMenuProps,
556
733
  openKeys: Array.from(openKeys),
557
734
  items,
735
+ subMenuCloseDelay: dropdownMenuProps.subMenuCloseDelay ?? SUBMENU_CLOSE_DELAY,
736
+ motion: dropdownMenuProps.motion ?? SUBMENU_MOTION_DISABLED,
558
737
  onClick: /* @__PURE__ */ __name(() => {
559
738
  }, "onClick"),
560
739
  onOpenChange: handleMenuOpenChange,
@@ -564,14 +743,19 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
564
743
  ...dropdownMenuProps == null ? void 0 : dropdownMenuProps.style
565
744
  }
566
745
  },
567
- onOpenChange: (visible) => {
568
- if (!visible && isSearching) {
746
+ onOpenChange: (visible, info) => {
747
+ if (!visible && activeSearchKey && (info == null ? void 0 : info.source) === "trigger" && !closeByOutsideClickRef.current) {
569
748
  return;
570
749
  }
571
750
  if (!visible && shouldPreventClose()) {
572
751
  return;
573
752
  }
574
- setMenuVisible(visible);
753
+ if (!visible) {
754
+ closeMenu();
755
+ } else {
756
+ setMenuVisible(visible);
757
+ }
758
+ closeByOutsideClickRef.current = false;
575
759
  }
576
760
  },
577
761
  props.children
@@ -2697,6 +2697,9 @@ const _FlowEngineContext = class _FlowEngineContext extends BaseFlowEngineContex
2697
2697
  }, "get")
2698
2698
  });
2699
2699
  this.defineMethod("aclCheck", function(params) {
2700
+ if (this.skipAclCheck) {
2701
+ return true;
2702
+ }
2700
2703
  return this.acl.aclCheck(params);
2701
2704
  });
2702
2705
  this.defineMethod("createResource", function(resourceType) {
package/lib/flowEngine.js CHANGED
@@ -63,6 +63,7 @@ var import_emitter = require("./emitter");
63
63
  var import_ModelOperationScheduler = __toESM(require("./scheduler/ModelOperationScheduler"));
64
64
  var import_utils = require("./utils");
65
65
  var _FlowEngine_instances, registerModel_fn;
66
+ const getFlowEngineLoggerLevel = /* @__PURE__ */ __name(() => process.env.NODE_ENV === "production" ? "warn" : "trace", "getFlowEngineLoggerLevel");
66
67
  const _FlowEngine = class _FlowEngine {
67
68
  /**
68
69
  * Constructor. Initializes React view, registers default model and form scopes.
@@ -195,7 +196,7 @@ const _FlowEngine = class _FlowEngine {
195
196
  MultiRecordResource: import_resources.MultiRecordResource
196
197
  });
197
198
  this.logger = (0, import_pino.default)({
198
- level: "trace",
199
+ level: getFlowEngineLoggerLevel(),
199
200
  browser: {
200
201
  write: {
201
202
  fatal: /* @__PURE__ */ __name((o) => console.trace(o), "fatal"),
@@ -859,7 +860,6 @@ const _FlowEngine = class _FlowEngine {
859
860
  const visited = /* @__PURE__ */ new Set();
860
861
  while (current) {
861
862
  if (visited.has(current)) {
862
- console.warn(`FlowEngine: resolveUse circular reference detected on '${current.name}'.`);
863
863
  break;
864
864
  }
865
865
  visited.add(current);
@@ -960,7 +960,7 @@ const _FlowEngine = class _FlowEngine {
960
960
  removeModel(uid) {
961
961
  var _a, _b, _c;
962
962
  if (!this._modelInstances.has(uid)) {
963
- console.warn(`FlowEngine: Model with UID '${uid}' does not exist.`);
963
+ this.logger.debug(`FlowEngine: Model with UID '${uid}' does not exist.`);
964
964
  return false;
965
965
  }
966
966
  const modelInstance = this._modelInstances.get(uid);
@@ -668,7 +668,7 @@ const _FlowModel = class _FlowModel {
668
668
  }
669
669
  const isFork = this.isFork === true;
670
670
  const target = this;
671
- console.log(
671
+ currentFlowEngine.logger.debug(
672
672
  `[FlowModel] applyFlow: uid=${this.uid}, flowKey=${flowKey}, isFork=${isFork}, cleanRun=${this.cleanRun}, targetIsFork=${(target == null ? void 0 : target.isFork) === true}`
673
673
  );
674
674
  return currentFlowEngine.executor.runFlow(target, flowKey, inputArgs, runId);
@@ -681,7 +681,7 @@ const _FlowModel = class _FlowModel {
681
681
  }
682
682
  const isFork = this.isFork === true;
683
683
  const target = this;
684
- console.log(
684
+ currentFlowEngine.logger.debug(
685
685
  `[FlowModel] dispatchEvent: uid=${this.uid}, event=${eventName}, isFork=${isFork}, cleanRun=${this.cleanRun}, targetIsFork=${(target == null ? void 0 : target.isFork) === true}`
686
686
  );
687
687
  return await currentFlowEngine.executor.dispatchEvent(target, eventName, inputArgs, options);
@@ -1056,7 +1056,7 @@ const _FlowModel = class _FlowModel {
1056
1056
  }
1057
1057
  clearForks() {
1058
1058
  var _a;
1059
- console.log(`FlowModel ${this.uid} clearing all forks.`);
1059
+ this.flowEngine.logger.debug(`FlowModel ${this.uid} clearing all forks.`);
1060
1060
  if ((_a = this.forks) == null ? void 0 : _a.size) {
1061
1061
  this.forks.forEach((fork) => fork.dispose());
1062
1062
  this.forks.clear();
@@ -16,6 +16,10 @@ export interface ViewParam {
16
16
  /** source Id */
17
17
  sourceId?: string;
18
18
  }
19
+ export interface ParsePathnameToViewParamsOptions {
20
+ rootPrefix?: string;
21
+ basePath?: string;
22
+ }
19
23
  /**
20
24
  * 解析路径名为视图参数数组
21
25
  *
@@ -31,4 +35,4 @@ export interface ViewParam {
31
35
  * parsePathnameToViewParams('/admin/xxx/view/yyy') // [{ viewUid: 'xxx' }, { viewUid: 'yyy' }]
32
36
  * ```
33
37
  */
34
- export declare const parsePathnameToViewParams: (pathname: string) => ViewParam[];
38
+ export declare const parsePathnameToViewParams: (pathname: string, options?: ParsePathnameToViewParamsOptions) => ViewParam[];
@@ -30,20 +30,44 @@ __export(parsePathnameToViewParams_exports, {
30
30
  parsePathnameToViewParams: () => parsePathnameToViewParams
31
31
  });
32
32
  module.exports = __toCommonJS(parsePathnameToViewParams_exports);
33
- const parsePathnameToViewParams = /* @__PURE__ */ __name((pathname) => {
33
+ const normalizePathname = /* @__PURE__ */ __name((pathname) => {
34
+ if (!pathname || pathname === "/") {
35
+ return "/";
36
+ }
37
+ return `/${pathname.replace(/^\/+/, "").replace(/\/+$/, "")}`;
38
+ }, "normalizePathname");
39
+ const normalizeBasePath = /* @__PURE__ */ __name((basePath) => `/${basePath.replace(/^\/+/, "").replace(/\/+$/, "")}`, "normalizeBasePath");
40
+ const stripBasePath = /* @__PURE__ */ __name((pathname, basePath) => {
41
+ const normalizedPathname = normalizePathname(pathname);
42
+ const normalizedBasePath = normalizeBasePath(basePath);
43
+ if (normalizedPathname === normalizedBasePath) {
44
+ return "";
45
+ }
46
+ if (normalizedPathname.startsWith(`${normalizedBasePath}/`)) {
47
+ return normalizedPathname.slice(normalizedBasePath.length + 1);
48
+ }
49
+ return "";
50
+ }, "stripBasePath");
51
+ const parsePathnameToViewParams = /* @__PURE__ */ __name((pathname, options = {}) => {
34
52
  if (!pathname || pathname === "/") {
35
53
  return [];
36
54
  }
37
- const segments = pathname.replace(/^\/+/, "").split("/").filter(Boolean);
38
- if (segments.length < 2) {
55
+ const rootPrefix = options.rootPrefix || "admin";
56
+ const relativePath = options.basePath ? stripBasePath(pathname, options.basePath) : "";
57
+ const segments = (options.basePath ? relativePath : pathname).replace(/^\/+/, "").split("/").filter(Boolean);
58
+ if (segments.length < (options.basePath ? 1 : 2)) {
39
59
  return [];
40
60
  }
41
61
  const result = [];
42
62
  let currentView = null;
43
63
  let i = 0;
64
+ if (options.basePath) {
65
+ currentView = { viewUid: segments[0] };
66
+ i = 1;
67
+ }
44
68
  while (i < segments.length) {
45
69
  const segment = segments[i];
46
- if (segment === "admin" || segment === "view") {
70
+ if (segment === rootPrefix || segment === "view") {
47
71
  if (currentView) {
48
72
  result.push(currentView);
49
73
  }
@@ -11,6 +11,14 @@ import { ViewParam as SharedViewParam } from '../utils';
11
11
  type ViewParams = Omit<SharedViewParam, 'viewUid'> & {
12
12
  viewUid?: string;
13
13
  };
14
+ export interface GeneratePathnameFromViewParamsOptions {
15
+ prefix?: string;
16
+ basePath?: string;
17
+ }
18
+ export interface ViewNavigationOptions {
19
+ basePath?: string;
20
+ layoutBasePath?: string;
21
+ }
14
22
  /**
15
23
  * 将 ViewParam 数组转换为 pathname
16
24
  *
@@ -24,12 +32,13 @@ type ViewParams = Omit<SharedViewParam, 'viewUid'> & {
24
32
  * generatePathnameFromViewParams([{ viewUid: 'xxx' }, { viewUid: 'yyy' }]) // '/admin/xxx/view/yyy'
25
33
  * ```
26
34
  */
27
- export declare function generatePathnameFromViewParams(viewParams: ViewParams[]): string;
35
+ export declare function generatePathnameFromViewParams(viewParams: ViewParams[], options?: GeneratePathnameFromViewParamsOptions): string;
28
36
  export declare class ViewNavigation {
29
37
  viewStack: ReadonlyArray<ViewParams>;
30
38
  ctx: FlowEngineContext;
31
39
  viewParams: ViewParams;
32
- constructor(ctx: FlowEngineContext, viewParams: ViewParams[]);
40
+ private readonly basePath?;
41
+ constructor(ctx: FlowEngineContext, viewParams: ViewParams[], options?: ViewNavigationOptions);
33
42
  setViewStack(viewParams: ViewParams[]): void;
34
43
  changeTo(viewParam: ViewParams): void;
35
44
  navigateTo(viewParam: ViewParams, opts?: {
@@ -37,5 +46,6 @@ export declare class ViewNavigation {
37
46
  state?: any;
38
47
  }): void;
39
48
  back(): void;
49
+ private getLayoutBasePath;
40
50
  }
41
51
  export {};
@@ -47,11 +47,17 @@ function hasUsableSourceId(sourceId) {
47
47
  return sourceId !== void 0 && sourceId !== null && String(sourceId) !== "";
48
48
  }
49
49
  __name(hasUsableSourceId, "hasUsableSourceId");
50
- function generatePathnameFromViewParams(viewParams) {
50
+ function normalizeBasePath(basePath) {
51
+ const value = basePath || "/admin";
52
+ return `/${value.replace(/^\/+/, "").replace(/\/+$/, "")}`;
53
+ }
54
+ __name(normalizeBasePath, "normalizeBasePath");
55
+ function generatePathnameFromViewParams(viewParams, options = {}) {
56
+ const basePath = normalizeBasePath(options.basePath || options.prefix);
51
57
  if (!viewParams || viewParams.length === 0) {
52
- return "/admin";
58
+ return basePath;
53
59
  }
54
- const segments = ["admin"];
60
+ const segments = basePath.replace(/^\/+/, "").split("/").filter(Boolean);
55
61
  viewParams.forEach((viewParam, index) => {
56
62
  if (index > 0) {
57
63
  segments.push("view");
@@ -78,9 +84,11 @@ const _ViewNavigation = class _ViewNavigation {
78
84
  // 只能通过 setViewStack 修改
79
85
  ctx;
80
86
  viewParams;
81
- constructor(ctx, viewParams) {
87
+ basePath;
88
+ constructor(ctx, viewParams, options = {}) {
82
89
  this.setViewStack(viewParams);
83
90
  this.ctx = ctx;
91
+ this.basePath = options.basePath || options.layoutBasePath;
84
92
  (0, import_reactive.define)(this, {
85
93
  viewParams: import_reactive.observable
86
94
  });
@@ -96,19 +104,26 @@ const _ViewNavigation = class _ViewNavigation {
96
104
  }
97
105
  return { ...item };
98
106
  });
99
- const newPathname = generatePathnameFromViewParams(newViewStack);
107
+ const newPathname = generatePathnameFromViewParams(newViewStack, { basePath: this.getLayoutBasePath() });
100
108
  this.ctx.router.navigate(newPathname, { replace: true });
101
109
  }
102
110
  navigateTo(viewParam, opts) {
103
- const newViewPathname = generatePathnameFromViewParams([...this.viewStack, viewParam]);
111
+ const newViewPathname = generatePathnameFromViewParams([...this.viewStack, viewParam], {
112
+ basePath: this.getLayoutBasePath()
113
+ });
104
114
  const newPathname = newViewPathname;
105
115
  this.ctx.router.navigate(newPathname, opts);
106
116
  }
107
117
  back() {
108
118
  const prevStack = this.viewStack.slice(0, -1);
109
- const prevPath = generatePathnameFromViewParams(prevStack);
119
+ const prevPath = generatePathnameFromViewParams(prevStack, { basePath: this.getLayoutBasePath() });
110
120
  this.ctx.router.navigate(prevPath, { replace: true });
111
121
  }
122
+ getLayoutBasePath() {
123
+ var _a, _b;
124
+ const routePath = (_a = this.ctx.layout) == null ? void 0 : _a.routePath;
125
+ return this.basePath || ((_b = this.ctx.layoutRoute) == null ? void 0 : _b.basePathname) || ((routePath == null ? void 0 : routePath.startsWith("/")) ? routePath : "/admin");
126
+ }
112
127
  };
113
128
  __name(_ViewNavigation, "ViewNavigation");
114
129
  let ViewNavigation = _ViewNavigation;