@nocobase/client-v2 2.2.0-beta.1 → 2.2.0-beta.11

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 (252) hide show
  1. package/es/BaseApplication.d.ts +3 -0
  2. package/es/PluginSettingsManager.d.ts +33 -0
  3. package/es/RouteRepository.d.ts +29 -6
  4. package/es/RouterManager.d.ts +15 -0
  5. package/es/authRedirect.d.ts +1 -0
  6. package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
  7. package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
  8. package/es/components/form/JsonTextArea.d.ts +2 -1
  9. package/es/components/form/VariableJsonTextArea.d.ts +19 -0
  10. package/es/components/form/index.d.ts +1 -0
  11. package/es/components/index.d.ts +1 -0
  12. package/es/entry-actions/EntryActionManager.d.ts +24 -0
  13. package/es/entry-actions/index.d.ts +9 -0
  14. package/es/flow/actions/afterSuccess.d.ts +8 -1
  15. package/es/flow/actions/index.d.ts +1 -1
  16. package/es/flow/admin-shell/BaseLayoutModel.d.ts +6 -0
  17. package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +7 -0
  18. package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +2 -0
  19. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.d.ts +2 -1
  20. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +2 -0
  21. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.d.ts +3 -2
  22. package/es/flow/admin-shell/admin-layout/AdminLayoutSlotModels.d.ts +5 -0
  23. package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +2 -1
  24. package/es/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.d.ts +24 -0
  25. package/es/flow/admin-shell/admin-layout/index.d.ts +1 -0
  26. package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +6 -0
  27. package/es/flow/admin-shell/admin-layout/useApplications.d.ts +7 -2
  28. package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
  29. package/es/flow/models/base/ActionModelCore.d.ts +2 -0
  30. package/es/flow/models/base/GridModel.d.ts +1 -1
  31. package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +5 -0
  32. package/es/flow/models/blocks/form/FormBlockModel.d.ts +4 -0
  33. package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
  34. package/es/flow/models/blocks/form/submitHandler.d.ts +1 -1
  35. package/es/flow/models/blocks/form/value-runtime/rules.d.ts +21 -0
  36. package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +30 -0
  37. package/es/flow/models/blocks/form/value-runtime/types.d.ts +2 -2
  38. package/es/flow/models/blocks/js-block/JSBlock.d.ts +1 -0
  39. package/es/flow/models/blocks/table/TableActionsColumnModel.d.ts +1 -0
  40. package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
  41. package/es/flow/models/blocks/table/dragSort/dragSortHooks.d.ts +1 -1
  42. package/es/flow/models/blocks/table/dragSort/dragSortSettings.d.ts +2 -1
  43. package/es/flow/models/blocks/table/dragSort/dragSortUtils.d.ts +6 -4
  44. package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +2 -0
  45. package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +6 -0
  46. package/es/flow/resolveViewParamsToViewList.d.ts +1 -1
  47. package/es/flow/routeTransientInputArgs.d.ts +14 -0
  48. package/es/flow/utils/dataScopeRowSnapshot.d.ts +59 -0
  49. package/es/flow/utils/dateTimeDisplayProps.d.ts +49 -0
  50. package/es/flow/utils/formValueDeps.d.ts +32 -0
  51. package/es/flow-compat/FieldValidation.d.ts +2 -1
  52. package/es/index.d.ts +4 -1
  53. package/es/index.mjs +315 -167
  54. package/es/utils/markdownSanitize.d.ts +11 -0
  55. package/lib/index.js +315 -167
  56. package/package.json +8 -7
  57. package/src/BaseApplication.tsx +12 -5
  58. package/src/PluginSettingsManager.ts +53 -0
  59. package/src/RouteRepository.ts +151 -24
  60. package/src/RouterManager.tsx +142 -1
  61. package/src/__tests__/PluginSettingsManager.test.ts +13 -0
  62. package/src/__tests__/RouteRepository.test.ts +239 -0
  63. package/src/__tests__/RouterManager.test.ts +125 -0
  64. package/src/__tests__/app.test.tsx +55 -0
  65. package/src/__tests__/authRedirect.test.ts +17 -0
  66. package/src/__tests__/exports.test.ts +16 -0
  67. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +199 -0
  68. package/src/__tests__/plugin-manager.test.tsx +44 -2
  69. package/src/__tests__/settings-center.test.tsx +40 -1
  70. package/src/authRedirect.ts +38 -0
  71. package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
  72. package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
  73. package/src/collection-manager/field-configure.ts +1 -1
  74. package/src/collection-manager/interfaces/id.ts +1 -1
  75. package/src/collection-manager/interfaces/m2m.tsx +2 -2
  76. package/src/collection-manager/interfaces/m2o.tsx +2 -2
  77. package/src/collection-manager/interfaces/nanoid.ts +1 -1
  78. package/src/collection-manager/interfaces/o2m.tsx +2 -2
  79. package/src/collection-manager/interfaces/obo.tsx +2 -2
  80. package/src/collection-manager/interfaces/oho.tsx +2 -2
  81. package/src/collection-manager/interfaces/properties/index.ts +2 -2
  82. package/src/collection-manager/interfaces/uuid.ts +1 -1
  83. package/src/components/AppComponents.tsx +19 -3
  84. package/src/components/form/JsonTextArea.tsx +21 -11
  85. package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +145 -2
  86. package/src/components/form/ScanInput/useCodeScanner.ts +154 -2
  87. package/src/components/form/VariableJsonTextArea.tsx +175 -0
  88. package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
  89. package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
  90. package/src/components/form/index.tsx +1 -0
  91. package/src/components/index.ts +1 -0
  92. package/src/entry-actions/EntryActionManager.ts +76 -0
  93. package/src/entry-actions/index.ts +10 -0
  94. package/src/flow/FlowPage.tsx +38 -3
  95. package/src/flow/__tests__/FlowPage.test.tsx +201 -27
  96. package/src/flow/__tests__/FlowRoute.test.tsx +1088 -105
  97. package/src/flow/__tests__/getKey.test.ts +7 -0
  98. package/src/flow/__tests__/resolveViewParamsToViewList.test.ts +34 -0
  99. package/src/flow/actions/__tests__/afterSuccess.test.ts +73 -0
  100. package/src/flow/actions/__tests__/dataScopeFilter.test.ts +120 -0
  101. package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +1022 -0
  102. package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +150 -1
  103. package/src/flow/actions/__tests__/linkageAssignField.legacy.test.ts +135 -0
  104. package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +19 -5
  105. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +92 -0
  106. package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
  107. package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +164 -0
  108. package/src/flow/actions/__tests__/subFormFieldLinkageRules.inputArgs.test.ts +2 -2
  109. package/src/flow/actions/afterSuccess.tsx +142 -3
  110. package/src/flow/actions/customVariable.tsx +1 -2
  111. package/src/flow/actions/dataScopeFilter.ts +20 -1
  112. package/src/flow/actions/dateTimeFormat.tsx +42 -28
  113. package/src/flow/actions/index.ts +1 -1
  114. package/src/flow/actions/linkageRules.tsx +123 -35
  115. package/src/flow/actions/linkageRulesFormValueRefresh.ts +22 -130
  116. package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
  117. package/src/flow/actions/openView.tsx +59 -5
  118. package/src/flow/actions/runjs.tsx +2 -14
  119. package/src/flow/actions/validation.tsx +62 -30
  120. package/src/flow/admin-shell/BaseLayoutModel.tsx +149 -3
  121. package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +187 -42
  122. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +1020 -110
  123. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +74 -13
  124. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +486 -0
  125. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +64 -8
  126. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.ts +5 -3
  127. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +61 -9
  128. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +71 -8
  129. package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +1 -1
  130. package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +23 -9
  131. package/src/flow/admin-shell/admin-layout/AppListRender.tsx +13 -2
  132. package/src/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.tsx +289 -0
  133. package/src/flow/admin-shell/admin-layout/HelpLite.tsx +1 -3
  134. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +188 -0
  135. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +99 -0
  136. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +518 -2
  137. package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +214 -2
  138. package/src/flow/admin-shell/admin-layout/index.ts +1 -0
  139. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +20 -0
  140. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +33 -5
  141. package/src/flow/admin-shell/admin-layout/useApplications.tsx +81 -12
  142. package/src/flow/common/Markdown/Display.tsx +14 -3
  143. package/src/flow/common/Markdown/Edit.tsx +19 -7
  144. package/src/flow/components/DefaultValue.tsx +1 -2
  145. package/src/flow/components/DynamicFlowsIcon.tsx +447 -126
  146. package/src/flow/components/FieldAssignRulesEditor.tsx +128 -21
  147. package/src/flow/components/FieldAssignValueInput.tsx +15 -11
  148. package/src/flow/components/FlowRoute.tsx +186 -25
  149. package/src/flow/components/__tests__/DynamicFlowsIcon.test.tsx +148 -2
  150. package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +508 -4
  151. package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +134 -0
  152. package/src/flow/components/__tests__/fieldAssignOptions.test.ts +151 -3
  153. package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +2 -17
  154. package/src/flow/components/code-editor/hooks/useCodeRunner.ts +2 -14
  155. package/src/flow/components/code-editor/runjsDiagnostics.ts +8 -45
  156. package/src/flow/components/fieldAssignOptions.ts +155 -27
  157. package/src/flow/flows/editMarkdownFlow.tsx +1 -1
  158. package/src/flow/getViewDiffAndUpdateHidden.tsx +1 -0
  159. package/src/flow/index.ts +1 -1
  160. package/src/flow/internal/components/Markdown/util.ts +2 -1
  161. package/src/flow/internal/utils/operatorSchemaHelper.ts +15 -1
  162. package/src/flow/models/actions/JSActionModel.tsx +2 -7
  163. package/src/flow/models/actions/JSCollectionActionModel.tsx +2 -7
  164. package/src/flow/models/actions/JSItemActionModel.tsx +2 -7
  165. package/src/flow/models/actions/JSRecordActionModel.tsx +2 -7
  166. package/src/flow/models/base/ActionModel.tsx +21 -8
  167. package/src/flow/models/base/ActionModelCore.tsx +11 -4
  168. package/src/flow/models/base/GridModel.tsx +38 -7
  169. package/src/flow/models/base/PageModel/PageModel.tsx +4 -1
  170. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +28 -4
  171. package/src/flow/models/base/__tests__/ActionModel.test.ts +83 -0
  172. package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +37 -0
  173. package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +239 -1
  174. package/src/flow/models/base/__tests__/transformRowsToSingleColumn.test.ts +48 -0
  175. package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +32 -3
  176. package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +27 -12
  177. package/src/flow/models/blocks/filter-form/FilterFormJSActionModel.tsx +2 -7
  178. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +36 -0
  179. package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +111 -0
  180. package/src/flow/models/blocks/filter-form/fields/FilterFormCustomFieldModel.tsx +1 -1
  181. package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/customizeFilterRender.test.tsx +56 -1
  182. package/src/flow/models/blocks/form/EditFormModel.tsx +1 -0
  183. package/src/flow/models/blocks/form/FormActionModel.tsx +1 -1
  184. package/src/flow/models/blocks/form/FormBlockModel.tsx +7 -0
  185. package/src/flow/models/blocks/form/JSFormActionModel.tsx +2 -7
  186. package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
  187. package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
  188. package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
  189. package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +54 -1
  190. package/src/flow/models/blocks/form/submitHandler.ts +17 -3
  191. package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +940 -75
  192. package/src/flow/models/blocks/form/value-runtime/rules.ts +445 -13
  193. package/src/flow/models/blocks/form/value-runtime/runtime.ts +348 -13
  194. package/src/flow/models/blocks/form/value-runtime/types.ts +2 -2
  195. package/src/flow/models/blocks/js-block/JSBlock.tsx +225 -9
  196. package/src/flow/models/blocks/js-block/__tests__/JSBlockModel.test.tsx +150 -0
  197. package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
  198. package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +38 -14
  199. package/src/flow/models/blocks/table/TableBlockModel.tsx +38 -17
  200. package/src/flow/models/blocks/table/TableColumnModel.tsx +42 -5
  201. package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +111 -1
  202. package/src/flow/models/blocks/table/__tests__/TableBlockModel.dragSort.test.ts +127 -0
  203. package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
  204. package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +1 -0
  205. package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +210 -1
  206. package/src/flow/models/blocks/table/dragSort/dragSortHooks.tsx +28 -17
  207. package/src/flow/models/blocks/table/dragSort/dragSortSettings.ts +13 -6
  208. package/src/flow/models/blocks/table/dragSort/dragSortUtils.ts +15 -2
  209. package/src/flow/models/fields/AssociationFieldModel/AssociationFieldModel.tsx +1 -1
  210. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +30 -6
  211. package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +61 -17
  212. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +141 -19
  213. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +13 -1
  214. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +197 -0
  215. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableFieldModel.reset.test.ts +180 -0
  216. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -4
  217. package/src/flow/models/fields/AssociationFieldModel/__tests__/AssociationFieldModel.updateAssociation.test.ts +72 -0
  218. package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +23 -0
  219. package/src/flow/models/fields/ClickableFieldModel.tsx +5 -0
  220. package/src/flow/models/fields/DisplayDateTimeFieldModel.tsx +29 -1
  221. package/src/flow/models/fields/InputFieldModel.tsx +40 -2
  222. package/src/flow/models/fields/JSEditableFieldModel.tsx +2 -11
  223. package/src/flow/models/fields/JSFieldModel.tsx +2 -7
  224. package/src/flow/models/fields/JSItemModel.tsx +2 -14
  225. package/src/flow/models/fields/SelectFieldModel.tsx +6 -4
  226. package/src/flow/models/fields/TextareaFieldModel.tsx +72 -3
  227. package/src/flow/models/fields/__tests__/DisplayDateTimeFieldModel.test.tsx +96 -0
  228. package/src/flow/models/fields/__tests__/InputFieldModel.test.tsx +117 -1
  229. package/src/flow/models/fields/__tests__/SelectFieldModel.test.tsx +43 -0
  230. package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +131 -0
  231. package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +20 -10
  232. package/src/flow/models/fields/mobile-components/MobileSelect.tsx +24 -12
  233. package/src/flow/models/topbar/TopbarActionModel.tsx +168 -10
  234. package/src/flow/resolveViewParamsToViewList.tsx +11 -3
  235. package/src/flow/routeTransientInputArgs.ts +27 -0
  236. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +300 -0
  237. package/src/flow/utils/dataScopeFormValueClear.ts +218 -81
  238. package/src/flow/utils/dataScopeRowSnapshot.ts +616 -0
  239. package/src/flow/utils/dateTimeDisplayProps.ts +135 -0
  240. package/src/flow/utils/formValueDeps.ts +170 -0
  241. package/src/flow-compat/FieldValidation.tsx +122 -60
  242. package/src/flow-compat/Popover.tsx +43 -4
  243. package/src/flow-compat/__tests__/Popover.test.tsx +34 -0
  244. package/src/index.ts +10 -1
  245. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
  246. package/src/nocobase-buildin-plugin/index.tsx +51 -15
  247. package/src/settings-center/AdminSettingsLayout.tsx +10 -2
  248. package/src/settings-center/SystemSettingsPage.tsx +1 -2
  249. package/src/settings-center/plugin-manager/PluginCard.tsx +2 -2
  250. package/src/settings-center/plugin-manager/index.tsx +3 -0
  251. package/src/settings-center/utils.tsx +0 -6
  252. package/src/utils/markdownSanitize.ts +88 -0
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { describe, expect, it, vi } from 'vitest';
11
11
  import { FlowEngine, FlowModel } from '@nocobase/flow-engine';
