@nocobase/client-v2 2.2.0-alpha.1 → 2.2.0-alpha.10

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 (334) hide show
  1. package/es/APIClient.d.ts +1 -3
  2. package/es/BaseApplication.d.ts +3 -0
  3. package/es/PluginSettingsManager.d.ts +33 -0
  4. package/es/RouteRepository.d.ts +29 -6
  5. package/es/RouterManager.d.ts +15 -0
  6. package/es/authRedirect.d.ts +1 -0
  7. package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
  8. package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
  9. package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
  10. package/es/components/category-tabs/index.d.ts +9 -0
  11. package/es/components/form/JsonTextArea.d.ts +2 -1
  12. package/es/components/form/ScanInput/useCodeScanner.d.ts +3 -2
  13. package/es/components/form/TypedVariableInput.d.ts +31 -5
  14. package/es/components/form/VariableJsonTextArea.d.ts +19 -0
  15. package/es/components/form/filter/CollectionFilter.d.ts +2 -0
  16. package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
  17. package/es/components/form/filter/CollectionFilterPanel.d.ts +2 -0
  18. package/es/components/form/filter/index.d.ts +2 -0
  19. package/es/components/form/filter/useFilterActionProps.d.ts +2 -0
  20. package/es/components/form/index.d.ts +1 -0
  21. package/es/components/index.d.ts +2 -0
  22. package/es/entry-actions/EntryActionManager.d.ts +24 -0
  23. package/es/entry-actions/index.d.ts +9 -0
  24. package/es/flow/actions/afterSuccess.d.ts +8 -1
  25. package/es/flow/actions/index.d.ts +2 -2
  26. package/es/flow/actions/linkageRules.d.ts +2 -0
  27. package/es/flow/admin-shell/BaseLayoutModel.d.ts +6 -0
  28. package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +7 -0
  29. package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +2 -0
  30. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.d.ts +2 -1
  31. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +3 -0
  32. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.d.ts +3 -2
  33. package/es/flow/admin-shell/admin-layout/AdminLayoutSlotModels.d.ts +5 -0
  34. package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +2 -1
  35. package/es/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.d.ts +24 -0
  36. package/es/flow/admin-shell/admin-layout/index.d.ts +1 -0
  37. package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +6 -0
  38. package/es/flow/admin-shell/admin-layout/useApplications.d.ts +7 -2
  39. package/es/flow/components/FieldAssignExactDatePicker.d.ts +1 -0
  40. package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
  41. package/es/flow/components/FieldAssignValueInput.d.ts +9 -0
  42. package/es/flow/components/FlowRoute.d.ts +2 -2
  43. package/es/flow/components/RunJSValueEditor.d.ts +1 -0
  44. package/es/flow/components/filter/FilterGroup.d.ts +1 -0
  45. package/es/flow/components/filter/VariableFilterItem.d.ts +6 -1
  46. package/es/flow/index.d.ts +1 -0
  47. package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
  48. package/es/flow/models/base/ActionModelCore.d.ts +2 -0
  49. package/es/flow/models/base/BlockGridModel.d.ts +3 -0
  50. package/es/flow/models/base/GridModel.d.ts +1 -1
  51. package/es/flow/models/base/PageModel/PageModel.d.ts +12 -0
  52. package/es/flow/models/base/PageModel/PageModelTabBar.d.ts +22 -0
  53. package/es/flow/models/base/PageModel/PageTabModel.d.ts +12 -0
  54. package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +5 -0
  55. package/es/flow/models/blocks/form/FormBlockModel.d.ts +4 -0
  56. package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
  57. package/es/flow/models/blocks/form/submitHandler.d.ts +1 -1
  58. package/es/flow/models/blocks/form/value-runtime/rules.d.ts +21 -0
  59. package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +30 -0
  60. package/es/flow/models/blocks/form/value-runtime/types.d.ts +2 -2
  61. package/es/flow/models/blocks/js-block/JSBlock.d.ts +1 -0
  62. package/es/flow/models/blocks/table/TableActionsColumnModel.d.ts +1 -0
  63. package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
  64. package/es/flow/models/blocks/table/TableColumnModel.d.ts +1 -0
  65. package/es/flow/models/blocks/table/dragSort/dragSortHooks.d.ts +1 -1
  66. package/es/flow/models/blocks/table/dragSort/dragSortSettings.d.ts +2 -1
  67. package/es/flow/models/blocks/table/dragSort/dragSortUtils.d.ts +6 -4
  68. package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +2 -0
  69. package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +6 -0
  70. package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
  71. package/es/flow/resolveViewParamsToViewList.d.ts +1 -1
  72. package/es/flow/routeTransientInputArgs.d.ts +14 -0
  73. package/es/flow/utils/dataScopeRowSnapshot.d.ts +59 -0
  74. package/es/flow/utils/dateTimeDisplayProps.d.ts +49 -0
  75. package/es/flow/utils/formValueDeps.d.ts +32 -0
  76. package/es/flow-compat/FieldValidation.d.ts +2 -1
  77. package/es/flow-compat/fieldValidationConstants.d.ts +1 -1
  78. package/es/flow-compat/routeTypes.d.ts +1 -0
  79. package/es/index.d.ts +8 -1
  80. package/es/index.mjs +367 -181
  81. package/es/utils/getRouteRuntimeVersion.d.ts +23 -0
  82. package/es/utils/index.d.ts +1 -0
  83. package/es/utils/markdownSanitize.d.ts +11 -0
  84. package/lib/index.js +367 -181
  85. package/lib/locale/languageCodes.js +3 -1
  86. package/package.json +9 -7
  87. package/src/APIClient.ts +1 -10
  88. package/src/Application.tsx +4 -0
  89. package/src/BaseApplication.tsx +13 -6
  90. package/src/PluginSettingsManager.ts +53 -0
  91. package/src/RouteRepository.ts +151 -24
  92. package/src/RouterManager.tsx +142 -1
  93. package/src/__tests__/PluginSettingsManager.test.ts +13 -0
  94. package/src/__tests__/RouteRepository.test.ts +239 -0
  95. package/src/__tests__/RouterManager.test.ts +125 -0
  96. package/src/__tests__/app.test.tsx +73 -0
  97. package/src/__tests__/authRedirect.test.ts +17 -0
  98. package/src/__tests__/browserChecker.test.ts +61 -0
  99. package/src/__tests__/exports.test.ts +16 -0
  100. package/src/__tests__/getRouteRuntimeVersion.test.ts +68 -0
  101. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +234 -2
  102. package/src/__tests__/plugin-manager.test.tsx +44 -2
  103. package/src/__tests__/settings-center.test.tsx +40 -1
  104. package/src/authRedirect.ts +38 -0
  105. package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
  106. package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
  107. package/src/collection-manager/field-configure.ts +1 -1
  108. package/src/collection-manager/field-validation.ts +1 -1
  109. package/src/collection-manager/interfaces/id.ts +1 -1
  110. package/src/collection-manager/interfaces/m2m.tsx +2 -2
  111. package/src/collection-manager/interfaces/m2o.tsx +2 -2
  112. package/src/collection-manager/interfaces/nanoid.ts +1 -1
  113. package/src/collection-manager/interfaces/o2m.tsx +2 -2
  114. package/src/collection-manager/interfaces/obo.tsx +2 -2
  115. package/src/collection-manager/interfaces/oho.tsx +2 -2
  116. package/src/collection-manager/interfaces/properties/index.ts +2 -2
  117. package/src/collection-manager/interfaces/uuid.ts +1 -1
  118. package/src/components/AppComponents.tsx +19 -3
  119. package/src/components/README.md +7 -1
  120. package/src/components/README.zh-CN.md +6 -1
  121. package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
  122. package/src/components/category-tabs/index.ts +10 -0
  123. package/src/components/form/JsonTextArea.tsx +25 -11
  124. package/src/components/form/ScanInput/CodeScanner.tsx +79 -35
  125. package/src/components/form/ScanInput/__tests__/CodeScanner.test.tsx +101 -0
  126. package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +284 -11
  127. package/src/components/form/ScanInput/useCodeScanner.ts +332 -24
  128. package/src/components/form/TypedVariableInput.tsx +452 -101
  129. package/src/components/form/VariableJsonTextArea.tsx +175 -0
  130. package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
  131. package/src/components/form/__tests__/TypedVariableInput.test.tsx +364 -12
  132. package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
  133. package/src/components/form/filter/CollectionFilter.tsx +4 -0
  134. package/src/components/form/filter/CollectionFilterItem.tsx +32 -7
  135. package/src/components/form/filter/CollectionFilterPanel.tsx +4 -0
  136. package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +38 -0
  137. package/src/components/form/filter/__tests__/useFilterActionProps.test.tsx +95 -0
  138. package/src/components/form/filter/index.ts +2 -0
  139. package/src/components/form/filter/useFilterActionProps.ts +37 -6
  140. package/src/components/form/index.tsx +1 -0
  141. package/src/components/index.ts +2 -0
  142. package/src/entry-actions/EntryActionManager.ts +76 -0
  143. package/src/entry-actions/index.ts +10 -0
  144. package/src/flow/FlowPage.tsx +38 -3
  145. package/src/flow/__tests__/FlowPage.test.tsx +201 -27
  146. package/src/flow/__tests__/FlowRoute.test.tsx +1181 -80
  147. package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
  148. package/src/flow/__tests__/getKey.test.ts +7 -0
  149. package/src/flow/__tests__/resolveViewParamsToViewList.test.ts +34 -0
  150. package/src/flow/actions/__tests__/actionLinkageRules.forkProps.test.ts +115 -0
  151. package/src/flow/actions/__tests__/afterSuccess.test.ts +73 -0
  152. package/src/flow/actions/__tests__/dataScopeFilter.test.ts +120 -0
  153. package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +1124 -0
  154. package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +150 -1
  155. package/src/flow/actions/__tests__/linkageAssignField.legacy.test.ts +135 -0
  156. package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +19 -5
  157. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +92 -0
  158. package/src/flow/actions/__tests__/linkageRules.tab.test.ts +171 -0
  159. package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
  160. package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +164 -0
  161. package/src/flow/actions/__tests__/subFormFieldLinkageRules.inputArgs.test.ts +2 -2
  162. package/src/flow/actions/afterSuccess.tsx +142 -3
  163. package/src/flow/actions/customVariable.tsx +1 -2
  164. package/src/flow/actions/dataScopeFilter.ts +20 -1
  165. package/src/flow/actions/dateTimeFormat.tsx +42 -28
  166. package/src/flow/actions/index.ts +3 -1
  167. package/src/flow/actions/linkageRules.tsx +209 -47
  168. package/src/flow/actions/linkageRulesFormValueRefresh.ts +22 -130
  169. package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
  170. package/src/flow/actions/openView.tsx +59 -5
  171. package/src/flow/actions/runjs.tsx +2 -14
  172. package/src/flow/actions/validation.tsx +62 -30
  173. package/src/flow/admin-shell/BaseLayoutModel.tsx +149 -3
  174. package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +187 -42
  175. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +1020 -110
  176. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +74 -14
  177. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +486 -0
  178. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +64 -8
  179. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.ts +5 -3
  180. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +61 -9
  181. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +71 -8
  182. package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +1 -1
  183. package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +23 -9
  184. package/src/flow/admin-shell/admin-layout/AppListRender.tsx +13 -2
  185. package/src/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.tsx +289 -0
  186. package/src/flow/admin-shell/admin-layout/HelpLite.tsx +1 -3
  187. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +435 -0
  188. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +99 -0
  189. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +518 -2
  190. package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +214 -2
  191. package/src/flow/admin-shell/admin-layout/index.ts +1 -0
  192. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +20 -0
  193. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +33 -5
  194. package/src/flow/admin-shell/admin-layout/useApplications.tsx +81 -12
  195. package/src/flow/common/Markdown/Display.tsx +14 -3
  196. package/src/flow/common/Markdown/Edit.tsx +19 -7
  197. package/src/flow/common/Markdown/style.ts +4 -0
  198. package/src/flow/components/BlockItemCard.tsx +2 -2
  199. package/src/flow/components/DefaultValue.tsx +1 -2
  200. package/src/flow/components/DynamicFlowsIcon.tsx +447 -126
  201. package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
  202. package/src/flow/components/FieldAssignRulesEditor.tsx +128 -21
  203. package/src/flow/components/FieldAssignValueInput.tsx +91 -20
  204. package/src/flow/components/FlowRoute.tsx +256 -53
  205. package/src/flow/components/RunJSValueEditor.tsx +9 -1
  206. package/src/flow/components/__tests__/DynamicFlowsIcon.test.tsx +148 -2
  207. package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +508 -4
  208. package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +216 -0
  209. package/src/flow/components/__tests__/fieldAssignOptions.test.ts +151 -3
  210. package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +2 -17
  211. package/src/flow/components/code-editor/hooks/useCodeRunner.ts +2 -14
  212. package/src/flow/components/code-editor/runjsDiagnostics.ts +8 -45
  213. package/src/flow/components/fieldAssignOptions.ts +155 -27
  214. package/src/flow/components/filter/FilterGroup.tsx +33 -5
  215. package/src/flow/components/filter/VariableFilterItem.tsx +158 -10
  216. package/src/flow/components/filter/__tests__/FilterGroup.test.tsx +45 -0
  217. package/src/flow/components/filter/__tests__/VariableFilterItem.leftMetaTree.test.tsx +9 -0
  218. package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +70 -1
  219. package/src/flow/flows/editMarkdownFlow.tsx +1 -1
  220. package/src/flow/getViewDiffAndUpdateHidden.tsx +1 -0
  221. package/src/flow/index.ts +4 -1
  222. package/src/flow/internal/components/Markdown/util.ts +2 -1
  223. package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
  224. package/src/flow/internal/utils/operatorSchemaHelper.ts +15 -1
  225. package/src/flow/models/actions/JSActionModel.tsx +2 -7
  226. package/src/flow/models/actions/JSCollectionActionModel.tsx +2 -7
  227. package/src/flow/models/actions/JSItemActionModel.tsx +2 -7
  228. package/src/flow/models/actions/JSRecordActionModel.tsx +2 -7
  229. package/src/flow/models/base/ActionModel.tsx +21 -8
  230. package/src/flow/models/base/ActionModelCore.tsx +11 -4
  231. package/src/flow/models/base/BlockGridModel.tsx +26 -0
  232. package/src/flow/models/base/CollectionBlockModel.tsx +6 -2
  233. package/src/flow/models/base/GridModel.tsx +38 -7
  234. package/src/flow/models/base/PageModel/PageModel.tsx +387 -50
  235. package/src/flow/models/base/PageModel/PageModelTabBar.tsx +114 -0
  236. package/src/flow/models/base/PageModel/PageTabModel.tsx +249 -18
  237. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +817 -13
  238. package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.module-isolation.test.tsx +130 -0
  239. package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.test.tsx +118 -0
  240. package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +1075 -7
  241. package/src/flow/models/base/__tests__/ActionModel.test.ts +83 -0
  242. package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +37 -0
  243. package/src/flow/models/base/__tests__/BlockGridModel.selectSceneActivation.test.ts +124 -0
  244. package/src/flow/models/base/__tests__/CollectionBlockModel.addAppends.test.ts +41 -0
  245. package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +239 -1
  246. package/src/flow/models/base/__tests__/transformRowsToSingleColumn.test.ts +48 -0
  247. package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +59 -6
  248. package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +27 -12
  249. package/src/flow/models/blocks/filter-form/FilterFormJSActionModel.tsx +2 -7
  250. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +36 -0
  251. package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +143 -4
  252. package/src/flow/models/blocks/filter-form/fields/FilterFormCustomFieldModel.tsx +1 -1
  253. package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/customizeFilterRender.test.tsx +56 -1
  254. package/src/flow/models/blocks/form/EditFormModel.tsx +1 -0
  255. package/src/flow/models/blocks/form/FormActionModel.tsx +1 -1
  256. package/src/flow/models/blocks/form/FormBlockModel.tsx +7 -0
  257. package/src/flow/models/blocks/form/JSFormActionModel.tsx +2 -7
  258. package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
  259. package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
  260. package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
  261. package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +54 -1
  262. package/src/flow/models/blocks/form/submitHandler.ts +17 -3
  263. package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +940 -75
  264. package/src/flow/models/blocks/form/value-runtime/rules.ts +445 -13
  265. package/src/flow/models/blocks/form/value-runtime/runtime.ts +348 -13
  266. package/src/flow/models/blocks/form/value-runtime/types.ts +2 -2
  267. package/src/flow/models/blocks/js-block/JSBlock.tsx +225 -9
  268. package/src/flow/models/blocks/js-block/__tests__/JSBlockModel.test.tsx +150 -0
  269. package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
  270. package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +38 -14
  271. package/src/flow/models/blocks/table/TableBlockModel.tsx +42 -19
  272. package/src/flow/models/blocks/table/TableColumnModel.tsx +72 -6
  273. package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +111 -1
  274. package/src/flow/models/blocks/table/__tests__/TableBlockModel.dragSort.test.ts +127 -0
  275. package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
  276. package/src/flow/models/blocks/table/__tests__/TableBlockModel.quickEditRefresh.test.ts +12 -0
  277. package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +1 -0
  278. package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +243 -1
  279. package/src/flow/models/blocks/table/dragSort/dragSortHooks.tsx +28 -17
  280. package/src/flow/models/blocks/table/dragSort/dragSortSettings.ts +13 -6
  281. package/src/flow/models/blocks/table/dragSort/dragSortUtils.ts +15 -2
  282. package/src/flow/models/fields/AssociationFieldModel/AssociationFieldModel.tsx +1 -1
  283. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +31 -7
  284. package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +61 -17
  285. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +141 -19
  286. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +13 -1
  287. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +197 -0
  288. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableFieldModel.reset.test.ts +180 -0
  289. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -4
  290. package/src/flow/models/fields/AssociationFieldModel/__tests__/AssociationFieldModel.updateAssociation.test.ts +72 -0
  291. package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +23 -0
  292. package/src/flow/models/fields/ClickableFieldModel.tsx +5 -0
  293. package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +149 -0
  294. package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +149 -113
  295. package/src/flow/models/fields/DisplayDateTimeFieldModel.tsx +29 -1
  296. package/src/flow/models/fields/InputFieldModel.tsx +40 -2
  297. package/src/flow/models/fields/JSEditableFieldModel.tsx +2 -11
  298. package/src/flow/models/fields/JSFieldModel.tsx +2 -7
  299. package/src/flow/models/fields/JSItemModel.tsx +2 -14
  300. package/src/flow/models/fields/SelectFieldModel.tsx +6 -4
  301. package/src/flow/models/fields/TextareaFieldModel.tsx +72 -3
  302. package/src/flow/models/fields/VariableFieldFormModel.tsx +3 -3
  303. package/src/flow/models/fields/__tests__/DisplayDateTimeFieldModel.test.tsx +96 -0
  304. package/src/flow/models/fields/__tests__/InputFieldModel.test.tsx +117 -1
  305. package/src/flow/models/fields/__tests__/SelectFieldModel.test.tsx +43 -0
  306. package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +131 -0
  307. package/src/flow/models/fields/__tests__/VariableFieldFormModel.test.tsx +51 -0
  308. package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +20 -10
  309. package/src/flow/models/fields/mobile-components/MobileSelect.tsx +24 -12
  310. package/src/flow/models/topbar/TopbarActionModel.tsx +168 -10
  311. package/src/flow/resolveViewParamsToViewList.tsx +11 -3
  312. package/src/flow/routeTransientInputArgs.ts +27 -0
  313. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +300 -0
  314. package/src/flow/utils/dataScopeFormValueClear.ts +219 -81
  315. package/src/flow/utils/dataScopeRowSnapshot.ts +616 -0
  316. package/src/flow/utils/dateTimeDisplayProps.ts +135 -0
  317. package/src/flow/utils/formValueDeps.ts +170 -0
  318. package/src/flow-compat/FieldValidation.tsx +122 -60
  319. package/src/flow-compat/Popover.tsx +43 -4
  320. package/src/flow-compat/__tests__/Popover.test.tsx +34 -0
  321. package/src/flow-compat/fieldValidationConstants.ts +1 -1
  322. package/src/flow-compat/routeTypes.ts +1 -0
  323. package/src/index.ts +14 -1
  324. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
  325. package/src/locale/languageCodes.ts +3 -1
  326. package/src/nocobase-buildin-plugin/index.tsx +63 -15
  327. package/src/settings-center/AdminSettingsLayout.tsx +10 -2
  328. package/src/settings-center/SystemSettingsPage.tsx +1 -2
  329. package/src/settings-center/plugin-manager/PluginCard.tsx +2 -2
  330. package/src/settings-center/plugin-manager/index.tsx +3 -0
  331. package/src/settings-center/utils.tsx +0 -6
  332. package/src/utils/getRouteRuntimeVersion.ts +185 -0
  333. package/src/utils/index.tsx +1 -0
  334. package/src/utils/markdownSanitize.ts +88 -0
