@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
@@ -16,13 +16,9 @@ import {
16
16
  FlowRuntimeContext,
17
17
  useFlowContext,
18
18
  useFlowEngine,
19
- createSafeWindow,
20
- createSafeDocument,
21
- createSafeNavigator,
22
19
  observer,
23
20
  isRunJSValue,
24
21
  normalizeRunJSValue,
25
- runjsWithSafeGlobals,
26
22
  } from '@nocobase/flow-engine';
27
23
  import { evaluateConditions, FilterGroupType, removeInvalidFilterItems } from '@nocobase/utils/client';
28
24
  import React from 'react';
@@ -40,7 +36,7 @@ import { FilterGroup } from '../components/filter/FilterGroup';
40
36
  import { LinkageFilterItem } from '../components/filter';
41
37
  import { CodeEditor } from '../components/code-editor';
42
38
  import { FieldAssignRulesEditor } from '../components/FieldAssignRulesEditor';
43
- import type { FieldAssignRuleItem } from '../components/FieldAssignRulesEditor';
39
+ import type { AssignMode, FieldAssignRuleItem } from '../components/FieldAssignRulesEditor';
44
40
  import { collectFieldAssignCascaderOptions } from '../components/fieldAssignOptions';
45
41
  import { useAssociationTitleFieldSync } from '../components/useAssociationTitleFieldSync';
46
42
  import _ from 'lodash';
@@ -492,7 +488,7 @@ async function resolveLinkageAssignRuntimeValue(ctx: FlowContext, rawValue: any)
492
488
 
493
489
  try {
494
490
  const { code, version } = normalizeRunJSValue(rawValue);
495
- const ret = await runjsWithSafeGlobals(ctx, code, { version });
491
+ const ret = await ctx.runjs(code, undefined, { version });
496
492
  if (!ret?.success) {
497
493
  return SKIP_RUNJS_ASSIGN_VALUE;
498
494
  }
@@ -735,6 +731,41 @@ export const linkageSetMenuItemProps = defineAction({
735
731
  },
736
732
  });
737
733
 
