@nocobase/client-v2 2.2.0-beta.8 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/es/APIClient.d.ts +13 -3
  2. package/es/BaseApplication.d.ts +3 -0
  3. package/es/RouteRepository.d.ts +8 -0
  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/category-tabs/SortableCategoryTabs.d.ts +55 -0
  9. package/es/components/category-tabs/index.d.ts +9 -0
  10. package/es/components/form/ScanInput/useCodeScanner.d.ts +3 -2
  11. package/es/components/form/TypedVariableInput.d.ts +31 -5
  12. package/es/components/form/filter/CollectionFilter.d.ts +2 -0
  13. package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
  14. package/es/components/form/filter/CollectionFilterPanel.d.ts +2 -0
  15. package/es/components/form/filter/index.d.ts +2 -0
  16. package/es/components/form/filter/useFilterActionProps.d.ts +2 -0
  17. package/es/components/index.d.ts +1 -0
  18. package/es/entry-actions/EntryActionManager.d.ts +24 -0
  19. package/es/entry-actions/index.d.ts +9 -0
  20. package/es/flow/actions/index.d.ts +1 -1
  21. package/es/flow/actions/linkageRules.d.ts +27 -0
  22. package/es/flow/admin-shell/BaseLayoutModel.d.ts +6 -0
  23. package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +7 -0
  24. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +1 -0
  25. package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +2 -1
  26. package/es/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.d.ts +24 -0
  27. package/es/flow/admin-shell/admin-layout/index.d.ts +1 -0
  28. package/es/flow/admin-shell/admin-layout/useApplications.d.ts +7 -2
  29. package/es/flow/components/FieldAssignExactDatePicker.d.ts +1 -0
  30. package/es/flow/components/FieldAssignValueInput.d.ts +7 -24
  31. package/es/flow/components/FlowRoute.d.ts +2 -2
  32. package/es/flow/components/RunJSValueEditor.d.ts +1 -0
  33. package/es/flow/components/field-value-variable/DateVariableEditor.d.ts +24 -0
  34. package/es/flow/components/field-value-variable/FieldValueVariableInput.d.ts +31 -0
  35. package/es/flow/components/field-value-variable/dateValue.d.ts +36 -0
  36. package/es/flow/components/field-value-variable/index.d.ts +11 -0
  37. package/es/flow/components/filter/FilterGroup.d.ts +1 -0
  38. package/es/flow/components/filter/VariableFilterItem.d.ts +6 -1
  39. package/es/flow/components/placeholders/BlockPlaceholder.d.ts +1 -0
  40. package/es/flow/index.d.ts +1 -0
  41. package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
  42. package/es/flow/internal/utils/operatorSchemaHelper.d.ts +2 -2
  43. package/es/flow/models/actions/UpdateRecordActionUtils.d.ts +8 -2
  44. package/es/flow/models/base/ActionModelCore.d.ts +6 -2
  45. package/es/flow/models/base/BlockGridModel.d.ts +3 -0
  46. package/es/flow/models/base/CollectionBlockModel.d.ts +3 -0
  47. package/es/flow/models/base/PageModel/PageModel.d.ts +12 -0
  48. package/es/flow/models/base/PageModel/PageModelTabBar.d.ts +22 -0
  49. package/es/flow/models/base/PageModel/PageTabModel.d.ts +12 -0
  50. package/es/flow/models/blocks/details/DetailsBlockModel.d.ts +3 -0
  51. package/es/flow/models/blocks/filter-form/FilterFormGridModel.d.ts +1 -0
  52. package/es/flow/models/blocks/filter-form/FilterFormSubmitActionModel.d.ts +3 -1
  53. package/es/flow/models/blocks/form/FormBlockModel.d.ts +7 -0
  54. package/es/flow/models/blocks/form/FormGridModel.d.ts +6 -0
  55. package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
  56. package/es/flow/models/blocks/form/value-runtime/rules.d.ts +1 -0
  57. package/es/flow/models/blocks/table/JSColumnModel.d.ts +2 -0
  58. package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
  59. package/es/flow/models/blocks/table/TableColumnModel.d.ts +12 -0
  60. package/es/flow/models/blocks/table/utils.d.ts +1 -0
  61. package/es/flow/models/fields/AssociationFieldModel/RecordPickerFieldModel.d.ts +2 -1
  62. package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +0 -17
  63. package/es/flow/models/fields/AssociationFieldModel/recordSelectShared.d.ts +41 -0
  64. package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
  65. package/es/flow/routeTransientInputArgs.d.ts +14 -0
  66. package/es/flow-compat/fieldValidationConstants.d.ts +1 -1
  67. package/es/flow-compat/passwordUtils.d.ts +1 -1
  68. package/es/flow-compat/routeTypes.d.ts +1 -0
  69. package/es/index.d.ts +7 -0
  70. package/es/index.mjs +301 -177
  71. package/es/ui-operation/index.d.ts +15 -0
  72. package/es/ui-operation/ui-operation-codec.d.ts +10 -0
  73. package/es/utils/getRouteRuntimeVersion.d.ts +23 -0
  74. package/es/utils/index.d.ts +1 -0
  75. package/es/utils/markdownSanitize.d.ts +11 -0
  76. package/lib/index.js +313 -189
  77. package/lib/locale/languageCodes.js +3 -1
  78. package/package.json +9 -7
  79. package/src/APIClient.ts +90 -7
  80. package/src/Application.tsx +7 -1
  81. package/src/BaseApplication.tsx +13 -6
  82. package/src/RouteRepository.ts +25 -0
  83. package/src/RouterManager.tsx +142 -1
  84. package/src/__tests__/APIClient.test.tsx +58 -0
  85. package/src/__tests__/RouteRepository.test.ts +23 -0
  86. package/src/__tests__/RouterManager.test.ts +125 -0
  87. package/src/__tests__/app.test.tsx +73 -0
  88. package/src/__tests__/authRedirect.test.ts +17 -0
  89. package/src/__tests__/browserChecker.test.ts +61 -0
  90. package/src/__tests__/getRouteRuntimeVersion.test.ts +68 -0
  91. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +35 -2
  92. package/src/__tests__/settings-center.test.tsx +270 -6
  93. package/src/acl/ACLProvider.tsx +2 -0
  94. package/src/acl/__tests__/ACLProvider.test.tsx +92 -0
  95. package/src/authRedirect.ts +38 -0
  96. package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
  97. package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
  98. package/src/collection-manager/field-validation.ts +1 -1
  99. package/src/components/README.md +7 -1
  100. package/src/components/README.zh-CN.md +6 -1
  101. package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
  102. package/src/components/category-tabs/index.ts +10 -0
  103. package/src/components/form/JsonTextArea.tsx +4 -0
  104. package/src/components/form/ScanInput/CodeScanner.tsx +79 -35
  105. package/src/components/form/ScanInput/__tests__/CodeScanner.test.tsx +101 -0
  106. package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +284 -11
  107. package/src/components/form/ScanInput/useCodeScanner.ts +332 -24
  108. package/src/components/form/TypedVariableInput.tsx +452 -101
  109. package/src/components/form/__tests__/TypedVariableInput.test.tsx +364 -12
  110. package/src/components/form/filter/CollectionFilter.tsx +4 -0
  111. package/src/components/form/filter/CollectionFilterItem.tsx +36 -9
  112. package/src/components/form/filter/CollectionFilterPanel.tsx +4 -0
  113. package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +55 -0
  114. package/src/components/form/filter/__tests__/useFilterActionProps.test.tsx +95 -0
  115. package/src/components/form/filter/index.ts +2 -0
  116. package/src/components/form/filter/useFilterActionProps.ts +37 -6
  117. package/src/components/form/table/Table.tsx +81 -6
  118. package/src/components/form/table/__tests__/Table.columnWidth.test.tsx +433 -0
  119. package/src/components/index.ts +1 -0
  120. package/src/entry-actions/EntryActionManager.ts +76 -0
  121. package/src/entry-actions/index.ts +10 -0
  122. package/src/flow/FlowPage.tsx +38 -3
  123. package/src/flow/__tests__/FlowPage.test.tsx +201 -27
  124. package/src/flow/__tests__/FlowRoute.test.tsx +675 -12
  125. package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
  126. package/src/flow/actions/__tests__/actionLinkageRules.forkProps.test.ts +314 -0
  127. package/src/flow/actions/__tests__/dataScopeFilter.test.ts +62 -0
  128. package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +102 -0
  129. package/src/flow/actions/__tests__/linkageRules.actionStates.test.ts +33 -0
  130. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +72 -0
  131. package/src/flow/actions/__tests__/linkageRules.tab.test.ts +171 -0
  132. package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
  133. package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +80 -20
  134. package/src/flow/actions/dataScopeFilter.ts +10 -1
  135. package/src/flow/actions/dateTimeFormat.tsx +2 -2
  136. package/src/flow/actions/index.ts +2 -0
  137. package/src/flow/actions/linkageRules.tsx +144 -26
  138. package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
  139. package/src/flow/actions/openView.tsx +21 -1
  140. package/src/flow/admin-shell/BaseLayoutModel.tsx +124 -0
  141. package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +184 -42
  142. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +1016 -119
  143. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +41 -5
  144. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +177 -1
  145. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +20 -0
  146. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +78 -59
  147. package/src/flow/admin-shell/admin-layout/AppListRender.tsx +13 -2
  148. package/src/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.tsx +289 -0
  149. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +253 -6
  150. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +228 -0
  151. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +436 -2
  152. package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +48 -0
  153. package/src/flow/admin-shell/admin-layout/index.ts +1 -0
  154. package/src/flow/admin-shell/admin-layout/useApplications.tsx +81 -12
  155. package/src/flow/common/Markdown/Display.tsx +14 -3
  156. package/src/flow/common/Markdown/Edit.tsx +19 -7
  157. package/src/flow/common/Markdown/style.ts +4 -0
  158. package/src/flow/components/BlockItemCard.tsx +2 -2
  159. package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
  160. package/src/flow/components/FieldAssignValueInput.tsx +72 -584
  161. package/src/flow/components/FlowRoute.tsx +204 -46
  162. package/src/flow/components/RunJSValueEditor.tsx +9 -1
  163. package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +216 -0
  164. package/src/flow/components/field-value-variable/DateVariableEditor.tsx +181 -0
  165. package/src/flow/components/field-value-variable/FieldValueVariableInput.tsx +326 -0
  166. package/src/flow/components/field-value-variable/__tests__/FieldValueVariableInput.test.tsx +380 -0
  167. package/src/flow/components/field-value-variable/dateValue.ts +223 -0
  168. package/src/flow/components/field-value-variable/index.ts +12 -0
  169. package/src/flow/components/filter/FilterGroup.tsx +33 -5
  170. package/src/flow/components/filter/VariableFilterItem.tsx +170 -13
  171. package/src/flow/components/filter/__tests__/FilterGroup.test.tsx +45 -0
  172. package/src/flow/components/filter/__tests__/VariableFilterItem.leftMetaTree.test.tsx +9 -0
  173. package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +195 -1
  174. package/src/flow/components/placeholders/BlockPlaceholder.tsx +70 -23
  175. package/src/flow/components/placeholders/__tests__/BlockPlaceholder.test.tsx +57 -7
  176. package/src/flow/index.ts +3 -0
  177. package/src/flow/internal/components/Markdown/util.ts +2 -1
  178. package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
  179. package/src/flow/internal/utils/operatorSchemaHelper.ts +3 -2
  180. package/src/flow/models/actions/UpdateRecordActionModel.tsx +18 -1
  181. package/src/flow/models/actions/UpdateRecordActionUtils.ts +18 -6
  182. package/src/flow/models/actions/__tests__/UpdateRecordActionModel.test.ts +76 -4
  183. package/src/flow/models/base/ActionModel.tsx +10 -8
  184. package/src/flow/models/base/ActionModelCore.tsx +16 -2
  185. package/src/flow/models/base/BlockGridModel.tsx +26 -0
  186. package/src/flow/models/base/CollectionBlockModel.tsx +38 -3
  187. package/src/flow/models/base/PageModel/PageModel.tsx +387 -53
  188. package/src/flow/models/base/PageModel/PageModelTabBar.tsx +114 -0
  189. package/src/flow/models/base/PageModel/PageTabModel.tsx +249 -18
  190. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +790 -10
  191. package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.module-isolation.test.tsx +130 -0
  192. package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.test.tsx +118 -0
  193. package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +1075 -7
  194. package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +49 -0
  195. package/src/flow/models/base/__tests__/BlockGridModel.selectSceneActivation.test.ts +124 -0
  196. package/src/flow/models/base/__tests__/CollectionBlockModel.addAppends.test.ts +41 -0
  197. package/src/flow/models/base/__tests__/CollectionBlockModel.initialBeforeRenderRefresh.test.ts +125 -9
  198. package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +28 -53
  199. package/src/flow/models/blocks/details/DetailsBlockModel.tsx +8 -3
  200. package/src/flow/models/blocks/details/__tests__/DetailsBlockModel.initialPaginationChangeRefresh.test.ts +32 -1
  201. package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +28 -3
  202. package/src/flow/models/blocks/filter-form/FilterFormGridModel.tsx +36 -5
  203. package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +155 -25
  204. package/src/flow/models/blocks/filter-form/FilterFormSubmitActionModel.tsx +57 -4
  205. package/src/flow/models/blocks/filter-form/__tests__/FilterFormGridModel.onModelCreated.test.ts +174 -1
  206. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +395 -1
  207. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.getFilterValue.test.ts +72 -0
  208. package/src/flow/models/blocks/filter-form/__tests__/FilterFormSubmitActionModel.lifecycle.test.ts +90 -0
  209. package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +66 -4
  210. package/src/flow/models/blocks/filter-manager/FilterManager.ts +5 -0
  211. package/src/flow/models/blocks/filter-manager/__tests__/FilterManager.test.ts +40 -0
  212. package/src/flow/models/blocks/form/FormBlockModel.tsx +47 -0
  213. package/src/flow/models/blocks/form/FormGridModel.tsx +4 -0
  214. package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
  215. package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +8 -2
  216. package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
  217. package/src/flow/models/blocks/form/__tests__/runJsFormSubmit.test.ts +131 -0
  218. package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +435 -0
  219. package/src/flow/models/blocks/form/value-runtime/rules.ts +67 -14
  220. package/src/flow/models/blocks/form/value-runtime/runtime.ts +43 -19
  221. package/src/flow/models/blocks/js-block/JSBlock.tsx +1 -1
  222. package/src/flow/models/blocks/table/JSColumnModel.tsx +10 -1
  223. package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +2 -1
  224. package/src/flow/models/blocks/table/TableBlockModel.tsx +29 -3
  225. package/src/flow/models/blocks/table/TableColumnModel.tsx +54 -11
  226. package/src/flow/models/blocks/table/__tests__/JSColumnModel.test.tsx +52 -5
  227. package/src/flow/models/blocks/table/__tests__/TableBlockModel.filterReset.test.ts +78 -0
  228. package/src/flow/models/blocks/table/__tests__/TableBlockModel.quickEditRefresh.test.ts +12 -0
  229. package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +84 -0
  230. package/src/flow/models/blocks/table/utils.ts +7 -0
  231. package/src/flow/models/fields/AssociationFieldModel/CascadeSelectFieldModel.tsx +33 -1
  232. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +10 -1
  233. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/__tests__/popupContext.test.ts +120 -0
  234. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/actions/PopupSubTableEditActionModel.tsx +10 -2
  235. package/src/flow/models/fields/AssociationFieldModel/RecordPickerFieldModel.tsx +24 -1
  236. package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +54 -149
  237. package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +167 -3
  238. package/src/flow/models/fields/AssociationFieldModel/recordSelectShared.tsx +156 -0
  239. package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +149 -0
  240. package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +149 -113
  241. package/src/flow/models/fields/DisplayNumberFieldModel.tsx +1 -1
  242. package/src/flow/models/fields/JsonFieldModel.tsx +31 -8
  243. package/src/flow/models/fields/NumberFieldModel.tsx +1 -1
  244. package/src/flow/models/fields/TextareaFieldModel.tsx +42 -19
  245. package/src/flow/models/fields/VariableFieldFormModel.tsx +3 -3
  246. package/src/flow/models/fields/__tests__/DisplayNumberFieldModel.test.ts +33 -0
  247. package/src/flow/models/fields/__tests__/JsonFieldModel.test.ts +82 -0
  248. package/src/flow/models/fields/__tests__/NumberFieldModel.test.tsx +47 -0
  249. package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +32 -1
  250. package/src/flow/models/fields/__tests__/VariableFieldFormModel.test.tsx +51 -0
  251. package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +26 -10
  252. package/src/flow/models/fields/mobile-components/MobileSelect.tsx +51 -14
  253. package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +139 -13
  254. package/src/flow/models/topbar/TopbarActionModel.tsx +78 -3
  255. package/src/flow/routeTransientInputArgs.ts +27 -0
  256. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +42 -0
  257. package/src/flow/utils/dataScopeFormValueClear.ts +4 -3
  258. package/src/flow-compat/fieldValidationConstants.ts +1 -1
  259. package/src/flow-compat/routeTypes.ts +1 -0
  260. package/src/index.ts +7 -0
  261. package/src/layout-manager/LayoutContentRoute.tsx +2 -1
  262. package/src/layout-manager/LayoutRoute.tsx +2 -1
  263. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +87 -1
  264. package/src/locale/languageCodes.ts +3 -1
  265. package/src/nocobase-buildin-plugin/index.tsx +19 -1
  266. package/src/settings-center/AdminSettingsLayout.tsx +23 -2
  267. package/src/ui-operation/index.ts +33 -0
  268. package/src/ui-operation/ui-operation-codec.ts +54 -0
  269. package/src/utils/getRouteRuntimeVersion.ts +185 -0
  270. package/src/utils/index.tsx +1 -0
  271. package/src/utils/markdownSanitize.ts +88 -0