@@ -140,4 +140,49 @@ describe('FilterGroup closeIcon', () => {
140
140
  expect(onChange).toHaveBeenCalledTimes(1);
141
141
  expect(value.items[0].items).toHaveLength(1);
142
142
  });
143
+
144
+ it('disables close and add actions when the filter group is disabled', () => {
145
+ const value = {
146
+ logic: '$and',
147
+ items: [
148
+ {
149
+ path: 'name',
150
+ operator: 'eq',
151
+ value: 'test',
152
+ },
153
+ ],
154
+ };
155
+ const onRemove = vi.fn();
156
+ const onChange = vi.fn();
157
+
158
+ renderWithProviders(
159
+ <FilterGroup
160
+ value={value}
161
+ FilterItem={DummyFilterItem}
162
+ showBorder
163
+ onRemove={onRemove}
164
+ onChange={onChange}
165
+ disabled
166
+ />,
167
+ );
168
+
169
+ const closeButtons = screen
170
+ .getAllByLabelText('icon-close')
171
+ .map((element) => element.closest('button'))
172
+ .filter(Boolean) as HTMLButtonElement[];
173
+ expect(closeButtons).toHaveLength(2);
174
+ expect(closeButtons[0]).toBeDisabled();
175
+ expect(closeButtons[1]).toBeDisabled();
176
+ expect(screen.getByText('Add condition').closest('button')).toBeDisabled();
177
+ expect(screen.getByText('Add condition group').closest('button')).toBeDisabled();
178
+
179
+ fireEvent.click(closeButtons[0]);
180
+ fireEvent.click(closeButtons[1]);
181
+ fireEvent.click(screen.getByText('Add condition').closest('button') as HTMLButtonElement);
182
+ fireEvent.click(screen.getByText('Add condition group').closest('button') as HTMLButtonElement);
183
+
184
+ expect(onRemove).not.toHaveBeenCalled();
185
+ expect(onChange).not.toHaveBeenCalled();
186
+ expect(value.items).toHaveLength(1);
187
+ });
143
188
  });
