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

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 (247) 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 +305 -157
  54. package/es/utils/markdownSanitize.d.ts +11 -0
  55. package/lib/index.js +305 -157
  56. package/package.json +7 -7
  57. package/src/BaseApplication.tsx +6 -0
  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__/authRedirect.test.ts +17 -0
  65. package/src/__tests__/exports.test.ts +16 -0
  66. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +199 -0
  67. package/src/__tests__/plugin-manager.test.tsx +44 -2
  68. package/src/__tests__/settings-center.test.tsx +40 -1
  69. package/src/authRedirect.ts +38 -0
  70. package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
  71. package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
  72. package/src/collection-manager/field-configure.ts +1 -1
  73. package/src/collection-manager/interfaces/id.ts +1 -1
  74. package/src/collection-manager/interfaces/m2m.tsx +2 -2
  75. package/src/collection-manager/interfaces/m2o.tsx +2 -2
  76. package/src/collection-manager/interfaces/nanoid.ts +1 -1
  77. package/src/collection-manager/interfaces/o2m.tsx +2 -2
  78. package/src/collection-manager/interfaces/obo.tsx +2 -2
  79. package/src/collection-manager/interfaces/oho.tsx +2 -2
  80. package/src/collection-manager/interfaces/properties/index.ts +2 -2
  81. package/src/collection-manager/interfaces/uuid.ts +1 -1
  82. package/src/components/AppComponents.tsx +19 -3
  83. package/src/components/form/JsonTextArea.tsx +21 -11
  84. package/src/components/form/VariableJsonTextArea.tsx +175 -0
  85. package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
  86. package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
  87. package/src/components/form/index.tsx +1 -0
  88. package/src/components/index.ts +1 -0
  89. package/src/entry-actions/EntryActionManager.ts +76 -0
  90. package/src/entry-actions/index.ts +10 -0
  91. package/src/flow/FlowPage.tsx +38 -3
  92. package/src/flow/__tests__/FlowPage.test.tsx +201 -27
  93. package/src/flow/__tests__/FlowRoute.test.tsx +1088 -105
  94. package/src/flow/__tests__/getKey.test.ts +7 -0
  95. package/src/flow/__tests__/resolveViewParamsToViewList.test.ts +34 -0
  96. package/src/flow/actions/__tests__/afterSuccess.test.ts +73 -0
  97. package/src/flow/actions/__tests__/dataScopeFilter.test.ts +120 -0
  98. package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +1022 -0
  99. package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +150 -1
  100. package/src/flow/actions/__tests__/linkageAssignField.legacy.test.ts +135 -0
  101. package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +19 -5
  102. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +92 -0
  103. package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +164 -0
  104. package/src/flow/actions/__tests__/subFormFieldLinkageRules.inputArgs.test.ts +2 -2
  105. package/src/flow/actions/afterSuccess.tsx +142 -3
  106. package/src/flow/actions/customVariable.tsx +1 -2
  107. package/src/flow/actions/dataScopeFilter.ts +20 -1
  108. package/src/flow/actions/dateTimeFormat.tsx +42 -28
  109. package/src/flow/actions/index.ts +1 -1
  110. package/src/flow/actions/linkageRules.tsx +123 -35
  111. package/src/flow/actions/linkageRulesFormValueRefresh.ts +22 -130
  112. package/src/flow/actions/openView.tsx +59 -5
  113. package/src/flow/actions/runjs.tsx +2 -14
  114. package/src/flow/actions/validation.tsx +62 -30
  115. package/src/flow/admin-shell/BaseLayoutModel.tsx +149 -3
  116. package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +187 -42
  117. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +1020 -110
  118. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +74 -13
  119. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +486 -0
  120. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +64 -8
  121. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.ts +5 -3
  122. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +61 -9
  123. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +71 -8
  124. package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +1 -1
  125. package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +23 -9
  126. package/src/flow/admin-shell/admin-layout/AppListRender.tsx +13 -2
  127. package/src/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.tsx +289 -0
  128. package/src/flow/admin-shell/admin-layout/HelpLite.tsx +1 -3
  129. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +188 -0
  130. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +99 -0
  131. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +518 -2
  132. package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +214 -2
  133. package/src/flow/admin-shell/admin-layout/index.ts +1 -0
  134. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +20 -0
  135. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +33 -5
  136. package/src/flow/admin-shell/admin-layout/useApplications.tsx +81 -12
  137. package/src/flow/common/Markdown/Display.tsx +14 -3
  138. package/src/flow/common/Markdown/Edit.tsx +19 -7
  139. package/src/flow/components/DefaultValue.tsx +1 -2
  140. package/src/flow/components/DynamicFlowsIcon.tsx +447 -126
  141. package/src/flow/components/FieldAssignRulesEditor.tsx +128 -21
  142. package/src/flow/components/FieldAssignValueInput.tsx +15 -11
  143. package/src/flow/components/FlowRoute.tsx +186 -25
  144. package/src/flow/components/__tests__/DynamicFlowsIcon.test.tsx +148 -2
  145. package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +508 -4
  146. package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +134 -0
  147. package/src/flow/components/__tests__/fieldAssignOptions.test.ts +151 -3
  148. package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +2 -17
  149. package/src/flow/components/code-editor/hooks/useCodeRunner.ts +2 -14
  150. package/src/flow/components/code-editor/runjsDiagnostics.ts +8 -45
  151. package/src/flow/components/fieldAssignOptions.ts +155 -27
  152. package/src/flow/flows/editMarkdownFlow.tsx +1 -1
  153. package/src/flow/getViewDiffAndUpdateHidden.tsx +1 -0
  154. package/src/flow/index.ts +1 -1
  155. package/src/flow/internal/components/Markdown/util.ts +2 -1
  156. package/src/flow/internal/utils/operatorSchemaHelper.ts +15 -1
  157. package/src/flow/models/actions/JSActionModel.tsx +2 -7
  158. package/src/flow/models/actions/JSCollectionActionModel.tsx +2 -7
  159. package/src/flow/models/actions/JSItemActionModel.tsx +2 -7
  160. package/src/flow/models/actions/JSRecordActionModel.tsx +2 -7
  161. package/src/flow/models/base/ActionModel.tsx +21 -8
  162. package/src/flow/models/base/ActionModelCore.tsx +11 -4
  163. package/src/flow/models/base/GridModel.tsx +38 -7
  164. package/src/flow/models/base/PageModel/PageModel.tsx +4 -1
  165. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +28 -4
  166. package/src/flow/models/base/__tests__/ActionModel.test.ts +83 -0
  167. package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +37 -0
  168. package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +239 -1
  169. package/src/flow/models/base/__tests__/transformRowsToSingleColumn.test.ts +48 -0
  170. package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +32 -3
  171. package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +27 -12
  172. package/src/flow/models/blocks/filter-form/FilterFormJSActionModel.tsx +2 -7
  173. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +36 -0
  174. package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +111 -0
  175. package/src/flow/models/blocks/filter-form/fields/FilterFormCustomFieldModel.tsx +1 -1
  176. package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/customizeFilterRender.test.tsx +56 -1
  177. package/src/flow/models/blocks/form/EditFormModel.tsx +1 -0
  178. package/src/flow/models/blocks/form/FormActionModel.tsx +1 -1
  179. package/src/flow/models/blocks/form/FormBlockModel.tsx +7 -0
  180. package/src/flow/models/blocks/form/JSFormActionModel.tsx +2 -7
  181. package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
  182. package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
  183. package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
  184. package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +54 -1
  185. package/src/flow/models/blocks/form/submitHandler.ts +17 -3
  186. package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +940 -75
  187. package/src/flow/models/blocks/form/value-runtime/rules.ts +445 -13
  188. package/src/flow/models/blocks/form/value-runtime/runtime.ts +348 -13
  189. package/src/flow/models/blocks/form/value-runtime/types.ts +2 -2
  190. package/src/flow/models/blocks/js-block/JSBlock.tsx +225 -9
  191. package/src/flow/models/blocks/js-block/__tests__/JSBlockModel.test.tsx +150 -0
  192. package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
  193. package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +36 -13
  194. package/src/flow/models/blocks/table/TableBlockModel.tsx +38 -17
  195. package/src/flow/models/blocks/table/TableColumnModel.tsx +42 -5
  196. package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +111 -1
  197. package/src/flow/models/blocks/table/__tests__/TableBlockModel.dragSort.test.ts +127 -0
  198. package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
  199. package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +1 -0
  200. package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +210 -1
  201. package/src/flow/models/blocks/table/dragSort/dragSortHooks.tsx +28 -17
  202. package/src/flow/models/blocks/table/dragSort/dragSortSettings.ts +13 -6
  203. package/src/flow/models/blocks/table/dragSort/dragSortUtils.ts +15 -2
  204. package/src/flow/models/fields/AssociationFieldModel/AssociationFieldModel.tsx +1 -1
  205. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +30 -6
  206. package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +61 -17
  207. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +141 -19
  208. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +13 -1
  209. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +197 -0
  210. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableFieldModel.reset.test.ts +180 -0
  211. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -4
  212. package/src/flow/models/fields/AssociationFieldModel/__tests__/AssociationFieldModel.updateAssociation.test.ts +72 -0
  213. package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +23 -0
  214. package/src/flow/models/fields/ClickableFieldModel.tsx +5 -0
  215. package/src/flow/models/fields/DisplayDateTimeFieldModel.tsx +29 -1
  216. package/src/flow/models/fields/InputFieldModel.tsx +40 -2
  217. package/src/flow/models/fields/JSEditableFieldModel.tsx +2 -11
  218. package/src/flow/models/fields/JSFieldModel.tsx +2 -7
  219. package/src/flow/models/fields/JSItemModel.tsx +2 -14
  220. package/src/flow/models/fields/SelectFieldModel.tsx +6 -4
  221. package/src/flow/models/fields/TextareaFieldModel.tsx +72 -3
  222. package/src/flow/models/fields/__tests__/DisplayDateTimeFieldModel.test.tsx +96 -0
  223. package/src/flow/models/fields/__tests__/InputFieldModel.test.tsx +117 -1
  224. package/src/flow/models/fields/__tests__/SelectFieldModel.test.tsx +43 -0
  225. package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +131 -0
  226. package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +20 -10
  227. package/src/flow/models/fields/mobile-components/MobileSelect.tsx +24 -12
  228. package/src/flow/models/topbar/TopbarActionModel.tsx +168 -10
  229. package/src/flow/resolveViewParamsToViewList.tsx +11 -3
  230. package/src/flow/routeTransientInputArgs.ts +27 -0
  231. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +300 -0
  232. package/src/flow/utils/dataScopeFormValueClear.ts +218 -81
  233. package/src/flow/utils/dataScopeRowSnapshot.ts +616 -0
  234. package/src/flow/utils/dateTimeDisplayProps.ts +135 -0
  235. package/src/flow/utils/formValueDeps.ts +170 -0
  236. package/src/flow-compat/FieldValidation.tsx +122 -60
  237. package/src/flow-compat/Popover.tsx +43 -4
  238. package/src/flow-compat/__tests__/Popover.test.tsx +34 -0
  239. package/src/index.ts +10 -1
  240. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
  241. package/src/nocobase-buildin-plugin/index.tsx +51 -15
  242. package/src/settings-center/AdminSettingsLayout.tsx +10 -2
  243. package/src/settings-center/SystemSettingsPage.tsx +1 -2
  244. package/src/settings-center/plugin-manager/PluginCard.tsx +2 -2
  245. package/src/settings-center/plugin-manager/index.tsx +3 -0
  246. package/src/settings-center/utils.tsx +0 -6
  247. package/src/utils/markdownSanitize.ts +88 -0