@@ -8,7 +8,13 @@
8
8
  */
9
9
 
10
10
  import { isObservable, reaction, toJS } from '@formily/reactive';
11
- import { FlowContext, FlowModel, isRunJSValue, normalizeRunJSValue } from '@nocobase/flow-engine';
11
+ import {
12
+ FlowContext,
13
+ FlowModel,
14
+ isRunJSValue,
15
+ normalizeRunJSValue,
16
+ type ResolveJsonTemplateOptions,
17
+ } from '@nocobase/flow-engine';
12
18
  import { getValuesByPath } from '@nocobase/shared';
13
19
  import _ from 'lodash';
14
20
  import { dayjs } from '@nocobase/utils/client';
@@ -49,6 +55,7 @@ type RuntimeRule = {
49
55
  getValue: () => any;
50
56
  getCondition?: () => any;
51
57
  getContext: () => any;
58
+ getContractModelUid?: () => string | undefined;
52
59
  };
53
60
 
54
61
  type ObservableBinding = {
@@ -83,8 +90,18 @@ type ToManyAggregateSourceInfo = {
83
90
  lastWrite?: FormValueWriteMeta;
84
91
  };
85
92
 
93
+ type RuntimeItemChain = {
94
+ index?: number;
95
+ length?: number;
96
+ __is_new__?: boolean;
97
+ __is_stored__?: boolean;
98
+ value: unknown;
99
+ parentItem?: RuntimeItemChain;
100
+ };
101
+
86
102
  export type RuleEngineOptions = {
87
103
  getBlockModelUid: () => string;
104
+ getAssignRulesModelUid: () => string | undefined;
88
105
  getActionName: () => string | undefined;
89
106
  getBlockContext: () => any;
90
107
  getEngine: () => any;
@@ -208,6 +225,7 @@ export class RuleEngine {
208
225
  getValue: () => template?.value,
209
226
  getCondition: () => template?.condition,
210
227
  getContext: () => this.options.getBlockContext(),
228
+ getContractModelUid: this.options.getAssignRulesModelUid,
211
229
  };
212
230
 
213
231
  this.rules.set(id, { rule, state: { deps: new Set(), depDisposers: [], runSeq: 0, scheduledAtWriteSeq: 0 } });
@@ -545,6 +563,7 @@ export class RuleEngine {
545
563
  getValue: () => template?.value,
546
564
  getCondition: () => template?.condition,
547
565
  getContext: () => this.options.getBlockContext(),
566
+ getContractModelUid: this.options.getAssignRulesModelUid,
548
567
  };
549
568
 
550
569
  this.rules.set(id, { rule, state: { deps: new Set(), depDisposers: [], runSeq: 0, scheduledAtWriteSeq: 0 } });
@@ -913,6 +932,7 @@ export class RuleEngine {
913
932
  getValue: () => template?.value,
914
933
  getCondition: () => template?.condition,
915
934
  getContext: () => model?.context,
935
+ getContractModelUid: this.options.getAssignRulesModelUid,
916
936
  };
917
937
 
918
938
  this.rules.set(id, { rule, state: { deps: new Set(), depDisposers: [], runSeq: 0, scheduledAtWriteSeq: 0 } });
@@ -1662,7 +1682,7 @@ export class RuleEngine {
1662
1682
  }
1663
1683
  }
1664
1684
 
1665
- const evalCtx = this.createRuleEvaluationContext(baseCtx, collector, targetNamePath);
1685
+ const evalCtx = this.createRuleEvaluationContext(baseCtx, collector, targetNamePath, rule.getContractModelUid?.());
1666
1686
  return { collector, evalCtx, rawValue, isRunJS };
1667
1687
  }
