@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
@@ -213,6 +213,7 @@ const ArrayNester = ({
213
213
  const isConfigMode = !!model.context.flowSettingsEnabled;
214
214
  const { t } = useTranslation();
215
215
  const rowIndex = model.context.fieldIndex || [];
216
+ const parentFieldPathArray = (model?.parent as any)?.context?.fieldPathArray || [];
216
217
  // 用来缓存每行的 fork,保证每行只创建一次
217
218
  const forksRef = useRef<Record<string, any>>({});
218
219
  const collectionName = model.context.collectionField.name;
@@ -251,7 +252,7 @@ const ArrayNester = ({
251
252
  {displayFields.map((field: any, index) => {
252
253
  const { key, name: fieldName, isDefault } = field;
253
254
  const fieldIndex = [...rowIndex, `${collectionName}:${index}`];
254
-
255
+ const fieldPathArray = parentFieldPathArray;
255
256
  // 每行只创建一次 fork
256
257
  if (!forksRef.current[key]) {
257
258
  const fork = gridModel.createFork({ disabled });
@@ -268,6 +269,11 @@ const ArrayNester = ({
268
269
  get: () => fieldIndex,
269
270
  cache: false,
270
271
  });
272
+ console.log(fieldPathArray);
273
+ currentFork.context.defineProperty('fieldPathArray', {
274
+ get: () => fieldPathArray,
275
+ cache: false,
276
+ });
271
277
 
272
278
  const getRowItem = createItemChainGetter({
273
279
  valueAccessor: () => currentFork.context.form.getFieldValue([name, fieldName]),
@@ -33,7 +33,7 @@ import React, { useRef, useMemo, useEffect } from 'react';
33
33
  import { SubTableFieldModel } from '.';
34
34
  import { FieldModel } from '../../../base/FieldModel';
35
35
  import { DetailsItemModel } from '../../../blocks/details/DetailsItemModel';
36
- import { FieldDeletePlaceholder, CustomWidth } from '../../../blocks/table/TableColumnModel';
36
+ import { FieldDeletePlaceholder, CustomWidth, normalizeTableColumnWidth } from '../../../blocks/table/TableColumnModel';
37
37
  import { buildDynamicNamePath } from '../../../blocks/form/dynamicNamePath';
38
38
  import { getSubTableRowIdentity } from './rowIdentity';
39
39
  import { getFieldBindingUse, rebuildFieldSubModel } from '../../../../internal/utils/rebuildFieldSubModel';
@@ -67,7 +67,7 @@ export function FieldWithoutPermissionPlaceholder({ targetModel }) {
67
67
  const dataSourcePrefix = `${t(dataSource.displayName || dataSource.key)} > `;
68
68
  const collectionPrefix = collection ? `${t(collection.title) || collection.name || collection.tableName} > ` : '';
69
69
  return `${dataSourcePrefix}${collectionPrefix}${name}`;
70
- }, []);
70
+ }, [collection, dataSource.displayName, dataSource.key, name, t]);
71
71
  const { actionName } = fieldModel.forbidden || {};
72
72
  const messageValue = useMemo(() => {
73
73
  return t(
@@ -77,7 +77,7 @@ export function FieldWithoutPermissionPlaceholder({ targetModel }) {
77
77
  actionName: t(capitalize(actionName)),
78
78
  },
79
79
  ).replaceAll('&gt;', '>');
80
- }, [nameValue, t]);
80
+ }, [actionName, nameValue, t]);
81
81
  return (
82
82
  <Tooltip title={messageValue}>
83
83
  <LockOutlined style={{ opacity: '0.3' }} />
@@ -100,10 +100,9 @@ const LargeFieldEdit = observer(({ model, params: { fieldPath, index }, defaultV
100
100
  const handleChange = useMemo(
101
101
  () =>
102
102
  debounce((val) => {
103
- if (props.onChange) props.onChange(val);
104
103
  if (onChange) onChange(val);
105
104
  }, 200),
106
- [props.onChange, onChange],
105
+ [onChange],
107
106
  );
108
107
 
109
108
  return <FieldModelRenderer model={model} {...rest} onChange={handleChange} />;
@@ -149,7 +148,7 @@ const LargeFieldEdit = observer(({ model, params: { fieldPath, index }, defaultV
149
148
  </Space>
150
149
  );
151
150
  }
152
- }, [collectionField.interface, defaultValue, fieldModel]);
151
+ }, [collectionField.interface, defaultValue, disabled, fieldModel]);
153
152
  return (
154
153
  <div
155
154
  ref={ref}
@@ -830,7 +829,7 @@ SubTableColumnModel.registerFlow({
830
829
  width: 200,
831
830
  },
832
831
  handler(ctx, params) {
833
- ctx.model.setProps('width', params.width);
832
+ ctx.model.setProps('width', normalizeTableColumnWidth(params.width));
834
833
  },
835
834
  },
836
835
  aclCheck: {
@@ -269,7 +269,7 @@ export function SubTableField(props) {
269
269
  display: 'flex',
270
270
  justifyContent: 'space-between',
271
271
  alignItems: 'center',
272
- minHeight: '20px',
272
+ minHeight: '36px',
273
273
  }}
274
274
  >
275
275
  <Space>
@@ -60,13 +60,13 @@ export class SubTableFieldModel extends AssociationFieldModel {
60
60
  setCurrentPage;
61
61
  currentPageSize;
62
62
 
63
- getCurrentValue = () => {
63
+ getCurrentValue() {
64
64
  const fallback = Array.isArray(this.props.value) ? this.props.value : [];
65
- const fieldPathArray = this.parent?.context?.fieldPathArray;
65
+ const fieldPathArray = this.context.fieldPathArray ?? this.parent?.context?.fieldPathArray;
66
66
  if (!Array.isArray(fieldPathArray) || !fieldPathArray.length) return fallback;
67
67
  const latest = this.context.blockModel?.context?.form?.getFieldValue?.(fieldPathArray as any);
68
68
  return Array.isArray(latest) ? latest : fallback;
69
- };
69
+ }
70
70
 
71
71
  get collection() {
72
72
  return this.context.collection;
@@ -111,6 +111,7 @@ export class SubTableFieldModel extends AssociationFieldModel {
111
111
  },
112
112
  };
113
113
  const isConfigMode = !!this.context.flowSettingsEnabled;
114
+ const fieldPathArray = this.context.fieldPathArray ?? this.parent?.context?.fieldPathArray;
114
115
  return (
115
116
  <SubTableField
116
117
  {...this.props}
@@ -121,8 +122,8 @@ export class SubTableFieldModel extends AssociationFieldModel {
121
122
  parentItem={this.context.item}
122
123
  filterTargetKey={this.collection.filterTargetKey}
123
124
  formValuesChangeEmitter={this.context.blockModel?.emitter}
124
- fieldPathArray={this.parent?.context?.fieldPathArray}
125
- getCurrentValue={this.getCurrentValue}
125
+ fieldPathArray={fieldPathArray}
126
+ getCurrentValue={() => this.getCurrentValue()}
126
127
  />
127
128
  );
128
129
  }
@@ -191,7 +191,15 @@ export class ClickableFieldModel extends FieldModel {
191
191
  }
192
192
 
193
193
  renderInDisplayStyle(value, record?, isToMany?, wrap?) {
194
- const { clickToOpen = false, displayStyle, titleField, overflowMode, disabled, ...restProps } = this.props;
194
+ const {
195
+ clickToOpen = false,
196
+ displayStyle,
197
+ titleField,
198
+ overflowMode,
199
+ disabled,
200
+ timeFormat,
201
+ ...restProps
202
+ } = this.props;
195
203
  const titleCollectionField = titleField
196
204
  ? this.context.collectionField?.targetCollection?.getField?.(titleField) || this.context.collectionField
197
205
  : this.context.collectionField;
@@ -38,6 +38,12 @@ const CollectionSelector = (props) => {
38
38
  );
39
39
  };
40
40
 
41
+ function getConfiguredCollectionNames(collectionField) {
42
+ return (collectionField?.uiSchema?.enum || collectionField?.enum || [])
43
+ .map((item) => (item && typeof item === 'object' ? item.value : item))
44
+ .filter((value) => value !== null && typeof value !== 'undefined');
45
+ }
46
+
41
47
  export class CollectionSelectorFieldModel extends FieldModel {
42
48
  render() {
43
49
  return <CollectionSelector {...this.props} />;
@@ -66,12 +72,12 @@ CollectionSelectorFieldModel.registerFlow({
66
72
  });
67
73
  } else {
68
74
  const collections = ctx.dataSourceManager.getDataSource('main').getCollections();
69
- const defaultOptions = ctx.model.context.collectionField.enum || [];
75
+ const defaultOptions = getConfiguredCollectionNames(ctx.model.context.collectionField);
70
76
  const options = collections
71
77
  .filter((item: any) => !item.options.hidden)
72
78
  .filter((v) => {
73
79
  if (defaultOptions.length) {
74
- return defaultOptions.find((c) => c.value === v.name);
80
+ return defaultOptions.includes(v.name);
75
81
  }
76
82
  return true;
77
83
  })
@@ -43,6 +43,12 @@ const fieldNames = {
43
43
  color: 'color',
44
44
  };
45
45
 
46
+ function getConfiguredCollectionNames(collectionField) {
47
+ return (collectionField?.uiSchema?.enum || collectionField?.enum || [])
48
+ .map((item) => (item && typeof item === 'object' ? item.value : item))
49
+ .filter((value) => value !== null && typeof value !== 'undefined');
50
+ }
51
+
46
52
  export class DisplayEnumFieldModel extends ClickableFieldModel {
47
53
  isEmpty(value: any) {
48
54
  return value === null || value === undefined || value === '';
@@ -134,12 +140,12 @@ DisplayEnumFieldModel.registerFlow({
134
140
  });
135
141
  } else {
136
142
  const collections = ctx.dataSourceManager.getDataSource('main').getCollections();
137
- const defaultOptions = ctx.model.context.collectionField.enum || [];
143
+ const defaultOptions = getConfiguredCollectionNames(ctx.model.context.collectionField);
138
144
  const options = collections
139
145
  .filter((item: any) => !item.options.hidden)
140
146
  .filter((v) => {
141
147
  if (defaultOptions.length) {
142
- return defaultOptions.find((c) => c.value === v.name);
148
+ return defaultOptions.includes(v.name);
143
149
  }
144
150
  return true;
145
151
  })
@@ -15,7 +15,7 @@ import { ClickableFieldModel } from './ClickableFieldModel';
15
15
  export class DisplayTimeFieldModel extends ClickableFieldModel {
16
16
  public renderComponent(value) {
17
17
  const { prefix, suffix, style, className } = this.props;
18
- const format = this.props['format'] || 'HH:mm:ss';
18
+ const format = this.props['timeFormat'] || this.props['format'] || 'HH:mm:ss';
19
19
  const result = value && dayjs(value, 'HH:mm:ss').format(format);
20
20
  return (
21
21
  <span className={className} style={style}>
@@ -15,7 +15,7 @@ import { FieldModel } from '../base/FieldModel';
15
15
  import { MobileTimePicker } from './mobile-components/MobileTimePicker';
16
16
 
17
17
  const TimePickerCom = (props) => {
18
- const format = props['format'] || 'HH:mm:ss';
18
+ const format = props['timeFormat'] || props['format'] || 'HH:mm:ss';
19
19
  const onChange = props.onChange;
20
20
  const ctx = useFlowModelContext();
21
21
  const componentProps = {
@@ -0,0 +1,61 @@
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 { FlowEngine, FlowModelProvider } from '@nocobase/flow-engine';
11
+ import { render, screen } from '@testing-library/react';
12
+ import { describe, expect, it } from 'vitest';
13
+ import React from 'react';
14
+ import { DisplayTimeFieldModel } from '../DisplayTimeFieldModel';
15
+ import { TimeFieldModel } from '../TimeFieldModel';
16
+
17
+ describe('TimeFieldModel', () => {
18
+ it('uses timeFormat before format when rendering the editable time picker', () => {
19
+ const engine = new FlowEngine();
20
+ engine.registerModels({ TimeFieldModel });
21
+
22
+ const model = engine.createModel<TimeFieldModel>({
23
+ use: TimeFieldModel,
24
+ uid: 'time-field-format',
25
+ props: {
26
+ value: '13:05:06',
27
+ format: 'HH:mm:ss',
28
+ timeFormat: 'hh:mm:ss a',
29
+ },
30
+ });
31
+
32
+ render(
33
+ <FlowModelProvider model={model}>
34
+ <>{model.render()}</>
35
+ </FlowModelProvider>,
36
+ );
37
+
38
+ expect(screen.getByDisplayValue('01:05:06 pm')).toBeInTheDocument();
39
+ });
40
+ });
41
+
42
+ describe('DisplayTimeFieldModel', () => {
43
+ it('uses timeFormat before format when rendering read pretty time text', () => {
44
+ const engine = new FlowEngine();
45
+ engine.registerModels({ DisplayTimeFieldModel });
46
+
47
+ const model = engine.createModel<DisplayTimeFieldModel>({
48
+ use: DisplayTimeFieldModel,
49
+ uid: 'display-time-field-format',
50
+ props: {
51
+ value: '13:05:06',
52
+ format: 'HH:mm:ss',
53
+ timeFormat: 'hh:mm:ss a',
54
+ },
55
+ });
56
+
57
+ render(model.render());
58
+
59
+ expect(screen.getByText('01:05:06 pm')).toBeInTheDocument();
60
+ });
61
+ });
@@ -9,11 +9,26 @@
9
9
 
10
10
  import { useFlowModelContext } from '@nocobase/flow-engine';
11
11
  import { dayjs } from '@nocobase/utils/client';
12
+ import type { Dayjs } from 'dayjs';
12
13
  import { DatePicker } from 'antd';
13
14
  import { DatePicker as AntdMobileDatePicker } from 'antd-mobile';
14
15
  import React from 'react';
15
16
  import { useCallback, useState } from 'react';
16
17
 
18
+ type DateValue = string | number | Date | Dayjs | null | undefined;
19
+
20
+ function toNativeDate(value: DateValue): Date | null {
21
+ if (value === null || value === undefined || value === '') {
22
+ return null;
23
+ }
24
+ if (value instanceof Date) {
25
+ return Number.isNaN(value.getTime()) ? null : value;
26
+ }
27
+
28
+ const parsed = dayjs.isDayjs(value) ? value : dayjs(value);
29
+ return parsed.isValid() ? parsed.toDate() : null;
30
+ }
31
+
17
32
  export const MobileDatePicker = (props) => {
18
33
  const {
19
34
  value,
@@ -56,9 +71,9 @@ export const MobileDatePicker = (props) => {
56
71
  return data;
57
72
  }
58
73
  }, []);
59
- // Convert dayjs min/max to native Date for Antd Mobile Picker
60
- const minDate = min ? (min as any).toDate() : new Date(1950, 0, 1);
61
- const maxDate = max ? (max as any).toDate() : new Date(2050, 11, 31);
74
+ const mobileValue = toNativeDate(value);
75
+ const minDate = toNativeDate(min) ?? new Date(1950, 0, 1);
76
+ const maxDate = toNativeDate(max) ?? new Date(2050, 11, 31);
62
77
 
63
78
  return (
64
79
  <>
@@ -78,6 +93,7 @@ export const MobileDatePicker = (props) => {
78
93
  cancelText={t('Cancel')}
79
94
  confirmText={t('Confirm')}
80
95
  visible={visible}
96
+ value={mobileValue}
81
97
  title={<a onClick={handleClear}>{t('Clear')}</a>}
82
98
  onClose={() => {
83
99
  setVisible(false);
@@ -10,7 +10,7 @@
10
10
  import { useFlowModelContext } from '@nocobase/flow-engine';
11
11
  import { Select } from 'antd';
12
12
  import type { CSSProperties } from 'react';
13
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
13
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
14
14
  import { Button, CheckList, Popup, SearchBar, SpinLoading } from 'antd-mobile';
15
15
  import { css } from '@emotion/css';
16
16
  import {
@@ -51,7 +51,7 @@ function deriveRecordsFromValue(
51
51
  ) {
52
52
  if (isMultiple) {
53
53
  if (Array.isArray(value)) {
54
- return (value.filter(Boolean) as any[]).map((item) => {
54
+ return (value.filter((item) => item !== null && item !== undefined) as any[]).map((item) => {
55
55
  if (valueMode === 'value') {
56
56
  return optionMap.get(item) ?? { [valueKey]: item };
57
57
  }
@@ -113,11 +113,13 @@ export function MobileLazySelect(props: Readonly<LazySelectProps>) {
113
113
  );
114
114
 
115
115
  const [selectedRecords, setSelectedRecords] = useState<AssociationOption[]>(() => derivedRecords);
116
+ const wasVisibleRef = useRef(visible);
116
117
 
117
118
  useEffect(() => {
118
- if (visible) {
119
+ if (visible && !wasVisibleRef.current) {
119
120
  setSelectedRecords(derivedRecords);
120
121
  }
122
+ wasVisibleRef.current = visible;
121
123
  }, [derivedRecords, visible]);
122
124
 
123
125
  useEffect(() => {
@@ -0,0 +1,94 @@
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 React from 'react';
11
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
12
+ import { act, fireEvent, render, screen } from '@nocobase/test/client';
13
+ import { dayjs } from '@nocobase/utils/client';
14
+ import { MobileDatePicker } from '../MobileDatePicker';
15
+
16
+ type MockDatePickerProps = Record<string, unknown> & {
17
+ value?: unknown;
18
+ visible?: boolean;
19
+ };
20
+
21
+ const mockState = vi.hoisted(
22
+ (): {
23
+ antdDatePickerProps?: MockDatePickerProps;
24
+ mobileDatePickerProps?: MockDatePickerProps;
25
+ } => ({
26
+ antdDatePickerProps: undefined,
27
+ mobileDatePickerProps: undefined,
28
+ }),
29
+ );
30
+
31
+ vi.mock('@nocobase/flow-engine', async () => {
32
+ const actual = await vi.importActual<typeof import('@nocobase/flow-engine')>('@nocobase/flow-engine');
33
+ return {
34
+ ...actual,
35
+ useFlowModelContext: () => ({
36
+ t: (value: string) => value,
37
+ }),
38
+ };
39
+ });
40
+
41
+ vi.mock('antd', async () => {
42
+ const actual = await vi.importActual<typeof import('antd')>('antd');
43
+ return {
44
+ ...actual,
45
+ DatePicker: (props: MockDatePickerProps) => {
46
+ mockState.antdDatePickerProps = props;
47
+ return <div data-testid="antd-date-picker" />;
48
+ },
49
+ };
50
+ });
51
+
52
+ vi.mock('antd-mobile', () => ({
53
+ DatePicker: (props: MockDatePickerProps) => {
54
+ mockState.mobileDatePickerProps = props;
55
+ return props.visible ? <div data-testid="mobile-date-picker" /> : null;
56
+ },
57
+ }));
58
+
59
+ describe('MobileDatePicker', () => {
60
+ beforeEach(() => {
61
+ mockState.antdDatePickerProps = undefined;
62
+ mockState.mobileDatePickerProps = undefined;
63
+ });
64
+
65
+ it('uses the form value as the mobile popup value', () => {
66
+ render(
67
+ <MobileDatePicker
68
+ value={dayjs('2037-03-06 12:34:56')}
69
+ showTime
70
+ picker="date"
71
+ timeFormat="HH:mm:ss"
72
+ onChange={vi.fn()}
73
+ />,
74
+ );
75
+
76
+ const trigger = screen.getByTestId('antd-date-picker').parentElement as HTMLElement;
77
+ act(() => {
78
+ fireEvent.click(trigger);
79
+ });
80
+
81
+ const popupValue = mockState.mobileDatePickerProps?.value;
82
+ expect(screen.getByTestId('mobile-date-picker')).toBeInTheDocument();
83
+ expect(popupValue).toBeInstanceOf(Date);
84
+ if (!(popupValue instanceof Date)) {
85
+ throw new Error('Expected popup value to be a Date');
86
+ }
87
+ expect(popupValue.getFullYear()).toBe(2037);
88
+ expect(popupValue.getMonth()).toBe(2);
89
+ expect(popupValue.getDate()).toBe(6);
90
+ expect(popupValue.getHours()).toBe(12);
91
+ expect(popupValue.getMinutes()).toBe(34);
92
+ expect(popupValue.getSeconds()).toBe(56);
93
+ });
94
+ });
@@ -10,6 +10,7 @@
10
10
  import React from 'react';
11
11
  import { beforeEach, describe, it, expect, vi } from 'vitest';
12
12
  import { act, fireEvent, render, screen } from '@nocobase/test/client';
13
+ import { MobileLazySelect } from '../MobileLazySelect';
13
14
  import { MobileSelect } from '../MobileSelect';
14
15
 
15
16
  const DEFAULT_OPTIONS = [
@@ -17,6 +18,11 @@ const DEFAULT_OPTIONS = [
17
18
  { label: 'Option B', value: 'b' },
18
19
  ];
19
20
 
21
+ const RELATION_OPTIONS = [
22
+ { uuid: '05f6a3b4-bfb7-7943-578a-3819e2687a7e' },
23
+ { uuid: 'c7d99828-a1de-9e70-4c2d-b0139abdf02e' },
24
+ ];
25
+
20
26
  const mockState = vi.hoisted(() => ({
21
27
  selectProps: undefined as any,
22
28
  popupProps: undefined as any,
@@ -44,6 +50,13 @@ function openPopup() {
44
50
  expect(screen.getByTestId('popup')).toBeInTheDocument();
45
51
  }
46
52
 
53
+ function openLazyPopup() {
54
+ act(() => {
55
+ mockState.selectProps?.onClick?.();
56
+ });
57
+ expect(screen.getByTestId('popup')).toBeInTheDocument();
58
+ }
59
+
47
60
  function selectValues(values: string[]) {
48
61
  act(() => {
49
62
  mockState.checklistProps?.onChange?.(values);
@@ -73,6 +86,30 @@ function renderMobileSelect(props: Record<string, any> = {}) {
73
86
  return { onChange, onChangeComplete };
74
87
  }
75
88
 
89
+ function renderMobileLazySelect(props: Record<string, any> = {}) {
90
+ const onChange = props.onChange ?? vi.fn();
91
+ const renderComponent = (nextProps: Record<string, any> = {}) => (
92
+ <MobileLazySelect
93
+ fieldNames={{ label: 'uuid', value: 'uuid' }}
94
+ value={[]}
95
+ multiple
96
+ allowMultiple
97
+ options={RELATION_OPTIONS}
98
+ {...props}
99
+ {...nextProps}
100
+ onChange={onChange}
101
+ />
102
+ );
103
+
104
+ const result = render(renderComponent());
105
+
106
+ return {
107
+ ...result,
108
+ onChange,
109
+ rerender: (nextProps: Record<string, any> = {}) => result.rerender(renderComponent(nextProps)),
110
+ };
111
+ }
112
+
76
113
  vi.mock('@nocobase/flow-engine', async () => {
77
114
  const actual = await vi.importActual<any>('@nocobase/flow-engine');
78
115
  return {
@@ -80,6 +117,12 @@ vi.mock('@nocobase/flow-engine', async () => {
80
117
  useFlowModelContext: () => ({
81
118
  t: (value: string) => value,
82
119
  }),
120
+ useFlowModel: () => ({
121
+ context: {
122
+ collectionField: {},
123
+ },
124
+ subModels: {},
125
+ }),
83
126
  };
84
127
  });
85
128
 
@@ -240,3 +283,30 @@ describe('MobileSelect in SubForm/SubTable containers', () => {
240
283
  expect(onCommit).toHaveBeenNthCalledWith(2, ['a', 'b']);
241
284
  });
242
285
  });
286
+
287
+ describe('MobileLazySelect', () => {
288
+ beforeEach(() => {
289
+ resetMockState();
290
+ });
291
+
292
+ it('keeps pending relation records selected until confirm', () => {
293
+ const { onChange, rerender } = renderMobileLazySelect();
294
+
295
+ openLazyPopup();
296
+ expect(mockState.checklistProps?.value).toEqual([]);
297
+
298
+ selectValues(['c7d99828-a1de-9e70-4c2d-b0139abdf02e']);
299
+ expect(mockState.checklistProps?.value).toEqual(['c7d99828-a1de-9e70-4c2d-b0139abdf02e']);
300
+
301
+ rerender({
302
+ options: RELATION_OPTIONS.map((item) => ({ ...item })),
303
+ });
304
+
305
+ expect(mockState.checklistProps?.value).toEqual(['c7d99828-a1de-9e70-4c2d-b0139abdf02e']);
306
+
307
+ confirmSelection();
308
+
309
+ expect(onChange).toHaveBeenCalledTimes(1);
310
+ expect(onChange).toHaveBeenCalledWith([RELATION_OPTIONS[1]]);
311
+ });
312
+ });
@@ -282,7 +282,7 @@ const UIEditorTopbarAction = observer(
282
282
  const { token } = theme.useToken();
283
283
  const customToken = token as CustomToken;
284
284
  const designable = !!flowEngine.context.flowSettingsEnabled;
285
- const isMobileLayout = !!flowEngine.context.isMobileLayout;
285
+ const isMobileLayout = !!model.context.isMobileLayout;
286
286
 
287
287
  useHotkeys(
288
288
  'ctrl+shift+u',
@@ -0,0 +1,91 @@
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 { describe, expect, it, vi } from 'vitest';
11
+ import { dateTimeFormat } from '../../actions/dateTimeFormat';
12
+
13
+ describe('dateTimeFormat', () => {
14
+ it('only shows the time format schema for interface time fields', () => {
15
+ const ctx = {
16
+ model: {
17
+ context: {
18
+ collectionField: {
19
+ type: 'string',
20
+ interface: 'time',
21
+ },
22
+ },
23
+ },
24
+ };
25
+
26
+ expect(Object.keys(dateTimeFormat.uiSchema(ctx))).toEqual(['timeFormat']);
27
+ });
28
+
29
+ it('treats interface time fields as time fields', () => {
30
+ const setProps = vi.fn();
31
+ const ctx = {
32
+ model: {
33
+ context: {
34
+ collectionField: {
35
+ type: 'string',
36
+ interface: 'time',
37
+ },
38
+ },
39
+ setProps,
40
+ },
41
+ };
42
+
43
+ dateTimeFormat.handler(ctx, { timeFormat: 'hh:mm:ss a' });
44
+
45
+ expect(setProps).toHaveBeenCalledWith({
46
+ timeFormat: 'hh:mm:ss a',
47
+ format: 'hh:mm:ss a',
48
+ });
49
+ });
50
+
51
+ it('uses format as the default time format when timeFormat is missing', () => {
52
+ const ctx = {
53
+ model: {
54
+ props: {},
55
+ context: {
56
+ collectionField: {
57
+ interface: 'time',
58
+ getComponentProps: () => ({
59
+ format: 'hh:mm:ss a',
60
+ }),
61
+ },
62
+ },
63
+ },
64
+ };
65
+
66
+ expect(dateTimeFormat.defaultParams(ctx)).toMatchObject({
67
+ timeFormat: 'hh:mm:ss a',
68
+ });
69
+ });
70
+
71
+ it('does not use datetime format as the default time format for non-time fields', () => {
72
+ const ctx = {
73
+ model: {
74
+ props: {},
75
+ context: {
76
+ collectionField: {
77
+ type: 'datetime',
78
+ interface: 'datetime',
79
+ getComponentProps: () => ({
80
+ format: 'YYYY-MM-DD hh:mm:ss a',
81
+ }),
82
+ },
83
+ },
84
+ },
85
+ };
86
+
87
+ expect(dateTimeFormat.defaultParams(ctx)).toMatchObject({
88
+ timeFormat: 'HH:mm:ss',
89
+ });
90
+ });
91
+ });