@nocobase/client-v2 2.1.0-alpha.40 → 2.1.0-alpha.46

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 (338) hide show
  1. package/es/Application.d.ts +7 -0
  2. package/es/BaseApplication.d.ts +13 -0
  3. package/es/RouterManager.d.ts +1 -0
  4. package/es/authRedirect.d.ts +12 -0
  5. package/es/collection-field-interface/CollectionFieldInterface.d.ts +51 -15
  6. package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +82 -3
  7. package/es/collection-manager/field-configure.d.ts +80 -0
  8. package/es/collection-manager/field-validation.d.ts +43 -0
  9. package/es/collection-manager/filter-operators/index.d.ts +46 -0
  10. package/es/collection-manager/filter-operators/operators.d.ts +30 -0
  11. package/es/collection-manager/interfaces/checkbox.d.ts +1 -41
  12. package/es/collection-manager/interfaces/checkboxGroup.d.ts +12 -44
  13. package/es/collection-manager/interfaces/collection.d.ts +12 -51
  14. package/es/collection-manager/interfaces/color.d.ts +1 -16
  15. package/es/collection-manager/interfaces/createdAt.d.ts +1 -44
  16. package/es/collection-manager/interfaces/createdBy.d.ts +0 -4
  17. package/es/collection-manager/interfaces/dateOnly.d.ts +7 -44
  18. package/es/collection-manager/interfaces/datetime.d.ts +1 -44
  19. package/es/collection-manager/interfaces/datetimeNoTz.d.ts +1 -44
  20. package/es/collection-manager/interfaces/email.d.ts +1 -29
  21. package/es/collection-manager/interfaces/id.d.ts +1 -16
  22. package/es/collection-manager/interfaces/index.d.ts +2 -3
  23. package/es/collection-manager/interfaces/input.d.ts +1 -102
  24. package/es/collection-manager/interfaces/integer.d.ts +1 -95
  25. package/es/collection-manager/interfaces/json.d.ts +16 -7
  26. package/es/collection-manager/interfaces/m2m.d.ts +11 -19
  27. package/es/collection-manager/interfaces/m2o.d.ts +11 -19
  28. package/es/collection-manager/interfaces/markdown.d.ts +1 -63
  29. package/es/collection-manager/interfaces/multipleSelect.d.ts +12 -44
  30. package/es/collection-manager/interfaces/nanoid.d.ts +1 -34
  31. package/es/collection-manager/interfaces/number.d.ts +1 -87
  32. package/es/collection-manager/interfaces/o2m.d.ts +12 -24
  33. package/es/collection-manager/interfaces/obo.d.ts +207 -0
  34. package/es/collection-manager/interfaces/oho.d.ts +207 -0
  35. package/es/collection-manager/interfaces/password.d.ts +1 -56
  36. package/es/collection-manager/interfaces/percent.d.ts +1 -84
  37. package/es/collection-manager/interfaces/phone.d.ts +1 -25
  38. package/es/collection-manager/interfaces/properties/index.d.ts +0 -28
  39. package/es/collection-manager/interfaces/radioGroup.d.ts +1 -29
  40. package/es/collection-manager/interfaces/richText.d.ts +1 -63
  41. package/es/collection-manager/interfaces/select.d.ts +12 -44
  42. package/es/collection-manager/interfaces/snowflake-id.d.ts +1 -34
  43. package/es/collection-manager/interfaces/tableoid.d.ts +1 -10
  44. package/es/collection-manager/interfaces/textarea.d.ts +1 -51
  45. package/es/collection-manager/interfaces/time.d.ts +1 -16
  46. package/es/collection-manager/interfaces/types.d.ts +3 -12
  47. package/es/collection-manager/interfaces/unixTimestamp.d.ts +1 -44
  48. package/es/collection-manager/interfaces/updatedAt.d.ts +1 -44
  49. package/es/collection-manager/interfaces/updatedBy.d.ts +0 -4
  50. package/es/collection-manager/interfaces/url.d.ts +1 -20
  51. package/es/collection-manager/interfaces/uuid.d.ts +1 -34
  52. package/es/collection-manager/template-fields.d.ts +53 -0
  53. package/es/components/KeepAlive.d.ts +22 -0
  54. package/es/components/RouterBridge.d.ts +9 -0
  55. package/es/components/form/DialogFormLayout.d.ts +5 -29
  56. package/es/components/form/TypedVariableInput.d.ts +75 -0
  57. package/es/components/form/VariableInput.d.ts +53 -2
  58. package/es/components/form/filter/CollectionFilter.d.ts +49 -0
  59. package/es/components/form/filter/CollectionFilterItem.d.ts +49 -0
  60. package/es/components/form/filter/DateFilterDynamicComponent.d.ts +57 -0
  61. package/es/components/form/filter/FilterValueInput.d.ts +29 -0
  62. package/es/components/form/filter/index.d.ts +11 -0
  63. package/es/components/form/filter/useFilterActionProps.d.ts +96 -0
  64. package/es/components/form/index.d.ts +2 -0
  65. package/es/data-source/ExtendCollectionsProvider.d.ts +50 -0
  66. package/es/data-source/index.d.ts +9 -0
  67. package/es/flow/FlowPage.d.ts +2 -1
  68. package/es/flow/admin-shell/AdminLayoutRouteCoordinator.d.ts +8 -40
  69. package/es/flow/admin-shell/BaseLayoutModel.d.ts +89 -0
  70. package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +74 -0
  71. package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +12 -0
  72. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +1 -0
  73. package/es/flow/admin-shell/admin-layout/AdminLayoutModel.d.ts +7 -92
  74. package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +11 -0
  75. package/es/flow/admin-shell/admin-layout/index.d.ts +3 -0
  76. package/es/flow/admin-shell/admin-layout/useApplications.d.ts +3 -2
  77. package/es/flow/admin-shell/useAdminLayoutRoutePage.d.ts +2 -2
  78. package/es/flow/admin-shell/useLayoutRoutePage.d.ts +23 -0
  79. package/es/flow/components/ConditionBuilder.d.ts +1 -0
  80. package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
  81. package/es/flow/components/FieldAssignValueInput.d.ts +1 -0
  82. package/es/flow/components/FlowRoute.d.ts +10 -1
  83. package/es/flow/components/fieldAssignOptions.d.ts +10 -0
  84. package/es/flow/components/filter/LinkageFilterItem.d.ts +1 -0
  85. package/es/flow/components/filter/VariableFilterItem.d.ts +1 -0
  86. package/es/flow/components/filter/index.d.ts +2 -0
  87. package/es/flow/components/filter/metaTreeAssociationDepth.d.ts +13 -0
  88. package/es/flow/components/filter/useFilterOptions.d.ts +66 -0
  89. package/es/flow/index.d.ts +4 -0
  90. package/es/flow/models/base/AssociationFieldGroupModel.d.ts +2 -33
  91. package/es/flow/models/base/PageModel/PageModel.d.ts +3 -1
  92. package/es/flow/models/blocks/assign-form/assignFieldValuesFlow.d.ts +92 -0
  93. package/es/flow/models/blocks/assign-form/index.d.ts +1 -0
  94. package/es/flow/models/blocks/form/FormActionGroupModel.d.ts +1 -0
  95. package/es/flow/models/blocks/form/FormActionModel.d.ts +9 -2
  96. package/es/flow/models/blocks/table/TableBlockModel.d.ts +10 -0
  97. package/es/flow/models/blocks/table/TableColumnModel.d.ts +2 -0
  98. package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.d.ts +1 -1
  99. package/es/flow-compat/passwordUtils.d.ts +1 -1
  100. package/es/index.d.ts +6 -0
  101. package/es/index.mjs +552 -459
  102. package/es/layout-manager/LayoutContentRoute.d.ts +14 -0
  103. package/es/layout-manager/LayoutManager.d.ts +22 -0
  104. package/es/layout-manager/LayoutRoute.d.ts +14 -0
  105. package/es/layout-manager/index.d.ts +13 -0
  106. package/es/layout-manager/types.d.ts +20 -0
  107. package/es/layout-manager/utils.d.ts +14 -0
  108. package/es/nocobase-buildin-plugin/index.d.ts +3 -10
  109. package/es/settings-center/index.d.ts +1 -1
  110. package/es/settings-center/plugin-manager/BulkEnableButton.d.ts +15 -0
  111. package/es/settings-center/plugin-manager/PluginCard.d.ts +15 -0
  112. package/es/settings-center/plugin-manager/PluginDetail.d.ts +16 -0
  113. package/es/settings-center/{PluginManagerPage.d.ts → plugin-manager/index.d.ts} +1 -7
  114. package/es/settings-center/plugin-manager/types.d.ts +34 -0
  115. package/lib/index.js +552 -459
  116. package/package.json +8 -7
  117. package/src/Application.tsx +51 -12
  118. package/src/BaseApplication.tsx +32 -0
  119. package/src/PluginSettingsManager.ts +1 -1
  120. package/src/RouterManager.tsx +17 -1
  121. package/src/__tests__/PluginSettingsManager.test.ts +41 -2
  122. package/src/__tests__/app.test.tsx +17 -1
  123. package/src/__tests__/globalDeps.test.ts +1 -0
  124. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +50 -2
  125. package/src/__tests__/plugin-manager.test.tsx +177 -0
  126. package/src/__tests__/settings-center.test.tsx +24 -2
  127. package/src/authRedirect.ts +43 -0
  128. package/src/collection-field-interface/CollectionFieldInterface.ts +71 -77
  129. package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +201 -4
  130. package/src/collection-manager/field-configure.ts +548 -0
  131. package/src/collection-manager/field-validation.ts +195 -0
  132. package/src/collection-manager/filter-operators/index.ts +176 -0
  133. package/src/collection-manager/{interfaces/properties → filter-operators}/operators.ts +24 -13
  134. package/src/collection-manager/interfaces/checkbox.ts +2 -9
  135. package/src/collection-manager/interfaces/checkboxGroup.ts +2 -10
  136. package/src/collection-manager/interfaces/collection.ts +2 -15
  137. package/src/collection-manager/interfaces/color.ts +2 -2
  138. package/src/collection-manager/interfaces/createdAt.ts +2 -2
  139. package/src/collection-manager/interfaces/createdBy.ts +1 -12
  140. package/src/collection-manager/interfaces/dateOnly.ts +8 -2
  141. package/src/collection-manager/interfaces/datetime.ts +2 -2
  142. package/src/collection-manager/interfaces/datetimeNoTz.ts +2 -2
  143. package/src/collection-manager/interfaces/email.ts +2 -9
  144. package/src/collection-manager/interfaces/id.ts +1 -2
  145. package/src/collection-manager/interfaces/index.ts +2 -3
  146. package/src/collection-manager/interfaces/input.ts +2 -133
  147. package/src/collection-manager/interfaces/integer.ts +2 -71
  148. package/src/collection-manager/interfaces/json.tsx +17 -11
  149. package/src/collection-manager/interfaces/m2m.tsx +0 -21
  150. package/src/collection-manager/interfaces/m2o.tsx +0 -22
  151. package/src/collection-manager/interfaces/markdown.ts +2 -51
  152. package/src/collection-manager/interfaces/multipleSelect.ts +2 -14
  153. package/src/collection-manager/interfaces/nanoid.ts +2 -2
  154. package/src/collection-manager/interfaces/number.ts +2 -85
  155. package/src/collection-manager/interfaces/o2m.tsx +1 -22
  156. package/src/collection-manager/interfaces/obo.tsx +145 -0
  157. package/src/collection-manager/interfaces/oho.tsx +145 -0
  158. package/src/collection-manager/interfaces/password.ts +2 -44
  159. package/src/collection-manager/interfaces/percent.ts +2 -74
  160. package/src/collection-manager/interfaces/phone.ts +2 -2
  161. package/src/collection-manager/interfaces/properties/index.ts +0 -133
  162. package/src/collection-manager/interfaces/radioGroup.ts +2 -2
  163. package/src/collection-manager/interfaces/richText.ts +2 -51
  164. package/src/collection-manager/interfaces/select.ts +2 -14
  165. package/src/collection-manager/interfaces/snowflake-id.ts +2 -2
  166. package/src/collection-manager/interfaces/tableoid.ts +1 -2
  167. package/src/collection-manager/interfaces/textarea.ts +2 -51
  168. package/src/collection-manager/interfaces/time.ts +2 -2
  169. package/src/collection-manager/interfaces/types.ts +4 -12
  170. package/src/collection-manager/interfaces/unixTimestamp.tsx +2 -2
  171. package/src/collection-manager/interfaces/updatedAt.ts +2 -2
  172. package/src/collection-manager/interfaces/updatedBy.ts +1 -12
  173. package/src/collection-manager/interfaces/url.ts +2 -4
  174. package/src/collection-manager/interfaces/uuid.ts +2 -2
  175. package/src/collection-manager/template-fields.ts +109 -0
  176. package/src/components/KeepAlive.tsx +131 -0
  177. package/src/components/README.md +138 -6
  178. package/src/components/README.zh-CN.md +138 -7
  179. package/src/components/RouterBridge.tsx +28 -4
  180. package/src/components/__tests__/KeepAlive.test.tsx +63 -0
  181. package/src/components/__tests__/RouterBridge.test.tsx +27 -0
  182. package/src/components/form/DialogFormLayout.tsx +5 -29
  183. package/src/components/form/TypedVariableInput.tsx +441 -0
  184. package/src/components/form/VariableInput.tsx +101 -28
  185. package/src/components/form/__tests__/TypedVariableInput.test.tsx +152 -0
  186. package/src/components/form/__tests__/VariableInput.test.ts +85 -0
  187. package/src/components/form/filter/CollectionFilter.tsx +111 -0
  188. package/src/components/form/filter/CollectionFilterItem.tsx +184 -0
  189. package/src/components/form/filter/DateFilterDynamicComponent.tsx +283 -0
  190. package/src/components/form/filter/FilterValueInput.tsx +198 -0
  191. package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +247 -0
  192. package/src/components/form/filter/__tests__/DateFilterDynamicComponent.test.tsx +148 -0
  193. package/src/components/form/filter/__tests__/FilterValueInput.test.tsx +243 -0
  194. package/src/components/form/filter/__tests__/compileFilterGroup.test.ts +146 -0
  195. package/src/components/form/filter/index.ts +13 -0
  196. package/src/components/form/filter/useFilterActionProps.ts +203 -0
  197. package/src/components/form/index.tsx +2 -0
  198. package/src/data-source/ExtendCollectionsProvider.tsx +144 -0
  199. package/src/data-source/__tests__/ExtendCollectionsProvider.test.tsx +264 -0
  200. package/src/data-source/index.ts +10 -0
  201. package/src/flow/FlowPage.tsx +35 -7
  202. package/src/flow/__tests__/FlowPage.test.tsx +79 -0
  203. package/src/flow/__tests__/FlowRoute.test.tsx +537 -3
  204. package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +100 -0
  205. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +191 -0
  206. package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +23 -1
  207. package/src/flow/actions/__tests__/openView.subModelKey.test.tsx +33 -0
  208. package/src/flow/actions/aclCheck.tsx +4 -0
  209. package/src/flow/actions/aclCheckRefresh.tsx +4 -0
  210. package/src/flow/actions/dataScope.tsx +8 -1
  211. package/src/flow/actions/dateTimeFormat.tsx +12 -8
  212. package/src/flow/actions/linkageRules.tsx +127 -2
  213. package/src/flow/actions/linkageRulesRefresh.tsx +2 -3
  214. package/src/flow/actions/openView.tsx +28 -4
  215. package/src/flow/actions/setTargetDataScope.tsx +1 -1
  216. package/src/flow/admin-shell/AdminLayoutRouteCoordinator.ts +11 -329
  217. package/src/flow/admin-shell/BaseLayoutModel.tsx +455 -0
  218. package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +502 -0
  219. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +547 -3
  220. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +35 -7
  221. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +160 -0
  222. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +15 -13
  223. package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +28 -201
  224. package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +11 -2
  225. package/src/flow/admin-shell/admin-layout/AppListRender.tsx +139 -0
  226. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +76 -27
  227. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +149 -27
  228. package/src/flow/admin-shell/admin-layout/index.ts +3 -0
  229. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +11 -0
  230. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +2 -4
  231. package/src/flow/admin-shell/admin-layout/useApplications.tsx +34 -1
  232. package/src/flow/admin-shell/useAdminLayoutRoutePage.ts +10 -26
  233. package/src/flow/admin-shell/useLayoutRoutePage.ts +61 -0
  234. package/src/flow/common/Markdown/Edit.tsx +32 -4
  235. package/src/flow/components/AdminLayout.tsx +4 -154
  236. package/src/flow/components/ConditionBuilder.tsx +12 -2
  237. package/src/flow/components/FieldAssignRulesEditor.tsx +37 -46
  238. package/src/flow/components/FieldAssignValueInput.tsx +8 -4
  239. package/src/flow/components/FlowRoute.tsx +105 -15
  240. package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +112 -1
  241. package/src/flow/components/__tests__/fieldAssignOptions.test.ts +71 -1
  242. package/src/flow/components/code-editor/__tests__/runjsCompletions.test.ts +272 -27
  243. package/src/flow/components/code-editor/runjsCompletions.ts +490 -9
  244. package/src/flow/components/fieldAssignOptions.ts +23 -6
  245. package/src/flow/components/filter/LinkageFilterItem.tsx +22 -9
  246. package/src/flow/components/filter/VariableFilterItem.tsx +27 -9
  247. package/src/flow/components/filter/__tests__/fieldsToOptions.test.ts +48 -0
  248. package/src/flow/components/filter/__tests__/metaTreeAssociationDepth.test.ts +60 -0
  249. package/src/flow/components/filter/fieldsToOptions.ts +9 -4
  250. package/src/flow/components/filter/index.ts +3 -0
  251. package/src/flow/components/filter/metaTreeAssociationDepth.ts +69 -0
  252. package/src/flow/components/filter/useFilterOptions.ts +102 -0
  253. package/src/flow/index.ts +4 -0
  254. package/src/flow/models/actions/FilterActionModel.tsx +15 -3
  255. package/src/flow/models/actions/UpdateRecordActionModel.tsx +14 -95
  256. package/src/flow/models/actions/UpdateRecordActionUtils.ts +4 -7
  257. package/src/flow/models/actions/__tests__/AssignFormRefill.test.ts +116 -1
  258. package/src/flow/models/base/ActionModel.tsx +8 -1
  259. package/src/flow/models/base/AssociationFieldGroupModel.tsx +20 -3
  260. package/src/flow/models/base/GridModel.tsx +1 -1
  261. package/src/flow/models/base/PageModel/ChildPageModel.tsx +5 -1
  262. package/src/flow/models/base/PageModel/PageModel.tsx +51 -18
  263. package/src/flow/models/base/PageModel/RootPageModel.tsx +6 -13
  264. package/src/flow/models/base/PageModel/__tests__/ChildPageModel.test.ts +12 -0
  265. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +102 -1
  266. package/src/flow/models/base/RouteModel.tsx +1 -1
  267. package/src/flow/models/base/__tests__/AssociationFieldGroupModel.test.ts +86 -0
  268. package/src/flow/models/base/__tests__/GridModel.render.test.tsx +53 -0
  269. package/src/flow/models/blocks/assign-form/AssignFormGridModel.tsx +12 -3
  270. package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +77 -6
  271. package/src/flow/models/blocks/assign-form/__tests__/assignFieldValuesFlow.editor.test.tsx +216 -0
  272. package/src/flow/models/blocks/assign-form/assignFieldValuesFlow.tsx +308 -0
  273. package/src/flow/models/blocks/assign-form/index.ts +1 -0
  274. package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +1 -1
  275. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +6 -1
  276. package/src/flow/models/blocks/filter-manager/flow-actions/connectFields.tsx +48 -6
  277. package/src/flow/models/blocks/form/FormActionGroupModel.tsx +14 -0
  278. package/src/flow/models/blocks/form/FormActionModel.tsx +30 -3
  279. package/src/flow/models/blocks/form/FormAssociationItemModel.tsx +9 -0
  280. package/src/flow/models/blocks/form/FormItemModel.tsx +17 -1
  281. package/src/flow/models/blocks/form/__tests__/FormActionGroupModel.test.ts +46 -0
  282. package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
  283. package/src/flow/models/blocks/form/__tests__/FormItemModel.defineChildren.test.ts +27 -2
  284. package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +71 -0
  285. package/src/flow/models/blocks/form/submitHandler.ts +8 -1
  286. package/src/flow/models/blocks/form/submitValues.ts +4 -1
  287. package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +29 -0
  288. package/src/flow/models/blocks/form/value-runtime/runtime.ts +5 -1
  289. package/src/flow/models/blocks/table/TableBlockModel.tsx +118 -16
  290. package/src/flow/models/blocks/table/TableColumnModel.tsx +15 -5
  291. package/src/flow/models/blocks/table/TableCustomColumnModel.tsx +2 -2
  292. package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +114 -0
  293. package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +18 -0
  294. package/src/flow/models/fields/AssociationFieldModel/SubFormFieldModel.tsx +7 -1
  295. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +6 -7
  296. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +1 -1
  297. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -5
  298. package/src/flow/models/fields/ClickableFieldModel.tsx +9 -1
  299. package/src/flow/models/fields/CollectionSelectorFieldModel.tsx +8 -2
  300. package/src/flow/models/fields/DisplayEnumFieldModel.tsx +8 -2
  301. package/src/flow/models/fields/DisplayTimeFieldModel.tsx +1 -1
  302. package/src/flow/models/fields/TimeFieldModel.tsx +1 -1
  303. package/src/flow/models/fields/__tests__/TimeFieldModel.test.tsx +61 -0
  304. package/src/flow/models/fields/mobile-components/MobileDatePicker.tsx +19 -3
  305. package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +5 -3
  306. package/src/flow/models/fields/mobile-components/__tests__/MobileDatePicker.test.tsx +94 -0
  307. package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +70 -0
  308. package/src/flow/models/topbar/TopbarActionModel.tsx +1 -1
  309. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +91 -0
  310. package/src/index.ts +6 -0
  311. package/src/layout-manager/LayoutContentRoute.tsx +90 -0
  312. package/src/layout-manager/LayoutManager.tsx +185 -0
  313. package/src/layout-manager/LayoutRoute.tsx +138 -0
  314. package/src/layout-manager/__tests__/LayoutManager.test.tsx +335 -0
  315. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +473 -0
  316. package/src/layout-manager/index.ts +14 -0
  317. package/src/layout-manager/types.ts +22 -0
  318. package/src/layout-manager/utils.ts +37 -0
  319. package/src/nocobase-buildin-plugin/index.tsx +69 -67
  320. package/src/nocobase-buildin-plugin/plugins/LocalePlugin.ts +1 -0
  321. package/src/settings-center/index.ts +1 -1
  322. package/src/settings-center/plugin-manager/BulkEnableButton.tsx +111 -0
  323. package/src/settings-center/plugin-manager/PluginCard.tsx +270 -0
  324. package/src/settings-center/plugin-manager/PluginDetail.tsx +195 -0
  325. package/src/settings-center/plugin-manager/index.tsx +254 -0
  326. package/src/settings-center/plugin-manager/types.ts +35 -0
  327. package/src/settings-center/utils.tsx +8 -1
  328. package/src/theme/__tests__/globalStyles.test.ts +24 -0
  329. package/src/theme/globalStyles.ts +10 -0
  330. package/src/utils/globalDeps.ts +2 -0
  331. package/es/collection-manager/interfaces/linkTo.d.ts +0 -90
  332. package/es/collection-manager/interfaces/o2o.d.ts +0 -621
  333. package/es/collection-manager/interfaces/properties/operators.d.ts +0 -294
  334. package/es/collection-manager/interfaces/subTable.d.ts +0 -172
  335. package/src/collection-manager/interfaces/linkTo.ts +0 -120
  336. package/src/collection-manager/interfaces/o2o.tsx +0 -561
  337. package/src/collection-manager/interfaces/subTable.ts +0 -218
  338. package/src/settings-center/PluginManagerPage.tsx +0 -162
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { describe, it, expect } from 'vitest';
11
11
  import { FlowEngine, FlowModel } from '@nocobase/flow-engine';
