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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/es/APIClient.d.ts +13 -3
  2. package/es/BaseApplication.d.ts +3 -0
  3. package/es/RouteRepository.d.ts +8 -0
  4. package/es/RouterManager.d.ts +15 -0
  5. package/es/authRedirect.d.ts +1 -0
  6. package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
  7. package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
  8. package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
  9. package/es/components/category-tabs/index.d.ts +9 -0
  10. package/es/components/form/ScanInput/useCodeScanner.d.ts +3 -2
  11. package/es/components/form/TypedVariableInput.d.ts +31 -5
  12. package/es/components/form/filter/CollectionFilter.d.ts +2 -0
  13. package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
  14. package/es/components/form/filter/CollectionFilterPanel.d.ts +2 -0
  15. package/es/components/form/filter/index.d.ts +2 -0
  16. package/es/components/form/filter/useFilterActionProps.d.ts +2 -0
  17. package/es/components/index.d.ts +1 -0
  18. package/es/entry-actions/EntryActionManager.d.ts +24 -0
  19. package/es/entry-actions/index.d.ts +9 -0
  20. package/es/flow/actions/index.d.ts +1 -1
  21. package/es/flow/actions/linkageRules.d.ts +27 -0
  22. package/es/flow/admin-shell/BaseLayoutModel.d.ts +6 -0
  23. package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +7 -0
  24. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +1 -0
  25. package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +2 -1
  26. package/es/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.d.ts +24 -0
  27. package/es/flow/admin-shell/admin-layout/index.d.ts +1 -0
  28. package/es/flow/admin-shell/admin-layout/useApplications.d.ts +7 -2
  29. package/es/flow/components/FieldAssignExactDatePicker.d.ts +1 -0
  30. package/es/flow/components/FieldAssignValueInput.d.ts +7 -24
  31. package/es/flow/components/FlowRoute.d.ts +2 -2
  32. package/es/flow/components/RunJSValueEditor.d.ts +1 -0
  33. package/es/flow/components/field-value-variable/DateVariableEditor.d.ts +24 -0
  34. package/es/flow/components/field-value-variable/FieldValueVariableInput.d.ts +31 -0
  35. package/es/flow/components/field-value-variable/dateValue.d.ts +36 -0
  36. package/es/flow/components/field-value-variable/index.d.ts +11 -0
  37. package/es/flow/components/filter/FilterGroup.d.ts +1 -0
  38. package/es/flow/components/filter/VariableFilterItem.d.ts +6 -1
  39. package/es/flow/components/placeholders/BlockPlaceholder.d.ts +1 -0
  40. package/es/flow/index.d.ts +1 -0
  41. package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
  42. package/es/flow/internal/utils/operatorSchemaHelper.d.ts +2 -2
  43. package/es/flow/models/actions/UpdateRecordActionUtils.d.ts +8 -2
  44. package/es/flow/models/base/ActionModelCore.d.ts +6 -2
  45. package/es/flow/models/base/BlockGridModel.d.ts +3 -0
  46. package/es/flow/models/base/CollectionBlockModel.d.ts +3 -0
  47. package/es/flow/models/base/PageModel/PageModel.d.ts +12 -0
  48. package/es/flow/models/base/PageModel/PageModelTabBar.d.ts +22 -0
  49. package/es/flow/models/base/PageModel/PageTabModel.d.ts +12 -0
  50. package/es/flow/models/blocks/details/DetailsBlockModel.d.ts +3 -0
  51. package/es/flow/models/blocks/filter-form/FilterFormGridModel.d.ts +1 -0
  52. package/es/flow/models/blocks/filter-form/FilterFormSubmitActionModel.d.ts +3 -1
  53. package/es/flow/models/blocks/form/FormBlockModel.d.ts +7 -0
  54. package/es/flow/models/blocks/form/FormGridModel.d.ts +6 -0
  55. package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
  56. package/es/flow/models/blocks/form/value-runtime/rules.d.ts +1 -0
  57. package/es/flow/models/blocks/table/JSColumnModel.d.ts +2 -0
  58. package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
  59. package/es/flow/models/blocks/table/TableColumnModel.d.ts +12 -0
  60. package/es/flow/models/blocks/table/utils.d.ts +1 -0
  61. package/es/flow/models/fields/AssociationFieldModel/RecordPickerFieldModel.d.ts +2 -1
  62. package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +0 -17
  63. package/es/flow/models/fields/AssociationFieldModel/recordSelectShared.d.ts +41 -0
  64. package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
  65. package/es/flow/routeTransientInputArgs.d.ts +14 -0
  66. package/es/flow-compat/fieldValidationConstants.d.ts +1 -1
  67. package/es/flow-compat/passwordUtils.d.ts +1 -1
  68. package/es/flow-compat/routeTypes.d.ts +1 -0
  69. package/es/index.d.ts +7 -0
  70. package/es/index.mjs +301 -177
  71. package/es/ui-operation/index.d.ts +15 -0
  72. package/es/ui-operation/ui-operation-codec.d.ts +10 -0
  73. package/es/utils/getRouteRuntimeVersion.d.ts +23 -0
  74. package/es/utils/index.d.ts +1 -0
  75. package/es/utils/markdownSanitize.d.ts +11 -0
  76. package/lib/index.js +313 -189
  77. package/lib/locale/languageCodes.js +3 -1
  78. package/package.json +9 -7
  79. package/src/APIClient.ts +90 -7
  80. package/src/Application.tsx +7 -1
  81. package/src/BaseApplication.tsx +13 -6
  82. package/src/RouteRepository.ts +25 -0
  83. package/src/RouterManager.tsx +142 -1
  84. package/src/__tests__/APIClient.test.tsx +58 -0
  85. package/src/__tests__/RouteRepository.test.ts +23 -0
  86. package/src/__tests__/RouterManager.test.ts +125 -0
  87. package/src/__tests__/app.test.tsx +73 -0
  88. package/src/__tests__/authRedirect.test.ts +17 -0
  89. package/src/__tests__/browserChecker.test.ts +61 -0
  90. package/src/__tests__/getRouteRuntimeVersion.test.ts +68 -0
  91. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +35 -2
  92. package/src/__tests__/settings-center.test.tsx +270 -6
  93. package/src/acl/ACLProvider.tsx +2 -0
  94. package/src/acl/__tests__/ACLProvider.test.tsx +92 -0
  95. package/src/authRedirect.ts +38 -0
  96. package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
  97. package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
  98. package/src/collection-manager/field-validation.ts +1 -1
  99. package/src/components/README.md +7 -1
  100. package/src/components/README.zh-CN.md +6 -1
  101. package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
  102. package/src/components/category-tabs/index.ts +10 -0
  103. package/src/components/form/JsonTextArea.tsx +4 -0
  104. package/src/components/form/ScanInput/CodeScanner.tsx +79 -35
  105. package/src/components/form/ScanInput/__tests__/CodeScanner.test.tsx +101 -0
  106. package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +284 -11
  107. package/src/components/form/ScanInput/useCodeScanner.ts +332 -24
  108. package/src/components/form/TypedVariableInput.tsx +452 -101
  109. package/src/components/form/__tests__/TypedVariableInput.test.tsx +364 -12
  110. package/src/components/form/filter/CollectionFilter.tsx +4 -0
  111. package/src/components/form/filter/CollectionFilterItem.tsx +36 -9
  112. package/src/components/form/filter/CollectionFilterPanel.tsx +4 -0
  113. package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +55 -0
  114. package/src/components/form/filter/__tests__/useFilterActionProps.test.tsx +95 -0
  115. package/src/components/form/filter/index.ts +2 -0
  116. package/src/components/form/filter/useFilterActionProps.ts +37 -6
  117. package/src/components/form/table/Table.tsx +81 -6
  118. package/src/components/form/table/__tests__/Table.columnWidth.test.tsx +433 -0
  119. package/src/components/index.ts +1 -0
  120. package/src/entry-actions/EntryActionManager.ts +76 -0
  121. package/src/entry-actions/index.ts +10 -0
  122. package/src/flow/FlowPage.tsx +38 -3
  123. package/src/flow/__tests__/FlowPage.test.tsx +201 -27
  124. package/src/flow/__tests__/FlowRoute.test.tsx +675 -12
  125. package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
  126. package/src/flow/actions/__tests__/actionLinkageRules.forkProps.test.ts +314 -0
  127. package/src/flow/actions/__tests__/dataScopeFilter.test.ts +62 -0
  128. package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +102 -0
  129. package/src/flow/actions/__tests__/linkageRules.actionStates.test.ts +33 -0
  130. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +72 -0
  131. package/src/flow/actions/__tests__/linkageRules.tab.test.ts +171 -0
  132. package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
  133. package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +80 -20
  134. package/src/flow/actions/dataScopeFilter.ts +10 -1
  135. package/src/flow/actions/dateTimeFormat.tsx +2 -2
  136. package/src/flow/actions/index.ts +2 -0
  137. package/src/flow/actions/linkageRules.tsx +144 -26
  138. package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
  139. package/src/flow/actions/openView.tsx +21 -1
  140. package/src/flow/admin-shell/BaseLayoutModel.tsx +124 -0
  141. package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +184 -42
  142. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +1016 -119
  143. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +41 -5
  144. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +177 -1
  145. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +20 -0
  146. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +78 -59
  147. package/src/flow/admin-shell/admin-layout/AppListRender.tsx +13 -2
  148. package/src/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.tsx +289 -0
  149. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +253 -6
  150. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +228 -0
  151. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +436 -2
  152. package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +48 -0
  153. package/src/flow/admin-shell/admin-layout/index.ts +1 -0
  154. package/src/flow/admin-shell/admin-layout/useApplications.tsx +81 -12
  155. package/src/flow/common/Markdown/Display.tsx +14 -3
  156. package/src/flow/common/Markdown/Edit.tsx +19 -7
  157. package/src/flow/common/Markdown/style.ts +4 -0
  158. package/src/flow/components/BlockItemCard.tsx +2 -2
  159. package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
  160. package/src/flow/components/FieldAssignValueInput.tsx +72 -584
  161. package/src/flow/components/FlowRoute.tsx +204 -46
  162. package/src/flow/components/RunJSValueEditor.tsx +9 -1
  163. package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +216 -0
  164. package/src/flow/components/field-value-variable/DateVariableEditor.tsx +181 -0
  165. package/src/flow/components/field-value-variable/FieldValueVariableInput.tsx +326 -0
  166. package/src/flow/components/field-value-variable/__tests__/FieldValueVariableInput.test.tsx +380 -0
  167. package/src/flow/components/field-value-variable/dateValue.ts +223 -0
  168. package/src/flow/components/field-value-variable/index.ts +12 -0
  169. package/src/flow/components/filter/FilterGroup.tsx +33 -5
  170. package/src/flow/components/filter/VariableFilterItem.tsx +170 -13
  171. package/src/flow/components/filter/__tests__/FilterGroup.test.tsx +45 -0
  172. package/src/flow/components/filter/__tests__/VariableFilterItem.leftMetaTree.test.tsx +9 -0
  173. package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +195 -1
  174. package/src/flow/components/placeholders/BlockPlaceholder.tsx +70 -23
  175. package/src/flow/components/placeholders/__tests__/BlockPlaceholder.test.tsx +57 -7
  176. package/src/flow/index.ts +3 -0
  177. package/src/flow/internal/components/Markdown/util.ts +2 -1
  178. package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
  179. package/src/flow/internal/utils/operatorSchemaHelper.ts +3 -2
  180. package/src/flow/models/actions/UpdateRecordActionModel.tsx +18 -1
  181. package/src/flow/models/actions/UpdateRecordActionUtils.ts +18 -6
  182. package/src/flow/models/actions/__tests__/UpdateRecordActionModel.test.ts +76 -4
  183. package/src/flow/models/base/ActionModel.tsx +10 -8
  184. package/src/flow/models/base/ActionModelCore.tsx +16 -2
  185. package/src/flow/models/base/BlockGridModel.tsx +26 -0
  186. package/src/flow/models/base/CollectionBlockModel.tsx +38 -3
  187. package/src/flow/models/base/PageModel/PageModel.tsx +387 -53
  188. package/src/flow/models/base/PageModel/PageModelTabBar.tsx +114 -0
  189. package/src/flow/models/base/PageModel/PageTabModel.tsx +249 -18
  190. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +790 -10
  191. package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.module-isolation.test.tsx +130 -0
  192. package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.test.tsx +118 -0
  193. package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +1075 -7
  194. package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +49 -0
  195. package/src/flow/models/base/__tests__/BlockGridModel.selectSceneActivation.test.ts +124 -0
  196. package/src/flow/models/base/__tests__/CollectionBlockModel.addAppends.test.ts +41 -0
  197. package/src/flow/models/base/__tests__/CollectionBlockModel.initialBeforeRenderRefresh.test.ts +125 -9
  198. package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +28 -53
  199. package/src/flow/models/blocks/details/DetailsBlockModel.tsx +8 -3
  200. package/src/flow/models/blocks/details/__tests__/DetailsBlockModel.initialPaginationChangeRefresh.test.ts +32 -1
  201. package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +28 -3
  202. package/src/flow/models/blocks/filter-form/FilterFormGridModel.tsx +36 -5
  203. package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +155 -25
  204. package/src/flow/models/blocks/filter-form/FilterFormSubmitActionModel.tsx +57 -4
  205. package/src/flow/models/blocks/filter-form/__tests__/FilterFormGridModel.onModelCreated.test.ts +174 -1
  206. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +395 -1
  207. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.getFilterValue.test.ts +72 -0
  208. package/src/flow/models/blocks/filter-form/__tests__/FilterFormSubmitActionModel.lifecycle.test.ts +90 -0
  209. package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +66 -4
  210. package/src/flow/models/blocks/filter-manager/FilterManager.ts +5 -0
  211. package/src/flow/models/blocks/filter-manager/__tests__/FilterManager.test.ts +40 -0
  212. package/src/flow/models/blocks/form/FormBlockModel.tsx +47 -0
  213. package/src/flow/models/blocks/form/FormGridModel.tsx +4 -0
  214. package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
  215. package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +8 -2
  216. package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
  217. package/src/flow/models/blocks/form/__tests__/runJsFormSubmit.test.ts +131 -0
  218. package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +435 -0
  219. package/src/flow/models/blocks/form/value-runtime/rules.ts +67 -14
  220. package/src/flow/models/blocks/form/value-runtime/runtime.ts +43 -19
  221. package/src/flow/models/blocks/js-block/JSBlock.tsx +1 -1
  222. package/src/flow/models/blocks/table/JSColumnModel.tsx +10 -1
  223. package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +2 -1
  224. package/src/flow/models/blocks/table/TableBlockModel.tsx +29 -3
  225. package/src/flow/models/blocks/table/TableColumnModel.tsx +54 -11
  226. package/src/flow/models/blocks/table/__tests__/JSColumnModel.test.tsx +52 -5
  227. package/src/flow/models/blocks/table/__tests__/TableBlockModel.filterReset.test.ts +78 -0
  228. package/src/flow/models/blocks/table/__tests__/TableBlockModel.quickEditRefresh.test.ts +12 -0
  229. package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +84 -0
  230. package/src/flow/models/blocks/table/utils.ts +7 -0
  231. package/src/flow/models/fields/AssociationFieldModel/CascadeSelectFieldModel.tsx +33 -1
  232. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +10 -1
  233. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/__tests__/popupContext.test.ts +120 -0
  234. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/actions/PopupSubTableEditActionModel.tsx +10 -2
  235. package/src/flow/models/fields/AssociationFieldModel/RecordPickerFieldModel.tsx +24 -1
  236. package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +54 -149
  237. package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +167 -3
  238. package/src/flow/models/fields/AssociationFieldModel/recordSelectShared.tsx +156 -0
  239. package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +149 -0
  240. package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +149 -113
  241. package/src/flow/models/fields/DisplayNumberFieldModel.tsx +1 -1
  242. package/src/flow/models/fields/JsonFieldModel.tsx +31 -8
  243. package/src/flow/models/fields/NumberFieldModel.tsx +1 -1
  244. package/src/flow/models/fields/TextareaFieldModel.tsx +42 -19
  245. package/src/flow/models/fields/VariableFieldFormModel.tsx +3 -3
  246. package/src/flow/models/fields/__tests__/DisplayNumberFieldModel.test.ts +33 -0
  247. package/src/flow/models/fields/__tests__/JsonFieldModel.test.ts +82 -0
  248. package/src/flow/models/fields/__tests__/NumberFieldModel.test.tsx +47 -0
  249. package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +32 -1
  250. package/src/flow/models/fields/__tests__/VariableFieldFormModel.test.tsx +51 -0
  251. package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +26 -10
  252. package/src/flow/models/fields/mobile-components/MobileSelect.tsx +51 -14
  253. package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +139 -13
  254. package/src/flow/models/topbar/TopbarActionModel.tsx +78 -3
  255. package/src/flow/routeTransientInputArgs.ts +27 -0
  256. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +42 -0
  257. package/src/flow/utils/dataScopeFormValueClear.ts +4 -3
  258. package/src/flow-compat/fieldValidationConstants.ts +1 -1
  259. package/src/flow-compat/routeTypes.ts +1 -0
  260. package/src/index.ts +7 -0
  261. package/src/layout-manager/LayoutContentRoute.tsx +2 -1
  262. package/src/layout-manager/LayoutRoute.tsx +2 -1
  263. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +87 -1
  264. package/src/locale/languageCodes.ts +3 -1
  265. package/src/nocobase-buildin-plugin/index.tsx +19 -1
  266. package/src/settings-center/AdminSettingsLayout.tsx +23 -2
  267. package/src/ui-operation/index.ts +33 -0
  268. package/src/ui-operation/ui-operation-codec.ts +54 -0
  269. package/src/utils/getRouteRuntimeVersion.ts +185 -0
  270. package/src/utils/index.tsx +1 -0
  271. package/src/utils/markdownSanitize.ts +88 -0
