@nocobase/flow-engine 2.0.17 → 2.0.19
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/executor/FlowExecutor.js +6 -0
- package/lib/types.d.ts +1 -1
- package/lib/utils/index.d.ts +2 -2
- package/lib/utils/index.js +4 -0
- package/lib/utils/schema-utils.d.ts +7 -1
- package/lib/utils/schema-utils.js +19 -0
- package/lib/views/FlowView.d.ts +7 -1
- package/lib/views/runViewBeforeClose.d.ts +10 -0
- package/lib/views/runViewBeforeClose.js +45 -0
- package/lib/views/useDialog.d.ts +2 -1
- package/lib/views/useDialog.js +10 -3
- package/lib/views/useDrawer.d.ts +2 -1
- package/lib/views/useDrawer.js +10 -3
- package/lib/views/usePage.d.ts +2 -1
- package/lib/views/usePage.js +10 -3
- package/package.json +4 -4
- package/src/executor/FlowExecutor.ts +6 -0
- package/src/executor/__tests__/flowExecutor.test.ts +31 -0
- package/src/types.ts +1 -0
- package/src/utils/__tests__/utils.test.ts +62 -0
- package/src/utils/index.ts +2 -1
- package/src/utils/schema-utils.ts +30 -1
- package/src/views/FlowView.tsx +11 -1
- package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +12 -12
- package/src/views/runViewBeforeClose.ts +19 -0
- package/src/views/useDialog.tsx +11 -3
- package/src/views/useDrawer.tsx +11 -3
- package/src/views/usePage.tsx +11 -3
|
@@ -459,6 +459,12 @@ const _FlowExecutor = class _FlowExecutor {
|
|
|
459
459
|
result,
|
|
460
460
|
...abortedByExitAll ? { aborted: true } : {}
|
|
461
461
|
});
|
|
462
|
+
if (result && typeof result === "object") {
|
|
463
|
+
Object.defineProperty(result, "__abortedByExitAll", {
|
|
464
|
+
value: abortedByExitAll,
|
|
465
|
+
configurable: true
|
|
466
|
+
});
|
|
467
|
+
}
|
|
462
468
|
return result;
|
|
463
469
|
} catch (error) {
|
|
464
470
|
try {
|
package/lib/types.d.ts
CHANGED
|
@@ -177,7 +177,7 @@ export interface ActionDefinition<TModel extends FlowModel = FlowModel, TCtx ext
|
|
|
177
177
|
* - 收录内置常用事件,便于智能提示;
|
|
178
178
|
* - 允许扩展字符串以保持向后兼容。
|
|
179
179
|
*/
|
|
180
|
-
export type FlowEventName = 'click' | 'submit' | 'reset' | 'remove' | 'openView' | 'dropdownOpen' | 'popupScroll' | 'search' | 'customRequest' | 'collapseToggle' | (string & {});
|
|
180
|
+
export type FlowEventName = 'click' | 'close' | 'submit' | 'reset' | 'remove' | 'openView' | 'dropdownOpen' | 'popupScroll' | 'search' | 'customRequest' | 'collapseToggle' | (string & {});
|
|
181
181
|
/**
|
|
182
182
|
* 事件流的执行时机(phase)。
|
|
183
183
|
*
|
package/lib/utils/index.d.ts
CHANGED
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export { BLOCK_GROUP_CONFIGS, BLOCK_TYPES, FLOW_ENGINE_NAMESPACE, MENU_KEYS, type BlockBuilderConfig, } from './constants';
|
|
10
10
|
export { escapeT, getT, tExpr } from './translation';
|
|
11
|
-
export { FlowCancelSaveException, FlowExitException } from './exceptions';
|
|
11
|
+
export { FlowCancelSaveException, FlowExitAllException, FlowExitException } from './exceptions';
|
|
12
12
|
export { defineAction } from './flow-definitions';
|
|
13
13
|
export { isInheritedFrom } from './inheritance';
|
|
14
14
|
export { resolveCreateModelOptions, resolveDefaultParams, resolveExpressions } from './params-resolvers';
|
|
15
|
-
export { compileUiSchema, resolveStepUiSchema, resolveStepDisabledInSettings, resolveUiMode, shouldHideStepInSettings, } from './schema-utils';
|
|
15
|
+
export { compileUiSchema, resolveStepUiSchema, resolveStepDisabledInSettings, resolveUiMode, shouldHideEventInSettings, shouldHideStepInSettings, } from './schema-utils';
|
|
16
16
|
export { setupRuntimeContextSteps } from './setupRuntimeContextSteps';
|
|
17
17
|
export { createCollectionContextMeta } from './createCollectionContextMeta';
|
|
18
18
|
export { createAssociationAwareObjectMetaFactory, createAssociationSubpathResolver } from './associationObjectVariable';
|
package/lib/utils/index.js
CHANGED
|
@@ -30,6 +30,7 @@ __export(utils_exports, {
|
|
|
30
30
|
BLOCK_TYPES: () => import_constants.BLOCK_TYPES,
|
|
31
31
|
FLOW_ENGINE_NAMESPACE: () => import_constants.FLOW_ENGINE_NAMESPACE,
|
|
32
32
|
FlowCancelSaveException: () => import_exceptions.FlowCancelSaveException,
|
|
33
|
+
FlowExitAllException: () => import_exceptions.FlowExitAllException,
|
|
33
34
|
FlowExitException: () => import_exceptions.FlowExitException,
|
|
34
35
|
MENU_KEYS: () => import_constants.MENU_KEYS,
|
|
35
36
|
buildRecordMeta: () => import_variablesParams.buildRecordMeta,
|
|
@@ -86,6 +87,7 @@ __export(utils_exports, {
|
|
|
86
87
|
serializeCtxDateValue: () => import_dateVariable.serializeCtxDateValue,
|
|
87
88
|
setAutoFlowError: () => import_autoFlowError.setAutoFlowError,
|
|
88
89
|
setupRuntimeContextSteps: () => import_setupRuntimeContextSteps.setupRuntimeContextSteps,
|
|
90
|
+
shouldHideEventInSettings: () => import_schema_utils.shouldHideEventInSettings,
|
|
89
91
|
shouldHideStepInSettings: () => import_schema_utils.shouldHideStepInSettings,
|
|
90
92
|
tExpr: () => import_translation.tExpr
|
|
91
93
|
});
|
|
@@ -119,6 +121,7 @@ var import_resolveModuleUrl = require("./resolveModuleUrl");
|
|
|
119
121
|
BLOCK_TYPES,
|
|
120
122
|
FLOW_ENGINE_NAMESPACE,
|
|
121
123
|
FlowCancelSaveException,
|
|
124
|
+
FlowExitAllException,
|
|
122
125
|
FlowExitException,
|
|
123
126
|
MENU_KEYS,
|
|
124
127
|
buildRecordMeta,
|
|
@@ -175,6 +178,7 @@ var import_resolveModuleUrl = require("./resolveModuleUrl");
|
|
|
175
178
|
serializeCtxDateValue,
|
|
176
179
|
setAutoFlowError,
|
|
177
180
|
setupRuntimeContextSteps,
|
|
181
|
+
shouldHideEventInSettings,
|
|
178
182
|
shouldHideStepInSettings,
|
|
179
183
|
tExpr
|
|
180
184
|
});
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import type { ISchema } from '@formily/json-schema';
|
|
10
10
|
import type { FlowModel } from '../models';
|
|
11
11
|
import { FlowRuntimeContext } from '../flowContext';
|
|
12
|
-
import type { StepDefinition, StepUIMode } from '../types';
|
|
12
|
+
import type { EventDefinition, StepDefinition, StepUIMode } from '../types';
|
|
13
13
|
/**
|
|
14
14
|
* 解析 uiMode,支持静态值和函数形式
|
|
15
15
|
* 函数可以接收 FlowRuntimeContext
|
|
@@ -38,6 +38,12 @@ export declare function compileUiSchema(scope: Record<string, any>, uiSchema: an
|
|
|
38
38
|
* @returns 合并后的uiSchema对象,如果为空则返回null
|
|
39
39
|
*/
|
|
40
40
|
export declare function resolveStepUiSchema<TModel extends FlowModel = FlowModel>(model: TModel, flow: any, step: StepDefinition): Promise<Record<string, ISchema> | null>;
|
|
41
|
+
/**
|
|
42
|
+
* 判断事件在设置菜单中是否应被隐藏。
|
|
43
|
+
* - 支持 EventDefinition.hideInSettings。
|
|
44
|
+
* - hideInSettings 可为布尔值或函数(接收 FlowRuntimeContext)。
|
|
45
|
+
*/
|
|
46
|
+
export declare function shouldHideEventInSettings<TModel extends FlowModel = FlowModel>(model: TModel, flow: any, event: EventDefinition<TModel> | undefined): Promise<boolean>;
|
|
41
47
|
/**
|
|
42
48
|
* 判断步骤在设置菜单中是否应被隐藏。
|
|
43
49
|
* - 支持 StepDefinition.hideInSettings 与 ActionDefinition.hideInSettings(step 优先)。
|
|
@@ -31,6 +31,7 @@ __export(schema_utils_exports, {
|
|
|
31
31
|
resolveStepDisabledInSettings: () => resolveStepDisabledInSettings,
|
|
32
32
|
resolveStepUiSchema: () => resolveStepUiSchema,
|
|
33
33
|
resolveUiMode: () => resolveUiMode,
|
|
34
|
+
shouldHideEventInSettings: () => shouldHideEventInSettings,
|
|
34
35
|
shouldHideStepInSettings: () => shouldHideStepInSettings
|
|
35
36
|
});
|
|
36
37
|
module.exports = __toCommonJS(schema_utils_exports);
|
|
@@ -195,6 +196,23 @@ async function resolveStepUiSchema(model, flow, step) {
|
|
|
195
196
|
return resolvedStepUiSchema;
|
|
196
197
|
}
|
|
197
198
|
__name(resolveStepUiSchema, "resolveStepUiSchema");
|
|
199
|
+
async function shouldHideEventInSettings(model, flow, event) {
|
|
200
|
+
if (!event) return true;
|
|
201
|
+
const { hideInSettings } = event;
|
|
202
|
+
if (typeof hideInSettings === "function") {
|
|
203
|
+
try {
|
|
204
|
+
const ctx = new import_flowContext.FlowRuntimeContext(model, flow.key, "settings");
|
|
205
|
+
(0, import_setupRuntimeContextSteps.setupRuntimeContextSteps)(ctx, flow.steps || {}, model, flow.key);
|
|
206
|
+
const result = await hideInSettings(ctx);
|
|
207
|
+
return !!result;
|
|
208
|
+
} catch (error) {
|
|
209
|
+
console.warn(`Error evaluating hideInSettings for event '${event.name || ""}' in flow '${flow.key}':`, error);
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return !!hideInSettings;
|
|
214
|
+
}
|
|
215
|
+
__name(shouldHideEventInSettings, "shouldHideEventInSettings");
|
|
198
216
|
async function shouldHideStepInSettings(model, flow, step) {
|
|
199
217
|
var _a;
|
|
200
218
|
if (!step) return true;
|
|
@@ -283,5 +301,6 @@ __name(resolveStepDisabledInSettings, "resolveStepDisabledInSettings");
|
|
|
283
301
|
resolveStepDisabledInSettings,
|
|
284
302
|
resolveStepUiSchema,
|
|
285
303
|
resolveUiMode,
|
|
304
|
+
shouldHideEventInSettings,
|
|
286
305
|
shouldHideStepInSettings
|
|
287
306
|
});
|
package/lib/views/FlowView.d.ts
CHANGED
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
import { PopoverProps as AntdPopoverProps } from 'antd';
|
|
11
11
|
import { FlowContext } from '../flowContext';
|
|
12
12
|
import { ViewNavigation } from './ViewNavigation';
|
|
13
|
+
export type FlowViewBeforeClosePayload = {
|
|
14
|
+
result?: any;
|
|
15
|
+
force?: boolean;
|
|
16
|
+
};
|
|
17
|
+
export type FlowViewBeforeCloseHandler = (payload: FlowViewBeforeClosePayload) => Promise<boolean | void> | boolean | void;
|
|
13
18
|
export type FlowView = {
|
|
14
19
|
type: 'drawer' | 'popover' | 'dialog' | 'embed';
|
|
15
20
|
inputArgs: any;
|
|
@@ -20,8 +25,9 @@ export type FlowView = {
|
|
|
20
25
|
Footer: React.FC<{
|
|
21
26
|
children?: React.ReactNode;
|
|
22
27
|
}> | null;
|
|
23
|
-
close: (result?: any, force?: boolean) => void;
|
|
28
|
+
close: (result?: any, force?: boolean) => Promise<boolean | void> | boolean | void;
|
|
24
29
|
update: (newConfig: any) => void;
|
|
30
|
+
beforeClose?: FlowViewBeforeCloseHandler;
|
|
25
31
|
navigation?: ViewNavigation;
|
|
26
32
|
/** 页面的销毁方法 */
|
|
27
33
|
destroy?: () => void;
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
import type { FlowView, FlowViewBeforeClosePayload } from './FlowView';
|
|
10
|
+
export declare function runViewBeforeClose(view: FlowView, payload: FlowViewBeforeClosePayload): Promise<boolean>;
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
15
|
+
var __export = (target, all) => {
|
|
16
|
+
for (var name in all)
|
|
17
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
18
|
+
};
|
|
19
|
+
var __copyProps = (to, from, except, desc) => {
|
|
20
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
21
|
+
for (let key of __getOwnPropNames(from))
|
|
22
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
23
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
24
|
+
}
|
|
25
|
+
return to;
|
|
26
|
+
};
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
var runViewBeforeClose_exports = {};
|
|
29
|
+
__export(runViewBeforeClose_exports, {
|
|
30
|
+
runViewBeforeClose: () => runViewBeforeClose
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(runViewBeforeClose_exports);
|
|
33
|
+
async function runViewBeforeClose(view, payload) {
|
|
34
|
+
var _a;
|
|
35
|
+
if (payload.force) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
const result = await ((_a = view.beforeClose) == null ? void 0 : _a.call(view, payload));
|
|
39
|
+
return result !== false;
|
|
40
|
+
}
|
|
41
|
+
__name(runViewBeforeClose, "runViewBeforeClose");
|
|
42
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
43
|
+
0 && (module.exports = {
|
|
44
|
+
runViewBeforeClose
|
|
45
|
+
});
|
package/lib/views/useDialog.d.ts
CHANGED
|
@@ -12,9 +12,10 @@ export declare function useDialog(): (React.JSX.Element | {
|
|
|
12
12
|
type: "dialog";
|
|
13
13
|
inputArgs: any;
|
|
14
14
|
preventClose: boolean;
|
|
15
|
+
beforeClose: any;
|
|
15
16
|
destroy: (result?: any) => void;
|
|
16
17
|
update: (newConfig: any) => void;
|
|
17
|
-
close: (result?: any, force?: boolean) =>
|
|
18
|
+
close: (result?: any, force?: boolean) => Promise<boolean>;
|
|
18
19
|
Footer: React.FC<{
|
|
19
20
|
children?: React.ReactNode;
|
|
20
21
|
}>;
|
package/lib/views/useDialog.js
CHANGED
|
@@ -52,6 +52,7 @@ var import_viewEvents = require("./viewEvents");
|
|
|
52
52
|
var import_provider = require("../provider");
|
|
53
53
|
var import_ViewScopedFlowEngine = require("../ViewScopedFlowEngine");
|
|
54
54
|
var import_variablesParams = require("../utils/variablesParams");
|
|
55
|
+
var import_runViewBeforeClose = require("./runViewBeforeClose");
|
|
55
56
|
let uuid = 0;
|
|
56
57
|
function useDialog() {
|
|
57
58
|
const holderRef = React.useRef(null);
|
|
@@ -108,6 +109,7 @@ function useDialog() {
|
|
|
108
109
|
type: "dialog",
|
|
109
110
|
inputArgs: config.inputArgs || {},
|
|
110
111
|
preventClose: !!config.preventClose,
|
|
112
|
+
beforeClose: void 0,
|
|
111
113
|
destroy: /* @__PURE__ */ __name((result) => {
|
|
112
114
|
var _a2, _b2, _c2, _d;
|
|
113
115
|
if (destroyed) return;
|
|
@@ -125,16 +127,21 @@ function useDialog() {
|
|
|
125
127
|
var _a2;
|
|
126
128
|
return (_a2 = dialogRef.current) == null ? void 0 : _a2.update(newConfig);
|
|
127
129
|
}, "update"),
|
|
128
|
-
close: /* @__PURE__ */ __name((result, force) => {
|
|
130
|
+
close: /* @__PURE__ */ __name(async (result, force) => {
|
|
129
131
|
var _a2, _b2;
|
|
130
132
|
if (config.preventClose && !force) {
|
|
131
|
-
return;
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
const shouldClose = await (0, import_runViewBeforeClose.runViewBeforeClose)(currentDialog, { result, force });
|
|
136
|
+
if (!shouldClose) {
|
|
137
|
+
return false;
|
|
132
138
|
}
|
|
133
139
|
if (config.triggerByRouter && ((_b2 = (_a2 = config.inputArgs) == null ? void 0 : _a2.navigation) == null ? void 0 : _b2.back)) {
|
|
134
140
|
config.inputArgs.navigation.back();
|
|
135
|
-
return;
|
|
141
|
+
return true;
|
|
136
142
|
}
|
|
137
143
|
currentDialog.destroy(result);
|
|
144
|
+
return true;
|
|
138
145
|
}, "close"),
|
|
139
146
|
Footer: FooterComponent,
|
|
140
147
|
Header: HeaderComponent,
|
package/lib/views/useDrawer.d.ts
CHANGED
|
@@ -13,9 +13,10 @@ export declare function useDrawer(): (React.JSX.Element | {
|
|
|
13
13
|
type: "drawer";
|
|
14
14
|
inputArgs: any;
|
|
15
15
|
preventClose: boolean;
|
|
16
|
+
beforeClose: any;
|
|
16
17
|
destroy: (result?: any) => void;
|
|
17
18
|
update: (newConfig: any) => void;
|
|
18
|
-
close: (result?: any, force?: boolean) =>
|
|
19
|
+
close: (result?: any, force?: boolean) => Promise<boolean>;
|
|
19
20
|
Footer: React.FC<{
|
|
20
21
|
children?: React.ReactNode;
|
|
21
22
|
}>;
|
package/lib/views/useDrawer.js
CHANGED
|
@@ -52,6 +52,7 @@ var import_viewEvents = require("./viewEvents");
|
|
|
52
52
|
var import_provider = require("../provider");
|
|
53
53
|
var import_ViewScopedFlowEngine = require("../ViewScopedFlowEngine");
|
|
54
54
|
var import_variablesParams = require("../utils/variablesParams");
|
|
55
|
+
var import_runViewBeforeClose = require("./runViewBeforeClose");
|
|
55
56
|
function useDrawer() {
|
|
56
57
|
const holderRef = React.useRef(null);
|
|
57
58
|
const drawerList = React.useMemo(() => import__.observable.shallow({ value: [] }), []);
|
|
@@ -127,6 +128,7 @@ function useDrawer() {
|
|
|
127
128
|
type: "drawer",
|
|
128
129
|
inputArgs: config.inputArgs || {},
|
|
129
130
|
preventClose: !!config.preventClose,
|
|
131
|
+
beforeClose: void 0,
|
|
130
132
|
destroy: /* @__PURE__ */ __name((result) => {
|
|
131
133
|
var _a2, _b2, _c, _d;
|
|
132
134
|
if (destroyed) return;
|
|
@@ -144,16 +146,21 @@ function useDrawer() {
|
|
|
144
146
|
var _a2;
|
|
145
147
|
return (_a2 = drawerRef.current) == null ? void 0 : _a2.update(newConfig);
|
|
146
148
|
}, "update"),
|
|
147
|
-
close: /* @__PURE__ */ __name((result, force) => {
|
|
149
|
+
close: /* @__PURE__ */ __name(async (result, force) => {
|
|
148
150
|
var _a2, _b2;
|
|
149
151
|
if (config.preventClose && !force) {
|
|
150
|
-
return;
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
const shouldClose = await (0, import_runViewBeforeClose.runViewBeforeClose)(currentDrawer, { result, force });
|
|
155
|
+
if (!shouldClose) {
|
|
156
|
+
return false;
|
|
151
157
|
}
|
|
152
158
|
if (config.triggerByRouter && ((_b2 = (_a2 = config.inputArgs) == null ? void 0 : _a2.navigation) == null ? void 0 : _b2.back)) {
|
|
153
159
|
config.inputArgs.navigation.back();
|
|
154
|
-
return;
|
|
160
|
+
return true;
|
|
155
161
|
}
|
|
156
162
|
currentDrawer.destroy(result);
|
|
163
|
+
return true;
|
|
157
164
|
}, "close"),
|
|
158
165
|
Footer: FooterComponent,
|
|
159
166
|
Header: HeaderComponent,
|
package/lib/views/usePage.d.ts
CHANGED
|
@@ -16,9 +16,10 @@ export declare function usePage(): (React.JSX.Element | {
|
|
|
16
16
|
type: "embed";
|
|
17
17
|
inputArgs: any;
|
|
18
18
|
preventClose: boolean;
|
|
19
|
+
beforeClose: any;
|
|
19
20
|
destroy: (result?: any) => void;
|
|
20
21
|
update: (newConfig: any) => void;
|
|
21
|
-
close: (result?: any, force?: boolean) =>
|
|
22
|
+
close: (result?: any, force?: boolean) => Promise<boolean>;
|
|
22
23
|
Header: React.FC<{
|
|
23
24
|
title?: React.ReactNode;
|
|
24
25
|
extra?: React.ReactNode;
|
package/lib/views/usePage.js
CHANGED
|
@@ -54,6 +54,7 @@ var import_viewEvents = require("./viewEvents");
|
|
|
54
54
|
var import_provider = require("../provider");
|
|
55
55
|
var import_ViewScopedFlowEngine = require("../ViewScopedFlowEngine");
|
|
56
56
|
var import_variablesParams = require("../utils/variablesParams");
|
|
57
|
+
var import_runViewBeforeClose = require("./runViewBeforeClose");
|
|
57
58
|
let uuid = 0;
|
|
58
59
|
const GLOBAL_EMBED_CONTAINER_ID = "nocobase-embed-container";
|
|
59
60
|
const EMBED_REPLACING_DATA_KEY = "nocobaseEmbedReplacing";
|
|
@@ -131,6 +132,7 @@ function usePage() {
|
|
|
131
132
|
type: "embed",
|
|
132
133
|
inputArgs: viewInputArgs,
|
|
133
134
|
preventClose: !!config.preventClose,
|
|
135
|
+
beforeClose: void 0,
|
|
134
136
|
destroy: /* @__PURE__ */ __name((result) => {
|
|
135
137
|
var _a2, _b2, _c2, _d, _e;
|
|
136
138
|
(_a2 = config.onClose) == null ? void 0 : _a2.call(config);
|
|
@@ -152,16 +154,21 @@ function usePage() {
|
|
|
152
154
|
var _a2;
|
|
153
155
|
return (_a2 = pageRef.current) == null ? void 0 : _a2.update(newConfig);
|
|
154
156
|
}, "update"),
|
|
155
|
-
close: /* @__PURE__ */ __name((result, force) => {
|
|
157
|
+
close: /* @__PURE__ */ __name(async (result, force) => {
|
|
156
158
|
var _a2, _b2;
|
|
157
159
|
if (preventClose && !force) {
|
|
158
|
-
return;
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
const shouldClose = await (0, import_runViewBeforeClose.runViewBeforeClose)(currentPage, { result, force });
|
|
163
|
+
if (!shouldClose) {
|
|
164
|
+
return false;
|
|
159
165
|
}
|
|
160
166
|
if (config.triggerByRouter && ((_b2 = (_a2 = config.inputArgs) == null ? void 0 : _a2.navigation) == null ? void 0 : _b2.back)) {
|
|
161
167
|
config.inputArgs.navigation.back();
|
|
162
|
-
return;
|
|
168
|
+
return true;
|
|
163
169
|
}
|
|
164
170
|
currentPage.destroy(result);
|
|
171
|
+
return true;
|
|
165
172
|
}, "close"),
|
|
166
173
|
Header: HeaderComponent,
|
|
167
174
|
Footer: FooterComponent,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/flow-engine",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.19",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A standalone flow engine for NocoBase, managing workflows, models, and actions.",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@formily/antd-v5": "1.x",
|
|
10
10
|
"@formily/reactive": "2.x",
|
|
11
|
-
"@nocobase/sdk": "2.0.
|
|
12
|
-
"@nocobase/shared": "2.0.
|
|
11
|
+
"@nocobase/sdk": "2.0.19",
|
|
12
|
+
"@nocobase/shared": "2.0.19",
|
|
13
13
|
"ahooks": "^3.7.2",
|
|
14
14
|
"dayjs": "^1.11.9",
|
|
15
15
|
"dompurify": "^3.0.2",
|
|
@@ -36,5 +36,5 @@
|
|
|
36
36
|
],
|
|
37
37
|
"author": "NocoBase Team",
|
|
38
38
|
"license": "Apache-2.0",
|
|
39
|
-
"gitHead": "
|
|
39
|
+
"gitHead": "0b865bb88a155478d45ea0178b14d76d5250f4b8"
|
|
40
40
|
}
|
|
@@ -519,6 +519,12 @@ export class FlowExecutor {
|
|
|
519
519
|
result,
|
|
520
520
|
...(abortedByExitAll ? { aborted: true } : {}),
|
|
521
521
|
});
|
|
522
|
+
if (result && typeof result === 'object') {
|
|
523
|
+
Object.defineProperty(result, '__abortedByExitAll', {
|
|
524
|
+
value: abortedByExitAll,
|
|
525
|
+
configurable: true,
|
|
526
|
+
});
|
|
527
|
+
}
|
|
522
528
|
return result;
|
|
523
529
|
} catch (error) {
|
|
524
530
|
// 进入错误钩子并记录
|
|
@@ -232,6 +232,37 @@ describe('FlowExecutor', () => {
|
|
|
232
232
|
expect(calls.sort()).toEqual(['a', 'b']);
|
|
233
233
|
});
|
|
234
234
|
|
|
235
|
+
it('dispatchEvent sequential exposes abortedByExitAll metadata on result array', async () => {
|
|
236
|
+
const flows = {
|
|
237
|
+
stopClose: {
|
|
238
|
+
on: { eventName: 'close' },
|
|
239
|
+
steps: {
|
|
240
|
+
only: {
|
|
241
|
+
handler: vi.fn().mockImplementation((ctx) => {
|
|
242
|
+
ctx.exit();
|
|
243
|
+
}),
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
afterClose: {
|
|
248
|
+
on: { eventName: 'close', phase: 'afterAllFlows' },
|
|
249
|
+
steps: {
|
|
250
|
+
only: {
|
|
251
|
+
handler: vi.fn(),
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
} satisfies Record<string, Omit<FlowDefinitionOptions, 'key'>>;
|
|
256
|
+
|
|
257
|
+
const model = createModelWithFlows('m-close-meta', flows);
|
|
258
|
+
|
|
259
|
+
const result = await engine.executor.dispatchEvent(model, 'close', {}, { sequential: true });
|
|
260
|
+
|
|
261
|
+
expect(Array.isArray(result)).toBe(true);
|
|
262
|
+
expect((result as any).__abortedByExitAll).toBe(true);
|
|
263
|
+
expect(flows.afterClose.steps.only.handler).not.toHaveBeenCalled();
|
|
264
|
+
});
|
|
265
|
+
|
|
235
266
|
it('dispatchEvent sequential respects sort order and stops on errors', async () => {
|
|
236
267
|
const calls: string[] = [];
|
|
237
268
|
const mkFlow = (key: string, sort: number, opts?: { throw?: boolean }) => ({
|
package/src/types.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
getT,
|
|
13
13
|
isInheritedFrom,
|
|
14
14
|
resolveDefaultParams,
|
|
15
|
+
shouldHideEventInSettings,
|
|
15
16
|
resolveStepUiSchema,
|
|
16
17
|
resolveStepDisabledInSettings,
|
|
17
18
|
shouldHideStepInSettings,
|
|
@@ -27,6 +28,7 @@ import type {
|
|
|
27
28
|
FlowDefinitionOptions,
|
|
28
29
|
ActionDefinition,
|
|
29
30
|
DeepPartial,
|
|
31
|
+
EventDefinition,
|
|
30
32
|
ModelConstructor,
|
|
31
33
|
StepParams,
|
|
32
34
|
StepDefinition,
|
|
@@ -1002,6 +1004,66 @@ describe('Utils', () => {
|
|
|
1002
1004
|
});
|
|
1003
1005
|
});
|
|
1004
1006
|
|
|
1007
|
+
// ==================== shouldHideEventInSettings() FUNCTION ====================
|
|
1008
|
+
describe('shouldHideEventInSettings()', () => {
|
|
1009
|
+
let mockFlow: any;
|
|
1010
|
+
let mockEvent: EventDefinition;
|
|
1011
|
+
|
|
1012
|
+
beforeEach(() => {
|
|
1013
|
+
mockFlow = {
|
|
1014
|
+
key: 'testFlow',
|
|
1015
|
+
title: 'Test Flow',
|
|
1016
|
+
steps: {},
|
|
1017
|
+
};
|
|
1018
|
+
|
|
1019
|
+
mockEvent = {
|
|
1020
|
+
name: 'close',
|
|
1021
|
+
title: 'Close',
|
|
1022
|
+
handler: vi.fn(),
|
|
1023
|
+
};
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
test('returns true for static hideInSettings=true', async () => {
|
|
1027
|
+
mockEvent.hideInSettings = true;
|
|
1028
|
+
|
|
1029
|
+
const result = await shouldHideEventInSettings(mockModel, mockFlow, mockEvent);
|
|
1030
|
+
|
|
1031
|
+
expect(result).toBe(true);
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
test('returns false for static hideInSettings=false', async () => {
|
|
1035
|
+
mockEvent.hideInSettings = false;
|
|
1036
|
+
|
|
1037
|
+
const result = await shouldHideEventInSettings(mockModel, mockFlow, mockEvent);
|
|
1038
|
+
|
|
1039
|
+
expect(result).toBe(false);
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
test('evaluates function hideInSettings with FlowRuntimeContext and can read ctx.view.preventClose', async () => {
|
|
1043
|
+
mockModel.context.defineProperty('view', { value: { preventClose: true } });
|
|
1044
|
+
const hideFn = vi.fn().mockImplementation((ctx) => !!ctx.view?.preventClose);
|
|
1045
|
+
mockEvent.hideInSettings = hideFn as any;
|
|
1046
|
+
|
|
1047
|
+
const result = await shouldHideEventInSettings(mockModel, mockFlow, mockEvent);
|
|
1048
|
+
|
|
1049
|
+
expect(hideFn).toHaveBeenCalledTimes(1);
|
|
1050
|
+
const ctx = hideFn.mock.calls[0][0] as FlowRuntimeContext;
|
|
1051
|
+
expect(ctx).toBeInstanceOf(FlowRuntimeContext);
|
|
1052
|
+
expect(result).toBe(true);
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1055
|
+
test('returns false and logs warning when event hideInSettings throws', async () => {
|
|
1056
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1057
|
+
mockEvent.hideInSettings = vi.fn().mockRejectedValue(new Error('boom')) as any;
|
|
1058
|
+
|
|
1059
|
+
const result = await shouldHideEventInSettings(mockModel, mockFlow, mockEvent);
|
|
1060
|
+
|
|
1061
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
1062
|
+
expect(result).toBe(false);
|
|
1063
|
+
consoleSpy.mockRestore();
|
|
1064
|
+
});
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1005
1067
|
// ==================== shouldHideStepInSettings() FUNCTION ====================
|
|
1006
1068
|
describe('shouldHideStepInSettings()', () => {
|
|
1007
1069
|
let mockFlow: any;
|
package/src/utils/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ export {
|
|
|
22
22
|
export { escapeT, getT, tExpr } from './translation';
|
|
23
23
|
|
|
24
24
|
// 异常类
|
|
25
|
-
export { FlowCancelSaveException, FlowExitException } from './exceptions';
|
|
25
|
+
export { FlowCancelSaveException, FlowExitAllException, FlowExitException } from './exceptions';
|
|
26
26
|
|
|
27
27
|
// 流程定义相关
|
|
28
28
|
export { defineAction } from './flow-definitions';
|
|
@@ -39,6 +39,7 @@ export {
|
|
|
39
39
|
resolveStepUiSchema,
|
|
40
40
|
resolveStepDisabledInSettings,
|
|
41
41
|
resolveUiMode,
|
|
42
|
+
shouldHideEventInSettings,
|
|
42
43
|
shouldHideStepInSettings,
|
|
43
44
|
} from './schema-utils';
|
|
44
45
|
|
|
@@ -11,7 +11,7 @@ import type { ISchema } from '@formily/json-schema';
|
|
|
11
11
|
import { Schema } from '@formily/json-schema';
|
|
12
12
|
import type { FlowModel } from '../models';
|
|
13
13
|
import { FlowRuntimeContext } from '../flowContext';
|
|
14
|
-
import type { StepDefinition, StepUIMode } from '../types';
|
|
14
|
+
import type { EventDefinition, StepDefinition, StepUIMode } from '../types';
|
|
15
15
|
import { setupRuntimeContextSteps } from './setupRuntimeContextSteps';
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -242,6 +242,35 @@ export async function resolveStepUiSchema<TModel extends FlowModel = FlowModel>(
|
|
|
242
242
|
return resolvedStepUiSchema;
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
+
/**
|
|
246
|
+
* 判断事件在设置菜单中是否应被隐藏。
|
|
247
|
+
* - 支持 EventDefinition.hideInSettings。
|
|
248
|
+
* - hideInSettings 可为布尔值或函数(接收 FlowRuntimeContext)。
|
|
249
|
+
*/
|
|
250
|
+
export async function shouldHideEventInSettings<TModel extends FlowModel = FlowModel>(
|
|
251
|
+
model: TModel,
|
|
252
|
+
flow: any,
|
|
253
|
+
event: EventDefinition<TModel> | undefined,
|
|
254
|
+
): Promise<boolean> {
|
|
255
|
+
if (!event) return true;
|
|
256
|
+
|
|
257
|
+
const { hideInSettings } = event;
|
|
258
|
+
|
|
259
|
+
if (typeof hideInSettings === 'function') {
|
|
260
|
+
try {
|
|
261
|
+
const ctx = new FlowRuntimeContext(model, flow.key, 'settings');
|
|
262
|
+
setupRuntimeContextSteps(ctx, flow.steps || {}, model, flow.key);
|
|
263
|
+
const result = await hideInSettings(ctx as any);
|
|
264
|
+
return !!result;
|
|
265
|
+
} catch (error) {
|
|
266
|
+
console.warn(`Error evaluating hideInSettings for event '${event.name || ''}' in flow '${flow.key}':`, error);
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return !!hideInSettings;
|
|
272
|
+
}
|
|
273
|
+
|
|
245
274
|
/**
|
|
246
275
|
* 判断步骤在设置菜单中是否应被隐藏。
|
|
247
276
|
* - 支持 StepDefinition.hideInSettings 与 ActionDefinition.hideInSettings(step 优先)。
|
package/src/views/FlowView.tsx
CHANGED
|
@@ -11,13 +11,23 @@ import { PopoverProps as AntdPopoverProps } from 'antd';
|
|
|
11
11
|
import { FlowContext } from '../flowContext';
|
|
12
12
|
import { ViewNavigation } from './ViewNavigation';
|
|
13
13
|
|
|
14
|
+
export type FlowViewBeforeClosePayload = {
|
|
15
|
+
result?: any;
|
|
16
|
+
force?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type FlowViewBeforeCloseHandler = (
|
|
20
|
+
payload: FlowViewBeforeClosePayload,
|
|
21
|
+
) => Promise<boolean | void> | boolean | void;
|
|
22
|
+
|
|
14
23
|
export type FlowView = {
|
|
15
24
|
type: 'drawer' | 'popover' | 'dialog' | 'embed';
|
|
16
25
|
inputArgs: any;
|
|
17
26
|
Header: React.FC<{ title?: React.ReactNode; extra?: React.ReactNode }> | null;
|
|
18
27
|
Footer: React.FC<{ children?: React.ReactNode }> | null;
|
|
19
|
-
close: (result?: any, force?: boolean) => void;
|
|
28
|
+
close: (result?: any, force?: boolean) => Promise<boolean | void> | boolean | void;
|
|
20
29
|
update: (newConfig: any) => void;
|
|
30
|
+
beforeClose?: FlowViewBeforeCloseHandler;
|
|
21
31
|
navigation?: ViewNavigation;
|
|
22
32
|
/** 页面的销毁方法 */
|
|
23
33
|
destroy?: () => void;
|
|
@@ -0,0 +1,30 @@
|
|
|
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, vi } from 'vitest';
|
|
11
|
+
import { runViewBeforeClose } from '../runViewBeforeClose';
|
|
12
|
+
|
|
13
|
+
describe('runViewBeforeClose', () => {
|
|
14
|
+
it('returns true when no beforeClose handler is configured', async () => {
|
|
15
|
+
await expect(runViewBeforeClose({} as any, { force: false })).resolves.toBe(true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('skips beforeClose handler for force close', async () => {
|
|
19
|
+
const beforeClose = vi.fn();
|
|
20
|
+
|
|
21
|
+
await expect(runViewBeforeClose({ beforeClose } as any, { force: true })).resolves.toBe(true);
|
|
22
|
+
expect(beforeClose).not.toHaveBeenCalled();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('returns false when beforeClose handler blocks the close', async () => {
|
|
26
|
+
const beforeClose = vi.fn().mockResolvedValue(false);
|
|
27
|
+
|
|
28
|
+
await expect(runViewBeforeClose({ beforeClose } as any, { force: false })).resolves.toBe(false);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -76,40 +76,40 @@ describe('useDialog - close/destroy logic', () => {
|
|
|
76
76
|
return api;
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
-
it('should call destroy (and thus closeFunc) when close is called without preventClose', () => {
|
|
79
|
+
it('should call destroy (and thus closeFunc) when close is called without preventClose', async () => {
|
|
80
80
|
const api = renderUseDialog();
|
|
81
81
|
const flowContext = createMockFlowContext();
|
|
82
82
|
|
|
83
83
|
const dialog = api.open({}, flowContext);
|
|
84
84
|
|
|
85
|
-
dialog.close();
|
|
85
|
+
await dialog.close();
|
|
86
86
|
|
|
87
87
|
expect(mockCloseFunc).toHaveBeenCalled();
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
-
it('should not call destroy (and thus closeFunc) when close is called with preventClose=true', () => {
|
|
90
|
+
it('should not call destroy (and thus closeFunc) when close is called with preventClose=true', async () => {
|
|
91
91
|
const api = renderUseDialog();
|
|
92
92
|
const flowContext = createMockFlowContext();
|
|
93
93
|
|
|
94
94
|
const dialog = api.open({ preventClose: true }, flowContext);
|
|
95
95
|
|
|
96
|
-
dialog.close();
|
|
96
|
+
await dialog.close();
|
|
97
97
|
|
|
98
98
|
expect(mockCloseFunc).not.toHaveBeenCalled();
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
-
it('should call destroy (and thus closeFunc) when close is called with preventClose=true but force=true', () => {
|
|
101
|
+
it('should call destroy (and thus closeFunc) when close is called with preventClose=true but force=true', async () => {
|
|
102
102
|
const api = renderUseDialog();
|
|
103
103
|
const flowContext = createMockFlowContext();
|
|
104
104
|
|
|
105
105
|
const dialog = api.open({ preventClose: true }, flowContext);
|
|
106
106
|
|
|
107
|
-
dialog.close(undefined, true);
|
|
107
|
+
await dialog.close(undefined, true);
|
|
108
108
|
|
|
109
109
|
expect(mockCloseFunc).toHaveBeenCalled();
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
-
it('should delegate to navigation.back when triggerByRouter is true', () => {
|
|
112
|
+
it('should delegate to navigation.back when triggerByRouter is true', async () => {
|
|
113
113
|
const api = renderUseDialog();
|
|
114
114
|
const flowContext = createMockFlowContext();
|
|
115
115
|
const backMock = vi.fn();
|
|
@@ -126,25 +126,25 @@ describe('useDialog - close/destroy logic', () => {
|
|
|
126
126
|
flowContext,
|
|
127
127
|
);
|
|
128
128
|
|
|
129
|
-
dialog.close();
|
|
129
|
+
await dialog.close();
|
|
130
130
|
|
|
131
131
|
expect(backMock).toHaveBeenCalled();
|
|
132
132
|
// Should not call destroy directly, let router handle it
|
|
133
133
|
expect(mockCloseFunc).not.toHaveBeenCalled();
|
|
134
134
|
});
|
|
135
135
|
|
|
136
|
-
it('should emit view activated event on opener engine', () => {
|
|
136
|
+
it('should emit view activated event on opener engine', async () => {
|
|
137
137
|
const api = renderUseDialog();
|
|
138
138
|
const flowContext = createMockFlowContext();
|
|
139
139
|
const emitSpy = flowContext.engine.emitter.emit;
|
|
140
140
|
|
|
141
141
|
const dialog = api.open({ inputArgs: { viewUid: 'child-view' } }, flowContext);
|
|
142
142
|
|
|
143
|
-
dialog.close();
|
|
143
|
+
await dialog.close();
|
|
144
144
|
expect(emitSpy).toHaveBeenCalledWith('view:activated', expect.anything());
|
|
145
145
|
});
|
|
146
146
|
|
|
147
|
-
it('should emit view events on immediate opener engine (previousEngine) when present', () => {
|
|
147
|
+
it('should emit view events on immediate opener engine (previousEngine) when present', async () => {
|
|
148
148
|
const api = renderUseDialog();
|
|
149
149
|
const flowContext = createMockFlowContext();
|
|
150
150
|
const rootEmitSpy = flowContext.engine.emitter.emit;
|
|
@@ -153,7 +153,7 @@ describe('useDialog - close/destroy logic', () => {
|
|
|
153
153
|
|
|
154
154
|
const dialog = api.open({ inputArgs: { viewUid: 'child-view' } }, flowContext);
|
|
155
155
|
|
|
156
|
-
dialog.close();
|
|
156
|
+
await dialog.close();
|
|
157
157
|
expect(openerEmitSpy).toHaveBeenCalledWith('view:activated', expect.anything());
|
|
158
158
|
expect(rootEmitSpy).not.toHaveBeenCalledWith('view:activated', expect.anything());
|
|
159
159
|
});
|
|
@@ -0,0 +1,19 @@
|
|
|
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 type { FlowView, FlowViewBeforeClosePayload } from './FlowView';
|
|
11
|
+
|
|
12
|
+
export async function runViewBeforeClose(view: FlowView, payload: FlowViewBeforeClosePayload): Promise<boolean> {
|
|
13
|
+
if (payload.force) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const result = await view.beforeClose?.(payload);
|
|
18
|
+
return result !== false;
|
|
19
|
+
}
|
package/src/views/useDialog.tsx
CHANGED
|
@@ -19,6 +19,7 @@ import { VIEW_ACTIVATED_EVENT, bumpViewActivatedVersion, resolveOpenerEngine } f
|
|
|
19
19
|
import { FlowEngineProvider } from '../provider';
|
|
20
20
|
import { createViewScopedEngine } from '../ViewScopedFlowEngine';
|
|
21
21
|
import { createViewRecordResolveOnServer, getViewRecordFromParent } from '../utils/variablesParams';
|
|
22
|
+
import { runViewBeforeClose } from './runViewBeforeClose';
|
|
22
23
|
|
|
23
24
|
let uuid = 0;
|
|
24
25
|
|
|
@@ -97,6 +98,7 @@ export function useDialog() {
|
|
|
97
98
|
type: 'dialog' as const,
|
|
98
99
|
inputArgs: config.inputArgs || {},
|
|
99
100
|
preventClose: !!config.preventClose,
|
|
101
|
+
beforeClose: undefined,
|
|
100
102
|
destroy: (result?: any) => {
|
|
101
103
|
if (destroyed) return;
|
|
102
104
|
destroyed = true;
|
|
@@ -112,18 +114,24 @@ export function useDialog() {
|
|
|
112
114
|
scopedEngine.unlinkFromStack();
|
|
113
115
|
},
|
|
114
116
|
update: (newConfig) => dialogRef.current?.update(newConfig),
|
|
115
|
-
close: (result?: any, force?: boolean) => {
|
|
117
|
+
close: async (result?: any, force?: boolean) => {
|
|
116
118
|
if (config.preventClose && !force) {
|
|
117
|
-
return;
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const shouldClose = await runViewBeforeClose(currentDialog, { result, force });
|
|
123
|
+
if (!shouldClose) {
|
|
124
|
+
return false;
|
|
118
125
|
}
|
|
119
126
|
|
|
120
127
|
if (config.triggerByRouter && config.inputArgs?.navigation?.back) {
|
|
121
128
|
// 交由路由系统来销毁当前视图
|
|
122
129
|
config.inputArgs.navigation.back();
|
|
123
|
-
return;
|
|
130
|
+
return true;
|
|
124
131
|
}
|
|
125
132
|
|
|
126
133
|
currentDialog.destroy(result);
|
|
134
|
+
return true;
|
|
127
135
|
},
|
|
128
136
|
Footer: FooterComponent,
|
|
129
137
|
Header: HeaderComponent,
|
package/src/views/useDrawer.tsx
CHANGED
|
@@ -19,6 +19,7 @@ import { VIEW_ACTIVATED_EVENT, bumpViewActivatedVersion, resolveOpenerEngine } f
|
|
|
19
19
|
import { FlowEngineProvider } from '../provider';
|
|
20
20
|
import { createViewScopedEngine } from '../ViewScopedFlowEngine';
|
|
21
21
|
import { createViewRecordResolveOnServer, getViewRecordFromParent } from '../utils/variablesParams';
|
|
22
|
+
import { runViewBeforeClose } from './runViewBeforeClose';
|
|
22
23
|
|
|
23
24
|
export function useDrawer() {
|
|
24
25
|
const holderRef = React.useRef(null);
|
|
@@ -126,6 +127,7 @@ export function useDrawer() {
|
|
|
126
127
|
type: 'drawer' as const,
|
|
127
128
|
inputArgs: config.inputArgs || {},
|
|
128
129
|
preventClose: !!config.preventClose,
|
|
130
|
+
beforeClose: undefined,
|
|
129
131
|
destroy: (result?: any) => {
|
|
130
132
|
if (destroyed) return;
|
|
131
133
|
destroyed = true;
|
|
@@ -141,18 +143,24 @@ export function useDrawer() {
|
|
|
141
143
|
scopedEngine.unlinkFromStack();
|
|
142
144
|
},
|
|
143
145
|
update: (newConfig) => drawerRef.current?.update(newConfig),
|
|
144
|
-
close: (result?: any, force?: boolean) => {
|
|
146
|
+
close: async (result?: any, force?: boolean) => {
|
|
145
147
|
if (config.preventClose && !force) {
|
|
146
|
-
return;
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const shouldClose = await runViewBeforeClose(currentDrawer, { result, force });
|
|
152
|
+
if (!shouldClose) {
|
|
153
|
+
return false;
|
|
147
154
|
}
|
|
148
155
|
|
|
149
156
|
if (config.triggerByRouter && config.inputArgs?.navigation?.back) {
|
|
150
157
|
// 交由路由系统来销毁当前视图
|
|
151
158
|
config.inputArgs.navigation.back();
|
|
152
|
-
return;
|
|
159
|
+
return true;
|
|
153
160
|
}
|
|
154
161
|
|
|
155
162
|
currentDrawer.destroy(result);
|
|
163
|
+
return true;
|
|
156
164
|
},
|
|
157
165
|
Footer: FooterComponent,
|
|
158
166
|
Header: HeaderComponent,
|
package/src/views/usePage.tsx
CHANGED
|
@@ -19,6 +19,7 @@ import { VIEW_ACTIVATED_EVENT, bumpViewActivatedVersion, resolveOpenerEngine } f
|
|
|
19
19
|
import { FlowEngineProvider } from '../provider';
|
|
20
20
|
import { createViewScopedEngine } from '../ViewScopedFlowEngine';
|
|
21
21
|
import { createViewRecordResolveOnServer, getViewRecordFromParent } from '../utils/variablesParams';
|
|
22
|
+
import { runViewBeforeClose } from './runViewBeforeClose';
|
|
22
23
|
|
|
23
24
|
let uuid = 0;
|
|
24
25
|
|
|
@@ -122,6 +123,7 @@ export function usePage() {
|
|
|
122
123
|
type: 'embed' as const,
|
|
123
124
|
inputArgs: viewInputArgs,
|
|
124
125
|
preventClose: !!config.preventClose,
|
|
126
|
+
beforeClose: undefined,
|
|
125
127
|
destroy: (result?: any) => {
|
|
126
128
|
config.onClose?.();
|
|
127
129
|
resolvePromise?.(result);
|
|
@@ -145,18 +147,24 @@ export function usePage() {
|
|
|
145
147
|
scopedEngine.unlinkFromStack();
|
|
146
148
|
},
|
|
147
149
|
update: (newConfig) => pageRef.current?.update(newConfig),
|
|
148
|
-
close: (result?: any, force?: boolean) => {
|
|
150
|
+
close: async (result?: any, force?: boolean) => {
|
|
149
151
|
if (preventClose && !force) {
|
|
150
|
-
return;
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const shouldClose = await runViewBeforeClose(currentPage, { result, force });
|
|
156
|
+
if (!shouldClose) {
|
|
157
|
+
return false;
|
|
151
158
|
}
|
|
152
159
|
|
|
153
160
|
if (config.triggerByRouter && config.inputArgs?.navigation?.back) {
|
|
154
161
|
// 交由路由系统来销毁当前视图
|
|
155
162
|
config.inputArgs.navigation.back();
|
|
156
|
-
return;
|
|
163
|
+
return true;
|
|
157
164
|
}
|
|
158
165
|
|
|
159
166
|
currentPage.destroy(result);
|
|
167
|
+
return true;
|
|
160
168
|
},
|
|
161
169
|
Header: HeaderComponent,
|
|
162
170
|
Footer: FooterComponent,
|