12
- import { UpdateRecordActionModel } from '../../..'; // 这样可以解决循环依赖问题
12
+ import { FormSubmitActionModel, UpdateRecordActionModel } from '../../..'; // 这样可以解决循环依赖问题
13
13
 
14
14
  /**
15
15
  * 精简版 AssignFormModel(仅用于单测):
@@ -52,4 +52,119 @@ describe('AssignForm value refill and save (beforeParamsSave)', () => {
52
52
  const saved = action.getStepParams('assignSettings', 'assignFieldValues');
53
53
  expect(saved?.assignedValues).toEqual({ nickname: 'Alice', score: 99 });
54
54
  });
55
+
56
+ it('UpdateRecordActionModel: saves assignedValues from assignForm subModel when assignFormUid is not ready', async () => {
57
+ const root = new FlowEngine();
58
+ root.registerModels({ UpdateRecordActionModel, AssignFormModel: TestAssignFormModel });
59
+
60
+ const action = root.createModel<UpdateRecordActionModel>({ use: 'UpdateRecordActionModel', uid: 'act-u-sub' });
61
+ const form = root.createModel<TestAssignFormModel>({
62
+ use: 'AssignFormModel',
63
+ uid: 'form-u-sub',
64
+ parentId: action.uid,
65
+ subKey: 'assignForm',
66
+ });
67
+ form.setAssignedValues({ nickname: 'Bob' });
68
+ action.setSubModel('assignForm', form);
69
+
70
+ const flow = action.getFlow('assignSettings') as any;
71
+ const step = flow?.steps?.assignFieldValues;
72
+
73
+ await step.beforeParamsSave({ engine: root, model: action }, {}, {});
74
+
75
+ const saved = action.getStepParams('assignSettings', 'assignFieldValues');
76
+ expect(saved?.assignedValues).toEqual({ nickname: 'Bob' });
77
+ });
78
+
79
+ it('UpdateRecordActionModel: keeps previous assignedValues when AssignForm is unavailable during save', async () => {
80
+ const root = new FlowEngine();
81
+ root.registerModels({ UpdateRecordActionModel, AssignFormModel: TestAssignFormModel });
82
+
83
+ const action = root.createModel<UpdateRecordActionModel>({ use: 'UpdateRecordActionModel', uid: 'act-u-missing' });
84
+ action.setStepParams('assignSettings', 'assignFieldValues', {});
85
+
86
+ const flow = action.getFlow('assignSettings') as any;
87
+ const step = flow?.steps?.assignFieldValues;
88
+
89
+ await step.beforeParamsSave(
90
+ { engine: root, model: action },
91
+ {},
92
+ {
93
+ assignedValues: { nickname: 'Previous' },
94
+ },
95
+ );
96
+
97
+ const saved = action.getStepParams('assignSettings', 'assignFieldValues');
98
+ expect(saved?.assignedValues).toEqual({ nickname: 'Previous' });
99
+ });
100
+
101
+ it('FormSubmitActionModel: reuses assignFieldValues step and saves assignedValues from AssignForm', async () => {
102
+ const root = new FlowEngine();
103
+
104
+ root.registerModels({ FormSubmitActionModel, AssignFormModel: TestAssignFormModel });
105
+
106
+ const action = root.createModel<FormSubmitActionModel>({ use: 'FormSubmitActionModel', uid: 'submit-u' });
107
+ const form = root.createModel<TestAssignFormModel>({
108
+ use: 'AssignFormModel',
109
+ uid: 'submit-form-u',
110
+ parentId: action.uid,
111
+ subKey: 'assignForm',
112
+ });
113
+ form.setAssignedValues({ status: 'published' });
114
+ action.assignFormUid = form.uid;
115
+
116
+ const flow = action.getFlow('submitSettings') as any;
117
+ const step = flow?.steps?.assignFieldValues;
118
+ expect(step?.beforeParamsSave).toBeTypeOf('function');
119
+
120
+ await step.beforeParamsSave({ engine: root, model: action });
121
+
122
+ const saved = action.getStepParams('submitSettings', 'assignFieldValues');
123
+ expect(saved?.assignedValues).toEqual({ status: 'published' });
124
+ });
125
+
126
+ it('FormSubmitActionModel: saves assignedValues from assignForm subModel when assignFormUid is not ready', async () => {
127
+ const root = new FlowEngine();
128
+ root.registerModels({ FormSubmitActionModel, AssignFormModel: TestAssignFormModel });
129
+
130
+ const action = root.createModel<FormSubmitActionModel>({ use: 'FormSubmitActionModel', uid: 'submit-u-sub' });
131
+ const form = root.createModel<TestAssignFormModel>({
132
+ use: 'AssignFormModel',
133
+ uid: 'submit-form-u-sub',
134
+ parentId: action.uid,
135
+ subKey: 'assignForm',
136
+ });
137
+ form.setAssignedValues({ status: 'draft' });
138
+ action.setSubModel('assignForm', form);
139
+
140
+ const flow = action.getFlow('submitSettings') as any;
141
+ const step = flow?.steps?.assignFieldValues;
142
+
143
+ await step.beforeParamsSave({ engine: root, model: action }, {}, {});
144
+
145
+ const saved = action.getStepParams('submitSettings', 'assignFieldValues');
146
+ expect(saved?.assignedValues).toEqual({ status: 'draft' });
147
+ });
148
+
149
+ it('FormSubmitActionModel: keeps previous assignedValues when AssignForm is unavailable during save', async () => {
150
+ const root = new FlowEngine();
151
+ root.registerModels({ FormSubmitActionModel, AssignFormModel: TestAssignFormModel });
152
+
153
+ const action = root.createModel<FormSubmitActionModel>({ use: 'FormSubmitActionModel', uid: 'submit-u-missing' });
154
+ action.setStepParams('submitSettings', 'assignFieldValues', {});
155
+
156
+ const flow = action.getFlow('submitSettings') as any;
157
+ const step = flow?.steps?.assignFieldValues;
158
+
159
+ await step.beforeParamsSave(
160
+ { engine: root, model: action },
161
+ {},
162
+ {
163
+ assignedValues: { status: 'published' },
164
+ },
165
+ );
166
+
167
+ const saved = action.getStepParams('submitSettings', 'assignFieldValues');
168
+ expect(saved?.assignedValues).toEqual({ status: 'published' });
169
+ });
55
170
  });
@@ -80,7 +80,14 @@ ActionModel.registerFlow({
80
80
  };
81
81
  },
82
82
  defaultParams(ctx) {
83
- return ctx.model.defaultProps;
83
+ const defaultProps = ctx.model.defaultProps || {};
84
+ if (!ctx.model.enableEditColor) {
85
+ return defaultProps;
86
+ }
87
+ return {
88
+ ...defaultProps,
89
+ color: ctx.model.props?.color ?? defaultProps.color ?? ctx.themeToken?.colorPrimary,
90
+ };
84
91
  },
85
92
  handler(ctx, params) {
86
93
  const { title, tooltip, ...rest } = params;
@@ -11,13 +11,20 @@ import { Collection, DisplayItemModel, FlowModel, FlowModelContext } from '@noco
11
11
 
12
12
  export class AssociationFieldGroupModel extends FlowModel {
13
13
  static itemModelName = 'DetailsItemModel';
14
+ static maxAssociationFieldDepth = 2;
14
15
  static defineChildren(ctx: FlowModelContext) {
15
16
  const itemModel = this.itemModelName;
16
- const displayAssociationFields = (targetCollection: Collection, fieldPath = '') => {
17
+ const maxAssociationFieldDepth = this.maxAssociationFieldDepth ?? 2;
18
+ const displayAssociationFields = (targetCollection: Collection, fieldPath = '', associationDepth = 0) => {
19
+ if (associationDepth >= maxAssociationFieldDepth) {
20
+ return [];
21
+ }
22
+
17
23
  return targetCollection
18
24
  .getToOneAssociationFields()
19
25
  .map((field) => {
20
26
  const fPath = fieldPath ? `${fieldPath}.${field.name}` : field.name;
27
+ const nextAssociationDepth = associationDepth + 1;
21
28
  if (!field.targetCollection) {
22
29
  console.error(
23
30
  `AssociationFieldGroupModel: target collection ${field.target} not found for field ${field.name}`,
@@ -37,6 +44,12 @@ export class AssociationFieldGroupModel extends FlowModel {
37
44
  children: field.targetCollection
38
45
  .getFields()
39
46
  .map((f) => {
47
+ if (
48
+ nextAssociationDepth >= maxAssociationFieldDepth &&
49
+ (f.isAssociationField?.() || f.target || f.targetCollection)
50
+ ) {
51
+ return;
52
+ }
40
53
  const fp = `${fPath}.${f.name}`;
41
54
  const binding = DisplayItemModel.getDefaultBindingByField(ctx, f, {
42
55
  fallbackToTargetTitleField: true,
@@ -81,9 +94,13 @@ export class AssociationFieldGroupModel extends FlowModel {
81
94
  key: `${fPath}-children-associationField`,
82
95
  label: 'Display association fields',
83
96
  type: 'group',
84
- children: (displayAssociationFields(field.targetCollection, fPath) || []).filter(Boolean),
97
+ children: (
98
+ displayAssociationFields(field.targetCollection, fPath, nextAssociationDepth) || []
99
+ ).filter(Boolean),
85
100
  },
86
- ];
101
+ ].filter((item) => {
102
+ return item.key !== `${fPath}-children-associationField` || (item.children as any[])?.length > 0;
103
+ });
87
104
  },
88
105
  };
89
106
  })
@@ -1006,7 +1006,7 @@ export class GridModel<T extends { subModels: { items: FlowModel[] } } = Default
1006
1006
  <Space
1007
1007
  ref={this.gridContainerRef}
1008
1008
  direction={'vertical'}
1009
- style={{ width: '100%', marginBottom: this.props.rowGap }}
1009
+ style={{ width: '100%', marginBottom: this.props.rowGap, display: 'flex' }}
1010
1010
  size={this.props.rowGap}
1011
1011
  >
1012
1012
  <DndProvider
@@ -49,7 +49,11 @@ export class ChildPageModel extends PageModel {
49
49
  throw new Error('Invalid drag event');
50
50
  }
51
51
 
52
- this.flowEngine.moveModel(event.active.id, event.over.id);
52
+ if (event.active.id === event.over.id) {
53
+ return;
54
+ }
55
+
56
+ await this.flowEngine.moveModel(event.active.id, event.over.id);
53
57
  }
54
58
 
55
59
  render() {
@@ -40,6 +40,15 @@ type PageModelStructure = {
40
40
  };
41
41
  };
42
42
 
43
+ type CurrentRouteWithTabs = {
44
+ id?: string | number | null;
45
+ enableTabs?: boolean;
46
+ };
47
+
48
+ type PageModelContextWithRoute = {
49
+ currentRoute?: CurrentRouteWithTabs | null;
50
+ };
51
+
43
52
  export class PageModel extends FlowModel<PageModelStructure> {
44
53
  tabBarExtraContent: { left?: ReactNode; right?: ReactNode } = {};
45
54
  private viewActivatedListener?: (_payload?: unknown) => void;
@@ -53,9 +62,15 @@ export class PageModel extends FlowModel<PageModelStructure> {
53
62
  * 根页面标签页开关以路由表为准,避免 flow model 里的旧配置覆盖路由管理设置。
54
63
  */
