@nocobase/client-v2 2.2.0-beta.1 → 2.2.0-beta.11

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 (252) hide show
  1. package/es/BaseApplication.d.ts +3 -0
  2. package/es/PluginSettingsManager.d.ts +33 -0
  3. package/es/RouteRepository.d.ts +29 -6
  4. package/es/RouterManager.d.ts +15 -0
  5. package/es/authRedirect.d.ts +1 -0
  6. package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
  7. package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
  8. package/es/components/form/JsonTextArea.d.ts +2 -1
  9. package/es/components/form/VariableJsonTextArea.d.ts +19 -0
  10. package/es/components/form/index.d.ts +1 -0
  11. package/es/components/index.d.ts +1 -0
  12. package/es/entry-actions/EntryActionManager.d.ts +24 -0
  13. package/es/entry-actions/index.d.ts +9 -0
  14. package/es/flow/actions/afterSuccess.d.ts +8 -1
  15. package/es/flow/actions/index.d.ts +1 -1
  16. package/es/flow/admin-shell/BaseLayoutModel.d.ts +6 -0
  17. package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +7 -0
  18. package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +2 -0
  19. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.d.ts +2 -1
  20. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +2 -0
  21. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.d.ts +3 -2
  22. package/es/flow/admin-shell/admin-layout/AdminLayoutSlotModels.d.ts +5 -0
  23. package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +2 -1
  24. package/es/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.d.ts +24 -0
  25. package/es/flow/admin-shell/admin-layout/index.d.ts +1 -0
  26. package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +6 -0
  27. package/es/flow/admin-shell/admin-layout/useApplications.d.ts +7 -2
  28. package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
  29. package/es/flow/models/base/ActionModelCore.d.ts +2 -0
  30. package/es/flow/models/base/GridModel.d.ts +1 -1
  31. package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +5 -0
  32. package/es/flow/models/blocks/form/FormBlockModel.d.ts +4 -0
  33. package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
  34. package/es/flow/models/blocks/form/submitHandler.d.ts +1 -1
  35. package/es/flow/models/blocks/form/value-runtime/rules.d.ts +21 -0
  36. package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +30 -0
  37. package/es/flow/models/blocks/form/value-runtime/types.d.ts +2 -2
  38. package/es/flow/models/blocks/js-block/JSBlock.d.ts +1 -0
  39. package/es/flow/models/blocks/table/TableActionsColumnModel.d.ts +1 -0
  40. package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
  41. package/es/flow/models/blocks/table/dragSort/dragSortHooks.d.ts +1 -1
  42. package/es/flow/models/blocks/table/dragSort/dragSortSettings.d.ts +2 -1
  43. package/es/flow/models/blocks/table/dragSort/dragSortUtils.d.ts +6 -4
  44. package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +2 -0
  45. package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +6 -0
  46. package/es/flow/resolveViewParamsToViewList.d.ts +1 -1
  47. package/es/flow/routeTransientInputArgs.d.ts +14 -0
  48. package/es/flow/utils/dataScopeRowSnapshot.d.ts +59 -0
  49. package/es/flow/utils/dateTimeDisplayProps.d.ts +49 -0
  50. package/es/flow/utils/formValueDeps.d.ts +32 -0
  51. package/es/flow-compat/FieldValidation.d.ts +2 -1
  52. package/es/index.d.ts +4 -1
  53. package/es/index.mjs +315 -167
  54. package/es/utils/markdownSanitize.d.ts +11 -0
  55. package/lib/index.js +315 -167
  56. package/package.json +8 -7
  57. package/src/BaseApplication.tsx +12 -5
  58. package/src/PluginSettingsManager.ts +53 -0
  59. package/src/RouteRepository.ts +151 -24
  60. package/src/RouterManager.tsx +142 -1
  61. package/src/__tests__/PluginSettingsManager.test.ts +13 -0
  62. package/src/__tests__/RouteRepository.test.ts +239 -0
  63. package/src/__tests__/RouterManager.test.ts +125 -0
  64. package/src/__tests__/app.test.tsx +55 -0
  65. package/src/__tests__/authRedirect.test.ts +17 -0
  66. package/src/__tests__/exports.test.ts +16 -0
  67. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +199 -0
  68. package/src/__tests__/plugin-manager.test.tsx +44 -2
  69. package/src/__tests__/settings-center.test.tsx +40 -1
  70. package/src/authRedirect.ts +38 -0
  71. package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
  72. package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
  73. package/src/collection-manager/field-configure.ts +1 -1
  74. package/src/collection-manager/interfaces/id.ts +1 -1
  75. package/src/collection-manager/interfaces/m2m.tsx +2 -2
  76. package/src/collection-manager/interfaces/m2o.tsx +2 -2
  77. package/src/collection-manager/interfaces/nanoid.ts +1 -1
  78. package/src/collection-manager/interfaces/o2m.tsx +2 -2
  79. package/src/collection-manager/interfaces/obo.tsx +2 -2
  80. package/src/collection-manager/interfaces/oho.tsx +2 -2
  81. package/src/collection-manager/interfaces/properties/index.ts +2 -2
  82. package/src/collection-manager/interfaces/uuid.ts +1 -1
  83. package/src/components/AppComponents.tsx +19 -3
  84. package/src/components/form/JsonTextArea.tsx +21 -11
  85. package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +145 -2
  86. package/src/components/form/ScanInput/useCodeScanner.ts +154 -2
  87. package/src/components/form/VariableJsonTextArea.tsx +175 -0
  88. package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
  89. package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
  90. package/src/components/form/index.tsx +1 -0
  91. package/src/components/index.ts +1 -0
  92. package/src/entry-actions/EntryActionManager.ts +76 -0
  93. package/src/entry-actions/index.ts +10 -0
  94. package/src/flow/FlowPage.tsx +38 -3
  95. package/src/flow/__tests__/FlowPage.test.tsx +201 -27
  96. package/src/flow/__tests__/FlowRoute.test.tsx +1088 -105
  97. package/src/flow/__tests__/getKey.test.ts +7 -0
  98. package/src/flow/__tests__/resolveViewParamsToViewList.test.ts +34 -0
  99. package/src/flow/actions/__tests__/afterSuccess.test.ts +73 -0
  100. package/src/flow/actions/__tests__/dataScopeFilter.test.ts +120 -0
  101. package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +1022 -0
  102. package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +150 -1
  103. package/src/flow/actions/__tests__/linkageAssignField.legacy.test.ts +135 -0
  104. package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +19 -5
  105. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +92 -0
  106. package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
  107. package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +164 -0
  108. package/src/flow/actions/__tests__/subFormFieldLinkageRules.inputArgs.test.ts +2 -2
  109. package/src/flow/actions/afterSuccess.tsx +142 -3
  110. package/src/flow/actions/customVariable.tsx +1 -2
  111. package/src/flow/actions/dataScopeFilter.ts +20 -1
  112. package/src/flow/actions/dateTimeFormat.tsx +42 -28
  113. package/src/flow/actions/index.ts +1 -1
  114. package/src/flow/actions/linkageRules.tsx +123 -35
  115. package/src/flow/actions/linkageRulesFormValueRefresh.ts +22 -130
  116. package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
  117. package/src/flow/actions/openView.tsx +59 -5
  118. package/src/flow/actions/runjs.tsx +2 -14
  119. package/src/flow/actions/validation.tsx +62 -30
  120. package/src/flow/admin-shell/BaseLayoutModel.tsx +149 -3
  121. package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +187 -42
  122. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +1020 -110
  123. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +74 -13
  124. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +486 -0
  125. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +64 -8
  126. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.ts +5 -3
  127. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +61 -9
  128. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +71 -8
  129. package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +1 -1
  130. package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +23 -9
  131. package/src/flow/admin-shell/admin-layout/AppListRender.tsx +13 -2
  132. package/src/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.tsx +289 -0
  133. package/src/flow/admin-shell/admin-layout/HelpLite.tsx +1 -3
  134. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +188 -0
  135. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +99 -0
  136. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +518 -2
  137. package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +214 -2
  138. package/src/flow/admin-shell/admin-layout/index.ts +1 -0
  139. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +20 -0
  140. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +33 -5
  141. package/src/flow/admin-shell/admin-layout/useApplications.tsx +81 -12
  142. package/src/flow/common/Markdown/Display.tsx +14 -3
  143. package/src/flow/common/Markdown/Edit.tsx +19 -7
  144. package/src/flow/components/DefaultValue.tsx +1 -2
  145. package/src/flow/components/DynamicFlowsIcon.tsx +447 -126
  146. package/src/flow/components/FieldAssignRulesEditor.tsx +128 -21
  147. package/src/flow/components/FieldAssignValueInput.tsx +15 -11
  148. package/src/flow/components/FlowRoute.tsx +186 -25
  149. package/src/flow/components/__tests__/DynamicFlowsIcon.test.tsx +148 -2
  150. package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +508 -4
  151. package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +134 -0
  152. package/src/flow/components/__tests__/fieldAssignOptions.test.ts +151 -3
  153. package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +2 -17
  154. package/src/flow/components/code-editor/hooks/useCodeRunner.ts +2 -14
  155. package/src/flow/components/code-editor/runjsDiagnostics.ts +8 -45
  156. package/src/flow/components/fieldAssignOptions.ts +155 -27
  157. package/src/flow/flows/editMarkdownFlow.tsx +1 -1
  158. package/src/flow/getViewDiffAndUpdateHidden.tsx +1 -0
  159. package/src/flow/index.ts +1 -1
  160. package/src/flow/internal/components/Markdown/util.ts +2 -1
  161. package/src/flow/internal/utils/operatorSchemaHelper.ts +15 -1
  162. package/src/flow/models/actions/JSActionModel.tsx +2 -7
  163. package/src/flow/models/actions/JSCollectionActionModel.tsx +2 -7
  164. package/src/flow/models/actions/JSItemActionModel.tsx +2 -7
  165. package/src/flow/models/actions/JSRecordActionModel.tsx +2 -7
  166. package/src/flow/models/base/ActionModel.tsx +21 -8
  167. package/src/flow/models/base/ActionModelCore.tsx +11 -4
  168. package/src/flow/models/base/GridModel.tsx +38 -7
  169. package/src/flow/models/base/PageModel/PageModel.tsx +4 -1
  170. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +28 -4
  171. package/src/flow/models/base/__tests__/ActionModel.test.ts +83 -0
  172. package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +37 -0
  173. package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +239 -1
  174. package/src/flow/models/base/__tests__/transformRowsToSingleColumn.test.ts +48 -0
  175. package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +32 -3
  176. package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +27 -12
  177. package/src/flow/models/blocks/filter-form/FilterFormJSActionModel.tsx +2 -7
  178. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +36 -0
  179. package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +111 -0
  180. package/src/flow/models/blocks/filter-form/fields/FilterFormCustomFieldModel.tsx +1 -1
  181. package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/customizeFilterRender.test.tsx +56 -1
  182. package/src/flow/models/blocks/form/EditFormModel.tsx +1 -0
  183. package/src/flow/models/blocks/form/FormActionModel.tsx +1 -1
  184. package/src/flow/models/blocks/form/FormBlockModel.tsx +7 -0
  185. package/src/flow/models/blocks/form/JSFormActionModel.tsx +2 -7
  186. package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
  187. package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
  188. package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
  189. package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +54 -1
  190. package/src/flow/models/blocks/form/submitHandler.ts +17 -3
  191. package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +940 -75
  192. package/src/flow/models/blocks/form/value-runtime/rules.ts +445 -13
  193. package/src/flow/models/blocks/form/value-runtime/runtime.ts +348 -13
  194. package/src/flow/models/blocks/form/value-runtime/types.ts +2 -2
  195. package/src/flow/models/blocks/js-block/JSBlock.tsx +225 -9
  196. package/src/flow/models/blocks/js-block/__tests__/JSBlockModel.test.tsx +150 -0
  197. package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
  198. package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +38 -14
  199. package/src/flow/models/blocks/table/TableBlockModel.tsx +38 -17
  200. package/src/flow/models/blocks/table/TableColumnModel.tsx +42 -5
  201. package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +111 -1
  202. package/src/flow/models/blocks/table/__tests__/TableBlockModel.dragSort.test.ts +127 -0
  203. package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
  204. package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +1 -0
  205. package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +210 -1
  206. package/src/flow/models/blocks/table/dragSort/dragSortHooks.tsx +28 -17
  207. package/src/flow/models/blocks/table/dragSort/dragSortSettings.ts +13 -6
  208. package/src/flow/models/blocks/table/dragSort/dragSortUtils.ts +15 -2
  209. package/src/flow/models/fields/AssociationFieldModel/AssociationFieldModel.tsx +1 -1
  210. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +30 -6
  211. package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +61 -17
  212. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +141 -19
  213. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +13 -1
  214. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +197 -0
  215. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableFieldModel.reset.test.ts +180 -0
  216. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -4
  217. package/src/flow/models/fields/AssociationFieldModel/__tests__/AssociationFieldModel.updateAssociation.test.ts +72 -0
  218. package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +23 -0
  219. package/src/flow/models/fields/ClickableFieldModel.tsx +5 -0
  220. package/src/flow/models/fields/DisplayDateTimeFieldModel.tsx +29 -1
  221. package/src/flow/models/fields/InputFieldModel.tsx +40 -2
  222. package/src/flow/models/fields/JSEditableFieldModel.tsx +2 -11
  223. package/src/flow/models/fields/JSFieldModel.tsx +2 -7
  224. package/src/flow/models/fields/JSItemModel.tsx +2 -14
  225. package/src/flow/models/fields/SelectFieldModel.tsx +6 -4
  226. package/src/flow/models/fields/TextareaFieldModel.tsx +72 -3
  227. package/src/flow/models/fields/__tests__/DisplayDateTimeFieldModel.test.tsx +96 -0
  228. package/src/flow/models/fields/__tests__/InputFieldModel.test.tsx +117 -1
  229. package/src/flow/models/fields/__tests__/SelectFieldModel.test.tsx +43 -0
  230. package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +131 -0
  231. package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +20 -10
  232. package/src/flow/models/fields/mobile-components/MobileSelect.tsx +24 -12
  233. package/src/flow/models/topbar/TopbarActionModel.tsx +168 -10
  234. package/src/flow/resolveViewParamsToViewList.tsx +11 -3
  235. package/src/flow/routeTransientInputArgs.ts +27 -0
  236. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +300 -0
  237. package/src/flow/utils/dataScopeFormValueClear.ts +218 -81
  238. package/src/flow/utils/dataScopeRowSnapshot.ts +616 -0
  239. package/src/flow/utils/dateTimeDisplayProps.ts +135 -0
  240. package/src/flow/utils/formValueDeps.ts +170 -0
  241. package/src/flow-compat/FieldValidation.tsx +122 -60
  242. package/src/flow-compat/Popover.tsx +43 -4
  243. package/src/flow-compat/__tests__/Popover.test.tsx +34 -0
  244. package/src/index.ts +10 -1
  245. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
  246. package/src/nocobase-buildin-plugin/index.tsx +51 -15
  247. package/src/settings-center/AdminSettingsLayout.tsx +10 -2
  248. package/src/settings-center/SystemSettingsPage.tsx +1 -2
  249. package/src/settings-center/plugin-manager/PluginCard.tsx +2 -2
  250. package/src/settings-center/plugin-manager/index.tsx +3 -0
  251. package/src/settings-center/utils.tsx +0 -6
  252. package/src/utils/markdownSanitize.ts +88 -0