734
+ export const linkageSetTabProps = defineAction({
735
+ name: 'linkageSetTabProps',
736
+ title: tExpr('Set tab state'),
737
+ scene: ActionScene.TAB_LINKAGE_RULES,
738
+ sort: 100,
739
+ uiSchema: {
740
+ value: {
741
+ type: 'string',
742
+ 'x-component': (props) => {
743
+ const { value, onChange } = props;
744
+ // eslint-disable-next-line react-hooks/rules-of-hooks
745
+ const ctx = useFlowContext();
746
+ const t = ctx.model.translate.bind(ctx.model);
747
+
748
+ return (
749
+ <Select
750
+ value={value}
751
+ onChange={onChange}
752
+ placeholder={t('Please select state')}
753
+ style={{ width: '100%' }}
754
+ options={[
755
+ { label: t('Visible'), value: 'visible' },
756
+ { label: t('Hidden'), value: 'hidden' },
757
+ ]}
758
+ allowClear
759
+ />
760
+ );
761
+ },
762
+ },
763
+ },
764
+ handler(ctx, { value, setProps }) {
765
+ setProps(ctx.model, { hiddenModel: value === 'hidden' });
766
+ },
767
+ });
768
+
738
769
  export const linkageSetFieldProps = defineAction({
739
770
  name: 'linkageSetFieldProps',
740
771
  title: tExpr('Set field state'),
@@ -888,6 +919,20 @@ type ArrayFieldComponentProps = {
888
919
 
889
920
  const LEGACY_ASSIGN_RULE = { mode: 'assign', valueKey: 'assignValue' } as const;
890
921
  const LEGACY_DEFAULT_RULE = { mode: 'default', valueKey: 'initialValue' } as const;
922
+ const LINKAGE_ASSIGN_MODE_PROP = '__linkageAssignMode';
923
+
924
+ function normalizeLinkageAssignMode(mode: unknown): AssignMode {
925
+ if (mode === 'default') return 'default';
926
+ if (mode === 'override') return 'override';
927
+ return 'assign';
928
+ }
929
+
930
+ type LinkageValuePatch = {
931
+ path: Array<string | number>;
932
+ value: unknown;
933
+ whenEmpty?: boolean;
934
+ mode?: AssignMode;
935
+ };
891
936
 
892
937
  const FieldAssignRulesActionComponent: React.FC<
893
938
  ArrayFieldComponentProps & {
@@ -1002,16 +1047,24 @@ export const linkageAssignField = defineAction({
1002
1047
  continue;
1003
1048
  }
1004
1049
 
1005
- const mode = it?.mode === 'default' ? 'default' : 'assign';
1050
+ const mode = normalizeLinkageAssignMode(it?.mode);
1006
1051
  if (fieldModel) {
1007
1052
  if (mode === 'default') {
1008
1053
  setProps(fieldModel as FlowModel, { initialValue: finalValue });
1009
1054
  } else {
1010
- setProps(fieldModel as FlowModel, { value: finalValue });
1055
+ setProps(fieldModel as FlowModel, {
1056
+ value: finalValue,
1057
+ ...(mode === 'override' ? { [LINKAGE_ASSIGN_MODE_PROP]: mode } : {}),
1058
+ });
1011
1059
  }
1012
1060
  } else if (typeof addFormValuePatch === 'function') {
1013
1061
  // 对关联字段子属性(如 user.name)等没有独立 FormItemModel 的目标,直接写入表单值
1014
- addFormValuePatch({ path: targetPath, value: finalValue, whenEmpty: mode === 'default' });
1062
+ addFormValuePatch({
1063
+ path: targetPath,
1064
+ value: finalValue,
1065
+ whenEmpty: mode === 'default',
1066
+ ...(mode === 'override' ? { mode } : {}),
1067
+ });
1015
1068
  }
1016
1069
  }
1017
1070
  } catch (error) {
@@ -1177,7 +1230,7 @@ export const subFormLinkageAssignField = defineAction({
1177
1230
  continue;
1178
1231
  }
1179
1232
 
1180
- const mode = it?.mode === 'default' ? 'default' : 'assign';
1233
+ const mode = normalizeLinkageAssignMode(it?.mode);
1181
1234
  const actionName = (ctx.model as any)?.getAclActionName?.() ?? (ctx.model as any)?.context?.actionName;
1182
1235
  const isEditForm = actionName === 'update';
1183
1236
  const isNewItem = (ctx as any)?.item?.__is_new__ === true;
@@ -1207,12 +1260,21 @@ export const subFormLinkageAssignField = defineAction({
1207
1260
  continue;
1208
1261
  }
1209
1262
 
1263
+ if (mode === 'override' && hasExplicitPathHit(targetPath)) {
1264
+ continue;
1265
+ }
1266
+
1210
1267
  if (!fieldUid) {
1211
1268
  if (mode === 'default' && hasExplicitPathHit(targetPath)) {
1212
1269
  continue;
1213
1270
  }
1214
1271
  if (typeof addFormValuePatch === 'function') {
1215
- addFormValuePatch({ path: targetPath, value: finalValue, whenEmpty: mode === 'default' });
1272
+ addFormValuePatch({
1273
+ path: targetPath,
1274
+ value: finalValue,
1275
+ whenEmpty: mode === 'default',
1276
+ ...(mode === 'override' ? { mode } : {}),
1277
+ });
1216
1278
  }
1217
1279
  continue;
1218
1280
  }
@@ -1223,7 +1285,10 @@ export const subFormLinkageAssignField = defineAction({
1223
1285
  if (mode === 'default') {
1224
1286
  setProps(model, { initialValue: finalValue });
1225
1287
  } else {
1226
- setProps(model, { value: finalValue });
1288
+ setProps(model, {
1289
+ value: finalValue,
1290
+ ...(mode === 'override' ? { [LINKAGE_ASSIGN_MODE_PROP]: mode } : {}),
1291
+ });
1227
1292
  }
1228
1293
  }
1229
1294
  } catch (error) {
@@ -1309,6 +1374,7 @@ export const linkageRunjs = defineAction({
1309
1374
  ActionScene.FIELD_LINKAGE_RULES,
1310
1375
  ActionScene.ACTION_LINKAGE_RULES,
1311
1376
  ActionScene.MENU_LINKAGE_RULES,
1377
+ ActionScene.TAB_LINKAGE_RULES,
1312
1378
  ActionScene.DETAILS_FIELD_LINKAGE_RULES,
1313
1379
  ActionScene.SUB_FORM_FIELD_LINKAGE_RULES,
1314
1380
  ],
@@ -1363,8 +1429,7 @@ export const linkageRunjs = defineAction({
1363
1429
  }
1364
1430
 
1365
1431
  try {
1366
- const navigator = createSafeNavigator();
1367
- await ctx.runjs(script, { window: createSafeWindow({ navigator }), document: createSafeDocument(), navigator });
1432
+ await ctx.runjs(script);
1368
1433
  } catch (error) {
1369
1434
  console.error('Script execution error:', error);
1370
1435
  // 可以选择显示错误信息给用户
@@ -1781,7 +1846,7 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
1781
1846
  const modelsToApply = new Set<FlowModel>(allModels);
1782
1847
  const patchPropsByModel = new Map<FlowModel, any>();
1783
1848
  const clearValueOnHiddenModelUids = new Set<string>();
1784
- const directValuePatches: Array<{ path: Array<string | number>; value: any; whenEmpty?: boolean }> = [];
1849
+ const directValuePatches: LinkageValuePatch[] = [];
1785
1850
  const rootCollection = getCollectionFromModel((ctx.model as any)?.context?.blockModel ?? ctx.model);
1786
1851
  const isSafeToWriteAssociationSubpath = (namePath: any): boolean => {
1787
1852
  if (!Array.isArray(namePath) || !namePath.length) return true;
@@ -1836,13 +1901,18 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
1836
1901
  }
1837
1902
 
1838
1903
  for (const patch of lastPatchByPathKey.values()) {
1839
- if (!patch.whenEmpty) continue;
1904
+ if (!patch.whenEmpty && patch.mode !== 'default') continue;
1840
1905
  runtime.recordDefaultValuePatch(patch.path, patch.value);
1841
1906
  }
1842
1907
  };
1843
- const addFormValuePatch = (patch: { path: any; value: any; whenEmpty?: boolean }) => {
1908
+ const getPatchMode = (patch: { mode?: unknown; whenEmpty?: boolean }): AssignMode => {
1909
+ if (patch?.mode === 'default') return 'default';
1910
+ if (patch?.mode === 'override') return 'override';
1911
+ return patch?.whenEmpty ? 'default' : 'assign';
1912
+ };
1913
+ const addFormValuePatch = (patch: { path: unknown; value: unknown; whenEmpty?: boolean; mode?: AssignMode }) => {
1844
1914
  if (!patch) return;
1845
- const path = (patch as any)?.path;
1915
+ const path = patch.path;
1846
1916
  if (!path) return;
1847
1917
  const resolvedPath = resolveNamePathForPatch(path);
1848
1918
  if (!resolvedPath) {
@@ -1862,8 +1932,9 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
1862
1932
  });
1863
1933
  return;
1864
1934
  }
1865
- const whenEmpty = !!(patch as any)?.whenEmpty;
1866
- const value = (patch as any)?.value;
1935
+ const mode = getPatchMode(patch);
1936
+ const whenEmpty = mode === 'default';
1937
+ const value = patch.value;
1867
1938
  try {
1868
1939
  const form = ctx.model?.context?.form;
1869
1940
  const current = form?.getFieldValue?.(resolvedPath);
@@ -1878,6 +1949,15 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
1878
1949
  return;
1879
1950
  }
1880
1951
  }
1952
+ if (mode === 'override') {
1953
+ const runtime = getDefaultPatchRuntime();
1954
+ if (
1955
+ typeof runtime?.canApplyOverrideValuePatch === 'function' &&
1956
+ !runtime.canApplyOverrideValuePatch(resolvedPath)
1957
+ ) {
1958
+ return;
1959
+ }
1960
+ }
1881
1961
  if (_.isEqual(current, value)) {
1882
1962
  return;
1883
1963
  }
@@ -1889,6 +1969,7 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
1889
1969
  path: resolvedPath,
1890
1970
  value,
1891
1971
  ...(whenEmpty ? { whenEmpty: true } : {}),
1972
+ ...(mode === 'override' ? { mode } : {}),
1892
1973
  });
1893
1974
  };
1894
1975
  const removePendingFormValuePatches = (path: any) => {
@@ -1948,6 +2029,21 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
1948
2029
  >;
1949
2030
  return normalized.length ? normalized : null;
1950
2031
  };
2032
+ const pathKeysEqual = (
2033
+ a: Array<string | number> | null | undefined,
2034
+ b: Array<string | number> | null | undefined,
2035
+ ) => {
2036
+ if (!a || !b) return false;
2037
+ return namePathToPathKey(a) === namePathToPathKey(b);
2038
+ };
2039
+ const namePathEndsWith = (
2040
+ namePath: Array<string | number> | null | undefined,
2041
+ suffix: Array<string | number> | null | undefined,
2042
+ ) => {
2043
+ if (!namePath || !suffix || suffix.length > namePath.length) return false;
2044
+ const offset = namePath.length - suffix.length;
2045
+ return suffix.every((seg, index) => namePath[offset + index] === seg);
2046
+ };
1951
2047
  const getFieldIndexEntries = (fieldIndex: any): Array<{ name: string; index: number }> => {
1952
2048
  if (!Array.isArray(fieldIndex)) return [];
1953
2049
  return fieldIndex
@@ -1996,17 +2092,35 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
1996
2092
 
1997
2093
  return out;
1998
2094
  };
2095
+ const getTrustedFieldPathArray = (
2096
+ fieldPathArray: Array<string | number> | null,
2097
+ targetPath: string | null,
2098
+ fieldIndex: unknown,
2099
+ ): Array<string | number> | null => {
2100
+ if (!fieldPathArray || !targetPath) return null;
2101
+
2102
+ const targetNamePath = normalizeNamePathForKey(
2103
+ parsePathString(targetPath).filter((seg) => typeof seg === 'string' || typeof seg === 'number'),
2104
+ );
2105
+ const resolvedTargetPath = normalizeNamePathForKey(resolveDynamicNamePath(targetPath, fieldIndex));
2106
+ const indexedRelativePath = resolveIndexedRelativePath(targetPath, fieldIndex);
2107
+
2108
+ if (
2109
+ pathKeysEqual(fieldPathArray, resolvedTargetPath) ||
2110
+ pathKeysEqual(fieldPathArray, indexedRelativePath) ||
2111
+ namePathEndsWith(fieldPathArray, targetNamePath)
2112
+ ) {
2113
+ return fieldPathArray;
2114
+ }
2115
+
2116
+ return null;
2117
+ };
1999
2118
  const getModelTargetPathForHiddenClear = (model: any): string | Array<string | number> | null => {
2000
2119
  const fieldPathArray = normalizeNamePathForKey(model?.context?.fieldPathArray);
2001
2120
  const targetPath = getModelTargetPathForPatch(model);
2002
- if (fieldPathArray) {
2003
- const targetPathLastString = targetPath
2004
- ? ([...parsePathString(targetPath)].reverse().find((seg) => typeof seg === 'string') as string | undefined)
2005
- : undefined;
2006
- const fieldPathArrayLastString = [...fieldPathArray].reverse().find((seg) => typeof seg === 'string');
2007
- if (!targetPathLastString || targetPathLastString === fieldPathArrayLastString) {
2008
- return fieldPathArray;
2009
- }
2121
+ const trustedFieldPathArray = getTrustedFieldPathArray(fieldPathArray, targetPath, model?.context?.fieldIndex);
2122
+ if (trustedFieldPathArray) {
2123
+ return trustedFieldPathArray;
2010
2124
  }
2011
2125
 
2012
2126
  if (!targetPath) return null;
@@ -2016,12 +2130,13 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
2016
2130
  const getModelTargetPathKeys = (model: any): Set<string> => {
2017
2131
  const keys = new Set<string>();
2018
2132
  const fieldPathArray = normalizeNamePathForKey(model?.context?.fieldPathArray);
2019
- if (fieldPathArray) {
2020
- keys.add(namePathToPathKey(fieldPathArray));
2133
+ const targetPath = getModelTargetPathForPatch(model);
2134
+ const trustedFieldPathArray = getTrustedFieldPathArray(fieldPathArray, targetPath, model?.context?.fieldIndex);
2135
+ if (trustedFieldPathArray) {
2136
+ keys.add(namePathToPathKey(trustedFieldPathArray));
2021
2137
  return keys;
2022
2138
  }
2023
2139
 
2024
- const targetPath = getModelTargetPathForPatch(model);
2025
2140
  if (targetPath) {
2026
2141
  const fieldIndexEntries = getFieldIndexEntries(model?.context?.fieldIndex);
2027
2142
  if (!fieldIndexEntries.length) {
@@ -2082,22 +2197,43 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
2082
2197
 
2083
2198
  for (const action of actions) {
2084
2199
  const setProps = (model: FlowModel & { __originalProps?: any; __shouldReset?: boolean }, props: any) => {
2085
- // 存储原始值,用于恢复
2086
- if (!model.__originalProps) {
2087
- model.__originalProps = {
2088
- hiddenModel: model.hidden,
2089
- hiddenText: undefined,
2090
- disabled: undefined,
2091
- required: undefined,
2092
- hidden: undefined,
2093
- ...model.props,
2094
- };
2095
- }
2200
+ const normalizedProps =
2201
+ props && typeof props === 'object' && Object.prototype.hasOwnProperty.call(props, 'value')
2202
+ ? {
2203
+ ...props,
2204
+ [LINKAGE_ASSIGN_MODE_PROP]: normalizeLinkageAssignMode(props?.[LINKAGE_ASSIGN_MODE_PROP]),
2205
+ }
2206
+ : props;
2207
+
2208
+ // 只记录联动实际控制的属性,避免之后恢复状态时把标题、路由等无关的新配置回滚到旧快照。
2209
+ const originalProps = model.__originalProps || (model.__originalProps = {});
2210
+ const rememberOriginalProp = (key: string, value: unknown) => {
2211
+ if (!Object.prototype.hasOwnProperty.call(originalProps, key)) {
2212
+ originalProps[key] = value;
2213
+ }
2214
+ };
2215
+ Object.keys(normalizedProps || {}).forEach((key) => {
2216
+ if (key === 'hiddenModel') {
2217
+ rememberOriginalProp(
2218
+ key,
2219
+ Object.prototype.hasOwnProperty.call(model.props || {}, key) ? model.props?.[key] : model.hidden,
2220
+ );
2221
+ return;
2222
+ }
2223
+
2224
+ rememberOriginalProp(key, model.props?.[key]);
2225
+ if (key === 'hiddenText' && normalizedProps[key]) {
2226
+ rememberOriginalProp('title', model.props?.title);
2227
+ }
2228
+ if (key === 'required') {
2229
+ rememberOriginalProp('rules', model.props?.rules);
2230
+ }
2231
+ });
2096
2232
 
2097
2233
  // 临时存起来,遍历完所有规则后,再统一处理
2098
2234
  patchPropsByModel.set(model, {
2099
2235
  ...(patchPropsByModel.get(model) || {}),
2100
- ...props,
2236
+ ...normalizedProps,
2101
2237
  });
2102
2238
 
2103
2239
  if (
@@ -2150,8 +2286,7 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
2150
2286
  const newProps = { ...model.__originalProps, ...patchProps };
2151
2287
  const prevHidden = !!model.hidden;
2152
2288
  const nextHidden = !!newProps.hiddenModel;
2153
-
2154
- model.setProps(_.omit(newProps, ['hiddenModel', 'value', 'hiddenText']));
2289
+ model.setProps(_.omit(newProps, ['hiddenModel', 'value', 'hiddenText', LINKAGE_ASSIGN_MODE_PROP]));
2155
2290
  syncFieldOptionsToForks(model, patchProps);
2156
2291
  if (typeof model.setHidden === 'function') {
2157
2292
  model.setHidden(nextHidden);
@@ -2190,7 +2325,8 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
2190
2325
  targetUid: model?.uid,
2191
2326
  });
2192
2327
  } else {
2193
- addFormValuePatch({ path: targetPath, value: newProps.value });
2328
+ const mode = normalizeLinkageAssignMode(patchProps?.[LINKAGE_ASSIGN_MODE_PROP]);
2329
+ addFormValuePatch({ path: targetPath, value: newProps.value, ...(mode === 'override' ? { mode } : {}) });
2194
2330
  }
2195
2331
  }
2196
2332
 
@@ -2375,6 +2511,32 @@ export const menuLinkageRules = defineAction({
2375
2511
  },
2376
2512
  });
2377
2513
 
2514
+ export const tabLinkageRules = defineAction({
2515
+ name: 'tabLinkageRules',
2516
+ title: tExpr('Tab linkage rules'),
2517
+ uiMode: 'embed',
2518
+ uiSchema(ctx) {
2519
+ return {
2520
+ value: {
2521
+ type: 'array',
2522
+ 'x-component': LinkageRulesUI,
2523
+ 'x-component-props': {
2524
+ supportedActions: getSupportedActions(ctx, ActionScene.TAB_LINKAGE_RULES),
2525
+ title: tExpr('Tab linkage rules'),
2526
+ },
2527
+ },
2528
+ };
2529
+ },
2530
+ defaultParams: {
2531
+ value: [],
2532
+ },
2533
+ useRawParams: true,
2534
+ handler: async (ctx, params) => {
2535
+ const resolved = await resolveLinkageRulesParamsPreservingRunJsScripts(ctx, params);
2536
+ return commonLinkageRulesHandler(ctx, resolved);
2537
+ },
2538
+ });
2539
+
2378
2540
  export const fieldLinkageRules = defineAction({
2379
2541
  name: 'fieldLinkageRules',
2380
2542
  title: tExpr('Field linkage rules'),
@@ -2842,7 +3004,7 @@ export const subFormFieldLinkageRules = defineAction({
2842
3004
  }
2843
3005
  } else {
2844
3006
  await Promise.all(
2845
- (grid.forks || []).map(async (forkModel: FlowModel) => {
3007
+ Array.from(grid.forks || []).map(async (forkModel: FlowModel) => {
2846
3008
  if (forkModel.hidden) {
2847
3009
  return;
2848
3010
  }
@@ -15,17 +15,29 @@ import {
15
15
  isRunJSValue,
16
16
  } from '@nocobase/flow-engine';
17
17
  import _ from 'lodash';
18
- import { namePathToPathKey, parsePathString, pathKeyToNamePath } from '../models/blocks/form/value-runtime/path';
18
+ import { namePathToPathKey } from '../models/blocks/form/value-runtime/path';
19
19
  import {
20
20
  collectStaticDepsFromRunJSValue,
21
21
  collectStaticDepsFromTemplateValue,
22
22
  recordDep,
23
23
  type DepCollector,
24
24
  } from '../models/blocks/form/value-runtime/deps';
25
+ import {
26
+ buildItemListRootPath,
27
+ buildItemRowPath,
28
+ dedupeNamePaths,
29
+ findFormValueChangeSource,
30
+ getChangedPathsFromPayload,
31
+ getFieldIndexEntriesFromContext,
32
+ isNamePathPrefix,
33
+ isSameNamePath,
34
+ minimizeNamePaths,
35
+ parseDependencyPath,
36
+ parsePathKey,
37
+ type NamePath,
38
+ } from '../utils/formValueDeps';
25
39
  import { linkageRulesRefresh } from './linkageRulesRefresh';
26
40
 
27
- type NamePath = Array<string | number>;
28
-
29
41
  type LinkageRefreshDeps = {
30
42
  wildcard: boolean;
31
43
  valuePaths: NamePath[];
@@ -40,75 +52,11 @@ type LinkageRefreshBinding = {
40
52
  dispose: () => void;
41
53
  };
42
54
 
43
- type FieldIndexEntry = {
44
- name: string;
45
- index: number;
46
- };
47
-
48
55
  const FORM_VALUES_CHANGE_EVENT = 'formValuesChange';
49
56
  const LINKAGE_REFRESH_BINDINGS_KEY = '__formValueDrivenLinkageRefreshBindings';
50
57
 
51
- function isSameNamePath(a: NamePath, b: NamePath) {
52
- return a.length === b.length && a.every((seg, index) => seg === b[index]);
53
- }
54
-
55
- function isNamePathPrefix(prefix: NamePath, path: NamePath) {
56
- if (prefix.length > path.length) return false;
57
- return prefix.every((seg, index) => seg === path[index]);
58
- }
59
-
60
- function dedupeNamePaths(paths: NamePath[]) {
61
- const byKey = new Map<string, NamePath>();
62
- for (const path of paths) {
63
- if (!path?.length) continue;
64
- byKey.set(namePathToPathKey(path), path);
65
- }
66
- return Array.from(byKey.values());
67
- }
68
-
69
- function minimizeValueNamePaths(paths: NamePath[]) {
70
- const deduped = dedupeNamePaths(paths);
71
- return deduped.filter((path, index) => {
72
- return !deduped.some((other, otherIndex) => otherIndex !== index && isNamePathPrefix(path, other));
73
- });
74
- }
75
-
76
- function parseFieldIndexEntries(fieldIndex: unknown): FieldIndexEntry[] {
77
- const arr = Array.isArray(fieldIndex) ? fieldIndex : [];
78
- const entries: FieldIndexEntry[] = [];
79
- for (const it of arr) {
80
- if (typeof it !== 'string') continue;
81
- const [name, indexStr] = it.split(':');
82
- const index = Number(indexStr);
83
- if (!name || Number.isNaN(index)) continue;
84
- entries.push({ name, index });
85
- }
86
- return entries;
87
- }
88
-
89
- function getFieldIndexEntriesFromContext(ctx: any): FieldIndexEntry[] {
90
- return parseFieldIndexEntries(ctx?.model?.context?.fieldIndex ?? ctx?.fieldIndex);
91
- }
92
-
93
- function buildItemRowPath(entries: FieldIndexEntry[], parentDepth: number): NamePath | null {
94
- const targetIndex = entries.length - 1 - parentDepth;
95
- if (targetIndex < 0) return null;
96
-
97
- const out: NamePath = [];
98
- for (let i = 0; i <= targetIndex; i++) {
99
- out.push(entries[i].name, entries[i].index);
100
- }
101
- return out;
102
- }
103
-
104
- function buildItemListRootPath(entries: FieldIndexEntry[], parentDepth: number): NamePath | null {
105
- const rowPath = buildItemRowPath(entries, parentDepth);
106
- if (!rowPath?.length) return null;
107
- return rowPath.slice(0, -1);
108
- }
109
-
110
58
  function resolveItemDependencyPath(ctx: FlowContext, depPath: NamePath): LinkageRefreshDeps {
111
- const entries = getFieldIndexEntriesFromContext(ctx as any);
59
+ const entries = getFieldIndexEntriesFromContext(ctx);
112
60
  if (!entries.length) {
113
61
  return { wildcard: true, valuePaths: [], structuralPaths: [] };
114
62
  }
@@ -171,8 +119,7 @@ function addRunjsUsageToCollector(script: string, collector: DepCollector) {
171
119
  collector.wildcard = true;
172
120
  continue;
173
121
  }
174
- const segs = parsePathString(String(subPath)).filter((seg) => typeof seg !== 'object') as NamePath;
175
- recordDep(segs, collector);
122
+ recordDep(parseDependencyPath(String(subPath)), collector);
176
123
  continue;
177
124
  }
178
125
  if (varName === 'item') {
@@ -233,13 +180,13 @@ function collectLinkageRefreshDeps(ctx: FlowContext, params: any): LinkageRefres
233
180
  wildcard = true;
234
181
  continue;
235
182
  }
236
- valuePaths.push(pathKeyToNamePath(inner));
183
+ valuePaths.push(parsePathKey(inner));
237
184
  continue;
238
185
  }
239
186
 
240
187
  if (depKey === 'ctx:item' || depKey.startsWith('ctx:item:')) {
241
188
  const subPath = depKey === 'ctx:item' ? '' : depKey.slice('ctx:item:'.length);
242
- const depPath = subPath ? (parsePathString(subPath).filter((seg) => typeof seg !== 'object') as NamePath) : [];
189
+ const depPath = subPath ? parseDependencyPath(subPath) : [];
243
190
  const resolved = resolveItemDependencyPath(ctx, depPath);
244
191
  wildcard ||= resolved.wildcard;
245
192
  valuePaths.push(...resolved.valuePaths);
@@ -249,7 +196,7 @@ function collectLinkageRefreshDeps(ctx: FlowContext, params: any): LinkageRefres
249
196
 
250
197
  return {
251
198
  wildcard,
252
- valuePaths: minimizeValueNamePaths(valuePaths),
199
+ valuePaths: minimizeNamePaths(valuePaths),
253
200
  structuralPaths: dedupeNamePaths(structuralPaths),
254
201
  };
255
202
  }
@@ -258,44 +205,9 @@ function hasLinkageRefreshDeps(deps: LinkageRefreshDeps) {
258
205
  return deps.wildcard || deps.valuePaths.length > 0 || deps.structuralPaths.length > 0;
259
206
  }
260
207
 
261
- function getChangedPathsFromPayload(payload: any): NamePath[] {
262
- const rawChangedPaths = Array.isArray(payload?.changedPaths) ? payload.changedPaths : [];
263
- const out: NamePath[] = [];
264
-
265
- for (const path of rawChangedPaths) {
266
- if (Array.isArray(path)) {
267
- if (path.length === 1 && typeof path[0] === 'string') {
268
- const namePath = pathKeyToNamePath(path[0]);
269
- if (namePath.length) out.push(namePath);
270
- continue;
271
- }
272
- const segs = path.filter((seg) => typeof seg === 'string' || typeof seg === 'number') as NamePath;
273
- if (segs.length) out.push(segs);
274
- continue;
275
- }
276
- if (typeof path === 'string' && path) {
277
- out.push(pathKeyToNamePath(path));
278
- }
279
- }
280
-
281
- if (out.length) {
282
- return out;
283
- }
284
-
285
- const changedValues = payload?.changedValues;
286
- if (changedValues && typeof changedValues === 'object') {
287
- for (const key of Object.keys(changedValues)) {
288
- const namePath = pathKeyToNamePath(key);
289
- if (namePath.length) out.push(namePath);
290
- }
291
- }
292
-
293
- return out;
294
- }
295
-
296
208
  function linkageRefreshDepsMatchPayload(deps: LinkageRefreshDeps, payload: any) {
297
209
  if (!hasLinkageRefreshDeps(deps)) return false;
298
- const changedPaths = getChangedPathsFromPayload(payload);
210
+ const changedPaths = getChangedPathsFromPayload(payload, { includeArrayChangedValues: true });
299
211
  if (deps.wildcard) return true;
300
212
  if (!changedPaths.length) return true;
301
213
 
@@ -330,28 +242,8 @@ function getLinkageRefreshBindings(model: any): Map<string, LinkageRefreshBindin
330
242
  return (model[LINKAGE_REFRESH_BINDINGS_KEY] ||= new Map<string, LinkageRefreshBinding>());
331
243
  }
332
244
 
333
- function isFormBlockForLinkageRefresh(model: any) {
334
- if (!model || typeof model !== 'object') return false;
335
- if (!model.emitter || typeof model.emitter.on !== 'function' || typeof model.emitter.off !== 'function') return false;
336
- return !!model.formValueRuntime || !!model.context?.form || typeof model.context?.setFormValues === 'function';
337
- }
338
-
339
245
  function findFormBlockForLinkageRefresh(ctx: FlowContext): any | null {
340
- const candidates: any[] = [];
341
- const push = (model: any) => {
342
- if (model && !candidates.includes(model)) candidates.push(model);
343
- };
344
-
345
- push((ctx.model as any)?.context?.blockModel);
346
- push(ctx.model);
347
-
348
- let cursor: any = (ctx.model as any)?.parent;
349
- while (cursor) {
350
- push(cursor);
351
- cursor = cursor?.parent;
352
- }
353
-
354
- return candidates.find(isFormBlockForLinkageRefresh) || null;
246
+ return findFormValueChangeSource(ctx) as any;
355
247
  }
356
248
 
357
249
  function disposeLinkageRefreshBinding(model: any, key: string) {
@@ -35,12 +35,8 @@ export const linkageRulesRefresh = defineAction({
35
35
  // Prefer running on the current model; fallback to blockModel when the current model doesn't own the flow.
36
36
  if (!hasFlow) return;
37
37
 
38
- // In runtime, only skip master when there are mounted forks that can handle the same flow.
38
+ // Skip master when unmounted forks can handle the same flow.
39
39
  // Otherwise master is likely the rendered model and still needs refresh.
40
- // In design mode, always refresh master so the currently edited model state stays in sync.
41
- const flowSettingsEnabled = Boolean(
42
- (ctx as any)?.flowSettingsEnabled || (model as any)?.context?.flowSettingsEnabled,
43
- );
44
40
  const hasForkWithFlow =
45
41
  !model?.isFork &&
46
42
  !!model?.forks?.size &&
@@ -49,7 +45,7 @@ export const linkageRulesRefresh = defineAction({
49
45
  return !!fork?.getFlow?.(flowKey);
50
46
  });
51
47
  const isMasterMounted = Boolean((model as any)?.context?.ref?.current);
52
- if (hasForkWithFlow && !isMasterMounted && !flowSettingsEnabled) {
48
+ if (hasForkWithFlow && !isMasterMounted) {
53
49
  return;
54
50
  }
55
51