@@ -0,0 +1,78 @@
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 { TableBlockModel } from '../TableBlockModel';
12
+
13
+ describe('TableBlockModel filter reset', () => {
14
+ it('collapses the filtered tree when rows are not expanded by default', () => {
15
+ const setExpandFlag = vi.fn();
16
+ const setProps = vi.fn();
17
+ const model = {
18
+ props: { treeTable: true, defaultExpandAllRows: false, expandedRowKeys: [1, 2] },
19
+ resource: { getData: () => [{ id: 3, children: [{ id: 4 }] }] },
20
+ setProps,
21
+ mapSubModels: (_key, callback) => callback({ setExpandFlag }),
22
+ };
23
+
24
+ TableBlockModel.prototype.resetAfterFilterChange.call(model);
25
+
26
+ expect(setProps).toHaveBeenCalledWith('expandedRowKeys', []);
27
+ expect(setExpandFlag).toHaveBeenCalledWith(false);
28
+ });
29
+
30
+ it('expands the filtered tree when rows are expanded by default', () => {
31
+ const setExpandFlag = vi.fn();
32
+ const setProps = vi.fn();
33
+ const model = {
34
+ props: { treeTable: true, defaultExpandAllRows: true, expandedRowKeys: [1, 2] },
35
+ collection: { filterTargetKey: 'id' },
36
+ resource: { getData: () => [{ id: 3, children: [{ id: 4 }] }] },
37
+ setProps,
38
+ mapSubModels: (_key, callback) => callback({ setExpandFlag }),
39
+ };
40
+
41
+ TableBlockModel.prototype.resetAfterFilterChange.call(model);
42
+
43
+ expect(setProps).toHaveBeenCalledWith('expandedRowKeys', [3, 4]);
44
+ expect(setExpandFlag).toHaveBeenCalledWith(true);
45
+ });
46
+
47
+ it('uses the collection filter target key when expanding the filtered tree', () => {
48
+ const setProps = vi.fn();
49
+ const model = {
50
+ props: { treeTable: true, defaultExpandAllRows: true },
51
+ collection: { filterTargetKey: ['tenant', 'slug'] },
52
+ resource: {
53
+ getData: () => [{ tenant: 'acme', slug: 'root', children: [{ tenant: 'acme', slug: 'child' }] }],
54
+ },
55
+ setProps,
56
+ mapSubModels: vi.fn(),
57
+ };
58
+
59
+ TableBlockModel.prototype.resetAfterFilterChange.call(model);
60
+
61
+ expect(setProps).toHaveBeenCalledWith('expandedRowKeys', ['acme-root', 'acme-child']);
62
+ });
63
+
64
+ it('does not change expansion state for a regular table', () => {
65
+ const setProps = vi.fn();
66
+ const mapSubModels = vi.fn();
67
+ const model = {
68
+ props: { treeTable: false },
69
+ setProps,
70
+ mapSubModels,
71
+ };
72
+
73
+ TableBlockModel.prototype.resetAfterFilterChange.call(model);
74
+
75
+ expect(setProps).not.toHaveBeenCalled();
76
+ expect(mapSubModels).not.toHaveBeenCalled();
77
+ });
78
+ });
@@ -9,8 +9,20 @@
9
9
 
