@nocobase/flow-engine 2.1.0-beta.2 → 2.1.0-beta.20

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 (126) hide show
  1. package/LICENSE +201 -661
  2. package/README.md +79 -10
  3. package/lib/JSRunner.d.ts +10 -1
  4. package/lib/JSRunner.js +50 -5
  5. package/lib/ViewScopedFlowEngine.js +5 -1
  6. package/lib/components/FlowModelRenderer.d.ts +1 -1
  7. package/lib/components/FlowModelRenderer.js +10 -6
  8. package/lib/components/MobilePopup.js +6 -5
  9. package/lib/components/dnd/gridDragPlanner.js +6 -2
  10. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.d.ts +3 -0
  11. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +48 -9
  12. package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +19 -43
  13. package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +339 -295
  14. package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +16 -2
  15. package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.d.ts +36 -0
  16. package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.js +272 -0
  17. package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.d.ts +30 -0
  18. package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.js +247 -0
  19. package/lib/components/subModel/AddSubModelButton.js +27 -1
  20. package/lib/components/subModel/utils.js +2 -2
  21. package/lib/data-source/index.js +6 -0
  22. package/lib/executor/FlowExecutor.js +31 -8
  23. package/lib/flowContext.js +31 -1
  24. package/lib/flowEngine.d.ts +151 -1
  25. package/lib/flowEngine.js +389 -15
  26. package/lib/flowSettings.d.ts +14 -6
  27. package/lib/flowSettings.js +34 -6
  28. package/lib/lazy-helper.d.ts +14 -0
  29. package/lib/lazy-helper.js +71 -0
  30. package/lib/locale/en-US.json +1 -0
  31. package/lib/locale/index.d.ts +2 -0
  32. package/lib/locale/zh-CN.json +1 -0
  33. package/lib/models/flowModel.d.ts +2 -1
  34. package/lib/models/flowModel.js +28 -9
  35. package/lib/reactive/observer.js +46 -16
  36. package/lib/runjs-context/registry.d.ts +1 -1
  37. package/lib/runjs-context/setup.js +20 -12
  38. package/lib/runjs-context/snippets/index.js +13 -2
  39. package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.d.ts +11 -0
  40. package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.js +50 -0
  41. package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.d.ts +11 -0
  42. package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.js +54 -0
  43. package/lib/scheduler/ModelOperationScheduler.d.ts +5 -1
  44. package/lib/scheduler/ModelOperationScheduler.js +3 -2
  45. package/lib/types.d.ts +47 -1
  46. package/lib/utils/index.d.ts +2 -2
  47. package/lib/utils/index.js +4 -0
  48. package/lib/utils/parsePathnameToViewParams.js +1 -1
  49. package/lib/utils/runjsTemplateCompat.js +1 -1
  50. package/lib/utils/runjsValue.js +41 -11
  51. package/lib/utils/schema-utils.d.ts +7 -1
  52. package/lib/utils/schema-utils.js +19 -0
  53. package/lib/views/FlowView.d.ts +7 -1
  54. package/lib/views/runViewBeforeClose.d.ts +10 -0
  55. package/lib/views/runViewBeforeClose.js +45 -0
  56. package/lib/views/useDialog.d.ts +2 -1
  57. package/lib/views/useDialog.js +20 -3
  58. package/lib/views/useDrawer.d.ts +2 -1
  59. package/lib/views/useDrawer.js +20 -3
  60. package/lib/views/usePage.d.ts +2 -1
  61. package/lib/views/usePage.js +10 -3
  62. package/package.json +6 -5
  63. package/src/JSRunner.ts +68 -4
  64. package/src/ViewScopedFlowEngine.ts +4 -0
  65. package/src/__tests__/JSRunner.test.ts +27 -1
  66. package/src/__tests__/flow-engine.test.ts +166 -0
  67. package/src/__tests__/flowContext.test.ts +65 -1
  68. package/src/__tests__/flowEngine.modelLoaders.test.ts +245 -0
  69. package/src/__tests__/flowSettings.test.ts +94 -15
  70. package/src/__tests__/renderHiddenInConfig.test.tsx +6 -6
  71. package/src/__tests__/runjsContext.test.ts +16 -0
  72. package/src/__tests__/runjsContextRuntime.test.ts +2 -0
  73. package/src/__tests__/runjsPreprocessDefault.test.ts +23 -0
  74. package/src/__tests__/runjsSnippets.test.ts +21 -0
  75. package/src/__tests__/viewScopedFlowEngine.test.ts +3 -3
  76. package/src/components/FlowModelRenderer.tsx +12 -6
  77. package/src/components/MobilePopup.tsx +4 -2
  78. package/src/components/__tests__/FlowModelRenderer.test.tsx +65 -2
  79. package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +20 -10
  80. package/src/components/__tests__/gridDragPlanner.test.ts +88 -0
  81. package/src/components/dnd/gridDragPlanner.ts +8 -2
  82. package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +63 -9
  83. package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +468 -440
  84. package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +18 -2
  85. package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +95 -0
  86. package/src/components/settings/wrappers/contextual/__tests__/FlowsFloatContextMenu.test.tsx +609 -0
  87. package/src/components/settings/wrappers/contextual/useFloatToolbarPortal.ts +358 -0
  88. package/src/components/settings/wrappers/contextual/useFloatToolbarVisibility.ts +281 -0
  89. package/src/components/subModel/AddSubModelButton.tsx +32 -2
  90. package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +142 -32
  91. package/src/components/subModel/utils.ts +1 -1
  92. package/src/data-source/index.ts +6 -0
  93. package/src/executor/FlowExecutor.ts +34 -9
  94. package/src/executor/__tests__/flowExecutor.test.ts +57 -0
  95. package/src/flowContext.ts +35 -3
  96. package/src/flowEngine.ts +445 -11
  97. package/src/flowSettings.ts +40 -6
  98. package/src/lazy-helper.tsx +57 -0
  99. package/src/locale/en-US.json +1 -0
  100. package/src/locale/zh-CN.json +1 -0
  101. package/src/models/__tests__/dispatchEvent.when.test.ts +214 -0
  102. package/src/models/flowModel.tsx +31 -10
  103. package/src/reactive/__tests__/observer.test.tsx +82 -0
  104. package/src/reactive/observer.tsx +87 -25
  105. package/src/runjs-context/registry.ts +1 -1
  106. package/src/runjs-context/setup.ts +22 -12
  107. package/src/runjs-context/snippets/index.ts +12 -1
  108. package/src/runjs-context/snippets/scene/detail/set-field-style.snippet.ts +30 -0
  109. package/src/runjs-context/snippets/scene/table/set-cell-style.snippet.ts +34 -0
  110. package/src/scheduler/ModelOperationScheduler.ts +14 -3
  111. package/src/types.ts +60 -0
  112. package/src/utils/__tests__/parsePathnameToViewParams.test.ts +7 -0
  113. package/src/utils/__tests__/runjsValue.test.ts +11 -0
  114. package/src/utils/__tests__/utils.test.ts +62 -0
  115. package/src/utils/index.ts +2 -1
  116. package/src/utils/parsePathnameToViewParams.ts +2 -2
  117. package/src/utils/runjsTemplateCompat.ts +1 -1
  118. package/src/utils/runjsValue.ts +50 -11
  119. package/src/utils/schema-utils.ts +30 -1
  120. package/src/views/FlowView.tsx +11 -1
  121. package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
  122. package/src/views/__tests__/useDialog.closeDestroy.test.tsx +13 -12
  123. package/src/views/runViewBeforeClose.ts +19 -0
  124. package/src/views/useDialog.tsx +25 -3
  125. package/src/views/useDrawer.tsx +25 -3
  126. package/src/views/usePage.tsx +12 -3