@@ -7,17 +7,365 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- import { describe, it, expect, vi, beforeEach } from 'vitest';
11
- import { FlowEngine, SingleRecordResource } from '@nocobase/flow-engine';
10
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
11
+ import { FlowEngine, FlowModel, SingleRecordResource } from '@nocobase/flow-engine';
12
12
  import { QuickEditFormModel } from '../QuickEditFormModel';
13
13
 
14
14
  describe('QuickEditFormModel - quick edit save triggers API (regression)', () => {
15
15
  let engine: FlowEngine;
16
+ const originalMatchMediaDescriptor = Object.getOwnPropertyDescriptor(window, 'matchMedia');
16
17
 
17
18
  beforeEach(() => {
18
19
  engine = new FlowEngine();
19
20
  });
20
21
 
22
+ afterEach(() => {
23
+ if (originalMatchMediaDescriptor) {
24
+ Object.defineProperty(window, 'matchMedia', originalMatchMediaDescriptor);
25
+ return;
26
+ }
27
+ delete (window as unknown as { matchMedia?: Window['matchMedia'] }).matchMedia;
28
+ });
29
+
30
+ const mockMatchMedia = (matches: boolean) => {
31
+ const matchMediaMock = vi.fn((query: string) => {
32
+ return {
33
+ matches,
34
+ media: query,
35
+ onchange: null,
36
+ addListener: vi.fn(),
37
+ removeListener: vi.fn(),
38
+ addEventListener: vi.fn(),
39
+ removeEventListener: vi.fn(),
40
+ dispatchEvent: vi.fn(),
41
+ } as unknown as MediaQueryList;
42
+ });
43
+
44
+ Object.defineProperty(window, 'matchMedia', {
45
+ ...(originalMatchMediaDescriptor || { configurable: true, writable: true }),
46
+ value: matchMediaMock,
47
+ });
48
+ };
49
+
50
+ const addUsersCollection = () => {
51
+ const ds = engine.context.dataSourceManager.getDataSource('main');
52
+ ds.addCollection({
53
+ name: 'users',
54
+ fields: [{ name: 'name', type: 'string', interface: 'input', uiSchema: { title: 'Name' } }],
55
+ });
56
+ };
57
+
58
+ it('uses source field context when opening quick edit', async () => {
59
+ engine.registerModels({ QuickEditFormModel });
60
+ engine.context.defineProperty('pageActive', { value: { value: false } });
61
+ engine.context.defineProperty('viewer', { value: { open: vi.fn(async () => undefined) } });
62
+
63
+ const page = engine.createModel<FlowModel>({ use: 'FlowModel', uid: 'page' });
64
+ page.context.defineProperty('pageActive', { value: { value: true } });
65
+ const source = engine.createModel<FlowModel>({ use: 'FlowModel', uid: 'source-field', parentId: page.uid });
66
+
67
+ await QuickEditFormModel.open({
68
+ flowEngine: engine,
69
+ target: document.createElement('div'),
70
+ dataSourceKey: 'main',
71
+ collectionName: 'users',
72
+ fieldPath: 'name',
73
+ record: {},
74
+ sourceFieldModelUid: source.uid,
75
+ });
76
+
77
+ let quickEditModel: QuickEditFormModel | undefined;
78
+ engine.forEachModel((model) => {
79
+ if (model instanceof QuickEditFormModel) {
80
+ quickEditModel = model;
81
+ }
82
+ });
83
+
84
+ expect(quickEditModel?.context.pageActive.value).toBe(true);
85
+ });
86
+
87
+ it('opens quick edit in a mobile drawer when the source field layout is mobile', async () => {
88
+ engine.registerModels({ QuickEditFormModel });
89
+ addUsersCollection();
90
+ const engineOpen = vi.fn(async () => undefined);
91
+ const sourceOpen = vi.fn(async () => undefined);
92
+ engine.context.defineProperty('isMobileLayout', { value: false });
93
+ engine.context.defineProperty('viewer', { value: { open: engineOpen } });
94
+ const source = engine.createModel<FlowModel>({ use: 'FlowModel', uid: 'source-field' });
95
+ source.context.defineProperty('isMobileLayout', { value: true });
96
+ source.context.defineProperty('viewer', { value: { open: sourceOpen } });
97
+
98
+ await QuickEditFormModel.open({
99
+ flowEngine: engine,
100
+ target: document.createElement('div'),
101
+ dataSourceKey: 'main',
102
+ collectionName: 'users',
103
+ fieldPath: 'name',
104
+ record: {},
105
+ sourceFieldModelUid: source.uid,
106
+ });
107
+
108
+ expect(engineOpen).not.toHaveBeenCalled();
109
+ expect(sourceOpen).toHaveBeenCalledTimes(1);
110
+ expect(sourceOpen).toHaveBeenCalledWith(
111
+ expect.objectContaining({
112
+ type: 'drawer',
113
+ title: 'Name',
114
+ closable: true,
115
+ placement: 'bottom',
116
+ styles: {
117
+ body: expect.objectContaining({
118
+ maxHeight: '50vh',
119
+ }),
120
+ },
121
+ inputArgs: {
122
+ isMobileLayout: true,
123
+ },
124
+ }),
125
+ );
126
+ let quickEditModel: QuickEditFormModel | undefined;
127
+ engine.forEachModel((model) => {
128
+ if (model instanceof QuickEditFormModel) {
129
+ quickEditModel = model;
130
+ }
131
+ });
132
+ expect(quickEditModel?.context.isMobileLayout).toBe(true);
133
+ });
134
+
135
+ it('uses the source table column title for the mobile drawer header', async () => {
136
+ engine.registerModels({ QuickEditFormModel });
137
+ addUsersCollection();
138
+ const sourceOpen = vi.fn(async () => undefined);
139
+ engine.context.defineProperty('viewer', { value: { open: vi.fn(async () => undefined) } });
140
+ const column = engine.createModel<FlowModel>({ use: 'FlowModel', uid: 'table-column' });
141
+ column.setProps({ title: 'Custom marital status' });
142
+ const source = engine.createModel<FlowModel>({ use: 'FlowModel', uid: 'source-field', parentId: column.uid });
143
+ source.context.defineProperty('isMobileLayout', { value: true });
144
+ source.context.defineProperty('viewer', { value: { open: sourceOpen } });
145
+
146
+ await QuickEditFormModel.open({
147
+ flowEngine: engine,
148
+ target: document.createElement('div'),
149
+ dataSourceKey: 'main',
150
+ collectionName: 'users',
151
+ fieldPath: 'name',
152
+ record: {},
153
+ sourceFieldModelUid: source.uid,
154
+ });
155
+
156
+ expect(sourceOpen).toHaveBeenCalledWith(
157
+ expect.objectContaining({
158
+ type: 'drawer',
159
+ title: 'Custom marital status',
160
+ }),
161
+ );
162
+ });
163
+
164
+ it('uses the mobile drawer fallback on narrow viewports', async () => {
165
+ engine.registerModels({ QuickEditFormModel });
166
+ addUsersCollection();
167
+ mockMatchMedia(true);
168
+ const viewContext = {
169
+ defineProperty: vi.fn(),
170
+ };
171
+ const open = vi.fn(
172
+ async (config: { content: (view: { close: () => void }, context: typeof viewContext) => unknown }) => {
173
+ config.content({ close: vi.fn() }, viewContext);
174
+ },
175
+ );
176
+ engine.context.defineProperty('viewer', { value: { open } });
177
+
178
+ await QuickEditFormModel.open({
179
+ flowEngine: engine,
180
+ target: document.createElement('div'),
181
+ dataSourceKey: 'main',
182
+ collectionName: 'users',
183
+ fieldPath: 'name',
184
+ record: {},
185
+ });
186
+
187
+ expect(open).toHaveBeenCalledTimes(1);
188
+ expect(viewContext.defineProperty).toHaveBeenCalledWith('isMobileLayout', { value: true });
189
+ expect(window.matchMedia).toHaveBeenCalledWith('(max-width: 768px)');
190
+ expect(open).toHaveBeenCalledWith(
191
+ expect.objectContaining({
192
+ type: 'drawer',
193
+ title: 'Name',
194
+ closable: true,
195
+ placement: 'bottom',
196
+ styles: {
197
+ body: expect.objectContaining({
198
+ maxHeight: '50vh',
199
+ }),
200
+ },
201
+ inputArgs: {
202
+ isMobileLayout: true,
203
+ },
204
+ }),
205
+ );
206
+ let quickEditModel: QuickEditFormModel | undefined;
207
+ engine.forEachModel((model) => {
208
+ if (model instanceof QuickEditFormModel) {
209
+ quickEditModel = model;
210
+ }
211
+ });
212
+ expect(quickEditModel?.context.isMobileLayout).toBe(true);
213
+ });
214
+
215
+ it('uses the engine mobile layout context without relying on matchMedia', async () => {
216
+ engine.registerModels({ QuickEditFormModel });
217
+ addUsersCollection();
218
+ mockMatchMedia(false);
219
+ const open = vi.fn(async () => undefined);
220
+ engine.context.defineProperty('isMobileLayout', { value: true });
221
+ engine.context.defineProperty('viewer', { value: { open } });
222
+
223
+ await QuickEditFormModel.open({
224
+ flowEngine: engine,
225
+ target: document.createElement('div'),
226
+ dataSourceKey: 'main',
227
+ collectionName: 'users',
228
+ fieldPath: 'name',
229
+ record: {},
230
+ });
231
+
232
+ expect(window.matchMedia).not.toHaveBeenCalled();
233
+ expect(open).toHaveBeenCalledTimes(1);
234
+ expect(open).toHaveBeenCalledWith(
235
+ expect.objectContaining({
236
+ type: 'drawer',
237
+ title: 'Name',
238
+ placement: 'bottom',
239
+ }),
240
+ );
241
+ });
242
+
243
+ it('keeps an explicit non-mobile layout context ahead of the narrow viewport fallback', async () => {
244
+ engine.registerModels({ QuickEditFormModel });
245
+ addUsersCollection();
246
+ mockMatchMedia(true);
247
+ const open = vi.fn(async () => undefined);
248
+ const target = document.createElement('div');
249
+ engine.context.defineProperty('isMobileLayout', { value: false });
250
+ engine.context.defineProperty('viewer', { value: { open } });
251
+
252
+ await QuickEditFormModel.open({
253
+ flowEngine: engine,
254
+ target,
255
+ dataSourceKey: 'main',
256
+ collectionName: 'users',
257
+ fieldPath: 'name',
258
+ record: {},
259
+ });
260
+
261
+ expect(window.matchMedia).not.toHaveBeenCalled();
262
+ expect(open).toHaveBeenCalledTimes(1);
263
+ expect(open).toHaveBeenCalledWith(
264
+ expect.objectContaining({
265
+ type: 'popover',
266
+ target,
267
+ placement: 'rightTop',
268
+ }),
269
+ );
270
+ });
271
+
272
+ it('keeps dynamic preventClose effective for mobile quick edit drawer containers', async () => {
273
+ engine.registerModels({ QuickEditFormModel });
274
+ addUsersCollection();
275
+ mockMatchMedia(true);
276
+ const originalBeforeClose = vi.fn(async () => true);
277
+ const drawerView = {
278
+ close: vi.fn(),
279
+ update: vi.fn(),
280
+ beforeClose: originalBeforeClose,
281
+ };
282
+ const open = vi.fn(async (config: { content: (view: typeof drawerView) => unknown }) => {
283
+ config.content(drawerView);
284
+ });
285
+ engine.context.defineProperty('viewer', { value: { open } });
286
+
287
+ await QuickEditFormModel.open({
288
+ flowEngine: engine,
289
+ target: document.createElement('div'),
290
+ dataSourceKey: 'main',
291
+ collectionName: 'users',
292
+ fieldPath: 'name',
293
+ record: {},
294
+ });
295
+
296
+ let quickEditModel: QuickEditFormModel | undefined;
297
+ engine.forEachModel((model) => {
298
+ if (model instanceof QuickEditFormModel) {
299
+ quickEditModel = model;
300
+ }
301
+ });
302
+
303
+ quickEditModel?.viewContainer.update?.({ preventClose: true });
304
+ expect(drawerView.update).toHaveBeenCalledWith({ preventClose: true });
305
+ await expect(drawerView.beforeClose?.({ force: false })).resolves.toBe(false);
306
+ expect(originalBeforeClose).not.toHaveBeenCalled();
307
+
308
+ quickEditModel?.viewContainer.update?.({ preventClose: false });
309
+ expect(drawerView.update).toHaveBeenCalledWith({ preventClose: false });
310
+ await expect(drawerView.beforeClose?.({ force: false })).resolves.toBe(true);
311
+ expect(originalBeforeClose).toHaveBeenCalledTimes(1);
312
+ });
313
+
314
+ it('keeps desktop quick edit in the existing right-top popover', async () => {
315
+ engine.registerModels({ QuickEditFormModel });
316
+ mockMatchMedia(false);
317
+ const open = vi.fn(async () => undefined);
318
+ const target = document.createElement('div');
319
+ engine.context.defineProperty('viewer', { value: { open } });
320
+
321
+ await QuickEditFormModel.open({
322
+ flowEngine: engine,
323
+ target,
324
+ dataSourceKey: 'main',
325
+ collectionName: 'users',
326
+ fieldPath: 'name',
327
+ record: {},
328
+ });
329
+
330
+ expect(open).toHaveBeenCalledTimes(1);
331
+ expect(open).toHaveBeenCalledWith(
332
+ expect.objectContaining({
333
+ type: 'popover',
334
+ target,
335
+ placement: 'rightTop',
336
+ styles: {
337
+ body: expect.objectContaining({
338
+ width: 420,
339
+ }),
340
+ },
341
+ }),
342
+ );
343
+ });
344
+
345
+ it('keeps mobile quick edit content naturally sized with half-window scroll bounds', () => {
346
+ engine.registerModels({ QuickEditFormModel });
347
+ const model = engine.createModel<QuickEditFormModel>({
348
+ use: QuickEditFormModel,
349
+ uid: 'quick-edit-style',
350
+ });
351
+ model.context.defineProperty('isMobileLayout', { value: true });
352
+
353
+ const result = QuickEditFormModel.prototype.render.call(model) as any;
354
+ const formBody = result.props.children[0];
355
+ const actions = result.props.children[1];
356
+
357
+ expect(formBody.props.style).toMatchObject({
358
+ maxHeight: 'calc(50vh - var(--nb-mobile-page-header-height, 40px) - 132px)',
359
+ overflowY: 'auto',
360
+ padding: '8px var(--nb-mobile-page-tabs-content-padding, 12px) 0',
361
+ });
362
+ expect(formBody.props.style.minHeight).toBe(0);
363
+ expect(actions.props.style).toMatchObject({
364
+ justifyContent: 'flex-end',
365
+ padding: '8px var(--nb-mobile-page-tabs-content-padding, 12px) calc(80px + env(safe-area-inset-bottom, 0px))',
366
+ });
367
+ });
368
+
21
369
  it('calls update with filterByTk and merges primary key from ctx.collection/record', async () => {
22
370
  // 1) 准备数据源与集合(含主键字段)
23
371
  const dsm = engine.context.dataSourceManager;
@@ -9,6 +9,7 @@
9
9
 
10
10
  import { SingleRecordResource } from '@nocobase/flow-engine';
11
11
  import { describe, expect, it, vi } from 'vitest';
12
+ import { EditFormModel } from '../EditFormModel';
12
13
  import { submitHandler } from '../submitHandler';
13
14
 
14
15
  describe('submitHandler', () => {
@@ -52,13 +53,14 @@ describe('submitHandler', () => {
52
53
  t: (value: string) => value,
53
54
  };
54
55
 
55
- await submitHandler(ctx, {
56
+ const responseRecord = await submitHandler(ctx, {
56
57
  assignedValues: {
57
58
  status: 'published',
58
59
  reviewer: 'Alice',
59
60
  },
60
61
  });
61
62
 
63
+ expect(responseRecord).toEqual({ id: 1 });
62
64
  expect(resource.save).toHaveBeenCalledWith(
63
65
  {
64
66
  title: 'Draft title',
@@ -68,4 +70,55 @@ describe('submitHandler', () => {
68
70
  undefined,
69
71
  );
70
72
  });
73
+
74
+ it('returns the refreshed record for edit forms when the save response is empty', async () => {
75
+ const refreshedRecord = { id: 2, title: 'Updated title' };
76
+ const resource = Object.create(SingleRecordResource.prototype);
77
+ resource.getMeta = vi.fn((key: string) => (key === 'currentFilterByTk' ? 2 : undefined));
78
+ resource.setFilterByTk = vi.fn(() => resource);
79
+ resource.save = vi.fn(async () => undefined);
80
+ resource.refresh = vi.fn(async () => undefined);
81
+ resource.getData = vi.fn(() => refreshedRecord);
82
+
83
+ const blockModel = Object.create(EditFormModel.prototype);
84
+ const form = {
85
+ validateFields: vi.fn(async () => undefined),
86
+ getFieldsValue: vi.fn(() => ({
87
+ title: 'Updated title',
88
+ })),
89
+ };
90
+ Object.defineProperty(blockModel, 'context', { value: { form } });
91
+ Object.defineProperty(blockModel, 'collection', {
92
+ value: {
93
+ name: 'posts',
94
+ getFilterByTK: (record: { id?: number }) => record.id,
95
+ },
96
+ });
97
+ blockModel.resetUserModifiedFields = vi.fn();
98
+
99
+ const responseRecord = await submitHandler(
100
+ {
101
+ resource,
102
+ blockModel,
103
+ model: {
104
+ getStepParams: vi.fn(),
105
+ },
106
+ message: {
107
+ error: vi.fn(),
108
+ },
109
+ t: (value: string) => value,
110
+ },
111
+ {},
112
+ );
113
+
114
+ expect(responseRecord).toBe(refreshedRecord);
115
+ expect(resource.setFilterByTk).toHaveBeenCalledWith(2);
116
+ expect(resource.save).toHaveBeenCalledWith(
117
+ {
118
+ title: 'Updated title',
119
+ },
120
+ undefined,
121
+ );
122
+ expect(resource.refresh).toHaveBeenCalled();
123
+ });
71
124
  });
@@ -12,6 +12,16 @@ import { mergeAssignFieldValues, resolveAssignFieldValues } from '../assign-form
12
12
  import type { FormBlockModel } from './FormBlockModel';
13
13
  import { omitHiddenModelValuesFromSubmit, shouldSkipSubmitValidation, validateSubmitForm } from './submitValues';
14
14
 
15
+ function getResponseRecord(response: unknown) {
16
+ if (!response || typeof response !== 'object') {
17
+ return response;
18
+ }
19
+ if ('data' in response) {
20
+ return (response as { data?: unknown }).data;
21
+ }
22
+ return response;
23
+ }
24
+
15
25
  export async function submitHandler(ctx, params, cb?: (values?: any, filterByTk?: any) => void) {
16
26
  const resource = ctx.resource;
17
27
  const blockModel = ctx.blockModel as FormBlockModel;
@@ -41,30 +51,34 @@ export async function submitHandler(ctx, params, cb?: (values?: any, filterByTk?
41
51
  resource.setFilterByTk(currentFilterByTk);
42
52
  }
43
53
  }
44
- const data: any = cb ? await cb(values) : await resource.save(values, params.requestConfig);
54
+ const data: unknown = cb ? await cb(values) : await resource.save(values, params.requestConfig);
55
+ let responseRecord = getResponseRecord(data);
45
56
  if (isEditFormModel) {
46
57
  resource.isNewRecord = false;
47
58
  // 编辑表单保存成功后,表单应回到“已同步”状态:下一次刷新应允许覆盖为服务端值
48
59
  blockModel.resetUserModifiedFields?.();
49
60
  await resource.refresh();
61
+ responseRecord = responseRecord ?? resource.getData?.();
50
62
  } else {
51
63
  blockModel.form.resetFields();
52
64
  blockModel.emitter.emit('onFieldReset');
53
65
  blockModel.resetUserModifiedFields?.();
54
66
  blockModel.formValueRuntime?.resetAfterFormReset?.();
55
67
  if (ctx.view.inputArgs.collectionName === blockModel.collection.name && ctx.view.inputArgs.onChange) {
56
- ctx.view.inputArgs.onChange(data?.data);
68
+ ctx.view.inputArgs.onChange(responseRecord);
57
69
  }
58
70
  }
71
+ return responseRecord;
59
72
  } else if (resource instanceof MultiRecordResource) {
60
73
  const currentFilterByTk = resource.getMeta('currentFilterByTk');
61
74
  if (!currentFilterByTk) {
62
75
  ctx.message.error(ctx.t('No filterByTk found for multi-record resource.'));
63
76
  return;
64
77
  }
65
- (await cb)
78
+ const data = cb
66
79
  ? await cb(values, currentFilterByTk)
67
80
  : await resource.update(currentFilterByTk, values, params.requestConfig);
68
81
  blockModel.resetUserModifiedFields?.();
82
+ return getResponseRecord(data) ?? blockModel.getCurrentRecord?.();
69
83
  }
70
84
  }