@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
@@ -12,7 +12,7 @@ import { FlowEngine, type FlowModelContext, type SubModelItem } from '@nocobase/
12
12
  // Import from the aggregate to preserve the model initialization order used by adjacent tests.
13
13
  import { FormItemModel, FormJSFieldItemModel, InputFieldModel, JSEditableFieldModel } from '../../../..';
14
14
 
15
- function createFormMenuContext(prefixFieldPath = 'roles') {
15
+ function createFormMenuContext(prefixFieldPath = 'roles', collectionName = 'roles') {
16
16
  const engine = new FlowEngine();
17
17
  engine.registerModels({
18
18
  FormItemModel,
@@ -36,15 +36,29 @@ function createFormMenuContext(prefixFieldPath = 'roles') {
36
36
  fields: [
37
37
  { name: 'id', type: 'integer', interface: 'number', title: 'ID' },
38
38
  { name: 'name', type: 'string', interface: 'input', title: 'Name' },
39
+ { name: 'department', type: 'belongsTo', interface: 'm2o', target: 'departments', title: 'Department' },
39
40
  ],
40
41
  });
42
+ dataSource.addCollection({
43
+ name: 'departments',
44
+ filterTargetKey: 'id',
45
+ fields: [
46
+ { name: 'name', type: 'string', interface: 'input', title: 'Name' },
47
+ { name: 'company', type: 'belongsTo', interface: 'm2o', target: 'companies', title: 'Company' },
48
+ ],
49
+ });
50
+ dataSource.addCollection({
51
+ name: 'companies',
52
+ filterTargetKey: 'id',
53
+ fields: [{ name: 'name', type: 'string', interface: 'input', title: 'Name' }],
54
+ });
41
55
 
42
56
  const blockModel = engine.createModel({ use: 'FlowModel', uid: 'users-form-block' });
43
57
  (blockModel as any).collection = dataSource.getCollection('users');
44
58
 
45
59
  const gridModel = engine.createModel({ use: 'FlowModel', uid: 'users-form-grid' });
46
60
  gridModel.context.defineProperty('blockModel', { value: blockModel });
47
- gridModel.context.defineProperty('collection', { value: dataSource.getCollection('roles') });
61
+ gridModel.context.defineProperty('collection', { value: dataSource.getCollection(collectionName) });
48
62
  gridModel.context.defineProperty('prefixFieldPath', { value: prefixFieldPath });
49
63
 
50
64
  return gridModel.context as FlowModelContext;
@@ -105,4 +119,15 @@ describe('FormItemModel defineChildren', () => {
105
119
 
106
120
  expect((jsNameItem?.toggleable as (model: any) => boolean)(createModelLike(normalCreateOptions))).toBe(true);
107
121
  });
122
+
123
+ it('hides association fields after two association levels in form add-field menus', async () => {
124
+ const ctx = createFormMenuContext('roles.department', 'departments');
125
+ const formItems = FormItemModel.defineChildren(ctx) as SubModelItem[];
126
+ const jsItems = (await FormJSFieldItemModel.defineChildren(ctx)) as SubModelItem[];
127
+
128
+ expect(formItems.map((item) => item.key)).toContain('roles.department.name');
129
+ expect(formItems.map((item) => item.key)).not.toContain('roles.department.company');
130
+ expect(jsItems.map((item) => item.key)).toContain('roles.department.name');
131
+ expect(jsItems.map((item) => item.key)).not.toContain('roles.department.company');
132
+ });
108
133
  });
@@ -0,0 +1,71 @@
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 { SingleRecordResource } from '@nocobase/flow-engine';
11
+ import { describe, expect, it, vi } from 'vitest';
12
+ import { submitHandler } from '../submitHandler';
13
+
14
+ describe('submitHandler', () => {
15
+ it('merges assigned values into submitted values and lets assigned values override form values', async () => {
16
+ const resource = Object.create(SingleRecordResource.prototype);
17
+ resource.save = vi.fn(async () => ({ data: { id: 1 } }));
18
+
19
+ const blockModel = {
20
+ form: {
21
+ validateFields: vi.fn(async () => undefined),
22
+ getFieldsValue: vi.fn(() => ({
23
+ title: 'Draft title',
24
+ status: 'draft',
25
+ })),
26
+ resetFields: vi.fn(),
27
+ },
28
+ emitter: {
29
+ emit: vi.fn(),
30
+ },
31
+ resetUserModifiedFields: vi.fn(),
32
+ formValueRuntime: {
33
+ resetAfterFormReset: vi.fn(),
34
+ },
35
+ collection: {
36
+ name: 'posts',
37
+ },
38
+ };
39
+
40
+ const ctx = {
41
+ resource,
42
+ blockModel,
43
+ model: {
44
+ getStepParams: vi.fn(),
45
+ },
46
+ view: {
47
+ inputArgs: {},
48
+ },
49
+ message: {
50
+ error: vi.fn(),
51
+ },
52
+ t: (value: string) => value,
53
+ };
54
+
55
+ await submitHandler(ctx, {
56
+ assignedValues: {
57
+ status: 'published',
58
+ reviewer: 'Alice',
59
+ },
60
+ });
61
+
62
+ expect(resource.save).toHaveBeenCalledWith(
63
+ {
64
+ title: 'Draft title',
65
+ status: 'published',
66
+ reviewer: 'Alice',
67
+ },
68
+ undefined,
69
+ );
70
+ });
71
+ });
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import { MultiRecordResource, SingleRecordResource } from '@nocobase/flow-engine';
11
+ import { mergeAssignFieldValues, resolveAssignFieldValues } from '../assign-form/assignFieldValuesFlow';
11
12
  import type { FormBlockModel } from './FormBlockModel';
12
13
  import { omitHiddenModelValuesFromSubmit, shouldSkipSubmitValidation, validateSubmitForm } from './submitValues';
13
14
 
@@ -24,7 +25,13 @@ export async function submitHandler(ctx, params, cb?: (values?: any, filterByTk?
24
25
  skipValidator: shouldSkipSubmitValidation(ctx?.model),
25
26
  });
26
27
  const rawValues = blockModel.form.getFieldsValue(true);
27
- const values = omitHiddenModelValuesFromSubmit(rawValues, blockModel);
28
+ const formValues = omitHiddenModelValuesFromSubmit(rawValues, blockModel);
29
+ const assignedValues = await resolveAssignFieldValues(ctx, params?.assignedValues, 'FormSubmitAction');
30
+ if (!assignedValues) {
31
+ ctx.exit?.();
32
+ return;
33
+ }
34
+ const values = mergeAssignFieldValues(formValues, assignedValues);
28
35
  if (resource instanceof SingleRecordResource) {
29
36
  if (isEditFormModel) {
30
37
  const currentFilterByTk = resource.getMeta('currentFilterByTk');
@@ -84,7 +84,10 @@ function forEachModelIncludingForks(engine: any, visitor: (model: any) => void)
84
84
 
85
85
  const forks: any = model?.forks;
86
86
  if (forks && typeof forks.forEach === 'function') {
87
- forks.forEach((fork: any) => visitor(fork));
87
+ forks.forEach((fork: any) => {
88
+ if (!fork || fork.disposed) return;
89
+ visitor(fork);
90
+ });
88
91
  }
89
92
  });
90
93
  }