1668
1688
 
@@ -2001,7 +2021,12 @@ export class RuleEngine {
2001
2021
  return canOverwrite;
2002
2022
  }
2003
2023
 
2004
- private createRuleEvaluationContext(baseCtx: any, collector: DepCollector, targetNamePath: NamePath | null) {
2024
+ private createRuleEvaluationContext(
2025
+ baseCtx: any,
2026
+ collector: DepCollector,
2027
+ targetNamePath: NamePath | null,
2028
+ contractModelUid?: string,
2029
+ ) {
2005
2030
  const trackingFormValues = this.options.createTrackingFormValues(collector);
2006
2031
  const ctx: any = new FlowContext();
2007
2032
  try {
@@ -2021,12 +2046,19 @@ export class RuleEngine {
2021
2046
  }
2022
2047
 
2023
2048
  const delegatedResolveJsonTemplate =
2024
- typeof ctx.resolveJsonTemplate === 'function' ? ctx.resolveJsonTemplate.bind(ctx) : undefined;
2049
+ typeof ctx.resolveJsonTemplate === 'function'
2050
+ ? (ctx.resolveJsonTemplate.bind(ctx) as (
2051
+ template: unknown,
2052
+ options?: ResolveJsonTemplateOptions,
2053
+ ) => Promise<unknown>)
2054
+ : undefined;
2025
2055
  ctx.defineMethod('resolveJsonTemplate', async (template: unknown) => {
2026
2056
  const tokenStore = this.createLocalTemplateTokenStore();
2027
2057
  const localResolved = this.resolveLocalFormValuesTemplates(baseCtx, template, collector, tokenStore);
2028
2058
  const nextTemplate = localResolved.matched ? localResolved.value : template;
2029
- const resolved = delegatedResolveJsonTemplate ? await delegatedResolveJsonTemplate(nextTemplate) : nextTemplate;
2059
+ const resolved = delegatedResolveJsonTemplate
2060
+ ? await delegatedResolveJsonTemplate(nextTemplate, contractModelUid ? { contractModelUid } : undefined)
2061
+ : nextTemplate;
2030
2062
  return this.restoreLocalTemplateTokens(resolved, tokenStore);
2031
2063
  });
2032
2064
 
@@ -2038,7 +2070,7 @@ export class RuleEngine {
2038
2070
  // - parentItem:上级项(同结构,可链式 parentItem.parentItem...)
2039
2071
  // 计算顺序:
2040
2072
  // 1) 优先按 targetNamePath 从 formValues 构建“关联链 item”
2041
- // 2) 若无法构建(例如目标字段是顶层路径),回退到上游显式注入的 baseCtx.item
2073
+ // 2) 若无法构建(例如目标字段是顶层非关联字段),回退到上游显式注入的 baseCtx.item
2042
2074
  // (如 PopupSubTable 新增弹窗传入的 parentItem 链)
2043
2075
  let itemCached: any;
2044
2076
  let itemCachedReady = false;
@@ -2209,9 +2241,23 @@ export class RuleEngine {
2209
2241
  parentItem,
2210
2242
  };
2211
2243
  };
2212
- const defaultRoot = buildNode(trackingFormValues, undefined, undefined, undefined);
2213
- // item 仅用于“关系字段的子路径”场景;
2214
- // 顶层字段/非关联嵌套对象字段应使用 formValues。
2244
+ const blockCtx = this.options.getBlockContext();
2245
+ const formRootItem = (() => {
2246
+ try {
2247
+ const item = blockCtx?.item as RuntimeItemChain | undefined;
2248
+ if (!item || typeof item !== 'object') return undefined;
2249
+ return item.value === blockCtx?.formValues ? item : undefined;
2250
+ } catch {
2251
+ return undefined;
2252
+ }
2253
+ })();
2254
+ const defaultRoot = {
2255
+ ...buildNode(trackingFormValues, formRootItem?.index, formRootItem?.length, formRootItem?.parentItem),
2256
+ __is_new__: formRootItem?.__is_new__ ?? trackingFormValues?.__is_new__,
2257
+ __is_stored__: formRootItem?.__is_stored__ ?? trackingFormValues?.__is_stored__,
2258
+ };
2259
+ // item 用于“关系字段目标或其子路径”场景;
2260
+ // 顶层非关联字段/非关联嵌套对象字段应使用 formValues。
2215
2261
  if (!targetNamePath || !Array.isArray(targetNamePath) || !targetNamePath.length) return undefined;
2216
2262
  if (!rootCollection?.getField) return undefined;
2217
2263
 
@@ -2219,7 +2265,9 @@ export class RuleEngine {
2219
2265
  const prefix: NamePath = [];
2220
2266
  let collection = rootCollection;
2221
2267
 
2222
- for (let i = 0; i < targetNamePath.length - 1; i++) {
2268
+ // The target itself can be an association. Keep that final association in the item chain so
2269
+ // ctx.item.parentItem continues to point at the containing row instead of skipping directly to the form root.
2270
+ for (let i = 0; i < targetNamePath.length; i++) {
2223
2271
  const seg = targetNamePath[i];
2224
2272
  if (typeof seg !== 'string') break;
2225
2273
 
@@ -2231,9 +2279,12 @@ export class RuleEngine {
2231
2279
 
2232
2280
  if (toMany) {
2233
2281
  const next = targetNamePath[i + 1];
2234
- if (typeof next !== 'number') break;
2235
- prefix.push(next);
2236
- i += 1;
2282
+ if (typeof next === 'number') {
2283
+ prefix.push(next);
2284
+ i += 1;
2285
+ } else if (i !== targetNamePath.length - 1) {
2286
+ break;
2287
+ }
2237
2288
  }
2238
2289
 
2239
2290
  const targetCollection = field?.targetCollection;
@@ -2248,9 +2299,11 @@ export class RuleEngine {
2248
2299
  const assocEntry = assocEntries[idx];
2249
2300
  const value = _.get(trackingFormValues, assocEntry.path);
2250
2301
  const lastSeg = assocEntry.path[assocEntry.path.length - 1];
2251
- const index = assocEntry.toMany && typeof lastSeg === 'number' ? lastSeg : undefined;
2302
+ const isIndexedToManyItem = assocEntry.toMany && typeof lastSeg === 'number';
2303
+ const index = isIndexedToManyItem ? lastSeg : undefined;
2252
2304
  const length = (() => {
2253
2305
  if (!assocEntry.toMany) return undefined;
2306
+ if (!isIndexedToManyItem) return Array.isArray(value) ? value.length : undefined;
2254
2307
  // assocEntry.path: [..., associationKey, rowIndex]
2255
2308
  const listPath = assocEntry.path.slice(0, -1);
2256
2309
  const list = _.get(trackingFormValues, listPath);
@@ -86,6 +86,10 @@ export class FormValueRuntime {
86
86
 
87
87
  this.ruleEngine = new RuleEngine({
88
88
  getBlockModelUid: () => String(this.model?.uid),
89
+ getAssignRulesModelUid: () => {
90
+ const grid = this.model?.subModels?.grid;
91
+ return !Array.isArray(grid) && grid?.uid ? String(grid.uid) : undefined;
92
+ },
89
93
  getActionName: () => this.model?.getAclActionName?.() ?? this.model?.context?.actionName,
90
94
  getBlockContext: () => this.model?.context,
91
95
  getEngine: () => this.model?.context?.engine,
@@ -190,13 +194,32 @@ export class FormValueRuntime {
190
194
  return values;
191
195
  }
192
196
 
193
- private toMirrorSnapshot(value: any) {
194
- const raw = isObservable(value) ? toJS(value) : value;
195
- return _.cloneDeepWith(raw, (item) => {
196
- if (!item || typeof item !== 'object') return undefined;
197
- if (Array.isArray(item) || _.isPlainObject(item)) return undefined;
198
- return item;
199
- });
197
+ private toMirrorSnapshot<T>(value: T): T {
198
+ const cloneValue = (input: unknown): unknown =>
199
+ _.cloneDeepWith(input, (item: unknown) => {
200
+ if (isObservable(item)) {
201
+ const plainItem = toJS(item);
202
+ if (plainItem !== item) return cloneValue(plainItem);
203
+ }
204
+
205
+ // Tracking form-value array proxies hide `constructor`, while Lodash's array
206
+ // clone relies on it being callable.
207
+ if (Array.isArray(item) && typeof item.constructor !== 'function') {
208
+ const plainArray = new Array(item.length);
209
+ for (let index = 0; index < item.length; index++) {
210
+ if (Object.hasOwn(item, index)) {
211
+ plainArray[index] = cloneValue(item[index]);
212
+ }
213
+ }
214
+ return plainArray;
215
+ }
216
+
217
+ if (!item || typeof item !== 'object') return undefined;
218
+ if (Array.isArray(item) || _.isPlainObject(item)) return undefined;
219
+ return item;
220
+ });
221
+
222
+ return cloneValue(value) as T;
200
223
  }
201
224
 
202
225
  canApplyDefaultValuePatch(namePath: NamePath, resolved: any) {
@@ -940,11 +963,12 @@ export class FormValueRuntime {
940
963
  const changedPaths: NamePath[] = [];
941
964
 
942
965
  if (!Array.isArray(patch)) {
943
- const patchEntries = Object.entries(patch || {}).filter(([pathKey, rawValue]) => {
944
- if (shouldSkipByLinkageScope(pathKey)) return false;
945
- const value = isObservable(rawValue) ? toJS(rawValue) : rawValue;
946
- return !_.isEqual(this.getFormValueAtPath([pathKey]), value);
947
- });
966
+ const patchEntries = Object.entries(patch || {})
967
+ .map(([pathKey, rawValue]) => [pathKey, this.toMirrorSnapshot(rawValue)] as const)
968
+ .filter(([pathKey, value]) => {
969
+ if (shouldSkipByLinkageScope(pathKey)) return false;
970
+ return !_.isEqual(this.getFormValueAtPath([pathKey]), value);
971
+ });
948
972
  const patchToApply = Object.fromEntries(patchEntries);
949
973
  const patchKeys = patchEntries.map(([pathKey]) => pathKey);
950
974
  if (!patchKeys.length) {
@@ -955,8 +979,7 @@ export class FormValueRuntime {
955
979
  }
956
980
  this.suppressFormCallbackDepth++;
957
981
  try {
958
- for (const [pathKey, rawValue] of patchEntries) {
959
- const value = isObservable(rawValue) ? toJS(rawValue) : rawValue;
982
+ for (const [pathKey, value] of patchEntries) {
960
983
  if (typeof form.setFieldValue === 'function') {
961
984
  form.setFieldValue(pathKey, value);
962
985
  } else if (typeof (form as any).setFields === 'function') {
@@ -1017,7 +1040,7 @@ export class FormValueRuntime {
1017
1040
  const namePath = this.resolveNamePath(callerCtx, item.path);
1018
1041
  const pathKey = namePathToPathKey(namePath);
1019
1042
  const rawValue = item.value;
1020
- const value = isObservable(rawValue) ? toJS(rawValue) : rawValue;
1043
+ const value = this.toMirrorSnapshot(rawValue);
1021
1044
 
1022
1045
  if (shouldSkipByLinkageScope(pathKey)) continue;
1023
1046
 
@@ -1156,17 +1179,18 @@ export class FormValueRuntime {
1156
1179
  if (!form) return;
1157
1180
  if (source === 'override' && this.findUserEditedHit(pathKey)) return;
1158
1181
 
1182
+ const value = this.toMirrorSnapshot(nextValue);
1159
1183
  const prevValue = _.get(this.valuesMirror, namePath);
1160
- if (_.isEqual(prevValue, nextValue)) return;
1184
+ if (_.isEqual(prevValue, value)) return;
1161
1185
 
1162
1186
  this.writeSeq += 1;
1163
1187
  const writeSeq = this.writeSeq;
1164
1188
 
1165
1189
  this.suppressFormCallbackDepth++;
1166
1190
  try {
1167
- form.setFieldValue?.(namePath, nextValue);
1168
- _.set(this.valuesMirror, namePath, this.toMirrorSnapshot(nextValue));
1169
- this.syncMountedFieldModelValue(namePath, nextValue);
1191
+ form.setFieldValue?.(namePath, value);
1192
+ _.set(this.valuesMirror, namePath, this.toMirrorSnapshot(value));
1193
+ this.syncMountedFieldModelValue(namePath, value);
1170
1194
  this.bumpChangeTick();
1171
1195
  } finally {
1172
1196
  this.suppressFormCallbackDepth--;
@@ -235,7 +235,7 @@ export class JSBlockModel extends BlockModel {
235
235
  const cardProps = {
236
236
  ...rest,
237
237
  height,
238
- style,
238
+ ...(style === undefined ? {} : { style }),
239
239
  ...(beforeContent === undefined ? {} : { beforeContent }),
240
240
  ...(afterContent === undefined ? {} : { afterContent }),
241
241
  };
@@ -9,13 +9,14 @@
9
9
 
10
10
  import { LockOutlined, QuestionCircleOutlined } from '@ant-design/icons';
11
11
  import { css } from '@emotion/css';
12
- import type { PropertyMetaFactory } from '@nocobase/flow-engine';
12
+ import type { FlowModelOptions, PropertyMetaFactory } from '@nocobase/flow-engine';
13
13
  import {
14
14
  Droppable,
15
15
  tExpr,
16
16
  FlowsFloatContextMenu,
17
17
  DragHandler,
18
18
  MemoFlowModelRenderer,
19
+ createCurrentRecordMetaFactory,
19
20
  createRecordMetaFactory,
20
21
  createRecordResolveOnServerWithLocal,
21
22
  ElementProxy,
@@ -51,6 +52,14 @@ function getRecordRenderSignature(record: any) {
51
52
  export class JSColumnModel extends TableCustomColumnModel {
52
53
  // Stable per‑instance render component to avoid remounts across re-renders
53
54
  private _RenderComponent?: React.ComponentType;
55
+
56
+ onInit(options: FlowModelOptions): void {
57
+ super.onInit(options);
58
+ this.context.defineProperty('record', {
59
+ meta: createCurrentRecordMetaFactory(this.context, () => this.context.collection),
60
+ });
61
+ }
62
+
54
63
  renderHiddenInConfig() {
55
64
  return (
56
65
  <Tooltip
@@ -121,10 +121,11 @@ const Columns = observer<any>(({ record, model, index }) => {
121
121
  fork.context.defineProperty('recordIndex', {
122
122
  get: () => index,
123
123
  });
124
+ const rendererKey = `${fork.uid}:${fork.forkId}`;
124
125
  const renderer = (
125
126
  <FlowModelRenderer
126
127
  showFlowSettings={{ showBorder: false, toolbarPosition: 'above' }}
127
- key={fork.uid}
128
+ key={rendererKey}
128
129
  model={fork}
129
130
  inputArgs={record}
130
131
  fallback={<Skeleton.Button size="small" />}
@@ -36,7 +36,15 @@ import { BlockSceneEnum } from '../../base/BlockModel';
36
36
  import { CollectionBlockModel } from '../../base/CollectionBlockModel';
37
37
  import { QuickEditFormModel } from '../form/QuickEditFormModel';
38
38
  import { TableColumnModel } from './TableColumnModel';
39
- import { extractIndex, adjustColumnOrder, setNestedValue, extractIds, getRowKey, useBlockHeight } from './utils';
39
+ import {
40
+ extractIndex,
41
+ adjustColumnOrder,
42
+ setNestedValue,
43
+ extractIds,
44
+ extractRowKeys,
45
+ getRowKey,
46
+ useBlockHeight,
47
+ } from './utils';
40
48
  import { resolveTableSorterField } from './sortUtils';
41
49
  import { commonConditionHandler, ConditionBuilder } from '../../../components/ConditionBuilder';
42
50
  import { BulkDeleteActionModel } from '../../actions/BulkDeleteActionModel';
@@ -222,6 +230,22 @@ export class TableBlockModel extends CollectionBlockModel<TableBlockModelStructu
222
230
  return super.resource as MultiRecordResource;
223
231
  }
224
232
 
233
+ resetAfterFilterChange() {
234
+ if (!this.props.treeTable) {
235
+ return;
236
+ }
237
+
238
+ const expandAll = !!this.props.defaultExpandAllRows;
239
+ const expandedRowKeys = expandAll ? extractRowKeys(this.resource.getData(), this.collection.filterTargetKey) : [];
240
+
241
+ this.setProps('expandedRowKeys', expandedRowKeys);
242
+ this.mapSubModels('actions', (action) => {
243
+ if ('setExpandFlag' in action && typeof action.setExpandFlag === 'function') {
244
+ action.setExpandFlag(expandAll);
245
+ }
246
+ });
247
+ }
248
+
225
249
  private readonly columns = observable.ref([]);
226
250
  private disposeAutorun: () => void;
227
251
 
@@ -244,7 +268,9 @@ export class TableBlockModel extends CollectionBlockModel<TableBlockModelStructu
244
268
  }
245
269
 
246
270
  createResource(ctx, params) {
247
- return this.context.createResource(MultiRecordResource);
271
+ const resource = this.context.createResource(MultiRecordResource);
272
+ resource.addRequestHeader('X-With-ACL-Meta', 'true');
273
+ return resource;
248
274
  }
249
275
 
250
276
  /**
@@ -310,7 +336,7 @@ export class TableBlockModel extends CollectionBlockModel<TableBlockModelStructu
310
336
  transform: translateY(-50%);
311
337
  }
312
338
  &:hover {
313
- background: rgba(24, 144, 255, 0.1) !important;
339
+ box-shadow: inset 0 0 0 9999px rgba(24, 144, 255, 0.1);
314
340
  }
315
341
  &:hover .edit-icon {
316
342
  display: inline-flex;
@@ -27,6 +27,7 @@ import {
27
27
  ModelRenderMode,
28
28
  useFlowModel,
29
29
  } from '@nocobase/flow-engine';
30
+ import type { FlowModel } from '@nocobase/flow-engine';
30
31
  import { useTranslation } from 'react-i18next';
31
32
  import { TableColumnProps, Tooltip, Space, InputNumber, Button, Divider } from 'antd';
32
33
  import { get, omit, capitalize } from 'lodash';
@@ -61,19 +62,28 @@ export function FieldDeletePlaceholder(props: any) {
61
62
  );
62
63
  }
63
64
 
64
- function FieldWithoutPermissionPlaceholder() {
65
+ type FieldPermissionPlaceholderModel = FlowModel & {
66
+ fieldPath?: string;
67
+ forbidden?: { actionName?: string } | null;
68
+ };
69
+
70
+ export function FieldWithoutPermissionPlaceholder({
71
+ targetModel,
72
+ }: {
73
+ targetModel?: FieldPermissionPlaceholderModel;
74
+ } = {}) {
65
75
  const { t } = useTranslation();
66
- const model: any = useFlowModel();
67
- const blockModel = model.context.blockModel;
68
- const collection = model.context.collectionField?.collection || blockModel.collection;
69
- const dataSource = collection.dataSource;
76
+ const contextModel = useFlowModel<FieldPermissionPlaceholderModel>();
77
+ const model = targetModel || contextModel;
78
+ const collection = model.context.collectionField?.collection || model.context.blockModel?.collection;
79
+ const dataSource = collection?.dataSource;
70
80
  const name = model.context.collectionField?.name || model.fieldPath;
71
81
  const nameValue = useMemo(() => {
72
- const dataSourcePrefix = `${t(dataSource.displayName || dataSource.key)} > `;
82
+ const dataSourcePrefix = dataSource ? `${t(dataSource.displayName || dataSource.key)} > ` : '';
73
83
  const collectionPrefix = collection ? `${t(collection.title) || collection.name || collection.tableName} > ` : '';
74
84
  return `${dataSourcePrefix}${collectionPrefix}${name}`;
75
- }, [collection, dataSource.displayName, dataSource.key, name, t]);
76
- const { actionName } = model.forbidden;
85
+ }, [collection, dataSource, name, t]);
86
+ const actionName = model.forbidden?.actionName || 'view';
77
87
  const messageValue = useMemo(() => {
78
88
  return t(
79
89
  `The current user only has the UI configuration permission, but don't have "{{actionName}}" permission for field "{{name}}"`,
@@ -194,6 +204,35 @@ export class TableColumnModel extends DisplayItemModel {
194
204
  .filter(Boolean);
195
205
  }
196
206
 
207
+ canQuickEdit(record: Record<string, unknown>): boolean {
208
+ if (!this.props.editable) {
209
+ return false;
210
+ }
211
+ if (this.context.skipAclCheck) {
212
+ return true;
213
+ }
214
+
215
+ const blockModel = this.context.blockModel;
216
+ const collection = blockModel?.collection;
217
+ const resource = blockModel?.resource;
218
+ const collectionField = this.collectionField;
219
+ if (!collection || !resource || !collectionField) {
220
+ return false;
221
+ }
222
+
223
+ const filterByTk = collection.getFilterByTK(record);
224
+ const recordPkValue = typeof filterByTk === 'string' || typeof filterByTk === 'number' ? filterByTk : undefined;
225
+
226
+ return this.context.acl.can({
227
+ dataSourceKey: collection.dataSourceKey,
228
+ resourceName: resource.getResourceName() || collection.name,
229
+ actionName: 'update',
230
+ recordPkValue,
231
+ allowedActions: resource.getMeta('allowedActions'),
232
+ fields: [collectionField.name],
233
+ });
234
+ }
235
+
197
236
  getColumnProps(): TableColumnProps & { sortField?: string } {
198
237
  if (!this.props.width) {
199
238
  return;
@@ -250,7 +289,7 @@ export class TableColumnModel extends DisplayItemModel {
250
289
  record,
251
290
  recordIndex: record?.__index || recordIndex,
252
291
  width: this.props.width - 16,
253
- editable: this.props.editable,
292
+ editable: this.canQuickEdit(record),
254
293
  dataIndex: this.props.dataIndex,
255
294
  title: this.props.title,
256
295
  overflowMode: this.props.overflowMode,
@@ -373,14 +412,18 @@ TableColumnModel.registerFlow({
373
412
  currentProps: ctx.model.props,
374
413
  })
375
414
  : undefined;
415
+ const collectionFieldComponentProps = collectionField.getComponentProps();
376
416
  const componentProps =
377
417
  collectionField.isAssociationField() && titleField
378
418
  ? {
379
- ...collectionField.getComponentProps(),
419
+ ...collectionFieldComponentProps,
380
420
  ...targetCollectionField?.getComponentProps?.(),
381
421
  ...savedDateTimeDisplayProps,
382
422
  }
383
- : collectionField.getComponentProps();
423
+ : {
424
+ ...collectionFieldComponentProps,
425
+ ...savedDateTimeDisplayProps,
426
+ };
384
427
  ctx.model.setProps('title', collectionField.title);
385
428
  ctx.model.setProps('dataIndex', collectionField.name);
386
429
  // for quick edit
@@ -11,17 +11,62 @@ import { FlowEngine } from '@nocobase/flow-engine';
11
11
  import { describe, expect, it, vi } from 'vitest';
12
12
  import { JSColumnModel } from '../JSColumnModel';
13
13
 
14
- describe('JSColumnModel row refresh', () => {
14
+ describe('JSColumnModel', () => {
15
+ it('declares current record metadata before rendering any row', async () => {
16
+ const engine = new FlowEngine();
17
+ engine.registerModels({ JSColumnModel });
18
+ const model = engine.createModel<JSColumnModel>({
19
+ use: 'JSColumnModel',
20
+ uid: 'js-column-record-meta',
21
+ props: {
22
+ width: 200,
23
+ title: 'JS column',
24
+ },
25
+ });
26
+
27
+ engine.context.dataSourceManager.getDataSource('main').addCollection({
28
+ name: 'users',
29
+ filterTargetKey: 'id',
30
+ fields: [
31
+ { name: 'id', type: 'integer', interface: 'number' },
32
+ { name: 'age', type: 'integer', interface: 'integer' },
33
+ ],
34
+ });
35
+
36
+ const collection = engine.context.dataSourceManager.getCollection('main', 'users');
37
+ model.context.defineProperty('collection', {
38
+ value: collection,
39
+ });
40
+
41
+ const recordNode = model.context.getPropertyMetaTree().find((node) => node.name === 'record');
42
+ const recordFields =
43
+ typeof recordNode?.children === 'function' ? await recordNode.children() : recordNode?.children || [];
44
+
45
+ expect(model.context.record).toBeUndefined();
46
+ expect(recordNode).toMatchObject({
47
+ name: 'record',
48
+ title: 'Current record',
49
+ paths: ['record'],
50
+ });
51
+ expect(recordFields).toEqual(
52
+ expect.arrayContaining([
53
+ expect.objectContaining({ name: 'id', paths: ['record', 'id'] }),
54
+ expect.objectContaining({ name: 'age', paths: ['record', 'age'] }),
55
+ ]),
56
+ );
57
+ });
58
+
15
59
  it('changes renderer key and invalidates beforeRender cache when row content changes', () => {
16
60
  const engine = new FlowEngine();
17
- const model = new JSColumnModel({
61
+ engine.registerModels({ JSColumnModel });
62
+ const model = engine.createModel<JSColumnModel>({
63
+ use: 'JSColumnModel',
18
64
  uid: 'js-column-row-refresh',
19
- flowEngine: engine,
20
65
  props: {
21
66
  width: 200,
22
67
  title: 'JS column',
23
68
  },
24
- } as any);
69
+ });
25
70
 
26
71
  engine.context.dataSourceManager.getDataSource('main').addCollection({
27
72
  name: 'users',
@@ -39,12 +84,14 @@ describe('JSColumnModel row refresh', () => {
39
84
  });
40
85
 
41
86
  const column = model.getColumnProps();
42
- const first = column.render(null, { id: 1, age: 3, workyears: 39.2 }, 0) as any;
87
+ const firstRecord = { id: 1, age: 3, workyears: 39.2 };
88
+ const first = column.render(null, firstRecord, 0) as any;
43
89
  const fork = model.getFork('1') as any;
44
90
  const invalidateFlowCache = vi.fn();
45
91
  fork.invalidateFlowCache = invalidateFlowCache;
46
92
  const second = column.render(null, { id: 1, age: 37, workyears: 39.2 }, 0) as any;
47
93
 
94
+ expect(fork.context.record).toEqual({ id: 1, age: 37, workyears: 39.2 });
48
95
  expect(first.key).not.toBe(second.key);
49
96
  expect(invalidateFlowCache).toHaveBeenCalledWith('beforeRender');
50
97
  });