@nocobase/flow-engine 2.0.0-beta.8 → 2.0.0
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/lib/BlockScopedFlowEngine.js +0 -1
- package/lib/FlowDefinition.d.ts +2 -0
- package/lib/JSRunner.d.ts +6 -0
- package/lib/JSRunner.js +32 -2
- package/lib/ViewScopedFlowEngine.js +3 -0
- package/lib/acl/Acl.js +13 -3
- package/lib/components/FlowContextSelector.js +155 -10
- package/lib/components/settings/wrappers/component/SwitchWithTitle.js +2 -1
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +76 -15
- package/lib/components/settings/wrappers/contextual/FlowsContextMenu.js +24 -4
- package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +5 -1
- package/lib/components/variables/VariableInput.js +9 -4
- package/lib/components/variables/VariableTag.js +46 -39
- package/lib/components/variables/utils.d.ts +7 -0
- package/lib/components/variables/utils.js +42 -2
- package/lib/data-source/index.d.ts +7 -27
- package/lib/data-source/index.js +81 -51
- package/lib/executor/FlowExecutor.d.ts +2 -1
- package/lib/executor/FlowExecutor.js +163 -22
- package/lib/flowContext.d.ts +230 -7
- package/lib/flowContext.js +2267 -148
- package/lib/flowEngine.d.ts +21 -0
- package/lib/flowEngine.js +56 -8
- package/lib/flowI18n.js +6 -4
- package/lib/flowSettings.js +17 -11
- package/lib/index.d.ts +7 -1
- package/lib/index.js +21 -0
- package/lib/locale/en-US.json +9 -2
- package/lib/locale/index.d.ts +14 -0
- package/lib/locale/zh-CN.json +8 -1
- package/lib/models/CollectionFieldModel.d.ts +1 -0
- package/lib/models/CollectionFieldModel.js +3 -2
- package/lib/models/flowModel.js +12 -1
- package/lib/provider.js +5 -5
- package/lib/resources/baseRecordResource.d.ts +5 -0
- package/lib/resources/baseRecordResource.js +24 -0
- package/lib/resources/multiRecordResource.d.ts +1 -0
- package/lib/resources/multiRecordResource.js +11 -4
- package/lib/resources/singleRecordResource.js +2 -0
- package/lib/resources/sqlResource.d.ts +4 -3
- package/lib/resources/sqlResource.js +8 -3
- package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +12 -2
- package/lib/runjs-context/contexts/JSBlockRunJSContext.js +2 -2
- package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.d.ts +16 -0
- package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.js +125 -0
- package/lib/runjs-context/contexts/JSItemRunJSContext.js +12 -2
- package/lib/runjs-context/contexts/base.js +706 -41
- package/lib/runjs-context/contributions.d.ts +33 -0
- package/lib/runjs-context/contributions.js +88 -0
- package/lib/runjs-context/helpers.js +12 -1
- package/lib/runjs-context/setup.js +6 -0
- package/lib/runjs-context/snippets/global/api-request.snippet.js +3 -3
- package/lib/runjs-context/snippets/global/import-esm.snippet.js +2 -3
- package/lib/runjs-context/snippets/global/query-selector.snippet.js +8 -3
- package/lib/runjs-context/snippets/global/require-amd.snippet.js +1 -1
- package/lib/runjs-context/snippets/index.d.ts +11 -1
- package/lib/runjs-context/snippets/index.js +61 -40
- package/lib/runjs-context/snippets/scene/block/add-event-listener.snippet.js +10 -7
- package/lib/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.js +3 -3
- package/lib/runjs-context/snippets/scene/block/chartjs-bar.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/block/echarts-init.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/block/render-iframe.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/block/render-react.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/block/render-statistics.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/block/render-timeline.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/block/resource-example.snippet.js +5 -5
- package/lib/runjs-context/snippets/scene/block/three-users-orbit.snippet.js +6 -6
- package/lib/runjs-context/snippets/scene/block/vue-component.snippet.js +3 -4
- package/lib/runjs-context/snippets/scene/detail/color-by-value.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.js +20 -3
- package/lib/runjs-context/snippets/scene/detail/format-number.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/detail/innerHTML-value.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/detail/percentage-bar.snippet.js +3 -3
- package/lib/runjs-context/snippets/scene/detail/relative-time.snippet.js +3 -3
- package/lib/runjs-context/snippets/scene/detail/status-tag.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/form/cascade-select.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/form/render-basic.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/table/cell-open-dialog.snippet.js +6 -3
- package/lib/runjs-context/snippets/scene/table/concat-fields.snippet.js +3 -1
- package/lib/runjsLibs.d.ts +28 -0
- package/lib/runjsLibs.js +532 -0
- package/lib/scheduler/ModelOperationScheduler.d.ts +2 -0
- package/lib/scheduler/ModelOperationScheduler.js +25 -21
- package/lib/types.d.ts +27 -0
- package/lib/utils/associationObjectVariable.d.ts +2 -2
- package/lib/utils/createCollectionContextMeta.js +1 -0
- package/lib/utils/createEphemeralContext.js +2 -2
- package/lib/utils/dateVariable.d.ts +16 -0
- package/lib/utils/dateVariable.js +380 -0
- package/lib/utils/exceptions.d.ts +7 -0
- package/lib/utils/exceptions.js +10 -0
- package/lib/utils/index.d.ts +8 -3
- package/lib/utils/index.js +45 -0
- package/lib/utils/params-resolvers.js +16 -9
- package/lib/utils/resolveModuleUrl.d.ts +58 -0
- package/lib/utils/resolveModuleUrl.js +65 -0
- package/lib/utils/resolveRunJSObjectValues.d.ts +16 -0
- package/lib/utils/resolveRunJSObjectValues.js +61 -0
- package/lib/utils/runjsModuleLoader.d.ts +58 -0
- package/lib/utils/runjsModuleLoader.js +422 -0
- package/lib/utils/runjsTemplateCompat.d.ts +35 -0
- package/lib/utils/runjsTemplateCompat.js +743 -0
- package/lib/utils/runjsValue.d.ts +29 -0
- package/lib/utils/runjsValue.js +275 -0
- package/lib/utils/safeGlobals.d.ts +18 -8
- package/lib/utils/safeGlobals.js +164 -17
- package/lib/utils/schema-utils.d.ts +10 -0
- package/lib/utils/schema-utils.js +61 -0
- package/lib/views/createViewMeta.d.ts +0 -7
- package/lib/views/createViewMeta.js +19 -70
- package/lib/views/index.d.ts +1 -2
- package/lib/views/index.js +4 -3
- package/lib/views/useDialog.js +7 -2
- package/lib/views/useDrawer.js +7 -2
- package/lib/views/usePage.d.ts +4 -0
- package/lib/views/usePage.js +43 -6
- package/lib/views/usePopover.js +4 -1
- package/lib/views/viewEvents.d.ts +17 -0
- package/lib/views/viewEvents.js +90 -0
- package/package.json +4 -4
- package/src/BlockScopedFlowEngine.ts +2 -5
- package/src/JSRunner.ts +44 -2
- package/src/ViewScopedFlowEngine.ts +4 -0
- package/src/__tests__/JSRunner.test.ts +64 -0
- package/src/__tests__/createViewMeta.popup.test.ts +62 -1
- package/src/__tests__/flowContext.test.ts +693 -1
- package/src/__tests__/flowEngine.dataSourceDirty.test.ts +63 -0
- package/src/__tests__/flowModel.openView.navigation.test.ts +28 -0
- package/src/__tests__/flowRunJSContextDefine.test.ts +63 -0
- package/src/__tests__/flowRuntimeContext.test.ts +2 -1
- package/src/__tests__/flowSettings.open.test.tsx +123 -19
- package/src/__tests__/runjsContext.test.ts +10 -7
- package/src/__tests__/runjsContextImplementations.test.ts +34 -3
- package/src/__tests__/runjsContextRuntime.test.ts +3 -3
- package/src/__tests__/runjsContributions.test.ts +89 -0
- package/src/__tests__/runjsExternalLibs.test.ts +242 -0
- package/src/__tests__/runjsLibsLazyLoading.test.ts +44 -0
- package/src/__tests__/runjsLocales.test.ts +4 -1
- package/src/__tests__/runjsPreprocessDefault.test.ts +49 -0
- package/src/__tests__/runjsRuntimeFeatures.test.ts +166 -0
- package/src/__tests__/runjsSnippets.test.ts +40 -3
- package/src/acl/Acl.tsx +3 -3
- package/src/components/FlowContextSelector.tsx +208 -12
- package/src/components/settings/wrappers/component/SwitchWithTitle.tsx +2 -1
- package/src/components/settings/wrappers/component/__tests__/InlineControls.test.tsx +74 -0
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +109 -16
- package/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx +41 -7
- package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +13 -2
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +157 -5
- package/src/components/variables/VariableInput.tsx +12 -4
- package/src/components/variables/VariableTag.tsx +54 -45
- package/src/components/variables/__tests__/FlowContextSelector.test.tsx +260 -3
- package/src/components/variables/__tests__/VariableTag.test.tsx +50 -0
- package/src/components/variables/__tests__/utils.test.ts +81 -3
- package/src/components/variables/utils.ts +67 -6
- package/src/data-source/index.ts +85 -110
- package/src/executor/FlowExecutor.ts +200 -23
- package/src/executor/__tests__/flowExecutor.test.ts +66 -0
- package/src/flowContext.ts +2986 -211
- package/src/flowEngine.ts +59 -8
- package/src/flowI18n.ts +7 -5
- package/src/flowSettings.ts +18 -12
- package/src/index.ts +14 -1
- package/src/locale/en-US.json +9 -2
- package/src/locale/zh-CN.json +8 -1
- package/src/models/CollectionFieldModel.tsx +3 -1
- package/src/models/__tests__/dispatchEvent.when.test.ts +554 -0
- package/src/models/__tests__/flowModel.test.ts +20 -4
- package/src/models/flowModel.tsx +13 -1
- package/src/provider.tsx +7 -6
- package/src/resources/__tests__/multiRecordResource.test.ts +44 -0
- package/src/resources/__tests__/sqlResource.test.ts +60 -0
- package/src/resources/baseRecordResource.ts +31 -0
- package/src/resources/multiRecordResource.ts +11 -4
- package/src/resources/singleRecordResource.ts +3 -0
- package/src/resources/sqlResource.ts +11 -6
- package/src/runjs-context/contexts/FormJSFieldItemRunJSContext.ts +10 -0
- package/src/runjs-context/contexts/JSBlockRunJSContext.ts +6 -2
- package/src/runjs-context/contexts/JSEditableFieldRunJSContext.ts +106 -0
- package/src/runjs-context/contexts/JSItemRunJSContext.ts +10 -0
- package/src/runjs-context/contexts/base.ts +715 -44
- package/src/runjs-context/contributions.ts +88 -0
- package/src/runjs-context/helpers.ts +11 -1
- package/src/runjs-context/setup.ts +6 -0
- package/src/runjs-context/snippets/global/api-request.snippet.ts +3 -3
- package/src/runjs-context/snippets/global/import-esm.snippet.ts +2 -3
- package/src/runjs-context/snippets/global/query-selector.snippet.ts +8 -3
- package/src/runjs-context/snippets/global/require-amd.snippet.ts +1 -1
- package/src/runjs-context/snippets/index.ts +75 -41
- package/src/runjs-context/snippets/scene/block/add-event-listener.snippet.ts +11 -13
- package/src/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.ts +3 -3
- package/src/runjs-context/snippets/scene/block/chartjs-bar.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/block/echarts-init.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/block/render-iframe.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/block/render-react.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/block/render-statistics.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/block/render-timeline.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/block/resource-example.snippet.ts +6 -11
- package/src/runjs-context/snippets/scene/block/three-users-orbit.snippet.ts +6 -6
- package/src/runjs-context/snippets/scene/block/vue-component.snippet.ts +3 -4
- package/src/runjs-context/snippets/scene/detail/color-by-value.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.ts +20 -3
- package/src/runjs-context/snippets/scene/detail/format-number.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/detail/innerHTML-value.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/detail/percentage-bar.snippet.ts +3 -3
- package/src/runjs-context/snippets/scene/detail/relative-time.snippet.ts +3 -3
- package/src/runjs-context/snippets/scene/detail/status-tag.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/form/cascade-select.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/form/render-basic.snippet.ts +3 -8
- package/src/runjs-context/snippets/scene/table/cell-open-dialog.snippet.ts +6 -3
- package/src/runjs-context/snippets/scene/table/concat-fields.snippet.ts +3 -1
- package/src/runjsLibs.ts +622 -0
- package/src/scheduler/ModelOperationScheduler.ts +27 -21
- package/src/types.ts +38 -1
- package/src/utils/__tests__/dateVariable.test.ts +101 -0
- package/src/utils/__tests__/params-resolvers.test.ts +40 -0
- package/src/utils/__tests__/runjsRequireAsyncAutoWhitelist.test.ts +38 -0
- package/src/utils/__tests__/runjsTemplateCompat.test.ts +159 -0
- package/src/utils/__tests__/runjsValue.test.ts +44 -0
- package/src/utils/__tests__/safeGlobals.test.ts +57 -2
- package/src/utils/__tests__/utils.test.ts +95 -0
- package/src/utils/associationObjectVariable.ts +2 -2
- package/src/utils/createCollectionContextMeta.ts +1 -0
- package/src/utils/createEphemeralContext.ts +5 -4
- package/src/utils/dateVariable.ts +397 -0
- package/src/utils/exceptions.ts +11 -0
- package/src/utils/index.ts +37 -3
- package/src/utils/params-resolvers.ts +23 -9
- package/src/utils/resolveModuleUrl.ts +91 -0
- package/src/utils/resolveRunJSObjectValues.ts +46 -0
- package/src/utils/runjsModuleLoader.ts +553 -0
- package/src/utils/runjsTemplateCompat.ts +828 -0
- package/src/utils/runjsValue.ts +287 -0
- package/src/utils/safeGlobals.ts +188 -17
- package/src/utils/schema-utils.ts +79 -0
- package/src/views/__tests__/FlowView.usePage.test.tsx +54 -1
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +35 -8
- package/src/views/__tests__/viewEvents.resolveOpenerEngine.test.ts +28 -0
- package/src/views/createViewMeta.ts +22 -75
- package/src/views/index.tsx +1 -2
- package/src/views/useDialog.tsx +8 -1
- package/src/views/useDrawer.tsx +8 -1
- package/src/views/usePage.tsx +51 -5
- package/src/views/usePopover.tsx +4 -1
- package/src/views/viewEvents.ts +55 -0
|
@@ -14,30 +14,28 @@ import { useDialog } from '../useDialog';
|
|
|
14
14
|
import { FlowContext } from '../../flowContext';
|
|
15
15
|
|
|
16
16
|
// Mock dependencies
|
|
17
|
-
vi.mock('
|
|
17
|
+
vi.mock('../../provider', () => ({
|
|
18
18
|
FlowEngineProvider: ({ children }) => children,
|
|
19
19
|
}));
|
|
20
20
|
|
|
21
|
-
vi.mock('
|
|
21
|
+
vi.mock('../../FlowContextProvider', () => ({
|
|
22
22
|
FlowViewContextProvider: ({ children }) => children,
|
|
23
23
|
}));
|
|
24
24
|
|
|
25
|
-
vi.mock('
|
|
25
|
+
vi.mock('../../ViewScopedFlowEngine', () => ({
|
|
26
26
|
createViewScopedEngine: (engine) => ({
|
|
27
27
|
context: new FlowContext(),
|
|
28
28
|
unlinkFromStack: vi.fn(),
|
|
29
|
+
// mimic real view stack linkage: previousEngine points to the last engine in chain
|
|
30
|
+
previousEngine: (engine as any)?.nextEngine || engine,
|
|
29
31
|
}),
|
|
30
32
|
}));
|
|
31
33
|
|
|
32
|
-
vi.mock('
|
|
34
|
+
vi.mock('../../utils/variablesParams', () => ({
|
|
33
35
|
createViewRecordResolveOnServer: vi.fn(),
|
|
34
36
|
getViewRecordFromParent: vi.fn(),
|
|
35
37
|
}));
|
|
36
38
|
|
|
37
|
-
vi.mock('../createViewMeta', () => ({
|
|
38
|
-
registerPopupVariable: vi.fn(),
|
|
39
|
-
}));
|
|
40
|
-
|
|
41
39
|
vi.mock('../DialogComponent', () => ({
|
|
42
40
|
default: ({ children }) => <div>{children}</div>,
|
|
43
41
|
}));
|
|
@@ -52,8 +50,12 @@ vi.mock('../usePatchElement', () => ({
|
|
|
52
50
|
describe('useDialog - close/destroy logic', () => {
|
|
53
51
|
const createMockFlowContext = () => {
|
|
54
52
|
const ctx = new FlowContext();
|
|
53
|
+
ctx.defineMethod('t', (key: string) => key);
|
|
55
54
|
ctx.engine = {
|
|
56
55
|
context: new FlowContext(),
|
|
56
|
+
emitter: {
|
|
57
|
+
emit: vi.fn(),
|
|
58
|
+
},
|
|
57
59
|
};
|
|
58
60
|
return ctx;
|
|
59
61
|
};
|
|
@@ -129,4 +131,29 @@ describe('useDialog - close/destroy logic', () => {
|
|
|
129
131
|
// Should not call destroy directly, let router handle it
|
|
130
132
|
expect(mockCloseFunc).not.toHaveBeenCalled();
|
|
131
133
|
});
|
|
134
|
+
|
|
135
|
+
it('should emit view activated event on opener engine', () => {
|
|
136
|
+
const api = renderUseDialog();
|
|
137
|
+
const flowContext = createMockFlowContext();
|
|
138
|
+
const emitSpy = flowContext.engine.emitter.emit;
|
|
139
|
+
|
|
140
|
+
const dialog = api.open({ inputArgs: { viewUid: 'child-view' } }, flowContext);
|
|
141
|
+
|
|
142
|
+
dialog.close();
|
|
143
|
+
expect(emitSpy).toHaveBeenCalledWith('view:activated', expect.anything());
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should emit view events on immediate opener engine (previousEngine) when present', () => {
|
|
147
|
+
const api = renderUseDialog();
|
|
148
|
+
const flowContext = createMockFlowContext();
|
|
149
|
+
const rootEmitSpy = flowContext.engine.emitter.emit;
|
|
150
|
+
const openerEmitSpy = vi.fn();
|
|
151
|
+
(flowContext.engine as any).nextEngine = { emitter: { emit: openerEmitSpy }, __NOCOBASE_ENGINE_SCOPE__: 'view' };
|
|
152
|
+
|
|
153
|
+
const dialog = api.open({ inputArgs: { viewUid: 'child-view' } }, flowContext);
|
|
154
|
+
|
|
155
|
+
dialog.close();
|
|
156
|
+
expect(openerEmitSpy).toHaveBeenCalledWith('view:activated', expect.anything());
|
|
157
|
+
expect(rootEmitSpy).not.toHaveBeenCalledWith('view:activated', expect.anything());
|
|
158
|
+
});
|
|
132
159
|
});
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
|
|
10
|
+
import { describe, expect, it } from 'vitest';
|
|
11
|
+
import { FlowEngine } from '../../flowEngine';
|
|
12
|
+
import { createViewScopedEngine } from '../../ViewScopedFlowEngine';
|
|
13
|
+
import { resolveOpenerEngine } from '../viewEvents';
|
|
14
|
+
|
|
15
|
+
describe('viewEvents.resolveOpenerEngine', () => {
|
|
16
|
+
it('prefers the parent view engine even when it is not the stack tail (cached page scenario)', () => {
|
|
17
|
+
const root = new FlowEngine();
|
|
18
|
+
const pageA = createViewScopedEngine(root);
|
|
19
|
+
createViewScopedEngine(root); // pageB appended after pageA
|
|
20
|
+
|
|
21
|
+
// Open a dialog from pageA while another kept-alive view exists after it.
|
|
22
|
+
// view scoped engines always link to the tail, so the dialog's previousEngine will be pageB.
|
|
23
|
+
const dialog = createViewScopedEngine(pageA);
|
|
24
|
+
|
|
25
|
+
const opener = resolveOpenerEngine(pageA, dialog);
|
|
26
|
+
expect(opener).toBe(pageA);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -70,66 +70,6 @@ function makeMetaFromValue(value: any, title?: string, seen?: WeakSet<any>): any
|
|
|
70
70
|
return { type: 'any', title };
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
/**
|
|
74
|
-
* Create a meta factory for ctx.view that includes:
|
|
75
|
-
* - buildVariablesParams: { record } via inferRecordRef
|
|
76
|
-
* - properties.record: full collection meta via buildRecordMeta
|
|
77
|
-
* - type/preventClose/inputArgs/navigation fields for better variable selection UX
|
|
78
|
-
*/
|
|
79
|
-
export function createViewMeta(ctx: FlowContext): PropertyMetaFactory {
|
|
80
|
-
const viewTitle = ctx.t('当前视图');
|
|
81
|
-
const factory: PropertyMetaFactory = async () => {
|
|
82
|
-
const view = ctx.view;
|
|
83
|
-
return {
|
|
84
|
-
type: 'object',
|
|
85
|
-
title: ctx.t('当前视图'),
|
|
86
|
-
buildVariablesParams: (c) => {
|
|
87
|
-
const params = inferViewRecordRef(c);
|
|
88
|
-
if (params) {
|
|
89
|
-
return {
|
|
90
|
-
record: params,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
return undefined;
|
|
94
|
-
},
|
|
95
|
-
properties: async () => {
|
|
96
|
-
const props: Record<string, any> = {};
|
|
97
|
-
// 仅当能推断到当前记录引用时,才暴露“当前视图记录”,避免出现空子菜单
|
|
98
|
-
const refNow = inferViewRecordRef(ctx);
|
|
99
|
-
if (refNow && refNow.collection) {
|
|
100
|
-
const recordFactory: PropertyMetaFactory = async () => {
|
|
101
|
-
try {
|
|
102
|
-
const ref = inferViewRecordRef(ctx);
|
|
103
|
-
if (!ref?.collection) return null;
|
|
104
|
-
const dsKey = ref.dataSourceKey || 'main';
|
|
105
|
-
const ds = ctx.dataSourceManager?.getDataSource?.(dsKey);
|
|
106
|
-
const col = ds?.collectionManager?.getCollection?.(ref.collection);
|
|
107
|
-
if (!col) return null;
|
|
108
|
-
return (await buildRecordMeta(
|
|
109
|
-
() => col,
|
|
110
|
-
ctx.t('当前视图记录'),
|
|
111
|
-
(c) => inferViewRecordRef(c),
|
|
112
|
-
)) as PropertyMeta;
|
|
113
|
-
} catch (e) {
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
recordFactory.title = ctx.t('当前视图记录');
|
|
118
|
-
recordFactory.hasChildren = true;
|
|
119
|
-
props.record = recordFactory;
|
|
120
|
-
}
|
|
121
|
-
props.type = { type: 'string', title: ctx.t?.('类型') || '类型' };
|
|
122
|
-
props.preventClose = { type: 'boolean', title: ctx.t?.('是否允许关闭') || '是否允许关闭' };
|
|
123
|
-
props.inputArgs = makeMetaFromValue(view?.inputArgs, ctx.t?.('输入参数') || '输入参数');
|
|
124
|
-
return props;
|
|
125
|
-
},
|
|
126
|
-
} as PropertyMeta;
|
|
127
|
-
};
|
|
128
|
-
// 设置工厂函数的 title,让未加载前的占位标题就是“当前视图”
|
|
129
|
-
factory.title = viewTitle;
|
|
130
|
-
return factory;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
73
|
/**
|
|
134
74
|
* 为 ctx.popup 构建元信息:
|
|
135
75
|
* - popup.record:当前弹窗记录(服务端解析)
|
|
@@ -142,14 +82,16 @@ export function createPopupMeta(ctx: FlowContext, anchorView?: FlowView): Proper
|
|
|
142
82
|
const isPopupView = (view?: FlowView): boolean => {
|
|
143
83
|
if (!view) return false;
|
|
144
84
|
const stack = Array.isArray(view.navigation?.viewStack) ? view.navigation.viewStack : [];
|
|
145
|
-
|
|
85
|
+
const openerUids = view?.inputArgs?.openerUids;
|
|
86
|
+
const hasOpener = Array.isArray(openerUids) && openerUids.length > 0;
|
|
87
|
+
return stack.length >= 2 || hasOpener;
|
|
146
88
|
};
|
|
147
89
|
|
|
148
90
|
const hasPopupNow = (): boolean => isPopupView(anchorView ?? ctx.view);
|
|
149
91
|
|
|
150
92
|
// 统一解析锚定视图下的 RecordRef,避免在设置弹窗等二级视图中被误导
|
|
151
93
|
const resolveRecordRef = async (flowCtx: FlowContext): Promise<RecordRef | undefined> => {
|
|
152
|
-
const view = anchorView ??
|
|
94
|
+
const view = anchorView ?? flowCtx.view;
|
|
153
95
|
if (!view || !isPopupView(view)) return undefined;
|
|
154
96
|
|
|
155
97
|
const base = await buildPopupRuntime(flowCtx, view);
|
|
@@ -353,19 +295,24 @@ export function createPopupMeta(ctx: FlowContext, anchorView?: FlowView): Proper
|
|
|
353
295
|
const props: Record<string, any> = {};
|
|
354
296
|
// 当前弹窗 UID(纯前端变量)
|
|
355
297
|
props.uid = { type: 'string', title: t('Popup uid') };
|
|
356
|
-
//
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
(
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
298
|
+
// 仅当存在 filterByTk(可推断具体记录)时才提供“当前弹窗记录”变量;
|
|
299
|
+
// 对于新增/选择类弹窗(无 filterByTk),不应展示该变量以避免误导。
|
|
300
|
+
const recordRef = await resolveRecordRef(ctx);
|
|
301
|
+
if (recordRef) {
|
|
302
|
+
// 基于锚定视图计算“当前弹窗记录”的集合与 RecordRef
|
|
303
|
+
const recordFactory: PropertyMetaFactory = async () => {
|
|
304
|
+
const col = await getCurrentCollection();
|
|
305
|
+
if (!col) return null;
|
|
306
|
+
return await buildRecordMeta(
|
|
307
|
+
() => col,
|
|
308
|
+
t('Current popup record'),
|
|
309
|
+
(c) => resolveRecordRef(c),
|
|
310
|
+
);
|
|
311
|
+
};
|
|
312
|
+
recordFactory.title = t('Current popup record');
|
|
313
|
+
recordFactory.hasChildren = true;
|
|
314
|
+
props.record = recordFactory;
|
|
315
|
+
}
|
|
369
316
|
// 当 view.inputArgs 带有 sourceId + associationName 时,提供“上级记录”变量(基于 sourceId 推断)
|
|
370
317
|
try {
|
|
371
318
|
const inputArgs = ctx.view?.inputArgs;
|
package/src/views/index.tsx
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
|
|
10
10
|
export { useDialog } from './useDialog';
|
|
11
11
|
export { useDrawer } from './useDrawer';
|
|
12
|
-
export { usePage } from './usePage';
|
|
12
|
+
export { usePage, GLOBAL_EMBED_CONTAINER_ID, EMBED_REPLACING_DATA_KEY } from './usePage';
|
|
13
13
|
export { usePopover } from './usePopover';
|
|
14
14
|
export { ViewNavigation } from './ViewNavigation';
|
|
15
|
-
export { createViewMeta } from './createViewMeta';
|
package/src/views/useDialog.tsx
CHANGED
|
@@ -15,6 +15,7 @@ import { FlowViewContextProvider } from '../FlowContextProvider';
|
|
|
15
15
|
import { registerPopupVariable } from './createViewMeta';
|
|
16
16
|
import DialogComponent from './DialogComponent';
|
|
17
17
|
import usePatchElement from './usePatchElement';
|
|
18
|
+
import { VIEW_ACTIVATED_EVENT, bumpViewActivatedVersion, resolveOpenerEngine } from './viewEvents';
|
|
18
19
|
import { FlowEngineProvider } from '../provider';
|
|
19
20
|
import { createViewScopedEngine } from '../ViewScopedFlowEngine';
|
|
20
21
|
import { createViewRecordResolveOnServer, getViewRecordFromParent } from '../utils/variablesParams';
|
|
@@ -25,6 +26,7 @@ export function useDialog() {
|
|
|
25
26
|
const holderRef = React.useRef(null);
|
|
26
27
|
|
|
27
28
|
const open = (config, flowContext) => {
|
|
29
|
+
const parentEngine = flowContext?.engine;
|
|
28
30
|
uuid += 1;
|
|
29
31
|
const dialogRef = React.createRef<{
|
|
30
32
|
destroy: () => void;
|
|
@@ -77,6 +79,8 @@ export function useDialog() {
|
|
|
77
79
|
const ctx = new FlowContext();
|
|
78
80
|
// 为当前视图创建作用域引擎(隔离实例与缓存)
|
|
79
81
|
const scopedEngine = createViewScopedEngine(flowContext.engine);
|
|
82
|
+
const openerEngine = resolveOpenerEngine(parentEngine, scopedEngine);
|
|
83
|
+
|
|
80
84
|
ctx.defineProperty('engine', { value: scopedEngine });
|
|
81
85
|
ctx.addDelegate(scopedEngine.context);
|
|
82
86
|
if (config.inheritContext !== false) {
|
|
@@ -95,6 +99,10 @@ export function useDialog() {
|
|
|
95
99
|
dialogRef.current?.destroy();
|
|
96
100
|
closeFunc?.();
|
|
97
101
|
resolvePromise?.(result);
|
|
102
|
+
// Notify opener view that it becomes active again.
|
|
103
|
+
const openerEmitter = openerEngine?.emitter;
|
|
104
|
+
bumpViewActivatedVersion(openerEmitter);
|
|
105
|
+
openerEmitter?.emit?.(VIEW_ACTIVATED_EVENT, { type: 'dialog', viewUid: currentDialog?.inputArgs?.viewUid });
|
|
98
106
|
// 关闭时修正 previous/next 指针
|
|
99
107
|
scopedEngine.unlinkFromStack();
|
|
100
108
|
},
|
|
@@ -130,7 +138,6 @@ export function useDialog() {
|
|
|
130
138
|
|
|
131
139
|
ctx.defineProperty('view', {
|
|
132
140
|
get: () => currentDialog,
|
|
133
|
-
// meta: createViewMeta(ctx),
|
|
134
141
|
resolveOnServer: createViewRecordResolveOnServer(ctx, () => getViewRecordFromParent(flowContext, ctx)),
|
|
135
142
|
});
|
|
136
143
|
// 顶层 popup 变量:弹窗记录/数据源/上级弹窗链(去重封装)
|
package/src/views/useDrawer.tsx
CHANGED
|
@@ -15,6 +15,7 @@ import { FlowViewContextProvider } from '../FlowContextProvider';
|
|
|
15
15
|
import { registerPopupVariable } from './createViewMeta';
|
|
16
16
|
import DrawerComponent from './DrawerComponent';
|
|
17
17
|
import usePatchElement from './usePatchElement';
|
|
18
|
+
import { VIEW_ACTIVATED_EVENT, bumpViewActivatedVersion, resolveOpenerEngine } from './viewEvents';
|
|
18
19
|
import { FlowEngineProvider } from '../provider';
|
|
19
20
|
import { createViewScopedEngine } from '../ViewScopedFlowEngine';
|
|
20
21
|
import { createViewRecordResolveOnServer, getViewRecordFromParent } from '../utils/variablesParams';
|
|
@@ -54,6 +55,7 @@ export function useDrawer() {
|
|
|
54
55
|
RenderNestedDrawer.displayName = 'RenderNestedDrawer';
|
|
55
56
|
|
|
56
57
|
const open = (config, flowContext: FlowEngineContext) => {
|
|
58
|
+
const parentEngine = flowContext.engine;
|
|
57
59
|
const drawerRef = React.createRef<{
|
|
58
60
|
destroy: () => void;
|
|
59
61
|
update: (config: any) => void;
|
|
@@ -105,6 +107,8 @@ export function useDrawer() {
|
|
|
105
107
|
const ctx = new FlowContext();
|
|
106
108
|
// 为当前视图创建作用域引擎(隔离实例与缓存)
|
|
107
109
|
const scopedEngine = createViewScopedEngine(flowContext.engine);
|
|
110
|
+
const openerEngine = resolveOpenerEngine(parentEngine, scopedEngine);
|
|
111
|
+
|
|
108
112
|
// 先将引擎暴露给视图上下文,再按需继承父上下文
|
|
109
113
|
ctx.defineProperty('engine', { value: scopedEngine });
|
|
110
114
|
ctx.addDelegate(scopedEngine.context);
|
|
@@ -124,6 +128,10 @@ export function useDrawer() {
|
|
|
124
128
|
drawerRef.current?.destroy();
|
|
125
129
|
closeFunc?.();
|
|
126
130
|
resolvePromise?.(result);
|
|
131
|
+
// Notify opener view that it becomes active again.
|
|
132
|
+
const openerEmitter = openerEngine?.emitter;
|
|
133
|
+
bumpViewActivatedVersion(openerEmitter);
|
|
134
|
+
openerEmitter?.emit?.(VIEW_ACTIVATED_EVENT, { type: 'drawer', viewUid: currentDrawer?.inputArgs?.viewUid });
|
|
127
135
|
// 关闭时修正 previous/next 指针
|
|
128
136
|
scopedEngine.unlinkFromStack();
|
|
129
137
|
},
|
|
@@ -159,7 +167,6 @@ export function useDrawer() {
|
|
|
159
167
|
|
|
160
168
|
ctx.defineProperty('view', {
|
|
161
169
|
get: () => currentDrawer,
|
|
162
|
-
// meta: createViewMeta(ctx),
|
|
163
170
|
resolveOnServer: createViewRecordResolveOnServer(ctx, () => getViewRecordFromParent(flowContext, ctx)),
|
|
164
171
|
});
|
|
165
172
|
// 顶层 popup 变量:弹窗记录/数据源/上级弹窗链(去重封装)
|
package/src/views/usePage.tsx
CHANGED
|
@@ -15,26 +15,33 @@ import { FlowViewContextProvider } from '../FlowContextProvider';
|
|
|
15
15
|
import { registerPopupVariable } from './createViewMeta';
|
|
16
16
|
import { PageComponent } from './PageComponent';
|
|
17
17
|
import usePatchElement from './usePatchElement';
|
|
18
|
+
import { VIEW_ACTIVATED_EVENT, bumpViewActivatedVersion, resolveOpenerEngine } from './viewEvents';
|
|
18
19
|
import { FlowEngineProvider } from '../provider';
|
|
19
20
|
import { createViewScopedEngine } from '../ViewScopedFlowEngine';
|
|
20
21
|
import { createViewRecordResolveOnServer, getViewRecordFromParent } from '../utils/variablesParams';
|
|
21
22
|
|
|
22
23
|
let uuid = 0;
|
|
23
24
|
|
|
25
|
+
/** Global embed container element ID */
|
|
26
|
+
export const GLOBAL_EMBED_CONTAINER_ID = 'nocobase-embed-container';
|
|
27
|
+
/** Dataset key used to signal embed replacement in progress (skip style reset on close) */
|
|
28
|
+
export const EMBED_REPLACING_DATA_KEY = 'nocobaseEmbedReplacing';
|
|
29
|
+
|
|
24
30
|
// 稳定的 Holder 组件,避免在父组件重渲染时更换组件类型导致卸载, 否则切换主题时会丢失所有页面内容
|
|
25
31
|
const PageElementsHolder = React.memo(
|
|
26
32
|
React.forwardRef((props: any, ref: any) => {
|
|
27
33
|
const [elements, patchElement] = usePatchElement();
|
|
28
34
|
React.useImperativeHandle(ref, () => ({ patchElement }), [patchElement]);
|
|
29
|
-
console.log('[NocoBase] Rendering PageElementsHolder with elements count:', elements.length);
|
|
30
35
|
return <>{elements}</>;
|
|
31
36
|
}),
|
|
32
37
|
);
|
|
33
38
|
|
|
34
39
|
export function usePage() {
|
|
35
40
|
const holderRef = React.useRef(null);
|
|
41
|
+
const globalEmbedActiveRef = React.useRef<null | { destroy: () => void }>(null);
|
|
36
42
|
|
|
37
43
|
const open = (config, flowContext) => {
|
|
44
|
+
const parentEngine = flowContext?.engine;
|
|
38
45
|
uuid += 1;
|
|
39
46
|
const pageRef = React.createRef<{
|
|
40
47
|
destroy: () => void;
|
|
@@ -75,11 +82,33 @@ export function usePage() {
|
|
|
75
82
|
return null; // Header 组件本身不渲染内容
|
|
76
83
|
};
|
|
77
84
|
|
|
78
|
-
const {
|
|
85
|
+
const {
|
|
86
|
+
target,
|
|
87
|
+
content,
|
|
88
|
+
preventClose,
|
|
89
|
+
inheritContext = true,
|
|
90
|
+
inputArgs: viewInputArgs = {},
|
|
91
|
+
...restConfig
|
|
92
|
+
} = config;
|
|
93
|
+
const isGlobalEmbedContainer = target instanceof HTMLElement && target.id === GLOBAL_EMBED_CONTAINER_ID;
|
|
94
|
+
|
|
95
|
+
// Global embed container uses "replace" behavior: opening a new view destroys the previous one.
|
|
96
|
+
if (isGlobalEmbedContainer && globalEmbedActiveRef.current) {
|
|
97
|
+
try {
|
|
98
|
+
// Avoid style "reset flicker" when replacing: tell embed wrappers to skip resetting container styles.
|
|
99
|
+
target.dataset[EMBED_REPLACING_DATA_KEY] = '1';
|
|
100
|
+
globalEmbedActiveRef.current.destroy();
|
|
101
|
+
} finally {
|
|
102
|
+
delete target.dataset[EMBED_REPLACING_DATA_KEY];
|
|
103
|
+
globalEmbedActiveRef.current = null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
79
106
|
|
|
80
107
|
const ctx = new FlowContext();
|
|
81
108
|
// 为当前视图创建作用域引擎(隔离实例与缓存)
|
|
82
109
|
const scopedEngine = createViewScopedEngine(flowContext.engine);
|
|
110
|
+
const openerEngine = resolveOpenerEngine(parentEngine, scopedEngine);
|
|
111
|
+
|
|
83
112
|
ctx.defineProperty('engine', { value: scopedEngine });
|
|
84
113
|
ctx.addDelegate(scopedEngine.context);
|
|
85
114
|
if (inheritContext) {
|
|
@@ -91,13 +120,27 @@ export function usePage() {
|
|
|
91
120
|
// 构造 currentPage 实例
|
|
92
121
|
const currentPage = {
|
|
93
122
|
type: 'embed' as const,
|
|
94
|
-
inputArgs:
|
|
123
|
+
inputArgs: viewInputArgs,
|
|
95
124
|
preventClose: !!config.preventClose,
|
|
96
125
|
destroy: (result?: any) => {
|
|
97
126
|
config.onClose?.();
|
|
98
127
|
resolvePromise?.(result);
|
|
99
128
|
pageRef.current?.destroy();
|
|
100
129
|
closeFunc?.();
|
|
130
|
+
|
|
131
|
+
if (isGlobalEmbedContainer) {
|
|
132
|
+
globalEmbedActiveRef.current = null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Notify opener view that it becomes active again.
|
|
136
|
+
const isReplacing =
|
|
137
|
+
isGlobalEmbedContainer && target instanceof HTMLElement && target.dataset?.[EMBED_REPLACING_DATA_KEY] === '1';
|
|
138
|
+
if (!isReplacing) {
|
|
139
|
+
const openerEmitter = openerEngine?.emitter;
|
|
140
|
+
bumpViewActivatedVersion(openerEmitter);
|
|
141
|
+
openerEmitter?.emit?.(VIEW_ACTIVATED_EVENT, { type: 'embed', viewUid: currentPage?.inputArgs?.viewUid });
|
|
142
|
+
}
|
|
143
|
+
|
|
101
144
|
// 关闭时修正 previous/next 指针
|
|
102
145
|
scopedEngine.unlinkFromStack();
|
|
103
146
|
},
|
|
@@ -132,7 +175,6 @@ export function usePage() {
|
|
|
132
175
|
|
|
133
176
|
ctx.defineProperty('view', {
|
|
134
177
|
get: () => currentPage,
|
|
135
|
-
// meta: createViewMeta(ctx),
|
|
136
178
|
// 仅当访问关联字段或前端无本地记录数据时,才交给服务端解析
|
|
137
179
|
resolveOnServer: createViewRecordResolveOnServer(ctx, () => getViewRecordFromParent(flowContext, ctx)),
|
|
138
180
|
});
|
|
@@ -180,7 +222,7 @@ export function usePage() {
|
|
|
180
222
|
},
|
|
181
223
|
);
|
|
182
224
|
|
|
183
|
-
const key =
|
|
225
|
+
const key = viewInputArgs?.viewUid || `page-${uuid}`;
|
|
184
226
|
const page = (
|
|
185
227
|
<FlowEngineProvider key={key} engine={scopedEngine}>
|
|
186
228
|
<FlowViewContextProvider context={ctx}>
|
|
@@ -195,6 +237,10 @@ export function usePage() {
|
|
|
195
237
|
closeFunc = holderRef.current?.patchElement(page);
|
|
196
238
|
}
|
|
197
239
|
|
|
240
|
+
if (isGlobalEmbedContainer) {
|
|
241
|
+
globalEmbedActiveRef.current = { destroy: currentPage.destroy };
|
|
242
|
+
}
|
|
243
|
+
|
|
198
244
|
return Object.assign(promise, currentPage);
|
|
199
245
|
};
|
|
200
246
|
|
package/src/views/usePopover.tsx
CHANGED
|
@@ -40,8 +40,11 @@ const PopoverComponent = React.forwardRef<any, any>(({ afterClose, content, plac
|
|
|
40
40
|
destroyTooltipOnHide
|
|
41
41
|
content={config.content}
|
|
42
42
|
placement={config.placement}
|
|
43
|
-
getPopupContainer={() => document.body}
|
|
43
|
+
getPopupContainer={() => document.querySelector('#nocobase-app-container') || document.body}
|
|
44
44
|
onOpenChange={(nextOpen) => {
|
|
45
|
+
if (!nextOpen && config.preventClose) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
45
48
|
setVisible(nextOpen);
|
|
46
49
|
if (!nextOpen) {
|
|
47
50
|
afterClose?.();
|
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
|
|
10
|
+
import { FlowEngine } from '../flowEngine';
|
|
11
|
+
|
|
12
|
+
export const VIEW_ACTIVATED_VERSION = Symbol.for('__NOCOBASE_VIEW_ACTIVATED_VERSION__');
|
|
13
|
+
|
|
14
|
+
export const VIEW_ACTIVATED_EVENT = 'view:activated' as const;
|
|
15
|
+
export const DATA_SOURCE_DIRTY_EVENT = 'dataSource:dirty' as const;
|
|
16
|
+
|
|
17
|
+
export const ENGINE_SCOPE_KEY = '__NOCOBASE_ENGINE_SCOPE__' as const;
|
|
18
|
+
export const VIEW_ENGINE_SCOPE = 'view' as const;
|
|
19
|
+
|
|
20
|
+
export function getEmitterViewActivatedVersion(emitter): number {
|
|
21
|
+
const raw = Reflect.get(emitter, VIEW_ACTIVATED_VERSION);
|
|
22
|
+
const num = typeof raw === 'number' ? raw : Number(raw);
|
|
23
|
+
return Number.isFinite(num) && num > 0 ? num : 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function bumpViewActivatedVersion(emitter): number {
|
|
27
|
+
const current = getEmitterViewActivatedVersion(emitter);
|
|
28
|
+
if (!Object.isExtensible(emitter)) return current;
|
|
29
|
+
const next = current + 1;
|
|
30
|
+
Reflect.set(emitter, VIEW_ACTIVATED_VERSION, next);
|
|
31
|
+
return next;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isViewEngine(engine: FlowEngine): boolean {
|
|
35
|
+
return Reflect.get(engine, ENGINE_SCOPE_KEY) === VIEW_ENGINE_SCOPE;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function findNearestViewEngine(engine: FlowEngine | undefined): FlowEngine | undefined {
|
|
39
|
+
let cur: FlowEngine | undefined = engine;
|
|
40
|
+
let guard = 0;
|
|
41
|
+
while (cur && guard++ < 50) {
|
|
42
|
+
if (isViewEngine(cur)) return cur;
|
|
43
|
+
cur = (cur as { previousEngine?: FlowEngine | undefined }).previousEngine;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function resolveOpenerEngine(parentEngine: FlowEngine, scopedEngine: FlowEngine): FlowEngine | undefined {
|
|
48
|
+
if (!parentEngine) return undefined;
|
|
49
|
+
const parentViewEngine = findNearestViewEngine(parentEngine);
|
|
50
|
+
if (parentViewEngine) return parentViewEngine;
|
|
51
|
+
|
|
52
|
+
// Fallback: resolve from previous engine in the stack (historical behavior).
|
|
53
|
+
const previousEngine = scopedEngine?.previousEngine;
|
|
54
|
+
return findNearestViewEngine(previousEngine) || parentEngine;
|
|
55
|
+
}
|