package/lib/flowEngine.js CHANGED
@@ -83,6 +83,28 @@ const _FlowEngine = class _FlowEngine {
83
83
  * @private
84
84
  */
85
85
  __publicField(this, "_modelClasses", import_reactive.observable.shallow(/* @__PURE__ */ new Map()));
86
+ /**
87
+ * Registered model entries.
88
+ * Key is the model class name, value is the model loader entry.
89
+ * @private
90
+ */
91
+ __publicField(this, "_modelLoaders", /* @__PURE__ */ new Map());
92
+ /**
93
+ * In-flight model loading promises.
94
+ * Key is the model class name, value is the loading promise.
95
+ * @private
96
+ */
97
+ __publicField(this, "_loadingModelPromises", /* @__PURE__ */ new Map());
98
+ /**
99
+ * Whether model-loader preload has completed in this session.
100
+ * @private
101
+ */
102
+ __publicField(this, "_modelLoadersPreloaded", false);
103
+ /**
104
+ * In-flight model-loader preload promise.
105
+ * @private
106
+ */
107
+ __publicField(this, "_modelLoadersPreloadPromise");
86
108
  /**
87
109
  * Created model instances.
88
110
  * Key is the model instance UID, value is the model instance object.
@@ -119,6 +141,12 @@ const _FlowEngine = class _FlowEngine {
119
141
  */
120
142
  __publicField(this, "_previousEngine");
121
143
  __publicField(this, "_nextEngine");
144
+ /**
145
+ * 视图销毁回调。由 useDrawer / useDialog 在创建弹窗视图时注册,
146
+ * 供外部(如 afterSuccess)通过引擎栈遍历来关闭多层弹窗。
147
+ * embed 视图(usePage)不注册此回调,因此 destroyView() 会自然跳过。
148
+ */
149
+ __publicField(this, "_destroyView");
122
150
  __publicField(this, "_resources", /* @__PURE__ */ new Map());
123
151
  /**
124
152
  * Data change registry used to coordinate "refresh on active" across view-scoped engines.
@@ -258,6 +286,26 @@ const _FlowEngine = class _FlowEngine {
258
286
  prev._nextEngine = void 0;
259
287
  }
260
288
  }
289
+ /**
290
+ * 注册视图销毁回调(由 useDrawer / useDialog 调用)。
291
+ */
292
+ setDestroyView(fn) {
293
+ this._destroyView = fn;
294
+ }
295
+ /**
296
+ * 关闭当前引擎关联的弹窗视图。
297
+ * 路由触发的弹窗会先 navigation.back() 清理 URL,再 destroy() 移除元素;
298
+ * 非路由弹窗直接 destroy()。
299
+ * embed 视图不注册回调,调用时返回 false 自动跳过。
300
+ * @returns 是否成功执行
301
+ */
302
+ destroyView() {
303
+ if (this._destroyView) {
304
+ this._destroyView();
305
+ return true;
306
+ }
307
+ return false;
308
+ }
261
309
  // (已移除)getModelGlobal/forEachModelGlobal/getAllModelsGlobal:不再维护冗余全局遍历 API
262
310
  /**
263
311
  * Get the flow engine context object.
@@ -346,6 +394,16 @@ const _FlowEngine = class _FlowEngine {
346
394
  getEvents() {
347
395
  return this._eventRegistry.getEvents();
348
396
  }
397
+ /**
398
+ * for proxy instance, the #registerModel can't be called.
399
+ */
400
+ _registerModel(name, modelClass) {
401
+ if (this._modelClasses.has(name)) {
402
+ console.warn(`FlowEngine: Model class with name '${name}' is already registered and will be overwritten.`);
403
+ }
404
+ Object.defineProperty(modelClass, "name", { value: name });
405
+ this._modelClasses.set(name, modelClass);
406
+ }
349
407
  /**
350
408
  * Register multiple model classes.
351
409
  * @param {Record<string, ModelConstructor>} models Model class map, key is model name, value is model constructor
@@ -358,6 +416,274 @@ const _FlowEngine = class _FlowEngine {
358
416
  __privateMethod(this, _FlowEngine_instances, registerModel_fn).call(this, name, modelClass);
359
417
  }
360
418
  }
419
+ /**
420
+ * Register multiple model loader entries.
421
+ * The `extends` field declares parent class(es) for async subclass discovery via `getSubclassesOfAsync`.
422
+ * It accepts `string | ModelConstructor | (string | ModelConstructor)[]` and is normalized to `string[]` internally.
423
+ * @param {FlowModelLoaderInputMap} loaders Model loader input map
424
+ * @returns {void}
425
+ * @example
426
+ * flowEngine.registerModelLoaders({
427
+ * DemoModel: {
428
+ * extends: 'BaseModel',
429
+ * loader: () => import('./models/DemoModel'),
430
+ * },
431
+ * });
432
+ */
433
+ registerModelLoaders(loaders) {
434
+ let changed = false;
435
+ for (const [name, input] of Object.entries(loaders)) {
436
+ if (this._modelLoaders.has(name)) {
437
+ console.warn(`FlowEngine: Model loader with name '${name}' is already registered and will be overwritten.`);
438
+ }
439
+ const entry = {
440
+ loader: input.loader
441
+ };
442
+ if (input.extends != null) {
443
+ const raw = Array.isArray(input.extends) ? input.extends : [input.extends];
444
+ entry.extends = raw.map((item) => typeof item === "string" ? item : item.name);
445
+ }
446
+ this._modelLoaders.set(name, entry);
447
+ changed = true;
448
+ }
449
+ if (changed) {
450
+ this._modelLoadersPreloaded = false;
451
+ this._modelLoadersPreloadPromise = void 0;
452
+ }
453
+ }
454
+ /**
455
+ * Get a registered model class (constructor) asynchronously.
456
+ * This will first ensure the model loader entry is resolved.
457
+ * @param {string} name Model class name
458
+ * @returns {Promise<ModelConstructor | undefined>} Model constructor, or undefined if not found
459
+ */
460
+ async getModelClassAsync(name) {
461
+ await this.ensureModel(name);
462
+ return this.getModelClass(name);
463
+ }
464
+ /**
465
+ * Get all registered model classes asynchronously.
466
+ * This will first ensure all registered model loader entries are resolved.
467
+ * @returns {Promise<Map<string, ModelConstructor>>} Model class map
468
+ */
469
+ async getModelClassesAsync() {
470
+ await this.ensureModels(Array.from(this._modelLoaders.keys()));
471
+ return this.getModelClasses();
472
+ }
473
+ /**
474
+ * Create and register a model instance asynchronously.
475
+ * This will first ensure all string-based model references in the model tree are resolved.
476
+ * @template T FlowModel subclass type, defaults to FlowModel.
477
+ * @param {CreateModelOptions} options Model creation options
478
+ * @returns {Promise<T>} Created model instance
479
+ */
480
+ async createModelAsync(options, extra) {
481
+ await this.resolveModelTree(options);
482
+ return this.createModel(options, extra);
483
+ }
484
+ /**
485
+ * Normalize a loader result into a model constructor.
486
+ * @param {string} name Model class name
487
+ * @param {FlowModelLoaderResult} loaded Loader result
488
+ * @returns {ModelConstructor | null} Normalized model constructor
489
+ * @private
490
+ */
491
+ normalizeModelLoaderResult(name, loaded) {
492
+ if (typeof loaded === "function") {
493
+ return loaded;
494
+ }
495
+ if (loaded && typeof loaded === "object") {
496
+ const defaultExport = loaded.default;
497
+ if (typeof defaultExport === "function") {
498
+ return defaultExport;
499
+ }
500
+ const namedExport = loaded[name];
501
+ if (typeof namedExport === "function") {
502
+ return namedExport;
503
+ }
504
+ }
505
+ console.warn(`FlowEngine: model loader for '${name}' did not resolve to a valid model constructor.`);
506
+ return null;
507
+ }
508
+ /**
509
+ * Collect string-based model names from a model tree.
510
+ * @param {unknown} data Model tree data
511
+ * @param {Set<string>} names Model name set
512
+ * @private
513
+ */
514
+ collectModelNamesFromTree(data, names) {
515
+ if (!data || typeof data !== "object") {
516
+ return;
517
+ }
518
+ if (Array.isArray(data)) {
519
+ data.forEach((item) => this.collectModelNamesFromTree(item, names));
520
+ return;
521
+ }
522
+ const tree = data;
523
+ if (typeof tree.use === "string") {
524
+ names.add(tree.use);
525
+ }
526
+ const subModels = tree.subModels;
527
+ if (!subModels || typeof subModels !== "object") {
528
+ return;
529
+ }
530
+ Object.values(subModels).forEach((value) => {
531
+ this.collectModelNamesFromTree(value, names);
532
+ });
533
+ }
534
+ /**
535
+ * Collect additional model names from object-form meta.createModelOptions defaults.
536
+ * @param {ModelConstructor} modelClass Model class constructor
537
+ * @param {Set<string>} names Model name set
538
+ * @private
539
+ */
540
+ collectModelNamesFromMetaDefaults(modelClass, names) {
541
+ var _a;
542
+ const metaCreate = (_a = modelClass.meta) == null ? void 0 : _a.createModelOptions;
543
+ if (metaCreate && typeof metaCreate === "object") {
544
+ this.collectModelNamesFromTree(metaCreate, names);
545
+ }
546
+ }
547
+ /**
548
+ * Ensure a single model class is available.
549
+ * @param {string} name Model class name
550
+ * @returns {Promise<ModelConstructor | null>} Model constructor or null when resolution fails
551
+ * @private
552
+ */
553
+ async ensureModel(name) {
554
+ const existing = this._modelClasses.get(name);
555
+ if (existing) {
556
+ return existing;
557
+ }
558
+ const inflight = this._loadingModelPromises.get(name);
559
+ if (inflight) {
560
+ return inflight;
561
+ }
562
+ const entry = this._modelLoaders.get(name);
563
+ if (!entry) {
564
+ console.warn(`FlowEngine: Model entry '${name}' not found. Falling back to ErrorFlowModel when needed.`);
565
+ return null;
566
+ }
567
+ const promise = (async () => {
568
+ try {
569
+ const loaded = await entry.loader();
570
+ const modelClass = this.normalizeModelLoaderResult(name, loaded);
571
+ if (!modelClass) {
572
+ return null;
573
+ }
574
+ this._registerModel(name, modelClass);
575
+ return modelClass;
576
+ } catch (error) {
577
+ console.warn(`FlowEngine: Failed to load model '${name}'. Falling back to ErrorFlowModel when needed.`, error);
578
+ return null;
579
+ } finally {
580
+ this._loadingModelPromises.delete(name);
581
+ }
582
+ })();
583
+ this._loadingModelPromises.set(name, promise);
584
+ return promise;
585
+ }
586
+ /**
587
+ * Ensure multiple model classes are available.
588
+ * @param {string[]} names Model class names
589
+ * @returns {Promise<EnsureBatchResult>} Batch ensure result
590
+ * @private
591
+ */
592
+ async ensureModels(names) {
593
+ const requested = Array.from(new Set(names.filter((name) => !!name)));
594
+ const loaded = [];
595
+ const failed = [];
596
+ const results = await Promise.all(
597
+ requested.map(async (name) => {
598
+ const modelClass = await this.ensureModel(name);
599
+ return { name, modelClass };
600
+ })
601
+ );
602
+ results.forEach(({ name, modelClass }) => {
603
+ if (modelClass) {
604
+ loaded.push(name);
605
+ } else {
606
+ failed.push({ name });
607
+ }
608
+ });
609
+ return { requested, loaded, failed };
610
+ }
611
+ /**
612
+ * Resolve all unresolved string-based model references in a model tree before synchronous creation begins.
613
+ *
614
+ * Use this when you already have a model tree object, such as repository-returned data or resolved
615
+ * `createModelOptions`, and you need to ensure every string `use` in that tree has been loaded and
616
+ * registered into `_modelClasses` before calling `createModel()`.
617
+ *
618
+ * @param {unknown} data Model tree data
619
+ * @returns {Promise<EnsureBatchResult>} Batch ensure result
620
+ */
621
+ async resolveModelTree(data) {
622
+ const requested = /* @__PURE__ */ new Set();
623
+ const loaded = /* @__PURE__ */ new Set();
624
+ const failed = /* @__PURE__ */ new Map();
625
+ const processed = /* @__PURE__ */ new Set();
626
+ const pending = /* @__PURE__ */ new Set();
627
+ this.collectModelNamesFromTree(data, pending);
628
+ while (pending.size > 0) {
629
+ const batch = Array.from(pending).filter((name) => !processed.has(name));
630
+ pending.clear();
631
+ if (batch.length === 0) {
632
+ break;
633
+ }
634
+ batch.forEach((name) => requested.add(name));
635
+ const result = await this.ensureModels(batch);
636
+ result.loaded.forEach((name) => {
637
+ processed.add(name);
638
+ loaded.add(name);
639
+ const modelClass = this.getModelClass(name);
640
+ if (modelClass) {
641
+ const discovered = /* @__PURE__ */ new Set();
642
+ this.collectModelNamesFromMetaDefaults(modelClass, discovered);
643
+ discovered.forEach((discoveredName) => {
644
+ if (!processed.has(discoveredName)) {
645
+ pending.add(discoveredName);
646
+ }
647
+ });
648
+ }
649
+ });
650
+ result.failed.forEach((item) => {
651
+ processed.add(item.name);
652
+ failed.set(item.name, item);
653
+ });
654
+ }
655
+ return {
656
+ requested: Array.from(requested),
657
+ loaded: Array.from(loaded),
658
+ failed: Array.from(failed.values())
659
+ };
660
+ }
661
+ /**
662
+ * Preload all currently registered unresolved model loaders.
663
+ *
664
+ * This method is intended for flow-settings/discovery style entry points that need registered model
665
+ * classes to exist before UI is rendered, without requiring callers to know which specific models
666
+ * will be touched next.
667
+ *
668
+ * @returns {Promise<EnsureBatchResult>} Batch ensure result
669
+ */
670
+ async preloadModelLoaders() {
671
+ const unresolved = Array.from(this._modelLoaders.keys()).filter((name) => !this._modelClasses.has(name));
672
+ if (unresolved.length === 0) {
673
+ this._modelLoadersPreloaded = true;
674
+ return { requested: [], loaded: [], failed: [] };
675
+ }
676
+ if (this._modelLoadersPreloadPromise) {
677
+ return this._modelLoadersPreloadPromise;
678
+ }
679
+ this._modelLoadersPreloadPromise = (async () => {
680
+ const result = await this.ensureModels(unresolved);
681
+ this._modelLoadersPreloaded = result.failed.length === 0;
682
+ this._modelLoadersPreloadPromise = void 0;
683
+ return result;
684
+ })();
685
+ return this._modelLoadersPreloadPromise;
686
+ }
361
687
  registerResources(resources) {
362
688
  for (const [name, resourceClass] of Object.entries(resources)) {
363
689
  this._resources.set(name, resourceClass);
@@ -421,6 +747,55 @@ const _FlowEngine = class _FlowEngine {
421
747
  }
422
748
  return result;
423
749
  }
750
+ /**
751
+ * Asynchronously get all subclasses of a base class, including those registered via model loaders.
752
+ * Merges results from already-loaded classes (_modelClasses) and async loader entries with matching `extends` declarations.
753
+ * Loader-resolved classes are validated with `isInheritedFrom`; mismatches are warned and excluded.
754
+ * @param {string | ModelConstructor} baseClass Base class name or constructor
755
+ * @param {(ModelClass: ModelConstructor, className: string) => boolean} [filter] Optional filter function
756
+ * @returns {Promise<Map<string, ModelConstructor>>} Model classes that are subclasses of the base class
757
+ */
758
+ async getSubclassesOfAsync(baseClass, filter) {
759
+ var _a;
760
+ const baseClassName = typeof baseClass === "string" ? baseClass : baseClass.name;
761
+ let parentModelClass;
762
+ if (typeof baseClass === "string") {
763
+ if (!this.getModelClass(baseClass)) {
764
+ await this.ensureModel(baseClass);
765
+ }
766
+ parentModelClass = this.getModelClass(baseClass);
767
+ } else {
768
+ parentModelClass = baseClass;
769
+ }
770
+ if (!parentModelClass) {
771
+ return /* @__PURE__ */ new Map();
772
+ }
773
+ const result = this.getSubclassesOf(parentModelClass, filter);
774
+ const loaderCandidates = [];
775
+ for (const [name, entry] of this._modelLoaders) {
776
+ if (result.has(name) || this._modelClasses.has(name)) continue;
777
+ if ((_a = entry.extends) == null ? void 0 : _a.includes(baseClassName)) {
778
+ loaderCandidates.push(name);
779
+ }
780
+ }
781
+ if (loaderCandidates.length > 0) {
782
+ await this.ensureModels(loaderCandidates);
783
+ }
784
+ for (const name of loaderCandidates) {
785
+ const ModelClass = this._modelClasses.get(name);
786
+ if (!ModelClass) continue;
787
+ if (!(0, import_utils.isInheritedFrom)(ModelClass, parentModelClass)) {
788
+ console.warn(
789
+ `FlowEngine: Model '${name}' declares extends '${baseClassName}' but does not actually inherit from it. Skipping.`
790
+ );
791
+ continue;
792
+ }
793
+ if (!filter || filter(ModelClass, name)) {
794
+ result.set(name, ModelClass);
795
+ }
796
+ }
797
+ return result;
798
+ }
424
799
  /**
425
800
  * Create and register a model instance.
426
801
  * If an instance with the same UID exists, returns the existing instance.
@@ -706,7 +1081,7 @@ const _FlowEngine = class _FlowEngine {
706
1081
  * Hydrate a model into current engine from an already-existing model instance in previous engines.
707
1082
  * - Avoids repository requests when the model tree is already present in memory.
708
1083
  */
709
- hydrateModelFromPreviousEngines(options, extra) {
1084
+ async hydrateModelFromPreviousEngines(options, extra) {
710
1085
  var _a;
711
1086
  const uid = options == null ? void 0 : options.uid;
712
1087
  const parentId = options == null ? void 0 : options.parentId;
@@ -718,7 +1093,7 @@ const _FlowEngine = class _FlowEngine {
718
1093
  }
719
1094
  if (existing) {
720
1095
  const data = existing.serialize();
721
- return this.createModel(data, extra);
1096
+ return this.createModelAsync(data, extra);
722
1097
  }
723
1098
  }
724
1099
  if (parentId && subKey) {
@@ -729,10 +1104,10 @@ const _FlowEngine = class _FlowEngine {
729
1104
  if (!localParent) {
730
1105
  const parentData = parentFromPrev.serialize();
731
1106
  delete parentData.subModels;
732
- localParent = this.createModel(parentData, extra);
1107
+ localParent = await this.createModelAsync(parentData, extra);
733
1108
  }
734
1109
  const modelData = modelFromPrev.serialize();
735
- const localModel = this.createModel(modelData, extra);
1110
+ const localModel = await this.createModelAsync(modelData, extra);
736
1111
  const mounted = (_a = localParent.subModels) == null ? void 0 : _a[subKey];
737
1112
  if (Array.isArray(mounted)) {
738
1113
  const exists = mounted.some((m) => (m == null ? void 0 : m.uid) === (localModel == null ? void 0 : localModel.uid));
@@ -768,20 +1143,21 @@ const _FlowEngine = class _FlowEngine {
768
1143
  if (model) {
769
1144
  return model;
770
1145
  }
771
- const hydrated = this.hydrateModelFromPreviousEngines(options);
1146
+ const hydrated = await this.hydrateModelFromPreviousEngines(options);
772
1147
  if (hydrated) {
773
1148
  return hydrated;
774
1149
  }
775
1150
  }
776
1151
  const data = await this._modelRepository.findOne(options);
777
1152
  if (!(data == null ? void 0 : data.uid)) return null;
1153
+ await this.resolveModelTree(data);
778
1154
  if (refresh) {
779
1155
  const existing = this.getModel(data.uid);
780
1156
  if (existing) {
781
1157
  this.removeModelWithSubModels(existing.uid);
782
1158
  }
783
1159
  }
784
- return this.createModel(data);
1160
+ return this.createModelAsync(data);
785
1161
  }
786
1162
  /**
787
1163
  * Find a sub-model by parent model ID and subKey.
@@ -819,17 +1195,19 @@ const _FlowEngine = class _FlowEngine {
819
1195
  if (m) {
820
1196
  return m;
821
1197
  }
822
- const hydrated = this.hydrateModelFromPreviousEngines(options, extra);
1198
+ const hydrated = await this.hydrateModelFromPreviousEngines(options, extra);
823
1199
  if (hydrated) {
824
1200
  return hydrated;
825
1201
  }
826
1202
  const data = await this._modelRepository.findOne(options);
827
1203
  let model = null;
828
1204
  if (data == null ? void 0 : data.uid) {
829
- model = this.createModel(data, extra);
1205
+ model = await this.createModelAsync(data, extra);
830
1206
  } else {
831
- model = this.createModel(options, extra);
832
- await model.save();
1207
+ model = await this.createModelAsync(options, extra);
1208
+ if (!(extra == null ? void 0 : extra.skipSave)) {
1209
+ await model.save();
1210
+ }
833
1211
  }
834
1212
  if (model.parent) {
835
1213
  const subModel = model.parent.findSubModel(model.subKey, (m2) => {
@@ -1070,11 +1448,7 @@ _FlowEngine_instances = new WeakSet();
1070
1448
  * @private
1071
1449
  */
1072
1450
  registerModel_fn = /* @__PURE__ */ __name(function(name, modelClass) {
1073
- if (this._modelClasses.has(name)) {
1074
- console.warn(`FlowEngine: Model class with name '${name}' is already registered and will be overwritten.`);
1075
- }
1076
- Object.defineProperty(modelClass, "name", { value: name });
1077
- this._modelClasses.set(name, modelClass);
1451
+ return this._registerModel(name, modelClass);
1078
1452
  }, "#registerModel");
1079
1453
  __name(_FlowEngine, "FlowEngine");
1080
1454
  let FlowEngine = _FlowEngine;
@@ -79,12 +79,19 @@ export interface FlowSettingsOpenOptions {
79
79
  /** 配置保存成功后触发的回调 */
80
80
  onSaved?: () => void | Promise<void>;
81
81
  }
82
+ export type FlowSettingsComponent = React.ComponentType<any>;
83
+ export type FlowSettingsComponentModule = {
84
+ default?: FlowSettingsComponent;
85
+ } | Record<string, FlowSettingsComponent>;
86
+ export type FlowSettingsComponentLoader = () => Promise<FlowSettingsComponentModule | FlowSettingsComponent>;
87
+ export type FlowSettingsComponentLoaderMap = Record<string, FlowSettingsComponentLoader>;
82
88
  export declare class FlowSettings {
83
89
  #private;
84
90
  components: Record<string, any>;
85
91
  scopes: Record<string, any>;
86
92
  private antdComponentsLoaded;
87
93
  enabled: boolean;
94
+ private engine;
88
95
  toolbarItems: ToolbarItemConfig[];
89
96
  constructor(engine: FlowEngine);
90
97
  on(event: 'beforeOpen', callback: (...args: any[]) => void): void;
@@ -110,6 +117,7 @@ export declare class FlowSettings {
110
117
  * flowSettings.registerComponents({ MyComponent, AnotherComponent });
111
118
  */
112
119
  registerComponents(components: Record<string, any>): void;
120
+ registerComponentLoaders(loaders: FlowSettingsComponentLoaderMap): void;
113
121
  /**
114
122
  * 添加作用域到 FlowSettings 的作用域注册表中。
115
123
  * 这些作用域可以在 flow step 的 uiSchema 中使用。
@@ -122,17 +130,17 @@ export declare class FlowSettings {
122
130
  /**
123
131
  * 启用流程设置组件的显示
124
132
  * @example
125
- * flowSettings.enable();
133
+ * await flowSettings.enable();
126
134
  */
127
- enable(): void;
128
- forceEnable(): void;
135
+ enable(): Promise<void>;
136
+ forceEnable(): Promise<void>;
129
137
  /**
130
138
  * 禁用流程设置组件的显示
131
139
  * @example
132
- * flowSettings.disable();
140
+ * await flowSettings.disable();
133
141
  */
134
- disable(): void;
135
- forceDisable(): void;
142
+ disable(): Promise<void>;
143
+ forceDisable(): Promise<void>;
136
144
  /**
137
145
  * 添加扩展工具栏项目
138
146
  * @param {ToolbarItemConfig} config 项目配置
@@ -65,6 +65,7 @@ var import_utils = require("./utils");
65
65
  var import_exceptions = require("./utils/exceptions");
66
66
  var import_useFlowStep = require("./hooks/useFlowStep");
67
67
  var import_views = require("./views");
68
+ var import_lazy_helper = require("./lazy-helper");
68
69
  var _forceEnabled, _emitter;
69
70
  const Panel = import_antd.Collapse.Panel;
70
71
  const _FlowSettings = class _FlowSettings {
@@ -73,10 +74,12 @@ const _FlowSettings = class _FlowSettings {
73
74
  __publicField(this, "scopes", {});
74
75
  __publicField(this, "antdComponentsLoaded", false);
75
76
  __publicField(this, "enabled");
77
+ __publicField(this, "engine");
76
78
  __privateAdd(this, _forceEnabled, false);
77
79
  // 强制启用状态,主要用于设计模式下的强制启用
78
80
  __publicField(this, "toolbarItems", []);
79
81
  __privateAdd(this, _emitter, new import_emitter.Emitter());
82
+ this.engine = engine;
80
83
  this.enabled = false;
81
84
  engine.context.defineProperty("flowSettingsEnabled", {
82
85
  get: /* @__PURE__ */ __name(() => this.enabled, "get"),
@@ -215,6 +218,29 @@ const _FlowSettings = class _FlowSettings {
215
218
  this.components[name] = components[name];
216
219
  });
217
220
  }
221
+ registerComponentLoaders(loaders) {
222
+ Object.entries(loaders).forEach(([name, loader]) => {
223
+ if (this.components[name]) {
224
+ console.warn(`FlowSettings: Component with name '${name}' is already registered and will be overwritten.`);
225
+ }
226
+ this.components[name] = (0, import_lazy_helper.lazy)(async () => {
227
+ const loaded = await loader();
228
+ if (typeof loaded === "function") {
229
+ return { default: loaded };
230
+ }
231
+ if ((loaded == null ? void 0 : loaded.default) && typeof loaded.default === "function") {
232
+ return { default: loaded.default };
233
+ }
234
+ const namedComponent = loaded == null ? void 0 : loaded[name];
235
+ if (typeof namedComponent === "function") {
236
+ return { default: namedComponent };
237
+ }
238
+ throw new Error(
239
+ `FlowSettings: component loader for '${name}' must resolve to a React component or a module exporting it.`
240
+ );
241
+ });
242
+ });
243
+ }
218
244
  /**
219
245
  * 添加作用域到 FlowSettings 的作用域注册表中。
220
246
  * 这些作用域可以在 flow step 的 uiSchema 中使用。
@@ -234,27 +260,29 @@ const _FlowSettings = class _FlowSettings {
234
260
  /**
235
261
  * 启用流程设置组件的显示
236
262
  * @example
237
- * flowSettings.enable();
263
+ * await flowSettings.enable();
238
264
  */
239
- enable() {
265
+ async enable() {
266
+ await this.engine.preloadModelLoaders();
240
267
  this.enabled = true;
241
268
  }
242
- forceEnable() {
269
+ async forceEnable() {
270
+ await this.engine.preloadModelLoaders();
243
271
  __privateSet(this, _forceEnabled, true);
244
272
  this.enabled = true;
245
273
  }
246
274
  /**
247
275
  * 禁用流程设置组件的显示
248
276
  * @example
249
- * flowSettings.disable();
277
+ * await flowSettings.disable();
250
278
  */
251
- disable() {
279
+ async disable() {
252
280
  if (__privateGet(this, _forceEnabled)) {
253
281
  return;
254
282
  }
255
283
  this.enabled = false;
256
284
  }
257
- forceDisable() {
285
+ async forceDisable() {
258
286
  __privateSet(this, _forceEnabled, false);
259
287
  this.enabled = false;
260
288
  }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ type LazyComponentType<M extends Record<string, any>, K extends keyof M> = {
10
+ [P in K]: M[P];
11
+ };
12
+ export declare function lazy<M extends Record<'default', any>>(factory: () => Promise<M>): M['default'];
13
+ export declare function lazy<M extends Record<string, any>, K extends keyof M = keyof M>(factory: () => Promise<M>, ...componentNames: K[]): LazyComponentType<M, K>;
14
+ export {};