@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
@@ -130,9 +130,14 @@ describe('TableColumnModel sorter settings', () => {
130
130
  const titleFieldStep = model.getFlow('tableColumnSettings')?.steps?.fieldNames as any;
131
131
  const setStepParams = vi.fn();
132
132
  const setProps = vi.fn();
133
+ const setFieldProps = vi.fn();
133
134
  const dispatchEvent = vi.fn();
135
+ const saveFieldModel = vi.fn();
134
136
  const targetCollectionField = {
135
- getComponentProps: () => ({}),
137
+ getComponentProps: () => ({
138
+ format: 'hh:mm:ss a',
139
+ timeFormat: 'hh:mm:ss a',
140
+ }),
136
141
  };
137
142
 
138
143
  await titleFieldStep.beforeParamsSave(
@@ -156,8 +161,10 @@ describe('TableColumnModel sorter settings', () => {
156
161
  subModels: {
157
162
  field: {
158
163
  use: 'DisplayTextFieldModel',
164
+ setProps: setFieldProps,
159
165
  setStepParams,
160
166
  dispatchEvent,
167
+ save: saveFieldModel,
161
168
  },
162
169
  },
163
170
  setStepParams,
@@ -169,6 +176,206 @@ describe('TableColumnModel sorter settings', () => {
169
176
  );
170
177
 
171
178
  expect(setStepParams).toHaveBeenCalledWith('tableColumnSettings', 'model', { use: 'DisplayTextFieldModel' });
179
+ expect(setFieldProps).toHaveBeenCalledWith(
180
+ expect.objectContaining({
181
+ format: 'hh:mm:ss a',
182
+ timeFormat: 'hh:mm:ss a',
183
+ titleField: 'code',
184
+ }),
185
+ );
186
+ expect(setProps).toHaveBeenCalledWith(
187
+ expect.objectContaining({
188
+ format: 'hh:mm:ss a',
189
+ timeFormat: 'hh:mm:ss a',
190
+ titleField: 'code',
191
+ }),
192
+ );
193
+ expect(saveFieldModel).toHaveBeenCalled();
194
+ });
195
+
196
+ it('clears stale datetime display props when association title field changes to date only', async () => {
197
+ const engine = new FlowEngine();
198
+ const model = new TableColumnModel({ uid: 'table-column-title-field-date-only', flowEngine: engine } as any);
199
+ const titleFieldStep = model.getFlow('tableColumnSettings')?.steps?.fieldNames as any;
200
+ const setStepParams = vi.fn();
201
+ const setProps = vi.fn();
202
+ const setFieldProps = vi.fn();
203
+ const targetCollectionField = {
204
+ getComponentProps: () => ({
205
+ dateOnly: true,
206
+ showTime: false,
207
+ }),
208
+ };
209
+
210
+ await titleFieldStep.beforeParamsSave(
211
+ {
212
+ collectionField: {
213
+ isAssociationField: () => true,
214
+ targetCollection: {
215
+ name: 'departments',
216
+ getField: () => targetCollectionField,
217
+ },
218
+ },
219
+ model: {
220
+ collectionField: {
221
+ dataSourceKey: 'main',
222
+ },
223
+ constructor: {
224
+ getDefaultBindingByField: () => ({
225
+ modelName: 'DisplayDateTimeFieldModel',
226
+ }),
227
+ },
228
+ subModels: {
229
+ field: {
230
+ use: 'DisplayDateTimeFieldModel',
231
+ setProps: setFieldProps,
232
+ setStepParams,
233
+ dispatchEvent: vi.fn(),
234
+ save: vi.fn(),
235
+ },
236
+ },
237
+ setStepParams,
238
+ setProps,
239
+ },
240
+ },
241
+ { label: 'dateOnly' },
242
+ { label: 'time' },
243
+ );
244
+
245
+ expect(setFieldProps).toHaveBeenCalledWith(
246
+ expect.objectContaining({
247
+ dateOnly: true,
248
+ format: undefined,
249
+ showTime: false,
250
+ timeFormat: undefined,
251
+ titleField: 'dateOnly',
252
+ }),
253
+ );
254
+ expect(setProps).toHaveBeenCalledWith(
255
+ expect.objectContaining({
256
+ dateOnly: true,
257
+ format: undefined,
258
+ showTime: false,
259
+ timeFormat: undefined,
260
+ titleField: 'dateOnly',
261
+ }),
262
+ );
263
+ });
264
+
265
+ it('keeps saved association title datetime format when table column initializes again', async () => {
266
+ const engine = new FlowEngine();
267
+ const model = new TableColumnModel({
268
+ uid: 'table-column-title-field-saved-datetime-format',
269
+ flowEngine: engine,
270
+ } as any);
271
+ const initStep = model.getFlow('tableColumnSettings')?.steps?.init as any;
272
+ const setProps = vi.fn();
273
+ const targetCollectionField = {
274
+ getComponentProps: () => ({
275
+ dateFormat: 'YYYY-MM-DD',
276
+ format: 'YYYY-MM-DD HH:mm:ss',
277
+ showTime: true,
278
+ timeFormat: 'HH:mm:ss',
279
+ }),
280
+ };
281
+
282
+ await initStep.handler({
283
+ model: {
284
+ context: {
285
+ collectionField: {
286
+ title: 'Shipments',
287
+ name: 'shipments',
288
+ isAssociationField: () => true,
289
+ getComponentProps: () => ({
290
+ fieldNames: {
291
+ label: 'shipmentsDatetime',
292
+ },
293
+ }),
294
+ targetCollection: {
295
+ getField: () => targetCollectionField,
296
+ },
297
+ },
298
+ },
299
+ props: {
300
+ titleField: 'shipmentsDatetime',
301
+ },
302
+ subModels: {
303
+ field: {
304
+ getStepParams: (flowKey, stepKey) =>
305
+ flowKey === 'datetimeSettings' && stepKey === 'dateFormat'
306
+ ? {
307
+ picker: 'date',
308
+ dateFormat: 'YYYY-MM-DD',
309
+ showTime: true,
310
+ timeFormat: 'h:mm a',
311
+ }
312
+ : undefined,
313
+ },
314
+ },
315
+ applySubModelsBeforeRenderFlows: vi.fn(),
316
+ setProps,
317
+ },
318
+ });
319
+
320
+ expect(setProps).toHaveBeenCalledWith(
321
+ expect.objectContaining({
322
+ dateFormat: 'YYYY-MM-DD',
323
+ format: 'YYYY-MM-DD h:mm a',
324
+ showTime: true,
325
+ timeFormat: 'h:mm a',
326
+ }),
327
+ );
328
+ });
329
+
330
+ it('keeps saved ordinary datetime format when table column initializes again', async () => {
331
+ const engine = new FlowEngine();
332
+ const model = new TableColumnModel({
333
+ uid: 'table-column-saved-datetime-format',
334
+ flowEngine: engine,
335
+ } as any);
336
+ const initStep = model.getFlow('tableColumnSettings')?.steps?.init as any;
337
+ const setProps = vi.fn();
338
+
339
+ await initStep.handler({
340
+ model: {
341
+ context: {
342
+ collectionField: {
343
+ title: 'Datetime',
344
+ name: 'datetime',
345
+ isAssociationField: () => false,
346
+ getComponentProps: () => ({
347
+ dateFormat: 'YYYY-MM-DD',
348
+ showTime: false,
349
+ }),
350
+ },
351
+ },
352
+ props: {},
353
+ subModels: {
354
+ field: {
355
+ getStepParams: (flowKey, stepKey) =>
356
+ flowKey === 'datetimeSettings' && stepKey === 'dateFormat'
357
+ ? {
358
+ picker: 'date',
359
+ dateFormat: 'YYYY-MM-DD',
360
+ showTime: true,
361
+ timeFormat: 'HH:mm:ss',
362
+ }
363
+ : undefined,
364
+ },
365
+ },
366
+ applySubModelsBeforeRenderFlows: vi.fn(),
367
+ setProps,
368
+ },
369
+ });
370
+
371
+ expect(setProps).toHaveBeenCalledWith(
372
+ expect.objectContaining({
373
+ dateFormat: 'YYYY-MM-DD',
374
+ format: 'YYYY-MM-DD HH:mm:ss',
375
+ showTime: true,
376
+ timeFormat: 'HH:mm:ss',
377
+ }),
378
+ );
172
379
  });
173
380
 
174
381
  it('does not update field component setting when title field refresh fails', async () => {
@@ -203,8 +410,10 @@ describe('TableColumnModel sorter settings', () => {
203
410
  subModels: {
204
411
  field: {
205
412
  use: 'DisplayTextFieldModel',
413
+ setProps: vi.fn(),
206
414
  setStepParams: vi.fn(),
207
415
  dispatchEvent: vi.fn().mockRejectedValue(new Error('beforeRender failed')),
416
+ save: vi.fn(),
208
417
  },
209
418
  },
210
419
  setStepParams,
@@ -19,16 +19,23 @@ export function useDragSortBodyWrapper(
19
19
  model: TableBlockModel,
20
20
  dataSourceRef: React.MutableRefObject<any>,
21
21
  getRowKeyFunc: (record: any) => string | number,
22
+ dragSortFieldName?: string,
22
23
  ) {
24
+ const expandedRowKeys = model.props.expandedRowKeys;
25
+ const defaultExpandAllRows = model.props.defaultExpandAllRows;
26
+ const resource = model.resource;
27
+ const filterTargetKey = model.collection.filterTargetKey;
28
+ const message = (model as { ctx?: { message?: { error?: (content: string) => void } } }).ctx?.message;
29
+
23
30
  return useMemo(() => {
24
- if (!model.props.dragSort) {
31
+ if (!dragSortFieldName) {
25
32
  return (props) => <tbody {...props} />;
26
33
  }
27
34
 
28
35
  const flattenTreeData = (data: any[]) => {
29
36
  const result: any[] = [];
30
- const expandedKeys = new Set((model.props.expandedRowKeys || []).map((key) => (key == null ? key : String(key))));
31
- const includeAll = !!model.props.defaultExpandAllRows;
37
+ const expandedKeys = new Set((expandedRowKeys || []).map((key) => (key == null ? key : String(key))));
38
+ const includeAll = !!defaultExpandAllRows;
32
39
 
33
40
  const walk = (nodes: any[]) => {
34
41
  if (!nodes?.length) return;
@@ -73,22 +80,22 @@ export function useDragSortBodyWrapper(
73
80
  }
74
81
 
75
82
  if (from && to) {
76
- model.resource
83
+ resource
77
84
  .runAction('move', {
78
85
  method: 'post',
79
86
  params: {
80
- sourceId: getRowKey(from, model.collection.filterTargetKey),
81
- targetId: getRowKey(to, model.collection.filterTargetKey),
82
- sortField: model.props.dragSort ? model.props.dragSortBy : undefined,
87
+ sourceId: getRowKey(from, filterTargetKey),
88
+ targetId: getRowKey(to, filterTargetKey),
89
+ sortField: dragSortFieldName,
83
90
  },
84
91
  })
85
92
  .then(() => {
86
- model.resource.refresh();
93
+ resource.refresh();
87
94
  })
88
95
  .catch((error) => {
89
96
  console.error('Move failed:', error);
90
97
  // Show a user-facing error message if the message system is available
91
- (model as any)?.ctx?.message?.error?.(error?.message || 'Move failed');
98
+ message?.error?.(error?.message || 'Move failed');
92
99
  });
93
100
  }
94
101
  };
@@ -104,13 +111,14 @@ export function useDragSortBodyWrapper(
104
111
  );
105
112
  };
106
113
  }, [
107
- model.props.dragSort,
108
- model.props.dragSortBy,
109
- model.props.expandedRowKeys,
110
- model.props.defaultExpandAllRows,
111
- model.resource,
112
- model.collection.filterTargetKey,
114
+ dataSourceRef,
115
+ defaultExpandAllRows,
116
+ dragSortFieldName,
117
+ expandedRowKeys,
118
+ filterTargetKey,
113
119
  getRowKeyFunc,
120
+ message,
121
+ resource,
114
122
  ]);
115
123
  }
116
124
 
@@ -124,7 +132,10 @@ export function useDragSortRowComponent(dragSort: boolean) {
124
132
  }
125
133
 
126
134
  export function initDragSortParams(model: TableBlockModel) {
127
- if (model.props.dragSort && model.props.dragSortBy) {
128
- model.resource.setSort([model.props.dragSortBy]);
135
+ const dragSortFieldName = model.getDragSortFieldName();
136
+ if (dragSortFieldName) {
137
+ model.resource.setSort([dragSortFieldName]);
138
+ } else if (model.props.dragSort && model.props.dragSortBy) {
139
+ model.resource.setSort(Array.isArray(model.props.globalSort) ? model.props.globalSort : []);
129
140
  }
130
141
  }
@@ -9,7 +9,11 @@
9
9
 
10
10
  import { tExpr } from '@nocobase/flow-engine';
11
11
  import type { TableBlockModel } from '../TableBlockModel';
12
- import { convertFieldsToOptions, getSortFields } from './dragSortUtils';
12
+ import { convertFieldsToOptions, getSortFields, hasSortField } from './dragSortUtils';
13
+
14
+ function getFallbackSort(model: TableBlockModel): string[] {
15
+ return Array.isArray(model.props.globalSort) ? model.props.globalSort : [];
16
+ }
13
17
 
14
18
  export const dragSortSettings = {
15
19
  title: tExpr('Enable drag and drop sorting'),
@@ -17,9 +21,12 @@ export const dragSortSettings = {
17
21
  defaultParams: {
18
22
  dragSort: false,
19
23
  },
20
- async handler(ctx, params) {
24
+ handler(ctx, params) {
21
25
  const model = ctx.model as TableBlockModel;
22
26
  model.setProps('dragSort', params.dragSort);
27
+ if (!params.dragSort) {
28
+ model.resource.setSort(getFallbackSort(model));
29
+ }
23
30
 
24
31
  // Note: automatic configuration of the drag sort field has been removed;
25
32
  // users now configure `dragSortBy` explicitly via `dragSortBySettings`.
@@ -45,6 +52,7 @@ export const dragSortBySettings = {
45
52
  key: 'dragSortBy',
46
53
  props: {
47
54
  options,
55
+ allowClear: true,
48
56
  placeholder: ctx.t('Select field'),
49
57
  },
50
58
  };
@@ -54,9 +62,8 @@ export const dragSortBySettings = {
54
62
  },
55
63
  handler(ctx, params) {
56
64
  const model = ctx.model as TableBlockModel;
57
- model.setProps('dragSortBy', params.dragSortBy);
58
- if (params.dragSortBy) {
59
- model.resource.setSort([params.dragSortBy]);
60
- }
65
+ const dragSortBy = hasSortField(model.collection, params.dragSortBy) ? params.dragSortBy : null;
66
+ model.setProps('dragSortBy', dragSortBy);
67
+ model.resource.setSort(dragSortBy ? [dragSortBy] : getFallbackSort(model));
61
68
  },
62
69
  };
@@ -7,7 +7,7 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- import type { Collection } from '@nocobase/flow-engine';
10
+ import type { Collection, CollectionField } from '@nocobase/flow-engine';
11
11
 
12
12
  /**
13
13
  * 从 collection 中获取所有 sort 类型的字段
@@ -24,6 +24,19 @@ export function getSortFields(collection: Collection | undefined) {
24
24
  });
25
25
  }
26
26
 
27
+ export function getSortField(
28
+ collection: Collection | undefined,
29
+ fieldName?: string | null,
30
+ ): CollectionField | undefined {
31
+ if (!collection || !fieldName) return undefined;
32
+
33
+ return getSortFields(collection).find((field) => field.name === fieldName);
34
+ }
35
+
36
+ export function hasSortField(collection: Collection | undefined, fieldName?: string | null): boolean {
37
+ return !!getSortField(collection, fieldName);
38
+ }
39
+
27
40
  /**
28
41
  * 获取 collection 中第一个可用的 sort 字段
29
42
  * @param collection - 集合对象
@@ -39,7 +52,7 @@ export function getFirstSortField(collection: Collection | undefined) {
39
52
  * @param fields - 字段数组
40
53
  * @returns 选项数组
41
54
  */
42
- export function convertFieldsToOptions(fields: any[]) {
55
+ export function convertFieldsToOptions(fields: CollectionField[]) {
43
56
  return fields.map((field) => ({
44
57
  label: field?.uiSchema?.title || field?.name,
45
58
  value: field?.name,
@@ -11,7 +11,7 @@ import { FormItemModel } from '../../blocks/form/FormItemModel';
11
11
  import { FieldModel } from '../../base/FieldModel';
12
12
  export class AssociationFieldModel extends FieldModel {
13
13
  operator = '$eq';
14
- updateAssociation = true;
14
+ updateAssociation = false;
15
15
  }
16
16
 
17
17
  AssociationFieldModel.registerFlow({
@@ -126,6 +126,7 @@ const RenderCell = observer<any>((props) => {
126
126
  fieldPath: dataIndex,
127
127
  record: record,
128
128
  fieldProps: { ...columnModel.props, ...columnModel.subModels.field.props },
129
+ sourceFieldModelUid: columnModel.subModels.field.uid,
129
130
  onOk: (values) => {
130
131
  record[dataIndex] = values[dataIndex];
131
132
  // 仅重渲染单元格
@@ -203,6 +204,8 @@ const DisplayTable = (props) => {
203
204
  onSelectExitRecordClick,
204
205
  resetPage,
205
206
  allowCreate,
207
+ formValuesChangeEmitter,
208
+ onResetFieldValue,
206
209
  } = props;
207
210
  const [currentPage, setCurrentPage] = useState(1);
208
211
  const [currentPageSize, setCurrentPageSize] = useState(pageSize);
@@ -223,6 +226,18 @@ const DisplayTable = (props) => {
223
226
  resetPage && setCurrentPage(1);
224
227
  }, [resetPage]);
225
228
 
229
+ useEffect(() => {
230
+ if (!formValuesChangeEmitter?.on || !formValuesChangeEmitter?.off || !onResetFieldValue) return;
231
+ const listener = () => {
232
+ onResetFieldValue();
233
+ setTableData([]);
234
+ };
235
+ formValuesChangeEmitter.on('onFieldReset', listener);
236
+ return () => {
237
+ formValuesChangeEmitter.off('onFieldReset', listener);
238
+ };
239
+ }, [formValuesChangeEmitter, onResetFieldValue]);
240
+
226
241
  const pagination = useMemo(() => {
227
242
  return {
228
243
  current: currentPage, // 当前页码
@@ -237,7 +252,7 @@ const DisplayTable = (props) => {
237
252
  return t('Total {{count}} items', { count: total });
238
253
  },
239
254
  } as any;
240
- }, [currentPage, currentPageSize, tableData]);
255
+ }, [currentPage, currentPageSize, tableData, t]);
241
256
 
242
257
  const columns = useMemo(() => {
243
258
  const cols = adjustColumnOrder(
@@ -421,10 +436,6 @@ export class PopupSubTableFieldModel extends AssociationFieldModel {
421
436
  currentPageSize,
422
437
  });
423
438
  };
424
- // 监听表单reset
425
- this.context.blockModel.emitter.on('onFieldReset', () => {
426
- this.props?.onChange([]);
427
- });
428
439
  }
429
440
  }
430
441
 
@@ -457,8 +468,21 @@ export class PopupSubTableFieldModel extends AssociationFieldModel {
457
468
  }
458
469
 
459
470
  public render() {
471
+ const fieldPathArray = this.context.fieldPathArray ?? this.parent?.context?.fieldPathArray;
472
+ const onResetFieldValue = () => {
473
+ const value = [];
474
+ this.setProps({ value });
475
+ this.context.blockModel?.setFieldValue?.(fieldPathArray, value);
476
+ };
460
477
  return (
461
- <DisplayTable {...this.props} collection={this.collection} baseColumns={this.getBaseColumns(this)} model={this} />
478
+ <DisplayTable
479
+ {...this.props}
480
+ collection={this.collection}
481
+ baseColumns={this.getBaseColumns(this)}
482
+ model={this}
483
+ formValuesChangeEmitter={this.context.blockModel?.emitter}
484
+ onResetFieldValue={onResetFieldValue}
485
+ />
462
486
  );
463
487
  }
464
488
  }
@@ -99,6 +99,18 @@ export function collectAssociationHydrationCandidates(options: {
99
99
  return candidates;
100
100
  }
101
101
 
102
+ export function getAssociationHydrationNamePath(model: any) {
103
+ return model?.context?.fieldPathArray ?? model?.context?.fieldPath ?? model?.props?.name;
104
+ }
105
+
106
+ export function getAssociationHydrationSetterContext(model: any) {
107
+ if (typeof model?.context?.setFormValue === 'function') {
108
+ return model.context;
109
+ }
110
+
111
+ return model?.context?.blockModel?.context;
112
+ }
113
+
102
114
  function markAssociationHydrationDone(statusMap: Map<string, HydrateStatus>, tkKey: string | null | undefined) {
103
115
  if (!tkKey) return;
104
116
  statusMap.set(tkKey, 'done');
@@ -186,6 +198,48 @@ const useFieldPermissionMessage = (model, allowEdit) => {
186
198
  return messageValue;
187
199
  };
188
200
 
201
+ const recordSelectClassName = css`
202
+ min-width: 0;
203
+
204
+ .ant-select-selector {
205
+ min-width: 0;
206
+ overflow: hidden;
207
+ }
208
+
209
+ .ant-select-selection-search,
210
+ .ant-select-selection-item,
211
+ .ant-select-selection-placeholder,
212
+ .ant-select-selection-overflow,
213
+ .ant-select-selection-overflow-item {
214
+ min-width: 0;
215
+ max-width: 100%;
216
+ }
217
+
218
+ .ant-select-selection-item,
219
+ .ant-select-selection-item-content {
220
+ overflow: hidden;
221
+ text-overflow: ellipsis;
222
+ white-space: nowrap;
223
+ }
224
+ `;
225
+
226
+ const recordSelectLabelClassName = css`
227
+ display: block;
228
+ min-width: 0;
229
+ max-width: 100%;
230
+ overflow: hidden;
231
+ text-overflow: ellipsis;
232
+ white-space: nowrap;
233
+
234
+ * {
235
+ min-width: 0;
236
+ max-width: 100%;
237
+ overflow: hidden;
238
+ text-overflow: ellipsis;
239
+ white-space: nowrap !important;
240
+ }
241
+ `;
242
+
189
243
  const LazySelect = (props: Readonly<LazySelectProps>) => {
190
244
  const {
191
245
  fieldNames,
@@ -198,6 +252,7 @@ const LazySelect = (props: Readonly<LazySelectProps>) => {
198
252
  onChange,
199
253
  allowCreate = true,
200
254
  allowEdit = true,
255
+ className,
201
256
  ...others
202
257
  } = props;
203
258
  const model: any = useFlowModel();
@@ -245,8 +300,8 @@ const LazySelect = (props: Readonly<LazySelectProps>) => {
245
300
  });
246
301
  if (!candidates.length) return;
247
302
 
248
- const namePath = model?.props?.name ?? model?.context?.fieldPathArray;
249
- const blockCtx: any = model?.context?.blockModel?.context;
303
+ const namePath = getAssociationHydrationNamePath(model);
304
+ const setterCtx: any = getAssociationHydrationSetterContext(model);
250
305
 
251
306
  candidates.forEach(({ item, tk, tkKey }) => {
252
307
  void (async () => {
@@ -268,8 +323,8 @@ const LazySelect = (props: Readonly<LazySelectProps>) => {
268
323
  })
269
324
  : merge;
270
325
 
271
- if (blockCtx && typeof blockCtx.setFormValue === 'function' && namePath != null) {
272
- await blockCtx.setFormValue(namePath, nextValue, {
326
+ if (setterCtx && typeof setterCtx.setFormValue === 'function' && namePath != null) {
327
+ await setterCtx.setFormValue(namePath, nextValue, {
273
328
  source: 'default',
274
329
  markExplicit: false,
275
330
  triggerEvent: false,
@@ -305,6 +360,7 @@ const LazySelect = (props: Readonly<LazySelectProps>) => {
305
360
  <Select
306
361
  style={{ width: '100%' }}
307
362
  {...others}
363
+ className={[recordSelectClassName, className].filter(Boolean).join(' ')}
308
364
  allowClear
309
365
  showSearch
310
366
  maxTagCount="responsive"
@@ -397,19 +453,7 @@ const LazySelect = (props: Readonly<LazySelectProps>) => {
397
453
  }}
398
454
  popupMatchSelectWidth
399
455
  labelRender={(data) => {
400
- return (
401
- <div
402
- className={css`
403
- div {
404
- white-space: nowrap !important;
405
- overflow: hidden;
406
- text-overflow: ellipsis;
407
- }
408
- `}
409
- >
410
- {data.label}
411
- </div>
412
- );
456
+ return <div className={recordSelectLabelClassName}>{data.label}</div>;
413
457
  }}
414
458
  dropdownRender={(menu) => {
415
459
  const isFullMatch = realOptions.some((v) => v[normalizedFieldNames.label] === others.searchText);