55
64
  private getEnableTabs(): boolean {
56
- const routeEnableTabs = (this.context as any)?.currentRoute?.enableTabs;
57
- if (this.props.routeId != null && typeof routeEnableTabs === 'boolean') {
58
- return routeEnableTabs;
65
+ const currentRoute = (this.context as PageModelContextWithRoute).currentRoute;
66
+ const routeId = this.props.routeId;
67
+ if (
68
+ routeId != null &&
69
+ currentRoute?.id != null &&
70
+ String(currentRoute.id) === String(routeId) &&
71
+ typeof currentRoute.enableTabs === 'boolean'
72
+ ) {
73
+ return currentRoute.enableTabs;
59
74
  }
60
75
  return !!this.props.enableTabs;
61
76
  }
@@ -77,16 +92,27 @@ export class PageModel extends FlowModel<PageModelStructure> {
77
92
  if (this.unmounted) return;
78
93
  // Only skip when explicitly inactive; treat "unknown" (undefined) as active for backward compatibility.
79
94
  if (getPageActive(this.context) === false) return;
80
- const activeKey = this.getActiveTabKey();
81
- if (activeKey) {
82
- this.invokeTabModelLifecycleMethod(activeKey, 'onActive', forceRefresh);
83
- }
95
+ this.activateCurrentTab(forceRefresh);
84
96
  })