@@ -15,6 +15,7 @@ import { observable } from '@formily/reactive';
15
15
  import { get as lodashGet, merge as lodashMerge, set as lodashSet } from 'lodash';
16
16
  import { FlowContext, JSRunner } from '@nocobase/flow-engine';
17
17
  import { FormValueRuntime } from '..';
18
+ import type { FormInstance } from 'antd';
18
19
 
19
20
  function createFormStub(initialValues: any = {}) {
20
21
  const store: any = JSON.parse(JSON.stringify(initialValues || {}));
@@ -121,6 +122,34 @@ function createFieldContext(runtime: FormValueRuntime) {
121
122
  }
122
123
 
123
124
  describe('FormValueRuntime (default rules)', () => {
125
+ it('skips object patches when values are unchanged', async () => {
126
+ const engineEmitter = new EventEmitter();
127
+ const blockEmitter = new EventEmitter();
128
+ const formStub = createFormStub({ name: 'old' });
129
+ const dispatchEvent = vi.fn();
130
+
131
+ const blockModel = {
132
+ uid: 'form-noop-patch',
133
+ flowEngine: { emitter: engineEmitter },
134
+ emitter: blockEmitter,
135
+ dispatchEvent,
136
+ getAclActionName: () => 'create',
137
+ } as ConstructorParameters<typeof FormValueRuntime>[0]['model'];
138
+
139
+ const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as unknown as FormInstance });
140
+ runtime.mount({ sync: true });
141
+
142
+ const blockCtx = createFieldContext(runtime);
143
+ await runtime.setFormValues(blockCtx, { name: 'old' }, { source: 'system' });
144
+
145
+ expect(dispatchEvent).not.toHaveBeenCalled();
146
+
147
+ await runtime.setFormValues(blockCtx, { name: 'new' }, { source: 'system' });
148
+
149
+ expect(formStub.getFieldValue(['name'])).toBe('new');
150
+ expect(dispatchEvent).toHaveBeenCalledTimes(1);
151
+ });
152
+
124
153
  it('recomputes default on dependency change when current equals last default; user change disables default permanently', async () => {
125
154
  const engineEmitter = new EventEmitter();
126
155
  const blockEmitter = new EventEmitter();
@@ -846,7 +846,11 @@ export class FormValueRuntime {
846
846
  const changedPaths: NamePath[] = [];
847
847
 
848
848
  if (!Array.isArray(patch)) {
849
- const patchEntries = Object.entries(patch || {}).filter(([pathKey]) => !shouldSkipByLinkageScope(pathKey));
849
+ const patchEntries = Object.entries(patch || {}).filter(([pathKey, rawValue]) => {
850
+ if (shouldSkipByLinkageScope(pathKey)) return false;
851
+ const value = isObservable(rawValue) ? toJS(rawValue) : rawValue;
852
+ return !_.isEqual(this.getFormValueAtPath([pathKey]), value);
853
+ });
850
854
  const patchToApply = Object.fromEntries(patchEntries);
851
855
  const patchKeys = patchEntries.map(([pathKey]) => pathKey);
852
856
  if (!patchKeys.length) {
@@ -39,6 +39,7 @@ import { TableColumnModel } from './TableColumnModel';
39
39
  import { extractIndex, adjustColumnOrder, setNestedValue, extractIds, getRowKey, useBlockHeight } from './utils';
40
40
  import { resolveTableSorterField } from './sortUtils';
41
41
  import { commonConditionHandler, ConditionBuilder } from '../../../components/ConditionBuilder';
42
+ import { BulkDeleteActionModel } from '../../actions/BulkDeleteActionModel';
42
43
  import {
43
44
  applyMobilePaginationProps,
44
45
  createCompactSimpleItemRender,
@@ -128,6 +129,18 @@ const rowSelectCheckboxWrapperNoIndexClass = css`
128
129
  }
129
130
  `;
130
131
 
132
+ const leftAuxiliaryColumnCellClass = css`
133
+ position: relative;
134
+ display: flex;
135
+ align-items: center;
136
+ justify-content: center;
137
+ min-height: 22px;
138
+ `;
139
+
140
+ const leftAuxiliaryColumnCellWithHandleClass = css`
141
+ padding-left: 24px;
142
+ `;
143
+
131
144
  const highlightedRowClass = css`
132
145
  & td {
133
146
  background-color: #e6f7ff !important;
@@ -382,21 +395,76 @@ export class TableBlockModel extends CollectionBlockModel<TableBlockModelStructu
382
395
  },
383
396
  };
384
397
 
385
- renderCell = (checked, record, index, originNode) => {
386
- if (!this.props.dragSort && !this.props.showIndex) {
387
- return originNode;
388
- }
389
- const current = this.resource.getPage();
398
+ isRowSelectionEnabled() {
399
+ return this.props.enableRowSelection !== false;
400
+ }
401
+
402
+ isShowIndexEnabled() {
403
+ return this.props.showIndex !== false;
404
+ }
390
405
 
406
+ getRecordIndex(record: Record<string, unknown>, index: number) {
407
+ let nextIndex = index;
408
+ const current = this.resource.getPage();
391
409
  const pageSize = this.resource.getPageSize() || 20;
392
410
  if (current) {
393
- index = index + (current - 1) * pageSize + 1;
411
+ nextIndex = nextIndex + (current - 1) * pageSize + 1;
394
412
  } else {
395
- index = index + 1;
413
+ nextIndex = nextIndex + 1;
396
414
  }
397
- if (record.__index) {
398
- index = extractIndex(record.__index);
415
+ const treeIndex = record?.__index;
416
+ if (treeIndex) {
417
+ return extractIndex(treeIndex);
399
418
  }
419
+ return nextIndex;
420
+ }
421
+
422
+ getLeftAuxiliaryColumn() {
423
+ const showIndex = this.isShowIndexEnabled();
424
+ const showDragHandle = this.props.dragSort && this.props.dragSortBy;
425
+ if (!showIndex && !showDragHandle) {
426
+ return null;
427
+ }
428
+
429
+ return {
430
+ key: '__rowSelectionDisabledAuxiliary__',
431
+ width: showDragHandle ? 74 : 50,
432
+ align: 'center' as const,
433
+ render: (_value: unknown, record: Record<string, unknown>, index: number) => {
434
+ const displayIndex = this.getRecordIndex(record, index);
435
+ const rowKey = getRowKey(record, this.collection.filterTargetKey);
436
+ const rowKeyString = rowKey == null ? rowKey : String(rowKey);
437
+ return (
438
+ <div
439
+ className={classNames(leftAuxiliaryColumnCellClass, {
440
+ [leftAuxiliaryColumnCellWithHandleClass]: showDragHandle,
441
+ })}
442
+ >
443
+ {showDragHandle && (
444
+ <SortHandle
445
+ id={rowKeyString}
446
+ style={{
447
+ position: 'absolute',
448
+ left: 0,
449
+ top: '50%',
450
+ justifyContent: 'center',
451
+ transform: 'translateY(-50%)',
452
+ }}
453
+ />
454
+ )}
455
+ {showIndex && <TableIndex index={displayIndex} aria-label={`table-index-${displayIndex}`} />}
456
+ </div>
457
+ );
458
+ },
459
+ };
460
+ }
461
+
462
+ renderCell = (checked, record, index, originNode) => {
463
+ const showIndex = this.isShowIndexEnabled();
464
+ if (!this.props.dragSort && !showIndex) {
465
+ return originNode;
466
+ }
467
+ index = this.getRecordIndex(record, index);
400
468
  const rowKey = getRowKey(record, this.collection.filterTargetKey);
401
469
  const rowKeyString = rowKey == null ? rowKey : String(rowKey);
402
470
  const showDragHandle = this.props.dragSort && this.props.dragSortBy;
@@ -406,7 +474,7 @@ export class TableBlockModel extends CollectionBlockModel<TableBlockModelStructu
406
474
  aria-label={`table-index-${index}`}
407
475
  className={classNames(checked ? 'checked' : null, rowSelectCheckboxWrapperClass, {
408
476
  [rowSelectCheckboxWrapperClassHover]: true,
409
- [rowSelectCheckboxWrapperNoIndexClass]: !this.props.showIndex,
477
+ [rowSelectCheckboxWrapperNoIndexClass]: !showIndex,
410
478
  })}
411
479
  >
412
480
  {showDragHandle && (
@@ -422,7 +490,7 @@ export class TableBlockModel extends CollectionBlockModel<TableBlockModelStructu
422
490
  />
423
491
  )}
424
492
  <div className={classNames(checked ? 'checked' : null, rowSelectCheckboxContentClass)}>
425
- {this.props.showIndex && <TableIndex index={index} />}
493
+ {showIndex && <TableIndex index={index} />}
426
494
  </div>
427
495
 
428
496
  <div className={classNames('nb-origin-node', checked ? 'checked' : null, rowSelectCheckboxCheckedClassHover)}>
@@ -445,6 +513,16 @@ export class TableBlockModel extends CollectionBlockModel<TableBlockModelStructu
445
513
  );
446
514
  }
447
515
 
516
+ shouldRenderAction(action: ActionModel, isConfigMode: boolean) {
517
+ if (action.hidden && !isConfigMode) {
518
+ return false;
519
+ }
520
+ if (!isConfigMode && !this.isRowSelectionEnabled() && action instanceof BulkDeleteActionModel) {
521
+ return false;
522
+ }
523
+ return true;
524
+ }
525
+
448
526
  pagination() {
449
527
  const totalCount = this.resource.getMeta('count');
450
528
  const pageSize = this.resource.getPageSize();
@@ -509,6 +587,9 @@ export class TableBlockModel extends CollectionBlockModel<TableBlockModelStructu
509
587
  >
510
588
  <Space wrap>
511
589
  {this.mapSubModels('actions', (action) => {
590
+ if (!this.shouldRenderAction(action, isConfigMode)) {
591
+ return null;
592
+ }
512
593
  // @ts-ignore
513
594
  if (action.props.position === 'left') {
514
595
  return (
@@ -527,8 +608,8 @@ export class TableBlockModel extends CollectionBlockModel<TableBlockModelStructu
527
608
  </Space>
528
609
  <Space wrap>
529
610
  {this.mapSubModels('actions', (action) => {
530
- if (action.hidden && !isConfigMode) {
531
- return;
611
+ if (!this.shouldRenderAction(action, isConfigMode)) {
612
+ return null;
532
613
  }
533
614
  // @ts-ignore
534
615
  if (action.props.position !== 'left') {
@@ -673,6 +754,20 @@ TableBlockModel.registerFlow({
673
754
  });
674
755
  },
675
756
  },
757
+ enableRowSelection: {
758
+ title: tExpr('Enable row selection'),
759
+ uiMode: { type: 'switch', key: 'enableRowSelection' },
760
+ defaultParams: {
761
+ enableRowSelection: true,
762
+ },
763
+ handler(ctx, params) {
764
+ const model = ctx.model as TableBlockModel;
765
+ model.setProps('enableRowSelection', params.enableRowSelection);
766
+ if (!params.enableRowSelection) {
767
+ model.resource.setSelectedRows([]);
768
+ }
769
+ },
770
+ },
676
771
  showRowNumbers: {
677
772
  title: tExpr('Show row numbers'),
678
773
  uiMode: { type: 'switch', key: 'showIndex' },
@@ -875,7 +970,11 @@ const HighPerformanceTable = React.memo(
875
970
  [model.collection.filterTargetKey],
876
971
  );
877
972
 
973
+ const enableRowSelection = model.isRowSelectionEnabled();
878
974
  const rowSelection = useMemo(() => {
975
+ if (!enableRowSelection) {
976
+ return undefined;
977
+ }
879
978
  return {
880
979
  columnWidth: 50,
881
980
  type: 'checkbox',
@@ -887,7 +986,10 @@ const HighPerformanceTable = React.memo(
887
986
  renderCell: model.renderCell,
888
987
  ...model.rowSelectionProps,
889
988
  };
890
- }, [model, selectedRowKeys]);
989
+ }, [enableRowSelection, model, selectedRowKeys]);
990
+
991
+ const leftAuxiliaryColumn = enableRowSelection ? null : model.getLeftAuxiliaryColumn();
992
+ const mergedColumns = leftAuxiliaryColumn ? [leftAuxiliaryColumn, ...columns] : columns;
891
993
 
892
994
  const handleChange = useCallback(
893
995
  async (pagination, filters, sorter) => {
@@ -923,7 +1025,7 @@ const HighPerformanceTable = React.memo(
923
1025
  [highlightedRowKey, model.collection?.filterTargetKey],
924
1026
  );
925
1027
 
926
- const pagination = useMemo(() => _pagination, [dataSource]);
1028
+ const pagination = _pagination;
927
1029
 
928
1030
  const onRow = useCallback(
929
1031
  (record, rowIndex) => {
@@ -1029,7 +1131,7 @@ const HighPerformanceTable = React.memo(
1029
1131
  virtual={virtual}
1030
1132
  scroll={tableScroll}
1031
1133
  dataSource={dataSource}
1032
- columns={columns}
1134
+ columns={mergedColumns}
1033
1135
  pagination={pagination}
1034
1136
  onChange={handleChange}
1035
1137
  rowClassName={rowClassName}
@@ -47,7 +47,7 @@ export function FieldDeletePlaceholder(props: any) {
47
47
  const dataSourcePrefix = `${t(dataSource.displayName || dataSource.key)} > `;
48
48
  const collectionPrefix = collection ? `${t(collection.title) || collection.name || collection.tableName} > ` : '';
49
49
  return `${dataSourcePrefix}${collectionPrefix}${name}`;
50
- }, []);
50
+ }, [collection, dataSource.displayName, dataSource.key, name, t]);
51
51
  const content = t(`The {{type}} "{{name}}" may have been deleted. Please remove this {{blockType}}.`, {
52
52
  type: t('Field'),
53
53
  name: nameValue,
@@ -71,7 +71,7 @@ function FieldWithoutPermissionPlaceholder() {
71
71
  const dataSourcePrefix = `${t(dataSource.displayName || dataSource.key)} > `;
72
72
  const collectionPrefix = collection ? `${t(collection.title) || collection.name || collection.tableName} > ` : '';
73
73
  return `${dataSourcePrefix}${collectionPrefix}${name}`;
74
- }, []);
74
+ }, [collection, dataSource.displayName, dataSource.key, name, t]);
75
75
  const { actionName } = model.forbidden;
76
76
  const messageValue = useMemo(() => {
77
77
  return t(
@@ -81,7 +81,7 @@ function FieldWithoutPermissionPlaceholder() {
81
81
  actionName: t(capitalize(actionName)),
82
82
  },
83
83
  ).replaceAll('&gt;', '>');
84
- }, [nameValue, t]);
84
+ }, [actionName, nameValue, t]);
85
85
  return (
86
86
  <Tooltip title={messageValue}>
87
87
  <LockOutlined style={{ opacity: '0.3' }} />
@@ -89,6 +89,15 @@ function FieldWithoutPermissionPlaceholder() {
89
89
  );
90
90
  }
91
91
 
92
+ export const TABLE_COLUMN_MIN_WIDTH = 10;
93
+
94
+ export function normalizeTableColumnWidth(width: number | null | undefined) {
95
+ if (typeof width === 'number' && width < TABLE_COLUMN_MIN_WIDTH) {
96
+ return TABLE_COLUMN_MIN_WIDTH;
97
+ }
98
+ return width;
99
+ }
100
+
92
101
  export const CustomWidth = ({ setOpen, t, handleChange, defaultValue }) => {
93
102
  const [customWidth, setCustomWidth] = useState(defaultValue);
94
103
  return (
@@ -104,13 +113,14 @@ export const CustomWidth = ({ setOpen, t, handleChange, defaultValue }) => {
104
113
  <Space.Compact block>
105
114
  <InputNumber
106
115
  placeholder={t('Custom column width')}
116
+ min={TABLE_COLUMN_MIN_WIDTH}
107
117
  value={customWidth}
108
118
  onChange={(val) => {
109
119
  setCustomWidth(val);
110
120
  }}
111
121
  style={{ width: '100%', minWidth: 200 }}
112
122
  />
113
- <Button type="primary" onClick={() => handleChange(customWidth)}>
123
+ <Button type="primary" onClick={() => handleChange(normalizeTableColumnWidth(customWidth))}>
114
124
  OK
115
125
  </Button>
116
126
  </Space.Compact>
@@ -442,7 +452,7 @@ TableColumnModel.registerFlow({
442
452
  width: 150,
443
453
  },
444
454
  handler(ctx, params) {
445
- ctx.model.setProps('width', params.width);
455
+ ctx.model.setProps('width', normalizeTableColumnWidth(params.width));
446
456
  },
447
457
  },
448
458
  aclCheck: {
@@ -10,7 +10,7 @@
10
10
  import { tExpr, FlowModel, ModelRenderMode } from '@nocobase/flow-engine';
11
11
  import { Divider } from 'antd';
12
12
  import React from 'react';
13
- import { CustomWidth } from './TableColumnModel';
13
+ import { CustomWidth, normalizeTableColumnWidth } from './TableColumnModel';
14
14
  export class TableCustomColumnModel extends FlowModel {
15
15
  static renderMode: ModelRenderMode = ModelRenderMode.RenderFunction;
16
16
  }
@@ -100,7 +100,7 @@ TableCustomColumnModel.registerFlow({
100
100
  width: 150,
101
101
  },
102
102
  handler(ctx, params) {
103
- ctx.model.setProps('width', params.width);
103
+ ctx.model.setProps('width', normalizeTableColumnWidth(params.width));
104
104
  },
105
105
  },
106
106
  fixed: {
@@ -0,0 +1,114 @@
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 { render, screen } from '@testing-library/react';
11
+ import { FlowEngine } from '@nocobase/flow-engine';
12
+ import '@nocobase/client';
13
+ import React from 'react';
14
+ import { describe, expect, it } from 'vitest';
15
+ import { BulkDeleteActionModel } from '../../../actions/BulkDeleteActionModel';
16
+ import { TableBlockModel } from '../TableBlockModel';
17
+
18
+ function createTableModel() {
19
+ const engine = new FlowEngine();
20
+ engine.registerModels({ TableBlockModel });
21
+
22
+ const ds = engine.dataSourceManager.getDataSource('main');
23
+ ds.addCollection({
24
+ name: 'posts',
25
+ filterTargetKey: 'id',
26
+ fields: [
27
+ { name: 'id', type: 'integer', interface: 'number' },
28
+ { name: 'title', type: 'string', interface: 'input' },
29
+ ],
30
+ });
31
+
32
+ return engine.createModel<TableBlockModel>({
33
+ uid: 'posts-table',
34
+ use: 'TableBlockModel',
35
+ stepParams: {
36
+ resourceSettings: {
37
+ init: {
38
+ dataSourceKey: 'main',
39
+ collectionName: 'posts',
40
+ },
41
+ },
42
+ },
43
+ });
44
+ }
45
+
46
+ describe('TableBlockModel row selection setting', () => {
47
+ it('keeps row selection enabled by default', () => {
48
+ const model = createTableModel();
49
+
50
+ expect(model.isRowSelectionEnabled()).toBe(true);
51
+
52
+ model.setProps('enableRowSelection', false);
53
+
54
+ expect(model.isRowSelectionEnabled()).toBe(false);
55
+ });
56
+
57
+ it('builds a standalone row-number column when row selection is disabled', () => {
58
+ const model = createTableModel();
59
+ model.resource.setPage(2);
60
+ model.resource.setPageSize(20);
61
+ model.setProps('enableRowSelection', false);
62
+ model.setProps('showIndex', true);
63
+
64
+ const column = model.getLeftAuxiliaryColumn();
65
+
66
+ expect(column).toMatchObject({
67
+ key: '__rowSelectionDisabledAuxiliary__',
68
+ width: 50,
69
+ align: 'center',
70
+ });
71
+
72
+ render(<>{column?.render(null, { id: 1, title: 'first post' }, 1)}</>);
73
+
74
+ expect(screen.getByLabelText('table-index-22').textContent).toBe('22');
75
+ });
76
+
77
+ it('omits the standalone column when row selection and row numbers are both disabled', () => {
78
+ const model = createTableModel();
79
+ model.setProps('enableRowSelection', false);
80
+ model.setProps('showIndex', false);
81
+
82
+ expect(model.getLeftAuxiliaryColumn()).toBeNull();
83
+ });
84
+
85
+ it('keeps a standalone drag handle column without row selection checkboxes', () => {
86
+ const model = createTableModel();
87
+ model.setProps('enableRowSelection', false);
88
+ model.setProps('showIndex', false);
89
+ model.setProps('dragSort', true);
90
+ model.setProps('dragSortBy', 'sort');
91
+
92
+ expect(model.getLeftAuxiliaryColumn()).toMatchObject({
93
+ key: '__rowSelectionDisabledAuxiliary__',
94
+ width: 74,
95
+ align: 'center',
96
+ });
97
+ });
98
+
99
+ it('hides bulk delete in runtime when row selection is disabled', () => {
100
+ const model = createTableModel();
101
+ const bulkDelete = new BulkDeleteActionModel({
102
+ uid: 'bulk-delete',
103
+ use: 'BulkDeleteActionModel',
104
+ flowEngine: model.flowEngine,
105
+ });
106
+
107
+ expect(model.shouldRenderAction(bulkDelete, false)).toBe(true);
108
+
109
+ model.setProps('enableRowSelection', false);
110
+
111
+ expect(model.shouldRenderAction(bulkDelete, false)).toBe(false);
112
+ expect(model.shouldRenderAction(bulkDelete, true)).toBe(true);
113
+ });
114
+ });
@@ -12,6 +12,24 @@ import { describe, expect, it, vi } from 'vitest';
12
12
  import { TableColumnModel } from '../TableColumnModel';
13
13
 
14
14
  describe('TableColumnModel sorter settings', () => {
15
+ it('clamps custom column width to the minimum value', () => {
16
+ const engine = new FlowEngine();
17
+ const model = new TableColumnModel({ uid: 'table-column-min-width', flowEngine: engine } as any);
18
+ const widthStep = model.getFlow('tableColumnSettings')?.steps?.width as any;
19
+ const setProps = vi.fn();
20
+
21
+ widthStep.handler(
22
+ {
23
+ model: {
24
+ setProps,
25
+ },
26
+ },
27
+ { width: 0 },
28
+ );
29
+
30
+ expect(setProps).toHaveBeenCalledWith('width', 10);
31
+ });
32
+
15
33
  it('hides quick edit setting for relation path columns added from association groups', async () => {
16
34
  const engine = new FlowEngine();
17
35
  const model = new TableColumnModel({ uid: 'table-column-relation-path-quick-edit', flowEngine: engine } as any);