@@ -193,4 +193,13 @@ describe('VariableFilterItem with leftMetaTree', () => {
193
193
  fireEvent.click(screen.getByTestId('variable-input'));
194
194
  expect(value.path).toBe('title');
195
195
  });
196
+
197
+ it('restores the saved left meta so the operator select shows its label on reopen', async () => {
198
+ const value = { path: 'title', operator: '$eq', value: 'x' } as any;
199
+ const model = createModelWithCollection();
200
+
201
+ render(<VariableFilterItem value={value} model={model} rightAsVariable={false} />);
202
+
203
+ expect(await screen.findByText('Equals')).toBeInTheDocument();
204
+ });
196
205
  });
@@ -11,7 +11,7 @@ import React from 'react';
11
11
  import { describe, it, expect, vi, beforeEach } from 'vitest';
12
12
  import { render, screen, fireEvent, waitFor } from '@testing-library/react';
13
13
  import { VariableFilterItem, VariableFilterItemValue } from '../VariableFilterItem';
14
- import { FlowEngine, FlowModel } from '@nocobase/flow-engine';
14
+ import { FlowEngine, FlowModel, type Converters, type MetaTreeNode } from '@nocobase/flow-engine';
15
15
  import { observable } from '@formily/reactive';
16
16
  import { createMockFlowApp, TestCollectionFieldInterface } from '../../../__tests__/helpers/mockFlowApp';