85
97
  .catch(() => {
86
98
  // ignore
87
99
  });
88
100
  }
89
101
 
102
+ activateCurrentTab(forceRefresh = false) {
103
+ const activeKey = this.getActiveTabKey();
104
+ if (activeKey) {
105
+ this.invokeTabModelLifecycleMethod(activeKey, 'onActive', forceRefresh);
106
+ }
107
+ }
108
+
109
+ deactivateCurrentTab() {
110
+ const activeKey = this.props.tabActiveKey || this.getFirstTab()?.uid;
111
+ if (activeKey) {
112
+ this.invokeTabModelLifecycleMethod(activeKey, 'onInactive');
113
+ }
114
+ }
115
+
90
116
  onMount(): void {
91
117
  super.onMount();
92
118
  this.unmounted = false;
@@ -98,10 +124,7 @@ export class PageModel extends FlowModel<PageModelStructure> {
98
124
  // We align this with the existing tab lifecycle by invoking `onActive` for the current tab blocks.
99
125
  if (!this.viewActivatedListener) {
100
126
  this.viewActivatedListener = (_payload?: unknown) => {
101
- const activeKey = this.getActiveTabKey();
102
- if (activeKey) {
103
- this.invokeTabModelLifecycleMethod(activeKey, 'onActive');
104
- }
127
+ this.activateCurrentTab();
105
128
  };
106
129
  this.flowEngine?.emitter?.on?.(VIEW_ACTIVATED_EVENT, this.viewActivatedListener);
107
130
  }
@@ -113,10 +136,7 @@ export class PageModel extends FlowModel<PageModelStructure> {
113
136
  emitterActivatedVersion > 0 && emitterActivatedVersion !== this.lastSeenEmitterViewActivatedVersion;
114
137
  this.lastSeenEmitterViewActivatedVersion = emitterActivatedVersion;
115
138
  if (shouldCatchUp && getPageActive(this.context) !== false) {
116
- const activeKey = this.getActiveTabKey();
117
- if (activeKey) {
118
- this.invokeTabModelLifecycleMethod(activeKey, 'onActive');
119
- }
139
+ this.activateCurrentTab();
120
140
  }
121
141
 
122
142
  // When data is written within the same view, trigger an "active" lifecycle pass so blocks can refresh based on dirty.
@@ -141,11 +161,21 @@ export class PageModel extends FlowModel<PageModelStructure> {
141
161
  super.onUnmount();
142
162
  }
143
163
 
144
- invokeTabModelLifecycleMethod(tabActiveKey: string, method: 'onActive' | 'onInactive', forceRefresh = false) {
164
+ invokeTabModelLifecycleMethod(
165
+ tabActiveKey: string | undefined,
166
+ method: 'onActive' | 'onInactive',
167
+ forceRefresh = false,
168
+ ) {
169
+ if (!tabActiveKey) {
170
+ return;
171
+ }
172
+
145
173
  if (method === 'onActive' && this.context?.pageInfo) {
146
174
  this.context.pageInfo.version = 'v2';
147
175
  }
148
- const tabModel = this.flowEngine.getModel(tabActiveKey) as BasePageTabModel | undefined;
176
+ const tabModel =
177
+ this.findSubModel('tabs', (model) => model.uid === tabActiveKey) ||
178
+ (this.flowEngine.getModel(tabActiveKey) as BasePageTabModel | undefined);
149
179
 
150
180
  if (tabModel) {
151
181
  if (tabModel.context.tabActive) {
@@ -183,7 +213,10 @@ export class PageModel extends FlowModel<PageModelStructure> {
183
213
  const routePathname = this.flowEngine?.context?.route?.pathname;
184
214
  // In route-managed multi-view mode, only the top view in URL should mutate document.title.
185
215
  if (hasRouteNavigation && currentViewUid && typeof routePathname === 'string') {
186
- const topViewUid = parsePathnameToViewParams(routePathname).at(-1)?.viewUid;
216
+ const layoutRoutePath = this.context?.layout?.routePath;
217
+ const topViewUid = parsePathnameToViewParams(routePathname, {
218
+ basePath: this.context?.layoutRoute?.basePathname || (layoutRoutePath?.startsWith('/') ? layoutRoutePath : ''),
219
+ }).at(-1)?.viewUid;
187
220
  if (topViewUid && topViewUid !== currentViewUid) {
188
221
  return;
189
222
  }
@@ -68,22 +68,15 @@ export class RootPageModel extends PageModel {
68
68
  reaction(
69
69
  () => this.context.pageActive.value,
70
70
  () => {
71
+ if (this.context.view?.inputArgs?.activationControlledByLayout) {
72
+ this.mounted = true;
73
+ return;
74
+ }
71
75
  if (this.context.pageActive.value && this.mounted) {
72
- const firstTab = this.subModels.tabs?.[0];
73
- if (firstTab) {
74
- this.setProps('tabActiveKey', firstTab.uid);
75
- this.invokeTabModelLifecycleMethod(firstTab.uid, 'onActive', true);
76
- }
76
+ this.activateCurrentTab(true);
77
77
  }
78
78
  if (this.context.pageActive.value === false) {
79
- if (this.props.tabActiveKey) {
80
- this.invokeTabModelLifecycleMethod(this.props.tabActiveKey, 'onInactive');
81
- } else {
82
- const firstTab = this.subModels.tabs?.[0];
83
- if (firstTab) {
84
- this.invokeTabModelLifecycleMethod(firstTab.uid, 'onInactive');
85
- }
86
- }
79
+ this.deactivateCurrentTab();
87
80
  }
88
81
  this.mounted = true;
89
82
  },
@@ -107,6 +107,18 @@ describe('ChildPageModel', () => {
107
107
  expect(mockFlowEngine.moveModel).toHaveBeenCalledWith('active-model-id', 'over-model-id');
108
108
  });
109
109
 
110
+ it('should ignore self-drop', async () => {
111
+ await childPageModel.handleDragEnd({
112
+ ...mockDragEndEvent,
113
+ over: {
114
+ ...(mockDragEndEvent.over as NonNullable<DragEndEvent['over']>),
115
+ id: 'active-model-id',
116
+ },
117
+ });
118
+
119
+ expect(mockFlowEngine.moveModel).not.toHaveBeenCalled();
120
+ });
121
+
110
122
  it('should handle case when over is null', async () => {
111
123
  const eventWithNullOver = {
112
124
  ...mockDragEndEvent,
@@ -47,6 +47,13 @@ vi.mock('@nocobase/flow-engine', () => {
47
47
  return [];
48
48
  }
49
49
 
50
+ findSubModel(key: string, callback: any) {
51
+ if (this.subModels[key]) {
52
+ return this.subModels[key].find(callback) || null;
53
+ }
54
+ return null;
55
+ }
56
+
50
57
  addSubModel() {}
51
58
  setSubModel() {}
52
59
 
@@ -260,6 +267,44 @@ describe('PageModel', () => {
260
267
  });
261
268
  });
262
269
 
270
+ describe('tab lifecycle', () => {
271
+ it('should invoke tab lifecycle on PageModel subModels before engine lookup', () => {
272
+ const blockOnActive = vi.fn();
273
+ const blockOnInactive = vi.fn();
274
+ const tabModel = {
275
+ uid: 'tab1',
276
+ context: {
277
+ tabActive: { value: false },
278
+ },
279
+ subModels: {
280
+ grid: {
281
+ mapSubModels: vi.fn((_key, callback) => {
282
+ callback({ onActive: blockOnActive, onInactive: blockOnInactive });
283
+ }),
284
+ },
285
+ },
286
+ };
287
+ (pageModel as any).subModels = { tabs: [tabModel] };
288
+ (pageModel as any).flowEngine = {
289
+ getModel: vi.fn(() => undefined),
290
+ };
291
+ (pageModel as any).context = {
292
+ pageInfo: {},
293
+ view: {
294
+ inputArgs: { pageActive: true },
295
+ },
296
+ };
297
+
298
+ pageModel.invokeTabModelLifecycleMethod('tab1', 'onActive', true);
299
+ pageModel.invokeTabModelLifecycleMethod('tab1', 'onInactive');
300
+
301
+ expect((pageModel as any).flowEngine.getModel).not.toHaveBeenCalled();
302
+ expect(tabModel.context.tabActive.value).toBe(false);
303
+ expect(blockOnActive).toHaveBeenCalledWith(true);
304
+ expect(blockOnInactive).toHaveBeenCalledWith(false);
305
+ });
306
+ });
307
+
263
308
  describe('renderTabs activeKey logic', () => {
264
309
  beforeEach(() => {
265
310
  // Mock mapTabs to avoid complex rendering logic inside it
@@ -455,6 +500,7 @@ describe('PageModel', () => {
455
500
  } as any;
456
501
  (pageModel as any).context = {
457
502
  currentRoute: {
503
+ id: 'route-1',
458
504
  enableTabs: false,
459
505
  },
460
506
  };
@@ -479,6 +525,7 @@ describe('PageModel', () => {
479
525
  } as any;
480
526
  (pageModel as any).context = {
481
527
  currentRoute: {
528
+ id: 'route-1',
482
529
  enableTabs: true,
483
530
  },
484
531
  };
@@ -495,6 +542,59 @@ describe('PageModel', () => {
495
542
  paddingBottom: 0,
496
543
  });
497
544
  });
545
+
546
+ it('should ignore stale desktop route enableTabs=false from another route', () => {
547
+ pageModel.props = {
548
+ routeId: 'route-1',
549
+ displayTitle: true,
550
+ enableTabs: true,
551
+ title: 'Title',
552
+ headerStyle: { backgroundColor: 'var(--colorBgLayout)' },
553
+ } as any;
554
+ (pageModel as any).context = {
555
+ currentRoute: {
556
+ id: 'route-2',
557
+ enableTabs: false,
558
+ },
559
+ };
560
+ pageModel.renderTabs = vi.fn(() => null);
561
+ pageModel.renderFirstTab = vi.fn(() => null);
562
+
563
+ const result = pageModel.render() as any;
564
+ const header = result.props.children[0];
565
+
566
+ expect(pageModel.renderTabs).toHaveBeenCalled();
567
+ expect(pageModel.renderFirstTab).not.toHaveBeenCalled();
568
+ expect(header.props.style).toMatchObject({
569
+ backgroundColor: 'var(--colorBgLayout)',
570
+ paddingBottom: 0,
571
+ });
572
+ });
573
+
574
+ it('should ignore stale desktop route enableTabs=true from another route', () => {
575
+ pageModel.props = {
576
+ routeId: 'route-1',
577
+ displayTitle: true,
578
+ enableTabs: false,
579
+ title: 'Title',
580
+ headerStyle: { backgroundColor: 'var(--colorBgLayout)' },
581
+ } as any;
582
+ (pageModel as any).context = {
583
+ currentRoute: {
584
+ id: 'route-2',
585
+ enableTabs: true,
586
+ },
587
+ };
588
+ pageModel.renderTabs = vi.fn(() => null);
589
+ pageModel.renderFirstTab = vi.fn(() => null);
590
+
591
+ const result = pageModel.render() as any;
592
+ const header = result.props.children[0];
593
+
594
+ expect(pageModel.renderTabs).not.toHaveBeenCalled();
595
+ expect(pageModel.renderFirstTab).toHaveBeenCalled();
596
+ expect(header.props.style).toEqual({ backgroundColor: 'var(--colorBgLayout)' });
597
+ });
498
598
  });
499
599
 
500
600
  describe('dirty refresh signal', () => {
@@ -554,7 +654,7 @@ describe('PageModel', () => {
554
654
  pageModel.onMount();
555
655
 
556
656
  expect(typeof listeners['view:activated']).toBe('function');
557
- expect(invokeSpy).toHaveBeenCalledWith('tab1', 'onActive');
657
+ expect(invokeSpy).toHaveBeenCalledWith('tab1', 'onActive', false);
558
658
  });
559
659
  });
560
660
 
@@ -630,6 +730,7 @@ describe('PageModel', () => {
630
730
  it('should use page documentTitle when desktop route disables tabs even if flow model enables tabs', async () => {
631
731
  pageModel.props = { routeId: 'route-1', enableTabs: true, title: 'Route page title' } as any;
632
732
  (pageModel as any).context.currentRoute = {
733
+ id: 'route-1',
633
734
  enableTabs: false,
634
735
  };
635
736
  (pageModel as any).stepParams = {
@@ -21,7 +21,7 @@ RouteModel.registerFlow({
21
21
  return {
22
22
  mode: 'embed',
23
23
  preventClose: true,
24
- pageModelClass: 'RootPageModel',
24
+ pageModelClass: ctx.layout?.rootPageModelClass || 'RootPageModel',
25
25
  };
26
26
  },
27
27
  },
@@ -0,0 +1,86 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import { afterEach, describe, expect, it, vi } from 'vitest';
11
+ import { DisplayItemModel } from '@nocobase/flow-engine';
12
+ import { AssociationFieldGroupModel } from '../AssociationFieldGroupModel';
13
+
14
+ function createCollection(name: string, fields: any[]) {
15
+ return {
16
+ name,
17
+ dataSourceKey: 'main',
18
+ getFields: () => fields,
19
+ getToOneAssociationFields: () => fields.filter((field) => field?.type === 'belongsTo' || field?.type === 'hasOne'),
20
+ };
21
+ }
22
+
23
+ describe('AssociationFieldGroupModel', () => {
24
+ afterEach(() => {
25
+ vi.restoreAllMocks();
26
+ });
27
+
28
+ it('limits display association fields to relation / relation / field', () => {
29
+ vi.spyOn(DisplayItemModel, 'getDefaultBindingByField').mockReturnValue({ modelName: 'InputFieldModel' } as any);
30
+
31
+ const companies = createCollection('companies', [
32
+ { name: 'title', title: 'Company title', interface: 'input', type: 'string' },
33
+ ]);
34
+ const departments = createCollection('departments', [
35
+ { name: 'name', title: 'Department name', interface: 'input', type: 'string' },
36
+ {
37
+ name: 'company',
38
+ title: 'Company',
39
+ interface: 'm2o',
40
+ type: 'belongsTo',
41
+ target: 'companies',
42
+ targetCollection: companies,
43
+ isAssociationField: () => true,
44
+ },
45
+ ]);
46
+ const users = createCollection('users', [
47
+ {
48
+ name: 'department',
49
+ title: 'Department',
50
+ interface: 'm2o',
51
+ type: 'belongsTo',
52
+ target: 'departments',
53
+ targetCollection: departments,
54
+ isAssociationField: () => true,
55
+ },
56
+ ]);
57
+ const posts = createCollection('posts', [
58
+ {
59
+ name: 'user',
60
+ title: 'User',
61
+ interface: 'm2o',
62
+ type: 'belongsTo',
63
+ target: 'users',
64
+ targetCollection: users,
65
+ isAssociationField: () => true,
66
+ },
67
+ ]);
68
+
69
+ const ctx = {
70
+ collection: posts,
71
+ t: (key: string) => key,
72
+ } as any;
73
+
74
+ const firstLevel = AssociationFieldGroupModel.defineChildren(ctx) as any[];
75
+ const firstChildren = firstLevel[0].children();
76
+ const firstAssociationGroup = firstChildren.find((item) => item.key === 'user-children-associationField');
77
+ const secondLevel = firstAssociationGroup.children;
78
+ const secondChildren = secondLevel[0].children();
79
+ const secondFieldsGroup = secondChildren.find((item) => item.key === 'user.department-children-collectionField');
80
+
81
+ expect(secondChildren.map((item) => item.key)).toContain('user.department-children-collectionField');
82
+ expect(secondChildren.map((item) => item.key)).not.toContain('user.department-children-associationField');
83
+ expect(secondFieldsGroup.children.map((item) => item.key)).toContain('c-user.department.name');
84
+ expect(secondFieldsGroup.children.map((item) => item.key)).not.toContain('c-user.department.company');
85
+ });
86
+ });