@@ -210,7 +210,7 @@ FilterFormCustomFieldModel.registerFlow({
210
210
  'x-decorator': 'FormItem',
211
211
  required: true,
212
212
  description:
213
- '{{t("Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.")}}',
213
+ '{{t("Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.")}}',
214
214
  },
215
215
  source: {
216
216
  type: 'array',
@@ -18,12 +18,23 @@ type TestFieldModel = {
18
18
  setupReactiveRender: ReturnType<typeof vi.fn>;
19
19
  _reactiveWrapperCache?: unknown;
20
20
  __originalRender?: () => React.ReactNode;
21
+ translate?: (text: string) => string;
21
22
  };
22
23
 
23
24
  const MultipleKeywordsInput = (props: Record<string, unknown>) => {
24
25
  return <div data-testid="multiple-keywords-input" {...props} />;
25
26
  };
26
27
 
28
+ const Select = (props: Record<string, unknown>) => {
29
+ return <div data-testid="select" {...props} />;
30
+ };
31
+
32
+ function mockT(text: string) {
33
+ if (text === '{{t("Yes")}}') return '是';
34
+ if (text === '{{t("No")}}') return '否';
35
+ return text;
36
+ }
37
+
27
38
  function createFieldModel() {
28
39
  return {
29
40
  props: {
@@ -52,7 +63,10 @@ function createFilterItemModel({
52
63
  collectionField,
53
64
  context: {
54
65
  app: {
55
- getComponent: (name: string) => (name === 'MultipleKeywordsInput' ? MultipleKeywordsInput : undefined),
66
+ getComponent: (name: string) => {
67
+ if (name === 'MultipleKeywordsInput') return MultipleKeywordsInput;
68
+ if (name === 'Select') return Select;
69
+ },
56
70
  },
57
71
  collectionField,
58
72
  },
@@ -112,6 +126,47 @@ describe('customizeFilterRender action', () => {
112
126
  expect((rerenderedElement as React.ReactElement).props.placeholder).toBe('updated runtime placeholder');
113
127
  });
114
128
 
129
+ it('translates operator schema select options', () => {
130
+ const fieldModel = {
131
+ ...createFieldModel(),
132
+ translate: mockT,
133
+ };
134
+ const model = createFilterItemModel({
135
+ fieldModel,
136
+ operator: '$isTruly',
137
+ collectionField: {
138
+ interface: 'checkbox',
139
+ type: 'boolean',
140
+ filterable: {
141
+ operators: [
142
+ {
143
+ label: '{{t("Yes")}}',
144
+ value: '$isTruly',
145
+ schema: {
146
+ 'x-component': 'Select',
147
+ 'x-component-props': {
148
+ options: [
149
+ { label: '{{t("Yes")}}', value: true },
150
+ { label: '{{t("No")}}', value: false },
151
+ ],
152
+ },
153
+ },
154
+ },
155
+ ],
156
+ },
157
+ },
158
+ });
159
+
160
+ customizeFilterRender.handler?.({ model } as any);
161
+
162
+ const element = fieldModel.render();
163
+ expect((element as React.ReactElement).type).toBe(Select);
164
+ expect((element as React.ReactElement).props.options).toEqual([
165
+ { label: '是', value: true },
166
+ { label: '否', value: false },
167
+ ]);
168
+ });
169
+
115
170
  it('restores original render when switching to an operator without a schema component', () => {
116
171
  const fieldModel = createFieldModel();
117
172
  const originalRender = fieldModel.render;
@@ -239,6 +239,7 @@ EditFormModel.registerFlow({
239
239
  // 切换记录(翻页等)时,清理“用户改动标记”,避免把上一条记录的编辑状态带到下一条
240
240
  if (recordChanged) {
241
241
  ctx.model.resetUserModifiedFields?.();
242
+ ctx.model.resetRuntimeUserEditedState?.();
242
243
  }
243
244
 
244
245
  ctx.form.setFieldsValue(currentRecord);
@@ -117,7 +117,7 @@ FormSubmitActionModel.registerFlow({
117
117
  try {
118
118
  ctx.model.setProps('loading', true);
119
119
  const { submitHandler } = await import('./submitHandler');
120
- await submitHandler(ctx, params);
120
+ return await submitHandler(ctx, params);
121
121
  } catch (error) {
122
122
  ctx.model.setProps('loading', false);
123
123
  if (error instanceof FlowExitAllException) {
@@ -166,6 +166,13 @@ export class FormBlockModel<
166
166
  this.userModifiedTopLevelFields.clear();
167
167
  }
168
168
 
169
+ /**
170
+ * @internal
171
+ */
172
+ resetRuntimeUserEditedState(): void {
173
+ this.formValueRuntime?.resetUserEditedState?.();
174
+ }
175
+
169
176
  public async onDispatchEventStart(eventName: string, options?: any, inputArgs?: Record<string, any>): Promise<void> {
170
177
  if (eventName === 'beforeRender') {
171
178
  const grid = this.subModels.grid;
@@ -10,7 +10,7 @@
10
10
  /**
11
11
  * JS Form Action:表单工具栏按钮点击执行 JS。
12
12
  */
13
- import { tExpr, createSafeWindow, createSafeDocument, createSafeNavigator } from '@nocobase/flow-engine';
13
+ import { tExpr } from '@nocobase/flow-engine';
14
14
  import { CodeEditor } from '../../../components/code-editor';
15
15
  import { FormActionModel } from './FormActionModel';
16
16
  import { resolveRunJsParams } from '../../utils/resolveRunJsParams';
@@ -73,12 +73,7 @@ ctx.message.success('Current form values: ' + JSON.stringify(values));
73
73
  await ctx.resource.refresh();
74
74
  }
75
75
  });
76
- const navigator = createSafeNavigator();
77
- await ctx.runjs(
78
- code,
79
- { window: createSafeWindow({ navigator }), document: createSafeDocument(), navigator },
80
- { version },
81
- );
76
+ await ctx.runjs(code, undefined, { version });
82
77
  },
83
78
  },
84
79
  },
@@ -22,7 +22,6 @@ import {
22
22
  createRecordResolveOnServerWithLocal,
23
23
  } from '@nocobase/flow-engine';
24
24
  import { Button, Form, Skeleton, Space } from 'antd';
25
- import _ from 'lodash';
26
25
  import React from 'react';
27
26
  import { FieldModel } from '../../base/FieldModel';
28
27
  import { FormComponent } from './FormBlockModel';
@@ -31,6 +30,37 @@ import { FormItemModel } from './FormItemModel';
31
30
  export const QUICK_EDIT_POPOVER_MAX_HEIGHT = 'calc(100vh - 96px)';
32
31
  export const QUICK_EDIT_FORM_MAX_HEIGHT = 'calc(100vh - 160px)';
33
32
  export const QUICK_EDIT_MARKDOWN_HEIGHT = 'min(480px, calc(100vh - 320px))';
33
+ const QUICK_EDIT_MOBILE_DRAWER_HEIGHT = '50vh';
34
+ const QUICK_EDIT_MOBILE_FORM_MAX_HEIGHT = 'calc(50vh - var(--nb-mobile-page-header-height, 40px) - 132px)';
35
+ const QUICK_EDIT_MOBILE_CONTENT_PADDING = '8px var(--nb-mobile-page-tabs-content-padding, 12px) 0';
36
+ const QUICK_EDIT_MOBILE_ACTIONS_PADDING =
37
+ '8px var(--nb-mobile-page-tabs-content-padding, 12px) calc(80px + env(safe-area-inset-bottom, 0px))';
38
+ const QUICK_EDIT_MOBILE_MEDIA_QUERY = '(max-width: 768px)';
39
+
40
+ type QuickEditViewBeforeClosePayload = {
41
+ result?: unknown;
42
+ force?: boolean;
43
+ [key: string]: unknown;
44
+ };
45
+
46
+ type QuickEditViewBeforeCloseHandler = (
47
+ payload: QuickEditViewBeforeClosePayload,
48
+ ) => Promise<boolean | void> | boolean | void;
49
+
50
+ type QuickEditViewUpdateConfig = {
51
+ preventClose?: boolean;
52
+ [key: string]: unknown;
53
+ };
54
+
55
+ type QuickEditViewContainer = {
56
+ close: (result?: unknown, force?: boolean) => Promise<boolean | void> | boolean | void;
57
+ update?: (newConfig: QuickEditViewUpdateConfig) => unknown;
58
+ beforeClose?: QuickEditViewBeforeCloseHandler;
59
+ };
60
+
61
+ type QuickEditViewContext = {
62
+ defineProperty?: (key: string, options: { value: unknown }) => void;
63
+ };
34
64
 
35
65
  export function getQuickEditFieldProps(collectionField: CollectionField, fieldProps?: Record<string, any>) {
36
66
  const nextProps = { ...collectionField.getComponentProps(), ...(fieldProps || {}) };
@@ -40,15 +70,92 @@ export function getQuickEditFieldProps(collectionField: CollectionField, fieldPr
40
70
  return nextProps;
41
71
  }
42
72
 
73
+ function getQuickEditMobileLayoutState(flowEngine: FlowEngine, sourceFieldModel?: FlowModel) {
74
+ const sourceMobileLayout = sourceFieldModel?.context?.isMobileLayout;
75
+ if (typeof sourceMobileLayout === 'boolean') {
76
+ return { isMobileLayout: sourceMobileLayout, inheritsMobileContext: sourceMobileLayout };
77
+ }
78
+
79
+ const engineMobileLayout = flowEngine.context?.isMobileLayout;
80
+ if (typeof engineMobileLayout === 'boolean') {
81
+ return { isMobileLayout: engineMobileLayout, inheritsMobileContext: engineMobileLayout };
82
+ }
83
+
84
+ if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
85
+ return { isMobileLayout: false, inheritsMobileContext: false };
86
+ }
87
+
88
+ return {
89
+ isMobileLayout: window.matchMedia(QUICK_EDIT_MOBILE_MEDIA_QUERY).matches,
90
+ inheritsMobileContext: false,
91
+ };
92
+ }
93
+
94
+ function getQuickEditTitle(
95
+ flowEngine: FlowEngine,
96
+ dataSourceKey: string,
97
+ collectionName: string,
98
+ fieldPath: string,
99
+ sourceFieldModel?: FlowModel,
100
+ fieldProps?: Record<string, unknown>,
101
+ ): React.ReactNode {
102
+ const sourceColumnTitle = sourceFieldModel?.parent?.props?.title;
103
+ if (sourceColumnTitle) {
104
+ return sourceColumnTitle;
105
+ }
106
+
107
+ const fieldPropsTitle = fieldProps?.title;
108
+ if (fieldPropsTitle) {
109
+ return fieldPropsTitle as React.ReactNode;
110
+ }
111
+
112
+ const collectionField = flowEngine.context.dataSourceManager.getCollectionField(
113
+ `${dataSourceKey}.${collectionName}.${fieldPath}`,
114
+ ) as CollectionField | undefined;
115
+ return collectionField?.title || fieldPath;
116
+ }
117
+
118
+ function createQuickEditViewContainer(view: QuickEditViewContainer): QuickEditViewContainer {
119
+ let preventClose = false;
120
+ let nextBeforeClose = view.beforeClose;
121
+ const beforeClose: QuickEditViewBeforeCloseHandler = async (payload) => {
122
+ if (preventClose && !payload?.force) {
123
+ return false;
124
+ }
125
+
126
+ const result = await nextBeforeClose?.(payload);
127
+ return result !== false;
128
+ };
129
+ view.beforeClose = beforeClose;
130
+
131
+ return {
132
+ ...view,
133
+ close(result, force) {
134
+ return view.close(result, force);
135
+ },
136
+ update(newConfig) {
137
+ if (Object.prototype.hasOwnProperty.call(newConfig, 'preventClose')) {
138
+ preventClose = !!newConfig.preventClose;
139
+ }
140
+ return view.update?.(newConfig);
141
+ },
142
+ get beforeClose() {
143
+ return view.beforeClose;
144
+ },
145
+ set beforeClose(value) {
146
+ nextBeforeClose = value;
147
+ view.beforeClose = beforeClose;
148
+ },
149
+ };
150
+ }
151
+
43
152
  export class QuickEditFormModel extends FlowModel {
44
153
  fieldPath: string;
45
154
 
46
155
  declare resource: SingleRecordResource;
47
156
  declare collection: Collection;
48
157
 
49
- now: number = Date.now();
50
-
51
- viewContainer: any;
158
+ declare viewContainer: QuickEditViewContainer;
52
159
  __onSubmitSuccess;
53
160
  _fieldProps: any;
54
161
  _onOk: any;
@@ -89,21 +196,75 @@ export class QuickEditFormModel extends FlowModel {
89
196
  onOk,
90
197
  sourceFieldModelUid,
91
198
  } = options;
92
- const model = flowEngine.createModel({
93
- use: 'QuickEditFormModel',
94
- stepParams: {
95
- quickEditFormSettings: {
96
- init: {
97
- dataSourceKey,
98
- collectionName,
99
- fieldPath,
199
+ const sourceFieldModel = sourceFieldModelUid ? flowEngine.getModel<FlowModel>(sourceFieldModelUid) : undefined;
200
+ const model = flowEngine.createModel(
201
+ {
202
+ use: 'QuickEditFormModel',
203
+ stepParams: {
204
+ quickEditFormSettings: {
205
+ init: {
206
+ dataSourceKey,
207
+ collectionName,
208
+ fieldPath,
209
+ },
100
210
  },
101
211
  },
102
212
  },
103
- }) as QuickEditFormModel;
213
+ sourceFieldModel ? { delegate: sourceFieldModel.context } : undefined,
214
+ ) as QuickEditFormModel;
104
215
 
105
- console.log('QuickEditFormModel.open2', Date.now() - model.now);
106
- model.now = Date.now();
216
+ const bodyStyles = {
217
+ maxHeight: QUICK_EDIT_POPOVER_MAX_HEIGHT,
218
+ overflowY: 'auto',
219
+ overscrollBehavior: 'contain',
220
+ };
221
+ const mobileBodyStyles = {
222
+ ...bodyStyles,
223
+ maxHeight: QUICK_EDIT_MOBILE_DRAWER_HEIGHT,
224
+ };
225
+ const content = (view: QuickEditViewContainer, viewContext?: QuickEditViewContext) => {
226
+ if (mobileLayoutState.isMobileLayout) {
227
+ viewContext?.defineProperty?.('isMobileLayout', { value: true });
228
+ }
229
+ model.viewContainer = createQuickEditViewContainer(view);
230
+ model.__onSubmitSuccess = onSuccess;
231
+ model._fieldProps = fieldProps;
232
+ model._onOk = onOk;
233
+ return (
234
+ <FlowModelRenderer
235
+ fallback={<Skeleton.Input size="small" />}
236
+ model={model}
237
+ inputArgs={{ filterByTk, record, sourceFieldModelUid }}
238
+ />
239
+ );
240
+ };
241
+ const mobileLayoutState = getQuickEditMobileLayoutState(flowEngine, sourceFieldModel);
242
+ if (mobileLayoutState.isMobileLayout) {
243
+ model.context.defineProperty('isMobileLayout', { value: true });
244
+ const viewer = sourceFieldModel?.context?.viewer || flowEngine.context.viewer;
245
+ const title = getQuickEditTitle(
246
+ flowEngine,
247
+ dataSourceKey,
248
+ collectionName,
249
+ fieldPath,
250
+ sourceFieldModel,
251
+ fieldProps,
252
+ );
253
+ await viewer.open({
254
+ type: 'drawer',
255
+ title,
256
+ closable: true,
257
+ placement: 'bottom',
258
+ inputArgs: {
259
+ isMobileLayout: true,
260
+ },
261
+ styles: {
262
+ body: mobileBodyStyles,
263
+ },
264
+ content,
265
+ });
266
+ return;
267
+ }
107
268
 
108
269
  await flowEngine.context.viewer.open({
109
270
  type: 'popover',
@@ -112,25 +273,10 @@ export class QuickEditFormModel extends FlowModel {
112
273
  styles: {
113
274
  body: {
114
275
  width: 420,
115
- maxHeight: QUICK_EDIT_POPOVER_MAX_HEIGHT,
116
- overflowY: 'auto',
117
- overscrollBehavior: 'contain',
276
+ ...bodyStyles,
118
277
  },
119
278
  },
120
- content: (popover) => {
121
- model.viewContainer = popover;
122
- model.__onSubmitSuccess = onSuccess;
123
- model._fieldProps = fieldProps;
124
- model._onOk = onOk;
125
- console.log('QuickEditFormModel.open3', Date.now() - model.now);
126
- return (
127
- <FlowModelRenderer
128
- fallback={<Skeleton.Input size="small" />}
129
- model={model}
130
- inputArgs={{ filterByTk, record, sourceFieldModelUid }}
131
- />
132
- );
133
- },
279
+ content,
134
280
  });
135
281
  }
136
282
 
@@ -171,15 +317,15 @@ export class QuickEditFormModel extends FlowModel {
171
317
  }
172
318
 
173
319
  render() {
174
- console.log('QuickEditFormModel.open4', Date.now() - this.now);
175
-
320
+ const isMobileLayout = this.context.isMobileLayout;
176
321
  return (
177
322
  <FormComponent model={this}>
178
323
  <div
179
324
  style={{
180
325
  minHeight: 0,
181
326
  overflowY: 'auto',
182
- maxHeight: QUICK_EDIT_FORM_MAX_HEIGHT,
327
+ maxHeight: isMobileLayout ? QUICK_EDIT_MOBILE_FORM_MAX_HEIGHT : QUICK_EDIT_FORM_MAX_HEIGHT,
328
+ padding: isMobileLayout ? QUICK_EDIT_MOBILE_CONTENT_PADDING : undefined,
183
329
  }}
184
330
  >
185
331
  {this.mapSubModels('fields', (field) => {
@@ -196,7 +342,14 @@ export class QuickEditFormModel extends FlowModel {
196
342
  );
197
343
  })}
198
344
  </div>
199
- <Space style={{ display: 'flex', justifyContent: 'flex-end', flexShrink: 0 }}>
345
+ <Space
346
+ style={{
347
+ display: 'flex',
348
+ justifyContent: 'flex-end',
349
+ flexShrink: 0,
350
+ padding: isMobileLayout ? QUICK_EDIT_MOBILE_ACTIONS_PADDING : undefined,
351
+ }}
352
+ >
200
353
  <Button
201
354
  onClick={() => {
202
355
  this.viewContainer.close();
@@ -420,6 +420,23 @@ describe('FormBlockModel (form/formValues injection & server resolve anchors)',
420
420
  expect(savedUids).toContain('grid-1');
421
421
  });
422
422
 
423
+ it('keeps dirty reset separate from runtime user-edited reset', async () => {
424
+ const model = await setupFormModel();
425
+ const resetUserEditedState = vi.fn();
426
+ model.formValueRuntime = { resetUserEditedState } as any;
427
+
428
+ model.markUserModifiedFields({ status: 'draft' });
429
+ expect(model.getUserModifiedFields().has('status')).toBe(true);
430
+
431
+ model.resetUserModifiedFields();
432
+
433
+ expect(model.getUserModifiedFields().size).toBe(0);
434
+ expect(resetUserEditedState).not.toHaveBeenCalled();
435
+
436
+ model.resetRuntimeUserEditedState();
437
+ expect(resetUserEditedState).toHaveBeenCalledTimes(1);
438
+ });
439
+
423
440
  it('re-syncs delegated assignRules when grid submodel is added after block init', async () => {
424
441
  const model = await setupFormModel();
425
442
  const syncAssignRules = vi.fn();