17
17
 
@@ -158,6 +158,75 @@ describe('VariableFilterItem', () => {
158
158
  expect(value.value).toBe('abc');
159
159
  });
160
160
 
161
+ it('passes disabled through to the left selector, operator select, and right variable input', async () => {
162
+ const value: VariableFilterItemValue = { path: '', operator: '', value: '' };
163
+ const model = CreateModel();
164
+
165
+ render(<VariableFilterItem value={value} model={model} rightAsVariable disabled />);
166
+
167
+ const leftVariableInputProps = (globalThis as any).__LAST_VARIABLE_INPUT_PROPS__;
168
+ expect(leftVariableInputProps.disabled).toBe(true);
169
+
170
+ fireEvent.click(screen.getAllByTestId('variable-input')[0]);
171
+
172
+ const variableInputs = screen.getAllByTestId('variable-input');
173
+ expect(variableInputs.length).toBeGreaterThanOrEqual(2);
174
+ const rightVariableInputProps = (globalThis as any).__LAST_VARIABLE_INPUT_PROPS__;
175
+ expect(rightVariableInputProps.disabled).toBe(true);
176
+
177
+ const operatorSelect = document.body.querySelector('.ant-select') as HTMLDivElement | null;
178
+ expect(operatorSelect).not.toBeNull();
179
+ expect(operatorSelect).toHaveClass('ant-select-disabled');
180
+ });
181
+
182
+ it('composes custom right variable converters with constant, null, and core fallbacks', () => {
183
+ const value: VariableFilterItemValue = {
184
+ path: 'name',
185
+ operator: '$eq',
186
+ value: '{{$context.data.id}}',
187
+ };
188
+ const model = CreateModel();
189
+ const rightVariableConverters: Pick<Converters, 'resolvePathFromValue' | 'resolveValueFromPath'> = {
190
+ resolvePathFromValue: (input) => (input === '{{$context.data.id}}' ? ['$context', 'data', 'id'] : undefined),
191
+ resolveValueFromPath: (metaTreeNode) =>
192
+ metaTreeNode.paths?.[0] === '$context' ? `{{${metaTreeNode.paths.join('.')}}}` : undefined,
193
+ };
194
+
195
+ render(
196
+ <VariableFilterItem
197
+ value={value}
198
+ model={model}
199
+ rightAsVariable
200
+ rightVariableConverters={rightVariableConverters}
201
+ />,
202
+ );
203
+
204
+ const variableInputProps = (
205
+ globalThis as {
206
+ __LAST_VARIABLE_INPUT_PROPS__?: {
207
+ converters?: Converters;
208
+ value?: unknown;
209
+ };
210
+ }
211
+ ).__LAST_VARIABLE_INPUT_PROPS__;
212
+ const converters = variableInputProps?.converters;
213
+ const constantNode: MetaTreeNode = { name: 'constant', title: 'Constant', type: 'string', paths: ['constant'] };
214
+ const nullNode: MetaTreeNode = { name: 'null', title: 'Null', type: 'object', paths: ['null'] };
215
+ const workflowNode: MetaTreeNode = {
216
+ name: 'id',
217
+ title: 'ID',
218
+ type: 'number',
219
+ paths: ['$context', 'data', 'id'],
220
+ };
221
+
222
+ expect(variableInputProps?.value).toBe('{{$context.data.id}}');
223
+ expect(converters?.resolvePathFromValue?.('{{$context.data.id}}')).toEqual(['$context', 'data', 'id']);
224
+ expect(converters?.resolvePathFromValue?.('{{ ctx.$context.data.id }}')).toEqual(['$context', 'data', 'id']);
225
+ expect(converters?.resolveValueFromPath?.(constantNode)).toBe('');
226
+ expect(converters?.resolveValueFromPath?.(nullNode)).toBeNull();
227
+ expect(converters?.resolveValueFromPath?.(workflowNode)).toBe('{{$context.data.id}}');
228
+ });
229
+
161
230
  it('uses scoped context dataSourceManager when app dataSourceManager has no field interface manager', async () => {
162
231
  const value = observable({ path: '', operator: '', value: '' }) as any;
163
232
  const model = CreateModel();
@@ -70,7 +70,7 @@ export const editMarkdownFlow = defineFlow<FlowModel>({
70
70
  {t('References')}:
71
71
  </span>
72
72
  <a
73
- href={`https://v2.docs.nocobase.com/interface-builder/blocks/other-blocks/markdown`}
73
+ href={`https://docs.nocobase.com/interface-builder/blocks/other-blocks/markdown`}
74
74
  target="_blank"
75
75
  rel="noreferrer"
76
76
  >
@@ -33,6 +33,7 @@ export const getKey = (viewItem: ViewItem) => {
33
33
  stableStringify(params.viewUid),
34
34
  stableStringify(params.sourceId),
35
35
  stableStringify(params.filterByTk),
36
+ stableStringify(params.openViewRouteState),
36
37
  String(index),
37
38
  ];
38
39
 
package/src/flow/index.ts CHANGED
@@ -22,12 +22,14 @@ import { Markdown } from './common/Markdown/Markdown';
22
22
  import { LiquidEngine } from './common/Liquid';
23
23
  import type { PreviewRunJSResult } from './components/code-editor/runjsDiagnostics';
24
24
  import { TextAreaWithContextSelector } from './components/TextAreaWithContextSelector';
25
+ import { registerDeviceTypeContext } from './internal/registerDeviceTypeContext';
25
26
 
26
27
  export class PluginFlowEngine<TApp extends BaseApplication<any> = BaseApplication<any>> extends Plugin<
27
28
  PluginOptions<any>,
28
29
  TApp
29
30
  > {
30
31
  async load() {
32
+ registerDeviceTypeContext(this.flowEngine);
31
33
  this.app.addComponents({ FlowRoute });
32
34
  this.app.flowEngine.setModelRepository(new FlowModelRepository(this.app));
33
35
  const filteredModels = Object.fromEntries(
@@ -49,7 +51,7 @@ export class PluginFlowEngine<TApp extends BaseApplication<any> = BaseApplicatio
49
51
  key: 'dynamic-flows-editor',
50
52
  component: DynamicFlowsIcon,
51
53
  visible(model) {
52
- return model.getEvents().size > 0;
54
+ return model.getEvents().size > 0 || model.flowEngine.flowSettings.hasDynamicFlowSourceProvider(model);
53
55
  },
54
56
  sort: 0,
55
57
  });
@@ -98,6 +100,7 @@ export * from './FlowPage';
98
100
  export * from './models';
99
101
  export * from './utils';
100
102
  export * from './actions';
103
+ export { FieldAssignValueInput } from './components/FieldAssignValueInput';
101
104
  export * from './system-settings';
102
105
  export * from './admin-shell/admin-layout';
103
106
  export * from './admin-shell/BaseLayoutModel';
@@ -9,13 +9,14 @@
9
9
 
10
10
  import _ from 'lodash';
11
11
  import { useEffect, useState } from 'react';
12
+ import { stripMarkdownIframes } from '../../../../utils/markdownSanitize';
12
13
 
13
14
  export const parseMarkdown = _.memoize(async (text: string) => {
14
15
  if (!text) {
15
16
  return text;
16
17
  }
17
18
  const m = await import('./md');
18
- return m.default.render(text);
19
+ return stripMarkdownIframes(m.default.render(text));
19
20
  });
20
21
 
21
22
  export function useParseMarkdown(text: string) {
@@ -0,0 +1,39 @@
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 type { FlowEngine } from '@nocobase/flow-engine';
11
+ import { deviceType } from 'react-device-detect';
12
+
13
+ export const registerDeviceTypeContext = (flowEngine: FlowEngine) => {
14
+ flowEngine.context.defineProperty('deviceType', {
15
+ get: () => (deviceType === 'browser' ? 'computer' : deviceType),
16
+ cache: false,
17
+ meta: {
18
+ type: 'string',
19
+ title: flowEngine.translate('Current device type'),
20
+ interface: 'select',
21
+ uiSchema: {
22
+ enum: [
23
+ { label: flowEngine.translate('Computer'), value: 'computer' },
24
+ { label: flowEngine.translate('Mobile'), value: 'mobile' },
25
+ { label: flowEngine.translate('Tablet'), value: 'tablet' },
26
+ { label: flowEngine.translate('SmartTv'), value: 'smarttv' },
27
+ { label: flowEngine.translate('Console'), value: 'console' },
28
+ { label: flowEngine.translate('Wearable'), value: 'wearable' },
29
+ { label: flowEngine.translate('Embedded'), value: 'embedded' },
30
+ ],
31
+ 'x-component': 'Select',
32
+ },
33
+ },
34
+ info: {
35
+ description: 'Current device type (computer/mobile/tablet/...).',
36
+ detail: 'string',
37
+ },
38
+ });
39
+ };
@@ -9,6 +9,7 @@
9
9
 
10
10
  import React, { type ComponentType, type CSSProperties, type ReactNode } from 'react';
11
11
  import { DateFilterDynamicComponent } from '../../models/blocks/filter-form/fields/date-time/components/DateFilterDynamicComponent';
12
+ import { translateOptions } from './enumOptionsUtils';
12
13
 
13
14
  type OperatorMeta = {
14
15
  value?: string;
@@ -26,6 +27,7 @@ type OperatorComponentFieldModel = {
26
27
  props?: Record<string, unknown>;
27
28
  render?: () => ReactNode;
28
29
  __originalRender?: () => ReactNode;
30
+ translate?: (text: string) => string;
29
31
  };
30
32
 
31
33
  type OperatorComponentRenderOptions = {
@@ -93,6 +95,17 @@ export function restoreOperatorComponentRender(fieldModel?: unknown) {
93
95
  return true;
94
96
  }
95
97
 
98
+ function translateComponentOptions(props: Record<string, unknown>, translate: ((text: string) => string) | undefined) {
99
+ if (!Array.isArray(props.options) || typeof translate !== 'function') {
100
+ return props;
101
+ }
102
+
103
+ return {
104
+ ...props,
105
+ options: translateOptions(props.options, translate),
106
+ };
107
+ }
108
+
96
109
  export function applyOperatorComponentRender({
97
110
  app,
98
111
  fieldModel,
@@ -118,8 +131,9 @@ export function applyOperatorComponentRender({
118
131
  mutableFieldModel.render = () => {
119
132
  const fieldProps = mutableFieldModel.props || {};
120
133
  const fieldStyle = pickOperatorStyle(fieldProps.style);
121
- const componentProps =
134
+ const mergedProps =
122
135
  propsPriority === 'operator' ? { ...fieldProps, ...operatorProps } : { ...operatorProps, ...fieldProps };
136
+ const componentProps = translateComponentOptions(mergedProps, mutableFieldModel.translate);
123
137
  const componentStyle =
124
138
  propsPriority === 'operator' ? { ...fieldStyle, ...operatorStyle } : { ...operatorStyle, ...fieldStyle };
125
139
  const mergedStyle = { ...(style || {}), ...componentStyle };
@@ -7,7 +7,7 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- import { createSafeDocument, createSafeWindow, createSafeNavigator, tExpr } from '@nocobase/flow-engine';
10
+ import { tExpr } from '@nocobase/flow-engine';
11
11
  import type { ButtonProps } from 'antd/es/button';
12
12
  import { CodeEditor } from '../../components/code-editor';
13
13
  import { ActionModel } from '../base';
@@ -72,12 +72,7 @@ ctx.message.info('Hello JS action.');
72
72
  },
73
73
  async handler(ctx, params) {
74
74
  const { code, version } = resolveRunJsParams(ctx, params);
75
- const navigator = createSafeNavigator();
76
- await ctx.runjs(
77
- code,
78
- { window: createSafeWindow({ navigator }), document: createSafeDocument(), navigator },
79
- { version },
80
- );
75
+ await ctx.runjs(code, undefined, { version });
81
76
  },
82
77
  },
83
78
  },
@@ -7,7 +7,7 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- import { createSafeDocument, createSafeWindow, createSafeNavigator, tExpr } from '@nocobase/flow-engine';
10
+ import { tExpr } from '@nocobase/flow-engine';
11
11
  import type { ButtonProps } from 'antd/es/button';
12
12
  import { CodeEditor } from '../../components/code-editor';
13
13
  import { ActionModel, ActionSceneEnum } from '../base';
@@ -78,12 +78,7 @@ if (!rows.length) {
78
78
  },
79
79
  async handler(ctx, params) {
80
80
  const { code, version } = resolveRunJsParams(ctx, params);
81
- const navigator = createSafeNavigator();
82
- await ctx.runjs(
83
- code,
84
- { window: createSafeWindow({ navigator }), document: createSafeDocument(), navigator },
85
- { version },
86
- );
81
+ await ctx.runjs(code, undefined, { version });
87
82
  },
88
83
  },
89
84
  },
@@ -7,7 +7,7 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- import { ElementProxy, createSafeDocument, createSafeNavigator, createSafeWindow, tExpr } from '@nocobase/flow-engine';
10
+ import { ElementProxy, tExpr } from '@nocobase/flow-engine';
11
11
  import React from 'react';
12
12
  import { CodeEditor } from '../../components/code-editor';
13
13
  import { ActionModel, ActionSceneEnum, CollectionActionGroupModel, RecordActionGroupModel } from '../base';
@@ -137,12 +137,7 @@ JSItemActionModel.registerFlow({
137
137
  ctx.defineProperty('element', {
138
138
  get: () => new ElementProxy(element),
139
139
  });
140
- const navigator = createSafeNavigator();
141
- await ctx.runjs(
142
- code,
143
- { window: createSafeWindow({ navigator }), document: createSafeDocument(), navigator },
144
- { version },
145
- );
140
+ await ctx.runjs(code, undefined, { version });
146
141
  });
147
142
  },
148
143
  },
@@ -7,7 +7,7 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- import { createSafeDocument, createSafeWindow, createSafeNavigator, tExpr } from '@nocobase/flow-engine';
10
+ import { tExpr } from '@nocobase/flow-engine';
11
11
  import type { ButtonProps } from 'antd/es/button';
12
12
  import { CodeEditor } from '../../components/code-editor';
13
13
  import { ActionSceneEnum, RecordActionModel } from '../base';
@@ -78,12 +78,7 @@ if (!ctx.record) {
78
78
  },
79
79
  async handler(ctx, params) {
80
80
  const { code, version } = resolveRunJsParams(ctx, params);
81
- const navigator = createSafeNavigator();
82
- await ctx.runjs(
83
- code,
84
- { window: createSafeWindow({ navigator }), document: createSafeDocument(), navigator },
85
- { version },
86
- );
81
+ await ctx.runjs(code, undefined, { version });
87
82
  },
88
83
  },
89
84
  },
@@ -18,6 +18,17 @@ import { commonConditionHandler, ConditionBuilder } from '../../components/Condi
18
18
  export { ActionModel, ActionSceneEnum, ActionWithoutPermission, type ActionSceneType } from './ActionModelCore';
19
19
  import { ActionModel } from './ActionModelCore';
20
20
 
21
+ function getMobileDefaultProps(ctx) {
22
+ const defaultProps = ctx.model.defaultProps || {};
23
+ if (ctx.isMobileLayout && defaultProps.icon && ctx.model.enableEditIconOnly !== false) {
24
+ return {
25
+ ...defaultProps,
26
+ iconOnly: true,
27
+ };
28
+ }
29
+ return defaultProps;
30
+ }
31
+
21
32
  ActionModel.registerFlow({
22
33
  key: 'buttonSettings',
23
34
  title: tExpr('Button settings'),
@@ -32,6 +43,7 @@ ActionModel.registerFlow({
32
43
  'x-decorator': 'FormItem',
33
44
  'x-component': 'Input',
34
45
  title: tExpr('Button title'),
46
+ description: ctx.model.getTitleFieldDescription?.(),
35
47
  }
36
48
  : undefined,
37
49
  tooltip: ctx.model.enableEditTooltip
@@ -48,13 +60,14 @@ ActionModel.registerFlow({
48
60
  title: tExpr('Button icon'),
49
61
  }
50
62
  : undefined,
51
- iconOnly: ctx.model.enableEditIcon
52
- ? {
53
- 'x-decorator': 'FormItem',
54
- 'x-component': 'Switch',
55
- title: tExpr('Icon only'),
56
- }
57
- : undefined,
63
+ iconOnly:
64
+ ctx.model.enableEditIcon && ctx.model.enableEditIconOnly !== false
65
+ ? {
66
+ 'x-decorator': 'FormItem',
67
+ 'x-component': 'Switch',
68
+ title: tExpr('Icon only'),
69
+ }
70
+ : undefined,
58
71
  type: ctx.model.enableEditType
59
72
  ? {
60
73
  'x-decorator': 'FormItem',
@@ -80,7 +93,7 @@ ActionModel.registerFlow({
80
93
  };
81
94
  },
82
95
  defaultParams(ctx) {
83
- const defaultProps = ctx.model.defaultProps || {};
96
+ const defaultProps = getMobileDefaultProps(ctx);
84
97
  if (!ctx.model.enableEditColor) {
85
98
  return defaultProps;
86
99
  }
@@ -25,7 +25,7 @@ export function ActionWithoutPermission(props) {
25
25
  const dataSourcePrefix = `${t(dataSource.displayName || dataSource.key)} > `;
26
26
  const collectionPrefix = collection ? `${t(collection.title) || collection.name || collection.tableName} ` : '';
27
27
  return `${dataSourcePrefix}${collectionPrefix}`;
28
- }, []);
28
+ }, [collection, dataSource.displayName, dataSource.key, t]);
29
29
  const { actionName } = props?.forbidden || model.forbidden;
30
30
  const messageValue = useMemo(() => {
31
31
  return t(
@@ -62,6 +62,7 @@ export class ActionModel<T extends DefaultStructure = DefaultStructure> extends
62
62
  enableEditTooltip = true;
63
63
  enableEditTitle = true;
64
64
  enableEditIcon = true;
65
+ enableEditIconOnly = true;
65
66
  enableEditType = true;
66
67
  enableEditDanger = true;
67
68
  enableEditColor = false;
@@ -136,6 +137,10 @@ export class ActionModel<T extends DefaultStructure = DefaultStructure> extends
136
137
  return this.props.title;
137
138
  }
138
139
 
140
+ getTitleFieldDescription() {
141
+ return undefined;
142
+ }
143
+
139
144
  getIcon() {
140
145
  return this.props.icon;
141
146
  }
@@ -143,10 +148,11 @@ export class ActionModel<T extends DefaultStructure = DefaultStructure> extends
143
148
  renderButton() {
144
149
  const { iconOnly, ...props } = this.props;
145
150
  const icon = this.getIcon() ? <Icon type={this.getIcon() as any} /> : undefined;
151
+ const titleContent = iconOnly && icon ? null : props.children || this.getTitle();
146
152
 
147
153
  return (
148
154
  <Button {...props} onClick={this.onClick.bind(this)} icon={icon}>
149
- {iconOnly ? null : props.children || this.getTitle()}
155
+ {titleContent}
150
156
  </Button>
151
157
  );
152
158
  }
@@ -162,11 +168,12 @@ export class ActionModel<T extends DefaultStructure = DefaultStructure> extends
162
168
  renderHiddenInConfig(): React.ReactNode | undefined {
163
169
  const { iconOnly, ...props } = this.props;
164
170
  const icon = this.getIcon() ? <Icon type={this.getIcon() as any} /> : undefined;
171
+ const titleContent = iconOnly && icon ? null : props.children || this.getTitle();
165
172
  if (this.forbidden) {
166
173
  return (
167
174
  <ActionWithoutPermission>
168
175
  <Button {...props} onClick={this.onClick.bind(this)} icon={icon} style={{ opacity: '0.3' }}>
169
- {iconOnly ? null : props.children || this.getTitle()}
176
+ {titleContent}
170
177
  </Button>
171
178
  </ActionWithoutPermission>
172
179
  );
@@ -174,7 +181,7 @@ export class ActionModel<T extends DefaultStructure = DefaultStructure> extends
174
181
  return (
175
182
  <Tooltip title={this.context.t('The button is hidden and only visible when the UI Editor is active')}>
176
183
  <Button {...props} onClick={this.onClick.bind(this)} icon={icon} style={{ opacity: '0.3' }}>
177
- {iconOnly ? null : props.children || this.getTitle()}
184
+ {titleContent}
178
185
  </Button>
179
186
  </Tooltip>
180
187
  );
@@ -14,8 +14,10 @@ import {
14
14
  FlowSettingsButton,
15
15
  buildSubModelGroups,
16
16
  buildSubModelItems,
17
+ type FlowModel,
17
18
  type SubModelItem,
18
19
  type SubModelItemsType,
20
+ VIEW_ACTIVATED_EVENT,
19
21
  } from '@nocobase/flow-engine';
20
22
  import React from 'react';
21
23
  import { FilterManager } from '../blocks/filter-manager/FilterManager';
@@ -24,6 +26,8 @@ import { GridModel } from './GridModel';
24
26
  const SELECT_SCENE_ALLOWED_OTHER_BLOCK_MODELS = ['JSBlockModel', 'IframeBlockModel', 'MarkdownBlockModel'] as const;
25
27
 
26
28
  export class BlockGridModel extends GridModel {
29
+ private viewActivatedListener?: () => void;
30
+
27
31
  dragOverlayConfig: DragOverlayConfig = {
28
32
  // 列内插入
29
33
  columnInsert: {
@@ -50,6 +54,28 @@ export class BlockGridModel extends GridModel {
50
54
  });
51
55
  }
52
56
 
57
+ onMount() {
58
+ super.onMount();
59
+ if (this.context.view?.inputArgs?.scene !== 'select' || this.viewActivatedListener) {
60
+ return;
61
+ }
62
+
63
+ this.viewActivatedListener = () => {
64
+ this.mapSubModels('items', (item) => {
65
+ (item as FlowModel & { onActive?: () => void }).onActive?.();
66
+ });
67
+ };
68
+ this.flowEngine.emitter.on(VIEW_ACTIVATED_EVENT, this.viewActivatedListener);
69
+ }
70
+
71
+ protected onUnmount() {
72
+ if (this.viewActivatedListener) {
73
+ this.flowEngine.emitter.off(VIEW_ACTIVATED_EVENT, this.viewActivatedListener);
74
+ this.viewActivatedListener = undefined;
75
+ }
76
+ super.onUnmount();
77
+ }
78
+
53
79
  get subModelBaseClasses() {
54
80
  const inputArgs = this.context.view?.inputArgs ?? {};
55
81
  if (inputArgs.collectionName && !inputArgs.filterByTk) {
@@ -569,9 +569,13 @@ export class CollectionBlockModel<T = DefaultStructure> extends DataBlockModel<T
569
569
  }
570
570
  if (fieldPath.includes('.')) {
571
571
  // 关系数据
572
+ const collection = this.collection;
573
+ if (!collection) {
574
+ return;
575
+ }
572
576
  const [field1, field2] = fieldPath.split('.');
573
577
  const associationField = this.context.dataSourceManager.getCollectionField(
574
- `${this.collection.dataSourceKey}.${this.collection.name}.${field1}`,
578
+ `${collection.dataSourceKey}.${collection.name}.${field1}`,
575
579
  ) as CollectionField;
576
580
  if (!associationField) {
577
581
  return;
@@ -583,7 +587,7 @@ export class CollectionBlockModel<T = DefaultStructure> extends DataBlockModel<T
583
587
  }
584
588
 
585
589
  const collectionField = this.context.dataSourceManager.getCollectionField(
586
- `${this.collection.dataSourceKey}.${targetCollectionName}.${field2}`,
590
+ `${collection.dataSourceKey}.${targetCollectionName}.${field2}`,
587
591
  ) as CollectionField;
588
592
 
589
593
  if (collectionField && collectionField.isAssociationField()) {
@@ -75,6 +75,12 @@ interface DragState {
75
75
  generatedIds: Map<string, string>;
76
76
  }
77
77
 
78
+ const MOBILE_GRID_DISABLED_DROP_SLOT_TYPES = new Set<LayoutSlot['type']>(['column-edge', 'item-edge', 'empty-column']);
79
+
80
+ const filterMobileGridDragSlots = (slots: LayoutSlot[]) => {
81
+ return slots.filter((slot) => !MOBILE_GRID_DISABLED_DROP_SLOT_TYPES.has(slot.type));
82
+ };
83
+
78
84
  const getClientPoint = (event: any): { x: number; y: number } | null => {
79
85
  if (!event) {
80
86
  return null;
@@ -625,7 +631,7 @@ export class GridModel<T extends { subModels: { items: FlowModel[] } } = Default
625
631
  return;
626
632
  }
627
633
  const snapshot = buildLayoutSnapshot({ container: this.getDragContainer() });
628
- this.dragState.slots = snapshot.slots;
634
+ this.dragState.slots = this.context.isMobileLayout ? filterMobileGridDragSlots(snapshot.slots) : snapshot.slots;
629
635
  this.dragState.containerRect = snapshot.containerRect;
630
636
  }
631
637
 
@@ -1135,7 +1141,7 @@ GridModel.registerFlow({
1135
1141
  /**
1136
1142
  * 将多列栅格 rows 转换为移动端单列 rows。
1137
1143
  * 遍历原 rows 的行与列顺序,把每个原列(过滤空白列后)变成一个新行(仅一列)。
1138
- * 行 key 使用 uid() 生成,保持插入顺序即可。
1144
+ * 行 key 使用原 rowId 与 columnIndex 生成,避免每次渲染产生随机 key 导致移动端滚动位置丢失。
1139
1145
  * @param rows 原始多列 rows
1140
1146
  * @returns 单列 rows
1141
1147
  */
@@ -1145,13 +1151,38 @@ export function transformRowsToSingleColumn(
1145
1151
  ): Record<string, string[][]> {
1146
1152
  const emptyColumnUid = options?.emptyColumnUid ?? EMPTY_COLUMN_UID;
1147
1153
  const singleColumnRows: Record<string, string[][]> = {};
1154
+
1155
+ const getAvailableRowId = (baseRowId: string) => {
1156
+ if (!singleColumnRows[baseRowId]) {
1157
+ return baseRowId;
1158
+ }
1159
+
1160
+ let duplicateIndex = 1;
1161
+ let rowId = `${baseRowId}:duplicate:${duplicateIndex}`;
1162
+ while (singleColumnRows[rowId]) {
1163
+ duplicateIndex += 1;
1164
+ rowId = `${baseRowId}:duplicate:${duplicateIndex}`;
1165
+ }
1166
+ return rowId;
1167
+ };
1168
+
1148
1169
  Object.keys(rows).forEach((rowId) => {
1149
1170
  const columns = rows[rowId];
1150
- columns.forEach((column) => {
1151
- const filtered = column.filter((id) => id !== emptyColumnUid);
1152
- if (filtered.length > 0) {
1153
- singleColumnRows[uid()] = [filtered];
1154
- }
1171
+ const effectiveColumns = columns
1172
+ .map((column, columnIndex) => ({
1173
+ columnIndex,
1174
+ items: column.filter((id) => id !== emptyColumnUid),
1175
+ }))
1176
+ .filter((column) => column.items.length > 0);
1177
+
1178
+ if (effectiveColumns.length === 1 && effectiveColumns[0].columnIndex === 0) {
1179
+ singleColumnRows[getAvailableRowId(rowId)] = [effectiveColumns[0].items];
1180
+ return;
1181
+ }
1182
+
1183
+ effectiveColumns.forEach((column) => {
1184
+ const generatedRowId = `mobile:${encodeURIComponent(rowId)}:${column.columnIndex}`;
1185
+ singleColumnRows[getAvailableRowId(generatedRowId)] = [column.items];
1155
1186
  });
1156
1187
  });
1157
1188
  return singleColumnRows;