10
10
  import { FlowEngine, MultiRecordResource } from '@nocobase/flow-engine';
11
11
  import { describe, expect, it } from 'vitest';
12
+ import { TableBlockModel } from '../TableBlockModel';
12
13
 
13
14
  describe('TableBlockModel quick edit refresh', () => {
15
+ it('requests record-level ACL metadata for table data', () => {
16
+ const engine = new FlowEngine();
17
+ const model = {
18
+ context: engine.context,
19
+ };
20
+
21
+ const resource = TableBlockModel.prototype.createResource.call(model, {}, {});
22
+
23
+ expect(resource.getRequestOptions().headers['X-With-ACL-Meta']).toBe('true');
24
+ });
25
+
14
26
  it('updates table data through a new array reference after quick editing a flat row', () => {
15
27
  const engine = new FlowEngine();
16
28
  const resource = engine.context.createResource(MultiRecordResource);
@@ -66,6 +66,39 @@ describe('TableColumnModel sorter settings', () => {
66
66
  expect(setProps).toHaveBeenCalledWith('editable', false);
67
67
  });
68
68
 
69
+ it('checks record update scope and field permission before enabling quick edit', () => {
70
+ const can = vi.fn(() => false);
71
+ const model = {
72
+ props: { editable: true },
73
+ collectionField: { name: 'title' },
74
+ context: {
75
+ skipAclCheck: false,
76
+ acl: { can },
77
+ blockModel: {
78
+ collection: {
79
+ dataSourceKey: 'main',
80
+ name: 'posts',
81
+ getFilterByTK: () => 2,
82
+ },
83
+ resource: {
84
+ getResourceName: () => 'posts',
85
+ getMeta: () => ({ update: [1] }),
86
+ },
87
+ },
88
+ },
89
+ };
90
+
91
+ expect(TableColumnModel.prototype.canQuickEdit.call(model, { id: 2 })).toBe(false);
92
+ expect(can).toHaveBeenCalledWith({
93
+ dataSourceKey: 'main',
94
+ resourceName: 'posts',
95
+ actionName: 'update',
96
+ recordPkValue: 2,
97
+ allowedActions: { update: [1] },
98
+ fields: ['title'],
99
+ });
100
+ });
101
+
69
102
  it('hides sortable setting for association fields', async () => {
70
103
  const engine = new FlowEngine();
71
104
  const model = new TableColumnModel({ uid: 'table-column-association-sorter', flowEngine: engine } as any);
@@ -327,6 +360,57 @@ describe('TableColumnModel sorter settings', () => {
327
360
  );
328
361
  });
329
362
 
363
+ it('keeps saved ordinary datetime format when table column initializes again', async () => {
364
+ const engine = new FlowEngine();
365
+ const model = new TableColumnModel({
366
+ uid: 'table-column-saved-datetime-format',
367
+ flowEngine: engine,
368
+ } as any);
369
+ const initStep = model.getFlow('tableColumnSettings')?.steps?.init as any;
370
+ const setProps = vi.fn();
371
+
372
+ await initStep.handler({
373
+ model: {
374
+ context: {
375
+ collectionField: {
376
+ title: 'Datetime',
377
+ name: 'datetime',
378
+ isAssociationField: () => false,
379
+ getComponentProps: () => ({
380
+ dateFormat: 'YYYY-MM-DD',
381
+ showTime: false,
382
+ }),
383
+ },
384
+ },
385
+ props: {},
386
+ subModels: {
387
+ field: {
388
+ getStepParams: (flowKey, stepKey) =>
389
+ flowKey === 'datetimeSettings' && stepKey === 'dateFormat'
390
+ ? {
391
+ picker: 'date',
392
+ dateFormat: 'YYYY-MM-DD',
393
+ showTime: true,
394
+ timeFormat: 'HH:mm:ss',
395
+ }
396
+ : undefined,
397
+ },
398
+ },
399
+ applySubModelsBeforeRenderFlows: vi.fn(),
400
+ setProps,
401
+ },
402
+ });
403
+
404
+ expect(setProps).toHaveBeenCalledWith(
405
+ expect.objectContaining({
406
+ dateFormat: 'YYYY-MM-DD',
407
+ format: 'YYYY-MM-DD HH:mm:ss',
408
+ showTime: true,
409
+ timeFormat: 'HH:mm:ss',
410
+ }),
411
+ );
412
+ });
413
+
330
414
  it('does not update field component setting when title field refresh fails', async () => {
331
415
  const engine = new FlowEngine();
332
416
  const model = new TableColumnModel({ uid: 'table-column-title-field-component-failed', flowEngine: engine } as any);
@@ -67,6 +67,13 @@ export function extractIds(data) {
67
67
  return ids;
68
68
  }
69
69
 
70
+ export function extractRowKeys(data: Record<string, unknown>[], key: string | string[]) {
71
+ return data.flatMap((item) => {
72
+ const children = Array.isArray(item.children) ? extractRowKeys(item.children, key) : [];
73
+ return [getRowKey(item, key), ...children];
74
+ });
75
+ }
76
+
70
77
  /**
71
78
  * 生成表格行唯一 key
72
79
  * @param record 当前行数据
@@ -8,7 +8,13 @@
8
8
  */
9
9
 
10
10
  import { Cascader, Space, Button } from 'antd';
11
- import { CollectionField, EditableItemModel, tExpr, MultiRecordResource } from '@nocobase/flow-engine';
11
+ import {
12
+ CollectionField,
13
+ EditableItemModel,
14
+ FilterableItemModel,
15
+ MultiRecordResource,
16
+ tExpr,
17
+ } from '@nocobase/flow-engine';
12
18
  import { DeleteOutlined } from '@ant-design/icons';
13
19
  import { css, cx } from '@emotion/css';
14
20
  import { debounce, castArray, omit, last, isEqual } from 'lodash';
@@ -31,6 +37,23 @@ function normalizeLabelKeys(labelKeyOrKeys?: string | string[]): string[] {
31
37
  return Array.from(new Set(list.filter((item): item is string => !!item && typeof item === 'string')));
32
38
  }
33
39
 
40
+ function normalizeCascadeFieldKey(key: unknown): string | undefined {
41
+ if (typeof key === 'string' && key) {
42
+ return key;
43
+ }
44
+ if (Array.isArray(key) && key.length === 1 && typeof key[0] === 'string') {
45
+ return key[0];
46
+ }
47
+ return undefined;
48
+ }
49
+
50
+ function getDefaultCascadeFieldNames(field: CollectionField) {
51
+ const targetCollection = field?.targetCollection;
52
+ const value = normalizeCascadeFieldKey(targetCollection?.filterTargetKey) || 'id';
53
+ const label = targetCollection?.titleCollectionField?.name || value;
54
+ return { label, value };
55
+ }
56
+
34
57
  function getRecordDisplayLabel(item: any, labelKeyOrKeys?: string | string[]): string | null {
35
58
  if (!isPlainRecord(item)) return null;
36
59
  const labelKeys = normalizeLabelKeys(labelKeyOrKeys);
@@ -815,6 +838,15 @@ EditableItemModel.bindModelToInterface('CascadeSelectFieldModel', ['m2o', 'o2o',
815
838
  order: 60,
816
839
  });
817
840
 
841
+ FilterableItemModel.bindModelToInterface('CascadeSelectFieldModel', ['m2o', 'o2o', 'oho', 'obo'], {
842
+ when: (ctx, field) => field.targetCollection?.template === 'tree',
843
+ isDefault: true,
844
+ order: 60,
845
+ defaultProps: (ctx, field) => ({
846
+ fieldNames: getDefaultCascadeFieldNames(field),
847
+ }),
848
+ });
849
+
818
850
  EditableItemModel.bindModelToInterface('CascadeSelectListFieldModel', ['m2m', 'o2m', 'mbm'], {
819
851
  when: (ctx, field) => field.targetCollection?.template === 'tree',
820
852
  isDefault: true,
@@ -28,6 +28,7 @@ import { observer } from '@formily/reactive-react';
28
28
  import React, { useEffect, useMemo, useState, useRef } from 'react';
29
29
  import { useTranslation } from 'react-i18next';
30
30
  import { buildRecordPickerPopupContextInputArgs, RecordPickerContent } from '../RecordPickerFieldModel';
31
+ import { buildOpenerUids } from '../recordSelectShared';
31
32
  import { AssociationFieldModel } from '../AssociationFieldModel';
32
33
  import { adjustColumnOrder } from '../../../blocks/table/utils';
33
34
  import { isSubTableColumnFieldComponentContext } from '../SubTableFieldModel/SubTableColumnModel';
@@ -102,7 +103,7 @@ const RenderCell = observer<any>((props) => {
102
103
  transform: translateY(-50%);
103
104
  }
104
105
  &:hover {
105
- background: rgba(24, 144, 255, 0.1) !important;
106
+ box-shadow: inset 0 0 0 9999px rgba(24, 144, 255, 0.1);
106
107
  }
107
108
  &:hover .edit-icon {
108
109
  display: inline-flex;
@@ -126,6 +127,7 @@ const RenderCell = observer<any>((props) => {
126
127
  fieldPath: dataIndex,
127
128
  record: record,
128
129
  fieldProps: { ...columnModel.props, ...columnModel.subModels.field.props },
130
+ sourceFieldModelUid: columnModel.subModels.field.uid,
129
131
  onOk: (values) => {
130
132
  record[dataIndex] = values[dataIndex];
131
133
  // 仅重渲染单元格
@@ -652,6 +654,11 @@ PopupSubTableFieldModel.registerFlow({
652
654
  const parentItemOptions = ctx?.getPropertyOptions?.('item');
653
655
  const itemIndex = Array.isArray(ctx.model?.props?.value) ? ctx.model.props.value.length : 0;
654
656
  const itemLength = itemIndex + 1;
657
+ const associationName = ctx.collectionField?.resourceName;
658
+ const sourceId = parentItem?.value
659
+ ? ctx.collectionField?.collection?.getFilterByTK?.(parentItem.value)
660
+ : undefined;
661
+ const openerUids = buildOpenerUids(ctx, ctx.inputArgs);
655
662
  ctx.viewer.open({
656
663
  type: openMode,
657
664
  width: sizeToWidthMap[openMode][size],
@@ -662,12 +669,14 @@ PopupSubTableFieldModel.registerFlow({
662
669
  scene: 'subForm',
663
670
  dataSourceKey: ctx.collection.dataSourceKey,
664
671
  collectionName: ctx.collectionField?.target,
672
+ ...(associationName && sourceId != null ? { associationName, sourceId } : {}),
665
673
  collectionField: ctx.collectionField,
666
674
  parentItem,
667
675
  parentItemMeta: parentItemOptions?.meta,
668
676
  parentItemResolver: parentItemOptions?.resolveOnServer,
669
677
  itemIndex,
670
678
  itemLength,
679
+ openerUids,
671
680
  },
672
681
  content: () => <EditFormContent model={ctx.model} scene="create" />,
673
682
  styles: {
@@ -0,0 +1,120 @@
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 { PopupSubTableFieldModel } from '../PopupSubTableFieldModel';
12
+ import { PopupSubTableEditActionModel } from '../actions/PopupSubTableEditActionModel';
13
+
14
+ function getOpenViewHandler(modelClass: typeof PopupSubTableFieldModel | typeof PopupSubTableEditActionModel) {
15
+ const flow = modelClass.globalFlowRegistry.getFlow('popupSettings');
16
+ const step = flow?.getStep('openView');
17
+ const handler = step?.serialize().handler;
18
+
19
+ if (!handler) {
20
+ throw new Error('popupSettings.openView handler is not registered');
21
+ }
22
+
23
+ return handler;
24
+ }
25
+
26
+ function createContext(record?: Record<string, unknown>, parentRecord: Record<string, unknown> = { id: 1 }) {
27
+ const open = vi.fn();
28
+ const sourceCollection = {
29
+ getFilterByTK: vi.fn((sourceRecord: Record<string, unknown>) => sourceRecord.id),
30
+ };
31
+ const model = {
32
+ uid: 'popup-subtable-uid',
33
+ props: {
34
+ value: record ? [record] : [],
35
+ },
36
+ context: {
37
+ inputArgs: {},
38
+ },
39
+ flowEngine: {
40
+ context: {
41
+ themeToken: {
42
+ colorBgLayout: '#fff',
43
+ },
44
+ },
45
+ },
46
+ };
47
+
48
+ return {
49
+ open,
50
+ context: {
51
+ inputArgs: {},
52
+ item: {
53
+ value: parentRecord,
54
+ },
55
+ view: {
56
+ inputArgs: {
57
+ viewUid: 'parent-popup-uid',
58
+ openerUids: ['root-page-uid'],
59
+ },
60
+ },
61
+ viewer: { open },
62
+ layoutContentElement: {},
63
+ model,
64
+ associationModel: model,
65
+ collection: {
66
+ dataSourceKey: 'main',
67
+ filterTargetKey: 'id',
68
+ },
69
+ collectionField: {
70
+ target: 'roles',
71
+ resourceName: 'users.roles',
72
+ collection: sourceCollection,
73
+ },
74
+ record,
75
+ getFormValues: () => ({ id: 1 }),
76
+ getPropertyOptions: () => undefined,
77
+ },
78
+ };
79
+ }
80
+
81
+ describe('PopupSubTable popup context', () => {
82
+ it('passes popup and parent record context to the add-new popup', () => {
83
+ const { context, open } = createContext();
84
+ const handler = getOpenViewHandler(PopupSubTableFieldModel);
85
+
86
+ handler(context, { mode: 'drawer', size: 'medium' });
87
+
88
+ expect(open).toHaveBeenCalledOnce();
89
+ expect(open.mock.calls[0][0].inputArgs).toMatchObject({
90
+ openerUids: ['root-page-uid', 'parent-popup-uid'],
91
+ associationName: 'users.roles',
92
+ sourceId: 1,
93
+ });
94
+ });
95
+
96
+ it('passes popup and parent record context to the edit popup', () => {
97
+ const { context, open } = createContext({ id: 2, name: 'Member' });
98
+ const handler = getOpenViewHandler(PopupSubTableEditActionModel);
99
+
100
+ handler(context, { mode: 'dialog', size: 'medium' });
101
+
102
+ expect(open).toHaveBeenCalledOnce();
103
+ expect(open.mock.calls[0][0].inputArgs).toMatchObject({
104
+ openerUids: ['root-page-uid', 'parent-popup-uid'],
105
+ associationName: 'users.roles',
106
+ sourceId: 1,
107
+ });
108
+ });
109
+
110
+ it('does not expose a parent record reference before the parent record is persisted', () => {
111
+ const { context, open } = createContext(undefined, { nickname: 'Draft user' });
112
+ const handler = getOpenViewHandler(PopupSubTableFieldModel);
113
+
114
+ handler(context, { mode: 'drawer', size: 'medium' });
115
+
116
+ expect(open).toHaveBeenCalledOnce();
117
+ expect(open.mock.calls[0][0].inputArgs).not.toHaveProperty('associationName');
118
+ expect(open.mock.calls[0][0].inputArgs).not.toHaveProperty('sourceId');
119
+ });
120
+ });
@@ -17,6 +17,7 @@ import { capitalize } from 'lodash';
17
17
  import '../../../../base/ActionModel';
18
18
  import { ActionModel, ActionWithoutPermission } from '../../../../base/ActionModelCore';
19
19
  import { SkeletonFallback } from '../../../../../components/SkeletonFallback';
20
+ import { buildOpenerUids } from '../../recordSelectShared';
20
21
  import { bindPopupSubTableBeforeClose } from './popupSubTableBeforeClose';
21
22
 
22
23
  function FieldWithoutPermissionPlaceholder({ targetModel, children }) {
@@ -29,7 +30,7 @@ function FieldWithoutPermissionPlaceholder({ targetModel, children }) {
29
30
  const dataSourcePrefix = `${t(dataSource.displayName || dataSource.key)} > `;
30
31
  const collectionPrefix = collection ? `${t(collection.title) || collection.name || collection.tableName} > ` : '';
31
32
  return `${dataSourcePrefix}${collectionPrefix}${name}`;
32
- }, []);
33
+ }, [collection, dataSource.displayName, dataSource.key, name, t]);
33
34
  const { actionName } = fieldModel.forbidden || {};
34
35
  const messageValue = useMemo(() => {
35
36
  return t(
@@ -39,7 +40,7 @@ function FieldWithoutPermissionPlaceholder({ targetModel, children }) {
39
40
  actionName: t(capitalize(actionName)),
40
41
  },
41
42
  ).replaceAll('&gt;', '>');
42
- }, [nameValue, t]);
43
+ }, [actionName, nameValue, t]);
43
44
  return <Tooltip title={messageValue}>{children}</Tooltip>;
44
45
  }
45
46
 
@@ -347,6 +348,11 @@ PopupSubTableEditActionModel.registerFlow({
347
348
  return undefined;
348
349
  }
349
350
  })();
351
+ const associationName = ctx.collectionField?.resourceName;
352
+ const sourceId = parentItem?.value
353
+ ? ctx.collectionField?.collection?.getFilterByTK?.(parentItem.value)
354
+ : undefined;
355
+ const openerUids = buildOpenerUids(ctx, ctx.inputArgs);
350
356
  ctx.viewer.open({
351
357
  type: openMode,
352
358
  width: sizeToWidthMap[openMode][size],
@@ -357,6 +363,7 @@ PopupSubTableEditActionModel.registerFlow({
357
363
  scene: 'subForm',
358
364
  dataSourceKey: ctx.collection.dataSourceKey,
359
365
  collectionName: ctx.collectionField?.target,
366
+ ...(associationName && sourceId != null ? { associationName, sourceId } : {}),
360
367
  collectionField: ctx.collectionField,
361
368
  record: ctx.record,
362
369
  parentItem,
@@ -364,6 +371,7 @@ PopupSubTableEditActionModel.registerFlow({
364
371
  parentItemResolver: parentItemOptions?.resolveOnServer,
365
372
  itemIndex,
366
373
  itemLength,
374
+ openerUids,
367
375
  },
368
376
  content: () => <EditFormContent model={ctx.model} />,
369
377
  styles: {
@@ -9,6 +9,7 @@
9
9
 
10
10
  import {
11
11
  CollectionField,
12
+ MultiRecordResource,
12
13
  EditableItemModel,
13
14
  tExpr,
14
15
  FlowModel,
@@ -31,7 +32,12 @@ import {
31
32
  createRootItemChain,
32
33
  type ItemChain,
33
34
  } from './itemChain';
34
- import { buildOpenerUids, LabelByField, type AssociationFieldNames } from './recordSelectShared';
35
+ import {
36
+ buildOpenerUids,
37
+ LabelByField,
38
+ type AssociationFieldNames,
39
+ useAssociationValueHydration,
40
+ } from './recordSelectShared';
35
41
 
36
42
  const MULTIPLE_ASSOCIATION_TYPES = ['belongsToMany', 'hasMany', 'belongsToArray'];
37
43
 
@@ -328,6 +334,13 @@ function RecordPickerField(props) {
328
334
  useEffect(() => {
329
335
  ctx.model.selectedRows.value = props.value;
330
336
  }, [ctx.model.selectedRows, props.value]);
337
+ useAssociationValueHydration({
338
+ model: ctx.model,
339
+ value: props.value,
340
+ isMultiple: allowMultiple,
341
+ fieldNames,
342
+ onChange: props.onChange,
343
+ });
331
344
 
332
345
  return (
333
346
  <Select
@@ -400,6 +413,7 @@ function RecordPickerField(props) {
400
413
  }
401
414
 
402
415
  export class RecordPickerFieldModel extends FieldModel {
416
+ declare resource: MultiRecordResource;
403
417
  selectedRows = observable.ref([]);
404
418
  _closeView;
405
419
  selectBlockModel;
@@ -564,6 +578,15 @@ RecordPickerFieldModel.registerFlow({
564
578
  title: tExpr('RecordPicker settings'),
565
579
  sort: 200,
566
580
  steps: {
581
+ init: {
582
+ handler(ctx) {
583
+ const { target, dataSourceKey } = ctx.model.collectionField;
584
+ const resource = ctx.createResource(MultiRecordResource);
585
+ resource.setDataSourceKey(dataSourceKey);
586
+ resource.setResourceName(target);
587
+ ctx.model.resource = resource;
588
+ },
589
+ },
567
590
  fieldNames: {
568
591
  use: 'titleField',
569
592
  },