12
- import { fieldLinkageRules } from '../linkageRules';
12
+ import { fieldLinkageRules, linkageAssignField } from '../linkageRules';
13
13
 
14
14
  describe('fieldLinkageRules action - linkage scope metadata', () => {
15
15
  const targetCollection: any = {
@@ -868,6 +868,155 @@ describe('fieldLinkageRules action - linkage scope metadata', () => {
868
868
  expect(setFormValues).toHaveBeenCalledTimes(2);
869
869
  });
870
870
 
871
+ it('keeps row-scoped override patches editable after the target is user edited', async () => {
872
+ const store = {
873
+ roles: [{ uid: 'role-uid-1', name: 'existing' }],
874
+ };
875
+ const getAt = (path: Array<string | number>) => path.reduce((acc: any, seg) => acc?.[seg], store as any);
876
+ const setAt = (path: Array<string | number>, value: any) => {
877
+ let cur: any = store;
878
+ for (const seg of path.slice(0, -1)) {
879
+ cur = cur[seg];
880
+ }
881
+ cur[path[path.length - 1]] = value;
882
+ };
883
+ const editedKeys = new Set<string>();
884
+ const toKey = (path: Array<string | number>) => JSON.stringify(path);
885
+ const form = {
886
+ getFieldValue: (path: Array<string | number>) => getAt(path),
887
+ };
888
+ const formValueRuntime = {
889
+ canApplyOverrideValuePatch: vi.fn((path: Array<string | number>) => !editedKeys.has(toKey(path))),
890
+ };
891
+ const setFormValues = vi.fn(async (patches: Array<{ path: Array<string | number>; value: any }>) => {
892
+ for (const patch of patches) {
893
+ setAt(patch.path, patch.value);
894
+ }
895
+ });
896
+
897
+ const engine = new FlowEngine();
898
+ const rowFork = new FlowModel({ uid: 'role-override-row-fork', flowEngine: engine }) as any;
899
+ rowFork.context.defineProperty('fieldIndex', {
900
+ value: ['roles:0'],
901
+ });
902
+ rowFork.context.defineProperty('form', {
903
+ value: form,
904
+ });
905
+ rowFork.context.defineProperty('setFormValues', {
906
+ value: setFormValues,
907
+ });
908
+ rowFork.context.defineProperty('app', {
909
+ value: {
910
+ jsonLogic: {
911
+ apply: () => true,
912
+ },
913
+ },
914
+ });
915
+ rowFork.subModels = { items: [] };
916
+ rowFork.getAction = vi.fn((name: string) => {
917
+ if (name === 'linkageAssignField') {
918
+ return linkageAssignField;
919
+ }
920
+ });
921
+
922
+ const masterModel: any = {
923
+ uid: 'master-role-override-grid',
924
+ forks: new Set([rowFork]),
925
+ };
926
+ const rolesField: any = {
927
+ type: 'hasMany',
928
+ isAssociationField: () => true,
929
+ targetCollection: {
930
+ getField: (name: string) => ({ name, isAssociationField: () => false }),
931
+ },
932
+ };
933
+ const blockModel: any = {
934
+ collection: {
935
+ getField: (name: string) => (name === 'roles' ? rolesField : null),
936
+ },
937
+ formValueRuntime,
938
+ };
939
+ rowFork.context.defineProperty('blockModel', {
940
+ value: blockModel,
941
+ });
942
+ const gridModel: any = {
943
+ uid: 'grid-model-role-override-row',
944
+ context: {
945
+ blockModel,
946
+ },
947
+ getAction: vi.fn((name: string) => {
948
+ if (name === 'linkageAssignField') {
949
+ return linkageAssignField;
950
+ }
951
+ }),
952
+ __allModels: [],
953
+ };
954
+ const ctx: any = {
955
+ model: gridModel,
956
+ engine: {
957
+ forEachModel: (cb: (m: any) => void) => {
958
+ cb(masterModel);
959
+ },
960
+ },
961
+ flowKey: 'eventSettings',
962
+ inputArgs: {
963
+ source: 'user',
964
+ txId: 'tx-role-override-row',
965
+ changedPaths: [['roles', 0, 'uid']],
966
+ },
967
+ app: {
968
+ jsonLogic: {
969
+ apply: () => true,
970
+ },
971
+ },
972
+ resolveJsonTemplate: async (v: any) => v,
973
+ };
974
+ const makeParams = (value: string) => ({
975
+ value: [
976
+ {
977
+ key: `rule-${value}`,
978
+ title: `rule-${value}`,
979
+ enable: true,
980
+ condition: { logic: '$and', items: [] },
981
+ actions: [
982
+ {
983
+ name: 'linkageAssignField',
984
+ params: {
985
+ value: [
986
+ {
987
+ key: `r-${value}`,
988
+ enable: true,
989
+ targetPath: 'roles.name',
990
+ mode: 'override',
991
+ value,
992
+ condition: { logic: '$and', items: [] },
993
+ },
994
+ ],
995
+ },
996
+ },
997
+ ],
998
+ },
999
+ ],
1000
+ });
1001
+
1002
+ await fieldLinkageRules.handler(ctx, makeParams('role-uid-1'));
1003
+ expect(store.roles[0].name).toBe('role-uid-1');
1004
+
1005
+ store.roles[0].uid = 'role-uid-2';
1006
+ await fieldLinkageRules.handler(ctx, makeParams('role-uid-2'));
1007
+ expect(store.roles[0].name).toBe('role-uid-2');
1008
+ expect(setFormValues).toHaveBeenCalledTimes(2);
1009
+
1010
+ store.roles[0].name = 'manual';
1011
+ store.roles[0].uid = 'role-uid-3';
1012
+ editedKeys.add(toKey(['roles', 0, 'name']));
1013
+ await fieldLinkageRules.handler(ctx, makeParams('role-uid-3'));
1014
+
1015
+ expect(store.roles[0].name).toBe('manual');
1016
+ expect(setFormValues).toHaveBeenCalledTimes(2);
1017
+ expect(formValueRuntime.canApplyOverrideValuePatch).toHaveBeenCalledWith(['roles', 0, 'name']);
1018
+ });
1019
+
871
1020
  it('skips stale subtable row forks after the row has been removed', async () => {
872
1021
  const setFormValues = vi.fn(async () => undefined);
873
1022
  const defaultHandler = vi.fn((actionCtx: any, { addFormValuePatch }: any) => {
@@ -69,6 +69,72 @@ describe('linkage assign actions - legacy params', () => {
69
69
  expect(setProps).not.toHaveBeenCalledWith(fieldModel, { value: 'default-in-update' });
70
70
  });
71
71
 
72
+ it('linkageAssignField should mark override mode for field targets', async () => {
73
+ const fieldModel: any = { uid: 'f-override', fieldPath: 'a.b' };
74
+ const ctx: any = {
75
+ model: {
76
+ subModels: { grid: { subModels: { items: [fieldModel] } } },
77
+ },
78
+ engine: { getModel: vi.fn(() => ({ fieldPath: 'a.b' })) },
79
+ app: { jsonLogic: { apply: vi.fn() } },
80
+ };
81
+ const setProps = vi.fn();
82
+
83
+ await linkageAssignField.handler(ctx, {
84
+ value: [
85
+ {
86
+ key: 'r-override',
87
+ enable: true,
88
+ targetPath: 'a.b',
89
+ mode: 'override',
90
+ condition: { logic: '$and', items: [] },
91
+ value: 'override-value',
92
+ },
93
+ ],
94
+ setProps,
95
+ });
96
+
97
+ expect(setProps).toHaveBeenCalledTimes(1);
98
+ const [, props] = setProps.mock.calls[0];
99
+ expect(props).toMatchObject({
100
+ value: 'override-value',
101
+ __linkageAssignMode: 'override',
102
+ });
103
+ });
104
+
105
+ it('linkageAssignField should emit override patches for targets without form item models', async () => {
106
+ const ctx: any = {
107
+ model: { subModels: { grid: { subModels: { items: [] } } } },
108
+ engine: { getModel: vi.fn(() => null) },
109
+ app: { jsonLogic: { apply: vi.fn() } },
110
+ };
111
+ const setProps = vi.fn();
112
+ const addFormValuePatch = vi.fn();
113
+
114
+ await linkageAssignField.handler(ctx, {
115
+ value: [
116
+ {
117
+ key: 'r-override-direct',
118
+ enable: true,
119
+ targetPath: 'user.name',
120
+ mode: 'override',
121
+ condition: { logic: '$and', items: [] },
122
+ value: 'override-direct',
123
+ },
124
+ ],
125
+ setProps,
126
+ addFormValuePatch,
127
+ });
128
+
129
+ expect(setProps).not.toHaveBeenCalled();
130
+ expect(addFormValuePatch).toHaveBeenCalledWith({
131
+ path: 'user.name',
132
+ value: 'override-direct',
133
+ whenEmpty: false,
134
+ mode: 'override',
135
+ });
136
+ });
137
+
72
138
  it('subFormLinkageAssignField should apply legacy object params at runtime', () => {
73
139
  const forkModel: any = {
74
140
  uid: 'f1',
@@ -381,6 +447,75 @@ describe('linkage assign actions - legacy params', () => {
381
447
  expect(addFormValuePatch).toHaveBeenCalledTimes(1);
382
448
  });
383
449
 
450
+ it('subFormLinkageAssignField should skip override patch after user edits the target path', () => {
451
+ const ctx: any = {
452
+ model: {
453
+ uid: 'sub-form-override-path',
454
+ getAclActionName: vi.fn(() => 'create'),
455
+ context: {},
456
+ parent: {
457
+ getStepParams: (flowKey: string, stepKey: string) => {
458
+ if (flowKey === 'fieldSettings' && stepKey === 'init') {
459
+ return { fieldPath: 'M2M.M2M' };
460
+ }
461
+ return {};
462
+ },
463
+ },
464
+ subModels: { grid: { subModels: { items: [] } } },
465
+ },
466
+ engine: {
467
+ getModel: vi.fn(() => null),
468
+ },
469
+ app: { jsonLogic: { apply: vi.fn() } },
470
+ };
471
+ const addFormValuePatch = vi.fn();
472
+
473
+ subFormLinkageAssignField.handler(ctx, {
474
+ value: [
475
+ {
476
+ key: 'r-override-path-1',
477
+ enable: true,
478
+ targetPath: 'Name',
479
+ mode: 'override',
480
+ condition: { logic: '$and', items: [] },
481
+ value: 'override-1',
482
+ },
483
+ ],
484
+ addFormValuePatch,
485
+ setProps: vi.fn(),
486
+ });
487
+
488
+ expect(addFormValuePatch).toHaveBeenCalledTimes(1);
489
+ expect(addFormValuePatch).toHaveBeenNthCalledWith(1, {
490
+ path: 'M2M.M2M.Name',
491
+ value: 'override-1',
492
+ whenEmpty: false,
493
+ mode: 'override',
494
+ });
495
+
496
+ ctx.inputArgs = {
497
+ source: 'user',
498
+ changedPaths: [['M2M', 'M2M', 'Name']],
499
+ };
500
+
501
+ subFormLinkageAssignField.handler(ctx, {
502
+ value: [
503
+ {
504
+ key: 'r-override-path-2',
505
+ enable: true,
506
+ targetPath: 'Name',
507
+ mode: 'override',
508
+ condition: { logic: '$and', items: [] },
509
+ value: 'override-2',
510
+ },
511
+ ],
512
+ addFormValuePatch,
513
+ setProps: vi.fn(),
514
+ });
515
+
516
+ expect(addFormValuePatch).toHaveBeenCalledTimes(1);
517
+ });
518
+
384
519
  it('linkageAssignField should evaluate RunJS value before assign', async () => {
385
520
  const fieldModel: any = { uid: 'f-runjs-assign', fieldPath: 'a.b' };
386
521
  const runjs = vi.fn(async () => ({ success: true, value: 'from-runjs' }));
@@ -27,12 +27,12 @@ describe('linkageRules hidden state propagation', () => {
27
27
  const hostModel: any = {
28
28
  uid: 'host',
29
29
  hidden: false,
30
- context: { blockModel },
30
+ context: { blockModel, fieldPathArray: ['children'] },
31
31
  __allModels: [],
32
32
  setProps: vi.fn(),
33
33
  getFlow: vi.fn(() => ({})),
34
34
  getStepParams: vi.fn((_flowKey: string, stepKey: string) => {
35
- if (stepKey === 'init') return { fieldPath: 'org_m2o' };
35
+ if (stepKey === 'init') return { fieldPath: 'children' };
36
36
  return undefined;
37
37
  }),
38
38
  translate: (s: string) => s,
@@ -41,10 +41,22 @@ describe('linkageRules hidden state propagation', () => {
41
41
  const sameTargetModel: any = {
42
42
  uid: 'same-target',
43
43
  hidden: false,
44
- context: { blockModel },
44
+ context: { blockModel, fieldPathArray: ['children'] },
45
45
  setProps: vi.fn(),
46
46
  getStepParams: vi.fn((_flowKey: string, stepKey: string) => {
47
- if (stepKey === 'init') return { fieldPath: 'org_m2o' };
47
+ if (stepKey === 'init') return { fieldPath: 'children' };
48
+ return undefined;
49
+ }),
50
+ translate: (s: string) => s,
51
+ };
52
+
53
+ const nestedColumnModel: any = {
54
+ uid: 'nested-column',
55
+ hidden: true,
56
+ context: { blockModel, fieldPathArray: ['children'] },
57
+ setProps: vi.fn(),
58
+ getStepParams: vi.fn((_flowKey: string, stepKey: string) => {
59
+ if (stepKey === 'init') return { fieldPath: 'children.children' };
48
60
  return undefined;
49
61
  }),
50
62
  translate: (s: string) => s,
@@ -52,7 +64,7 @@ describe('linkageRules hidden state propagation', () => {
52
64
 
53
65
  const engine = {
54
66
  forEachModel: (visitor: (m: any) => void) => {
55
- [hostModel, sameTargetModel].forEach(visitor);
67
+ [hostModel, sameTargetModel, nestedColumnModel].forEach(visitor);
56
68
  },
57
69
  };
58
70
 
@@ -92,9 +104,11 @@ describe('linkageRules hidden state propagation', () => {
92
104
  });
93
105
  expect(hostModel.hidden).toBe(true);
94
106
  expect(sameTargetModel.hidden).toBe(true);
107
+ expect(nestedColumnModel.hidden).toBe(true);
95
108
 
96
109
  await actionLinkageRules.handler(ctx, { value: [] });
97
110
  expect(hostModel.hidden).toBe(false);
98
111
  expect(sameTargetModel.hidden).toBe(false);
112
+ expect(nestedColumnModel.hidden).toBe(true);
99
113
  });
100
114
  });
@@ -728,6 +728,98 @@ describe('linkageSetFieldProps action', () => {
728
728
  expect(form.setFieldValue).not.toHaveBeenCalled();
729
729
  });
730
730
 
731
+ it('should not clear the parent field when a nested same-name field inherits the parent path', async () => {
732
+ const setFormValues = vi.fn(async () => undefined);
733
+ const form = {
734
+ getFieldValue: vi.fn((path: Array<string | number>) => {
735
+ if (JSON.stringify(path) === JSON.stringify(['children', 'children'])) {
736
+ return [{ title: 'nested' }];
737
+ }
738
+ if (JSON.stringify(path) === JSON.stringify(['children'])) {
739
+ return [{ title: 'parent' }];
740
+ }
741
+ return undefined;
742
+ }),
743
+ setFieldValue: vi.fn(),
744
+ };
745
+ const fieldModel: any = {
746
+ uid: 'nested-children-field',
747
+ hidden: false,
748
+ context: {
749
+ form,
750
+ fieldPathArray: ['children'],
751
+ },
752
+ props: {
753
+ label: 'Children',
754
+ },
755
+ getStepParams: vi.fn((flowKey: string, stepKey: string) => {
756
+ if (flowKey === 'fieldSettings' && stepKey === 'init') {
757
+ return { fieldPath: 'children.children' };
758
+ }
759
+ }),
760
+ setProps(key: any, value?: any) {
761
+ if (typeof key === 'string') {
762
+ this.props[key] = value;
763
+ } else {
764
+ this.props = { ...this.props, ...key };
765
+ }
766
+ },
767
+ };
768
+ const ctx: any = {
769
+ app: {
770
+ jsonLogic: {
771
+ apply: vi.fn(() => true),
772
+ },
773
+ },
774
+ model: {
775
+ context: { form },
776
+ subModels: {
777
+ grid: {
778
+ subModels: {
779
+ items: [fieldModel],
780
+ },
781
+ },
782
+ },
783
+ },
784
+ setFormValues,
785
+ getAction: (name: string) => (name === 'linkageSetFieldProps' ? linkageSetFieldProps : null),
786
+ resolveJsonTemplate: vi.fn(async (value) => value),
787
+ };
788
+
789
+ await fieldLinkageRules.handler(ctx, {
790
+ value: [
791
+ {
792
+ key: 'rule-1',
793
+ enable: true,
794
+ condition: { logic: '$and', items: [] },
795
+ actions: [
796
+ {
797
+ key: 'action-1',
798
+ name: 'linkageSetFieldProps',
799
+ params: {
800
+ value: {
801
+ fields: ['nested-children-field'],
802
+ state: 'hidden',
803
+ },
804
+ },
805
+ },
806
+ ],
807
+ },
808
+ ],
809
+ });
810
+
811
+ expect(fieldModel.hidden).toBe(true);
812
+ expect(setFormValues).toHaveBeenCalledWith(
813
+ [{ path: ['children', 'children'], value: undefined }],
814
+ expect.objectContaining({ source: 'linkage' }),
815
+ );
816
+ expect(setFormValues).not.toHaveBeenCalledWith(
817
+ [{ path: ['children'], value: undefined }],
818
+ expect.objectContaining({ source: 'linkage' }),
819
+ );
820
+ expect(form.setFieldValue).not.toHaveBeenCalled();
821
+ });
822
+
731
823
  it('should keep hidden clear after same-round assignment for the same field', async () => {
732
824
  const formValues: { name?: string } = {};
733
825
  const setFormValues = vi.fn(async (patches: Array<{ path: Array<string | number>; value: string | undefined }>) => {
@@ -135,11 +135,15 @@ describe('linkageRulesRefresh action', () => {
135
135
  expect(handler).toHaveBeenCalledWith(ctx, { value: ['master-mounted'] });
136
136
  });
137
137
 
138
- it('runs linkage action on master model when forks exist in design mode', async () => {
138
+ it('skips master model when forks can handle flow in design mode', async () => {
139
139
  const handler = vi.fn(async () => {});
140
140
  const model: any = {
141
141
  isFork: false,
142
- forks: new Set([{}]),
142
+ forks: new Set([
143
+ {
144
+ getFlow: vi.fn(() => ({})),
145
+ },
146
+ ]),
143
147
  getFlow: vi.fn(() => ({})),
144
148
  getStepParams: vi.fn(() => ({ value: ['master'] })),
145
149
  context: {
@@ -157,7 +161,7 @@ describe('linkageRulesRefresh action', () => {
157
161
  flowKey: 'buttonSettings',
158
162
  });
159
163
 
160
- expect(handler).toHaveBeenCalledWith(ctx, { value: ['master'] });
164
+ expect(handler).not.toHaveBeenCalled();
161
165
  });
162
166
 
163
167
  it('runs linkage action on fork model and resolves params', async () => {
@@ -9,8 +9,10 @@
9
9
 
10
10
  import React from 'react';
11
11
  import { describe, it, expect, vi } from 'vitest';
12
+ import { RUNJS_OPEN_VIEW_ROUTE_STATE } from '@nocobase/flow-engine';
12
13
  import { openView } from '../openView';
13
14
  import { FlowPage } from '../../FlowPage';
15
+ import { ROUTE_TRANSIENT_INPUT_ARGS_KEY } from '../../routeTransientInputArgs';
14
16
 
15
17
  describe('openView action - route mode defineProperties/defineMethods', () => {
16
18
  const createRouteManagedCtx = () => {
@@ -50,6 +52,27 @@ describe('openView action - route mode defineProperties/defineMethods', () => {
50
52
  return { ctx, engine };
51
53
  };
52
54
 
55
+ const createFirstStageCtx = (inputArgs: Record<PropertyKey, unknown>) => {
56
+ const navigateTo = vi.fn();
57
+ const ctx: any = {
58
+ inputArgs,
59
+ engine: { context: { themeToken: { colorBgLayout: '#fff' } } },
60
+ model: {
61
+ uid: 'popup-uid',
62
+ context: {
63
+ inputArgs: {},
64
+ defineProperty: vi.fn(),
65
+ },
66
+ flowEngine: { context: { themeToken: { colorBgLayout: '#fff' } } },
67
+ },
68
+ view: {
69
+ navigation: { navigateTo },
70
+ },
71
+ };
72
+
73
+ return { ctx, navigateTo };
74
+ };
75
+
53
76
  it('injects defineProperties/defineMethods on onModelLoaded even when not present in current inputArgs', async () => {
54
77
  const { ctx } = createRouteManagedCtx();
55
78
 
@@ -149,4 +172,145 @@ describe('openView action - route mode defineProperties/defineMethods', () => {
149
172
  const defineMethodCalls = pageModelContext.defineMethod.mock.calls.map((c: any[]) => c[0]);
150
173
  expect(defineMethodCalls).toContain('pong');
151
174
  });
175
+
176
+ it('passes formData through route navigation state', async () => {
177
+ const navigateTo = vi.fn();
178
+ const ctx: any = {
179
+ inputArgs: {
180
+ formData: {
181
+ start: '2026-06-24 08:00:00',
182
+ end: '2026-06-24 09:00:00',
183
+ },
184
+ },
185
+ engine: {
186
+ context: {},
187
+ },
188
+ model: {
189
+ uid: 'popup-uid',
190
+ context: {
191
+ defineProperty: vi.fn(),
192
+ },
193
+ },
194
+ view: {
195
+ navigation: {
196
+ navigateTo,
197
+ },
198
+ },
199
+ isNavigationEnabled: true,
200
+ };
201
+
202
+ await openView.handler(ctx, { navigation: true });
203
+
204
+ expect(navigateTo).toHaveBeenCalledWith(
205
+ {
206
+ viewUid: 'popup-uid',
207
+ filterByTk: undefined,
208
+ sourceId: undefined,
209
+ tabUid: undefined,
210
+ },
211
+ {
212
+ state: {
213
+ [ROUTE_TRANSIENT_INPUT_ARGS_KEY]: {
214
+ 'popup-uid': {
215
+ formData: {
216
+ start: '2026-06-24 08:00:00',
217
+ end: '2026-06-24 09:00:00',
218
+ },
219
+ },
220
+ },
221
+ },
222
+ },
223
+ );
224
+ });
225
+
226
+ it('adds route state to first-stage navigation only for RunJS ctx.openView calls', async () => {
227
+ const { ctx, navigateTo } = createFirstStageCtx({
228
+ mode: 'dialog',
229
+ size: 'large',
230
+ [RUNJS_OPEN_VIEW_ROUTE_STATE]: { mode: 'dialog', size: 'large' },
231
+ });
232
+
233
+ await openView.handler(ctx, { mode: 'drawer', size: 'medium', navigation: true });
234
+
235
+ expect(navigateTo).toHaveBeenCalledWith(
236
+ {
237
+ viewUid: 'popup-uid',
238
+ filterByTk: undefined,
239
+ sourceId: undefined,
240
+ tabUid: undefined,
241
+ openViewRouteState: { mode: 'dialog', size: 'large' },
242
+ },
243
+ undefined,
244
+ );
245
+ });
246
+
247
+ it('normalizes first-stage RunJS route state mode on mobile layout', async () => {
248
+ const { ctx, navigateTo } = createFirstStageCtx({
249
+ isMobileLayout: true,
250
+ mode: 'dialog',
251
+ size: 'large',
252
+ [RUNJS_OPEN_VIEW_ROUTE_STATE]: { mode: 'dialog', size: 'large' },
253
+ });
254
+
255
+ await openView.handler(ctx, { mode: 'drawer', size: 'medium', navigation: true });
256
+
257
+ expect(navigateTo).toHaveBeenCalledWith(
258
+ {
259
+ viewUid: 'popup-uid',
260
+ filterByTk: undefined,
261
+ sourceId: undefined,
262
+ tabUid: undefined,
263
+ openViewRouteState: { mode: 'embed', size: 'large' },
264
+ },
265
+ undefined,
266
+ );
267
+ });
268
+
269
+ it('uses route replay mode and size before persisted openView defaults', async () => {
270
+ const { ctx } = createRouteManagedCtx();
271
+ ctx.inputArgs.mode = 'dialog';
272
+ ctx.inputArgs.size = 'large';
273
+
274
+ await openView.handler(ctx, { mode: 'drawer', size: 'medium', navigation: true });
275
+
276
+ expect(ctx.viewer.open).toHaveBeenCalledWith(
277
+ expect.objectContaining({
278
+ type: 'dialog',
279
+ width: '80%',
280
+ }),
281
+ );
282
+ });
283
+
284
+ it('uses decoded openView route state during route replay', async () => {
285
+ const { ctx } = createRouteManagedCtx();
286
+ ctx.inputArgs.openViewRouteState = { mode: 'dialog', size: 'large' };
287
+
288
+ await openView.handler(ctx, { mode: 'drawer', size: 'medium', navigation: true });
289
+
290
+ expect(ctx.viewer.open).toHaveBeenCalledWith(
291
+ expect.objectContaining({
292
+ type: 'dialog',
293
+ width: '80%',
294
+ }),
295
+ );
296
+ });
297
+
298
+ it('keeps first-stage navigation unchanged for non-RunJS openView calls', async () => {
299
+ const { ctx, navigateTo } = createFirstStageCtx({
300
+ mode: 'dialog',
301
+ size: 'large',
302
+ });
303
+
304
+ await openView.handler(ctx, { mode: 'drawer', size: 'medium', navigation: true });
305
+
306
+ expect(navigateTo).toHaveBeenCalledWith(
307
+ {
308
+ viewUid: 'popup-uid',
309
+ filterByTk: undefined,
310
+ sourceId: undefined,
311
+ tabUid: undefined,
312
+ },
313
+ undefined,
314
+ );
315
+ });
152
316
  });
@@ -13,7 +13,7 @@ import { subFormFieldLinkageRules } from '../linkageRules';
13
13
  import { SubFormFieldModel, SubFormListFieldModel } from '../../models/fields/AssociationFieldModel/SubFormFieldModel';
14
14
 
15
15
  describe('subFormFieldLinkageRules action', () => {
16
- it('passes inputArgs to fork runtime context', async () => {
16
+ it('passes inputArgs to fork runtime context when grid forks are stored in a Set', async () => {
17
17
  const linkageAssignHandler = vi.fn();
18
18
  const engine = new FlowEngine();
19
19
  const forkModel = new FlowModel({ uid: 'fork-model', flowEngine: engine }) as any;
@@ -38,7 +38,7 @@ describe('subFormFieldLinkageRules action', () => {
38
38
  hidden: false,
39
39
  subModels: {
40
40
  grid: {
41
- forks: [forkModel],
41
+ forks: new Set([forkModel]),
42
42
  },
43
43
  },
44
44
  },