@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
@@ -93,4 +93,1128 @@ describe('ensureFormValueDrivenDataScopeClear', () => {
93
93
 
94
94
  expect(onChange).not.toHaveBeenCalled();
95
95
  });
96
+
97
+ it('maps ctx.item.value dependencies to the current row path', () => {
98
+ const emitter = new EventEmitter();
99
+ const formBlock = {
100
+ uid: 'form-1',
101
+ disposed: false,
102
+ emitter,
103
+ context: { form: {} },
104
+ };
105
+
106
+ const onChange = vi.fn();
107
+ const model: any = {
108
+ disposed: false,
109
+ props: {
110
+ value: { id: 1 },
111
+ onChange,
112
+ },
113
+ context: {
114
+ blockModel: formBlock,
115
+ fieldIndex: ['users:1'],
116
+ },
117
+ };
118
+
119
+ const ctx: any = {
120
+ model,
121
+ flowKey: 'selectSettings',
122
+ };
123
+
124
+ const filter = {
125
+ logic: '$and',
126
+ items: [{ path: 'nickname', operator: '$eq', value: '{{ ctx.item.value.nickname }}' }],
127
+ };
128
+
129
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
130
+
131
+ emitter.emit('formValuesChange', {
132
+ changedPaths: [['users', 0, 'nickname']],
133
+ });
134
+ expect(onChange).not.toHaveBeenCalled();
135
+
136
+ emitter.emit('formValuesChange', {
137
+ changedPaths: [['users', 1, 'nickname']],
138
+ });
139
+ expect(onChange).toHaveBeenCalledWith(null);
140
+ });
141
+
142
+ it('parses object-patch changedPaths before matching ctx.item.value dependencies', () => {
143
+ const emitter = new EventEmitter();
144
+ const formBlock = {
145
+ uid: 'form-1',
146
+ disposed: false,
147
+ emitter,
148
+ context: { form: {} },
149
+ };
150
+
151
+ const onChange = vi.fn();
152
+ const model: any = {
153
+ disposed: false,
154
+ props: {
155
+ value: { id: 1 },
156
+ onChange,
157
+ },
158
+ context: {
159
+ blockModel: formBlock,
160
+ fieldIndex: ['users:1'],
161
+ },
162
+ };
163
+
164
+ const ctx: any = {
165
+ model,
166
+ flowKey: 'selectSettings',
167
+ };
168
+
169
+ const filter = {
170
+ logic: '$and',
171
+ items: [{ path: 'nickname', operator: '$eq', value: '{{ ctx.item.value.nickname }}' }],
172
+ };
173
+
174
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
175
+
176
+ emitter.emit('formValuesChange', {
177
+ changedPaths: [['users[1].nickname']],
178
+ });
179
+
180
+ expect(onChange).toHaveBeenCalledWith(null);
181
+ });
182
+
183
+ it('maps root parentItem.value dependencies to the root form path', () => {
184
+ const emitter = new EventEmitter();
185
+ const formBlock = {
186
+ uid: 'form-1',
187
+ disposed: false,
188
+ emitter,
189
+ context: { form: {} },
190
+ };
191
+
192
+ const onChange = vi.fn();
193
+ const model: any = {
194
+ disposed: false,
195
+ props: {
196
+ value: { id: 1 },
197
+ onChange,
198
+ },
199
+ context: {
200
+ blockModel: formBlock,
201
+ fieldIndex: ['roles:0'],
202
+ },
203
+ };
204
+
205
+ const ctx: any = {
206
+ model,
207
+ flowKey: 'selectSettings',
208
+ };
209
+
210
+ const filter = {
211
+ logic: '$and',
212
+ items: [{ path: 'departmentId', operator: '$eq', value: '{{ ctx.item.parentItem.value.departmentId }}' }],
213
+ };
214
+
215
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
216
+
217
+ emitter.emit('formValuesChange', {
218
+ changedPaths: [['roles', 0, 'name']],
219
+ });
220
+ expect(onChange).not.toHaveBeenCalled();
221
+
222
+ emitter.emit('formValuesChange', {
223
+ changedPaths: [['departmentId']],
224
+ });
225
+ expect(onChange).toHaveBeenCalledWith(null);
226
+ });
227
+
228
+ it('does not clear a popup form field when data scope depends on the external parent item', () => {
229
+ const emitter = new EventEmitter();
230
+ const formBlock = {
231
+ uid: 'form-1',
232
+ disposed: false,
233
+ emitter,
234
+ context: {
235
+ form: {},
236
+ formValues: { staff_m2o: null },
237
+ },
238
+ };
239
+
240
+ const onChange = vi.fn();
241
+ const model: any = {
242
+ disposed: false,
243
+ props: {
244
+ value: { id: 10 },
245
+ onChange,
246
+ },
247
+ context: {
248
+ blockModel: formBlock,
249
+ },
250
+ };
251
+
252
+ const ctx: any = {
253
+ model,
254
+ flowKey: 'selectSettings',
255
+ };
256
+
257
+ const filter = {
258
+ logic: '$and',
259
+ items: [{ path: 'orgId', operator: '$eq', value: '{{ ctx.item.parentItem.value.org_m2o.id }}' }],
260
+ };
261
+
262
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
263
+
264
+ emitter.emit('formValuesChange', {
265
+ changedPaths: [['staff_m2o']],
266
+ allValues: { staff_m2o: { id: 10 } },
267
+ });
268
+
269
+ expect(onChange).not.toHaveBeenCalled();
270
+ });
271
+
272
+ it('maps popup current item dependencies to the popup form root', () => {
273
+ const emitter = new EventEmitter();
274
+ const formBlock = {
275
+ uid: 'form-1',
276
+ disposed: false,
277
+ emitter,
278
+ context: {
279
+ form: {},
280
+ formValues: {
281
+ org_m2o: { id: 1 },
282
+ staff_m2o: { id: 10 },
283
+ },
284
+ },
285
+ };
286
+
287
+ const onChange = vi.fn();
288
+ const model: any = {
289
+ disposed: false,
290
+ props: {
291
+ value: { id: 10 },
292
+ onChange,
293
+ },
294
+ context: {
295
+ blockModel: formBlock,
296
+ },
297
+ };
298
+
299
+ const ctx: any = {
300
+ model,
301
+ flowKey: 'selectSettings',
302
+ };
303
+
304
+ const filter = {
305
+ logic: '$and',
306
+ items: [{ path: 'orgId', operator: '$eq', value: '{{ ctx.item.value.org_m2o.id }}' }],
307
+ };
308
+
309
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
310
+
311
+ emitter.emit('formValuesChange', {
312
+ changedPaths: [['staff_m2o']],
313
+ allValues: {
314
+ org_m2o: { id: 1 },
315
+ staff_m2o: { id: 10 },
316
+ },
317
+ });
318
+ expect(onChange).not.toHaveBeenCalled();
319
+
320
+ emitter.emit('formValuesChange', {
321
+ changedPaths: [['org_m2o']],
322
+ allValues: {
323
+ org_m2o: { id: 2 },
324
+ staff_m2o: { id: 10 },
325
+ },
326
+ });
327
+ expect(onChange).toHaveBeenCalledWith(null);
328
+ });
329
+
330
+ it('does not clear a row field when a sibling field changes but the item dependency value is unchanged', () => {
331
+ const emitter = new EventEmitter();
332
+ const formBlock = {
333
+ uid: 'form-1',
334
+ disposed: false,
335
+ emitter,
336
+ context: {
337
+ form: {},
338
+ formValues: {
339
+ org_o2m: [{ companyName: 'a', user_m2o: { id: 1 }, staff_m2o: null }],
340
+ },
341
+ },
342
+ };
343
+
344
+ const onChange = vi.fn();
345
+ const model: any = {
346
+ disposed: false,
347
+ props: {
348
+ value: { id: 1 },
349
+ onChange,
350
+ },
351
+ context: {
352
+ blockModel: formBlock,
353
+ fieldIndex: ['org_o2m:0'],
354
+ },
355
+ };
356
+
357
+ const ctx: any = {
358
+ model,
359
+ flowKey: 'selectSettings',
360
+ };
361
+
362
+ const filter = {
363
+ logic: '$and',
364
+ items: [{ path: 'name', operator: '$includes', value: '{{ ctx.item.value.companyName }}' }],
365
+ };
366
+
367
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
368
+
369
+ emitter.emit('formValuesChange', {
370
+ changedPaths: [['org_o2m', 0]],
371
+ allValues: {
372
+ org_o2m: [{ companyName: 'a', user_m2o: { id: 1 }, staff_m2o: { id: 10 } }],
373
+ },
374
+ });
375
+
376
+ expect(onChange).not.toHaveBeenCalled();
377
+ });
378
+
379
+ it('clears a row field when a coarse row change includes an actual item dependency value change', () => {
380
+ const emitter = new EventEmitter();
381
+ const formBlock = {
382
+ uid: 'form-1',
383
+ disposed: false,
384
+ emitter,
385
+ context: {
386
+ form: {},
387
+ formValues: {
388
+ org_o2m: [{ companyName: 'a', user_m2o: { id: 1 } }],
389
+ },
390
+ },
391
+ };
392
+
393
+ const onChange = vi.fn();
394
+ const model: any = {
395
+ disposed: false,
396
+ props: {
397
+ value: { id: 1 },
398
+ onChange,
399
+ },
400
+ context: {
401
+ blockModel: formBlock,
402
+ fieldIndex: ['org_o2m:0'],
403
+ },
404
+ };
405
+
406
+ const ctx: any = {
407
+ model,
408
+ flowKey: 'selectSettings',
409
+ };
410
+
411
+ const filter = {
412
+ logic: '$and',
413
+ items: [{ path: 'name', operator: '$includes', value: '{{ ctx.item.value.companyName }}' }],
414
+ };
415
+
416
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
417
+
418
+ emitter.emit('formValuesChange', {
419
+ changedPaths: [['org_o2m', 0]],
420
+ allValues: {
421
+ org_o2m: [{ companyName: 'b', user_m2o: { id: 1 } }],
422
+ },
423
+ });
424
+
425
+ expect(onChange).toHaveBeenCalledWith(null);
426
+ });
427
+
428
+ it('clears a dependent row field when the sibling relation used by data scope changes', () => {
429
+ const emitter = new EventEmitter();
430
+ const formBlock = {
431
+ uid: 'form-1',
432
+ disposed: false,
433
+ emitter,
434
+ context: {
435
+ form: {},
436
+ formValues: {
437
+ org_o2m: [{ companyName: 'a', user_m2o: { id: 1 }, staff_m2o: { id: 10 } }],
438
+ },
439
+ },
440
+ };
441
+
442
+ const onChange = vi.fn();
443
+ const model: any = {
444
+ disposed: false,
445
+ props: {
446
+ value: { id: 10 },
447
+ onChange,
448
+ },
449
+ context: {
450
+ blockModel: formBlock,
451
+ fieldIndex: ['org_o2m:0'],
452
+ },
453
+ };
454
+
455
+ const ctx: any = {
456
+ model,
457
+ flowKey: 'selectSettings',
458
+ };
459
+
460
+ const filter = {
461
+ logic: '$and',
462
+ items: [{ path: 'id', operator: '$eq', value: '{{ ctx.item.value.user_m2o.id }}' }],
463
+ };
464
+
465
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
466
+
467
+ emitter.emit('formValuesChange', {
468
+ changedPaths: [['org_o2m', 0]],
469
+ allValues: {
470
+ org_o2m: [{ companyName: 'a', user_m2o: { id: 2 }, staff_m2o: { id: 10 } }],
471
+ },
472
+ });
473
+
474
+ expect(onChange).toHaveBeenCalledWith(null);
475
+ });
476
+
477
+ it('does not clear a row field when another row is removed after it', () => {
478
+ const emitter = new EventEmitter();
479
+ const formBlock = {
480
+ uid: 'form-1',
481
+ disposed: false,
482
+ emitter,
483
+ context: {
484
+ form: {},
485
+ formValues: {
486
+ org_o2m: [
487
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
488
+ { __index__: 'row-b', companyName: 'b', user_m2o: { id: 2 } },
489
+ ],
490
+ },
491
+ },
492
+ };
493
+
494
+ const onChange = vi.fn();
495
+ const model: any = {
496
+ disposed: false,
497
+ props: {
498
+ value: { id: 1 },
499
+ onChange,
500
+ },
501
+ context: {
502
+ blockModel: formBlock,
503
+ fieldIndex: ['org_o2m:0'],
504
+ },
505
+ };
506
+
507
+ const ctx: any = {
508
+ model,
509
+ flowKey: 'selectSettings',
510
+ };
511
+
512
+ const filter = {
513
+ logic: '$and',
514
+ items: [{ path: 'name', operator: '$includes', value: '{{ ctx.item.value.companyName }}' }],
515
+ };
516
+
517
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
518
+
519
+ emitter.emit('formValuesChange', {
520
+ changedPaths: [['org_o2m']],
521
+ allValues: {
522
+ org_o2m: [{ __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } }],
523
+ },
524
+ });
525
+
526
+ expect(onChange).not.toHaveBeenCalled();
527
+ });
528
+
529
+ it('does not clear a row field when another row is removed before it and the row index changes', () => {
530
+ const emitter = new EventEmitter();
531
+ const formBlock = {
532
+ uid: 'form-1',
533
+ disposed: false,
534
+ emitter,
535
+ context: {
536
+ form: {},
537
+ formValues: {
538
+ org_o2m: [
539
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
540
+ { __index__: 'row-b', companyName: 'b', user_m2o: { id: 2 } },
541
+ ],
542
+ },
543
+ },
544
+ };
545
+
546
+ const onChange = vi.fn();
547
+ const model: any = {
548
+ disposed: false,
549
+ props: {
550
+ value: { id: 2 },
551
+ onChange,
552
+ },
553
+ context: {
554
+ blockModel: formBlock,
555
+ fieldIndex: ['org_o2m:1'],
556
+ },
557
+ };
558
+
559
+ const ctx: any = {
560
+ model,
561
+ flowKey: 'selectSettings',
562
+ };
563
+
564
+ const filter = {
565
+ logic: '$and',
566
+ items: [{ path: 'name', operator: '$includes', value: '{{ ctx.item.value.companyName }}' }],
567
+ };
568
+
569
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
570
+
571
+ emitter.emit('formValuesChange', {
572
+ changedPaths: [['org_o2m']],
573
+ allValues: {
574
+ org_o2m: [{ __index__: 'row-b', companyName: 'b', user_m2o: { id: 2 } }],
575
+ },
576
+ });
577
+
578
+ expect(onChange).not.toHaveBeenCalled();
579
+ });
580
+
581
+ it('does not clear a row field after reindexing when rows use a custom filter target key', () => {
582
+ const emitter = new EventEmitter();
583
+ const usersField = {
584
+ targetCollection: { filterTargetKey: 'name' },
585
+ isAssociationField: () => true,
586
+ };
587
+ const formBlock = {
588
+ uid: 'form-1',
589
+ disposed: false,
590
+ emitter,
591
+ context: {
592
+ form: {},
593
+ collection: { getField: (name: string) => (name === 'org_o2m' ? usersField : null) },
594
+ formValues: {
595
+ org_o2m: [
596
+ { name: 'alice', companyName: 'a', user_m2o: { id: 1 } },
597
+ { name: 'bob', companyName: 'b', user_m2o: { id: 2 } },
598
+ ],
599
+ },
600
+ },
601
+ };
602
+
603
+ const onChange = vi.fn();
604
+ const model: any = {
605
+ disposed: false,
606
+ props: {
607
+ value: { id: 2 },
608
+ onChange,
609
+ },
610
+ context: {
611
+ blockModel: formBlock,
612
+ fieldIndex: ['org_o2m:1'],
613
+ },
614
+ };
615
+
616
+ const ctx: any = {
617
+ model,
618
+ flowKey: 'selectSettings',
619
+ };
620
+
621
+ const filter = {
622
+ logic: '$and',
623
+ items: [{ path: 'name', operator: '$includes', value: '{{ ctx.item.value.companyName }}' }],
624
+ };
625
+
626
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
627
+
628
+ emitter.emit('formValuesChange', {
629
+ changedPaths: [['org_o2m']],
630
+ allValues: {
631
+ org_o2m: [{ name: 'bob', companyName: 'b', user_m2o: { id: 2 } }],
632
+ },
633
+ });
634
+
635
+ expect(onChange).not.toHaveBeenCalled();
636
+ });
637
+
638
+ it('does not clear a row field after reindexing when rows use a composite filter target key', () => {
639
+ const emitter = new EventEmitter();
640
+ const usersField = {
641
+ targetCollection: { filterTargetKey: ['tenantId', 'code'] },
642
+ isAssociationField: () => true,
643
+ };
644
+ const formBlock = {
645
+ uid: 'form-1',
646
+ disposed: false,
647
+ emitter,
648
+ context: {
649
+ form: {},
650
+ collection: { getField: (name: string) => (name === 'org_o2m' ? usersField : null) },
651
+ formValues: {
652
+ org_o2m: [
653
+ { tenantId: 1, code: 'a', companyName: 'a', user_m2o: { id: 1 } },
654
+ { tenantId: 1, code: 'b', companyName: 'b', user_m2o: { id: 2 } },
655
+ ],
656
+ },
657
+ },
658
+ };
659
+
660
+ const onChange = vi.fn();
661
+ const model: any = {
662
+ disposed: false,
663
+ props: {
664
+ value: { id: 2 },
665
+ onChange,
666
+ },
667
+ context: {
668
+ blockModel: formBlock,
669
+ fieldIndex: ['org_o2m:1'],
670
+ },
671
+ };
672
+
673
+ const ctx: any = {
674
+ model,
675
+ flowKey: 'selectSettings',
676
+ };
677
+
678
+ const filter = {
679
+ logic: '$and',
680
+ items: [{ path: 'name', operator: '$includes', value: '{{ ctx.item.value.companyName }}' }],
681
+ };
682
+
683
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
684
+
685
+ emitter.emit('formValuesChange', {
686
+ changedPaths: [['org_o2m']],
687
+ allValues: {
688
+ org_o2m: [{ tenantId: 1, code: 'b', companyName: 'b', user_m2o: { id: 2 } }],
689
+ },
690
+ });
691
+
692
+ expect(onChange).not.toHaveBeenCalled();
693
+ });
694
+
695
+ it('keeps tracking a row after its custom filter target key changes', () => {
696
+ const emitter = new EventEmitter();
697
+ const usersField = {
698
+ targetCollection: { filterTargetKey: 'name' },
699
+ isAssociationField: () => true,
700
+ };
701
+ const formBlock = {
702
+ uid: 'form-1',
703
+ disposed: false,
704
+ emitter,
705
+ context: {
706
+ form: {},
707
+ collection: { getField: (name: string) => (name === 'org_o2m' ? usersField : null) },
708
+ formValues: {
709
+ org_o2m: [
710
+ { name: 'alice', companyName: 'a', user_m2o: { id: 1 } },
711
+ { name: 'bob', companyName: 'b', user_m2o: { id: 2 } },
712
+ ],
713
+ },
714
+ },
715
+ };
716
+
717
+ const onChange = vi.fn();
718
+ const model: any = {
719
+ disposed: false,
720
+ props: {
721
+ value: { id: 2 },
722
+ onChange,
723
+ },
724
+ context: {
725
+ blockModel: formBlock,
726
+ fieldIndex: ['org_o2m:1'],
727
+ },
728
+ };
729
+
730
+ const ctx: any = {
731
+ model,
732
+ flowKey: 'selectSettings',
733
+ };
734
+
735
+ const filter = {
736
+ logic: '$and',
737
+ items: [{ path: 'name', operator: '$includes', value: '{{ ctx.item.value.companyName }}' }],
738
+ };
739
+
740
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
741
+
742
+ emitter.emit('formValuesChange', {
743
+ changedPaths: [['org_o2m', 1, 'name']],
744
+ allValues: {
745
+ org_o2m: [
746
+ { name: 'alice', companyName: 'a', user_m2o: { id: 1 } },
747
+ { name: 'robert', companyName: 'b', user_m2o: { id: 2 } },
748
+ ],
749
+ },
750
+ });
751
+
752
+ expect(onChange).not.toHaveBeenCalled();
753
+
754
+ emitter.emit('formValuesChange', {
755
+ changedPaths: [['org_o2m', 1, 'companyName']],
756
+ allValues: {
757
+ org_o2m: [
758
+ { name: 'alice', companyName: 'a', user_m2o: { id: 1 } },
759
+ { name: 'robert', companyName: 'c', user_m2o: { id: 2 } },
760
+ ],
761
+ },
762
+ });
763
+
764
+ expect(onChange).toHaveBeenCalledWith(null);
765
+ });
766
+
767
+ it('keeps tracking a row after its temporary identity changes to a persisted key', () => {
768
+ const emitter = new EventEmitter();
769
+ const usersField = {
770
+ targetCollection: { filterTargetKey: 'id' },
771
+ isAssociationField: () => true,
772
+ };
773
+ const formBlock = {
774
+ uid: 'form-1',
775
+ disposed: false,
776
+ emitter,
777
+ context: {
778
+ form: {},
779
+ collection: { getField: (name: string) => (name === 'org_o2m' ? usersField : null) },
780
+ formValues: {
781
+ org_o2m: [
782
+ { id: 1, companyName: 'a', user_m2o: { id: 1 } },
783
+ { __index__: 'row-b', __is_new__: true, companyName: 'b', user_m2o: { id: 2 } },
784
+ ],
785
+ },
786
+ },
787
+ };
788
+
789
+ const onChange = vi.fn();
790
+ const model: any = {
791
+ disposed: false,
792
+ props: {
793
+ value: { id: 2 },
794
+ onChange,
795
+ },
796
+ context: {
797
+ blockModel: formBlock,
798
+ fieldIndex: ['org_o2m:1'],
799
+ },
800
+ };
801
+
802
+ const ctx: any = {
803
+ model,
804
+ flowKey: 'selectSettings',
805
+ };
806
+
807
+ const filter = {
808
+ logic: '$and',
809
+ items: [{ path: 'name', operator: '$includes', value: '{{ ctx.item.value.companyName }}' }],
810
+ };
811
+
812
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
813
+
814
+ emitter.emit('formValuesChange', {
815
+ changedPaths: [['org_o2m', 1, 'id']],
816
+ allValues: {
817
+ org_o2m: [
818
+ { id: 1, companyName: 'a', user_m2o: { id: 1 } },
819
+ { __index__: 'row-b', id: 2, companyName: 'b', user_m2o: { id: 2 } },
820
+ ],
821
+ },
822
+ });
823
+
824
+ expect(onChange).not.toHaveBeenCalled();
825
+
826
+ emitter.emit('formValuesChange', {
827
+ changedPaths: [['org_o2m', 1, 'companyName']],
828
+ allValues: {
829
+ org_o2m: [
830
+ { id: 1, companyName: 'a', user_m2o: { id: 1 } },
831
+ { __index__: 'row-b', id: 2, companyName: 'c', user_m2o: { id: 2 } },
832
+ ],
833
+ },
834
+ });
835
+
836
+ expect(onChange).toHaveBeenCalledWith(null);
837
+ });
838
+
839
+ it('does not clear a stale row field binding when its row has been removed', () => {
840
+ const emitter = new EventEmitter();
841
+ const formBlock = {
842
+ uid: 'form-1',
843
+ disposed: false,
844
+ emitter,
845
+ context: {
846
+ form: {},
847
+ formValues: {
848
+ org_o2m: [
849
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
850
+ { __index__: 'row-b', companyName: 'b', user_m2o: { id: 2 } },
851
+ ],
852
+ },
853
+ },
854
+ };
855
+
856
+ const onChange = vi.fn();
857
+ const model: any = {
858
+ disposed: false,
859
+ props: {
860
+ value: { id: 2 },
861
+ onChange,
862
+ },
863
+ context: {
864
+ blockModel: formBlock,
865
+ fieldIndex: ['org_o2m:1'],
866
+ },
867
+ };
868
+
869
+ const ctx: any = {
870
+ model,
871
+ flowKey: 'selectSettings',
872
+ };
873
+
874
+ const filter = {
875
+ logic: '$and',
876
+ items: [{ path: 'name', operator: '$includes', value: '{{ ctx.item.value.companyName }}' }],
877
+ };
878
+
879
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
880
+
881
+ emitter.emit('formValuesChange', {
882
+ changedPaths: [['org_o2m']],
883
+ allValues: {
884
+ org_o2m: [{ __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } }],
885
+ },
886
+ });
887
+
888
+ expect(onChange).not.toHaveBeenCalled();
889
+ });
890
+
891
+ it('does not reattach a stale row field binding to the next row after deletion', () => {
892
+ const emitter = new EventEmitter();
893
+ const formBlock = {
894
+ uid: 'form-1',
895
+ disposed: false,
896
+ emitter,
897
+ context: {
898
+ form: {},
899
+ formValues: {
900
+ org_o2m: [
901
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
902
+ { __index__: 'row-b', companyName: 'b', user_m2o: { id: 2 } },
903
+ { __index__: 'row-c', companyName: 'c', user_m2o: { id: 3 } },
904
+ ],
905
+ },
906
+ },
907
+ };
908
+
909
+ const onChange = vi.fn();
910
+ const model: any = {
911
+ disposed: false,
912
+ props: {
913
+ value: { id: 2 },
914
+ onChange,
915
+ },
916
+ context: {
917
+ blockModel: formBlock,
918
+ fieldIndex: ['org_o2m:1'],
919
+ },
920
+ };
921
+
922
+ const ctx: any = {
923
+ model,
924
+ flowKey: 'selectSettings',
925
+ };
926
+
927
+ const filter = {
928
+ logic: '$and',
929
+ items: [{ path: 'name', operator: '$includes', value: '{{ ctx.item.value.companyName }}' }],
930
+ };
931
+
932
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
933
+
934
+ emitter.emit('formValuesChange', {
935
+ changedPaths: [['org_o2m']],
936
+ allValues: {
937
+ org_o2m: [
938
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
939
+ { __index__: 'row-c', companyName: 'c', user_m2o: { id: 3 } },
940
+ ],
941
+ },
942
+ });
943
+
944
+ emitter.emit('formValuesChange', {
945
+ changedPaths: [['org_o2m', 1, 'companyName']],
946
+ allValues: {
947
+ org_o2m: [
948
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
949
+ { __index__: 'row-c', companyName: 'changed', user_m2o: { id: 3 } },
950
+ ],
951
+ },
952
+ });
953
+
954
+ expect(onChange).not.toHaveBeenCalled();
955
+ });
956
+
957
+ it('does not reattach a stale row field binding when the first stale event is an old index child change', () => {
958
+ const emitter = new EventEmitter();
959
+ const formBlock = {
960
+ uid: 'form-1',
961
+ disposed: false,
962
+ emitter,
963
+ context: {
964
+ form: {},
965
+ formValues: {
966
+ org_o2m: [
967
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
968
+ { __index__: 'row-b', companyName: 'b', user_m2o: { id: 2 } },
969
+ { __index__: 'row-c', companyName: 'c', user_m2o: { id: 3 } },
970
+ ],
971
+ },
972
+ },
973
+ };
974
+
975
+ const onChange = vi.fn();
976
+ const model: any = {
977
+ disposed: false,
978
+ props: {
979
+ value: { id: 2 },
980
+ onChange,
981
+ },
982
+ context: {
983
+ blockModel: formBlock,
984
+ fieldIndex: ['org_o2m:1'],
985
+ },
986
+ };
987
+
988
+ const ctx: any = {
989
+ model,
990
+ flowKey: 'selectSettings',
991
+ };
992
+
993
+ const filter = {
994
+ logic: '$and',
995
+ items: [{ path: 'name', operator: '$includes', value: '{{ ctx.item.value.companyName }}' }],
996
+ };
997
+
998
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
999
+
1000
+ emitter.emit('formValuesChange', {
1001
+ changedPaths: [['org_o2m', 1, 'companyName']],
1002
+ allValues: {
1003
+ org_o2m: [
1004
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
1005
+ { __index__: 'row-c', companyName: 'changed', user_m2o: { id: 3 } },
1006
+ ],
1007
+ },
1008
+ });
1009
+
1010
+ expect(onChange).not.toHaveBeenCalled();
1011
+ });
1012
+
1013
+ it('does not clear a stale removed row binding when data scope depends on the item length', () => {
1014
+ const emitter = new EventEmitter();
1015
+ const formBlock = {
1016
+ uid: 'form-1',
1017
+ disposed: false,
1018
+ emitter,
1019
+ context: {
1020
+ form: {},
1021
+ formValues: {
1022
+ org_o2m: [
1023
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
1024
+ { __index__: 'row-b', companyName: 'b', user_m2o: { id: 2 } },
1025
+ ],
1026
+ },
1027
+ },
1028
+ };
1029
+
1030
+ const onChange = vi.fn();
1031
+ const model: any = {
1032
+ disposed: false,
1033
+ props: {
1034
+ value: { id: 2 },
1035
+ onChange,
1036
+ },
1037
+ context: {
1038
+ blockModel: formBlock,
1039
+ fieldIndex: ['org_o2m:1'],
1040
+ },
1041
+ };
1042
+
1043
+ const ctx: any = {
1044
+ model,
1045
+ flowKey: 'selectSettings',
1046
+ };
1047
+
1048
+ const filter = {
1049
+ logic: '$and',
1050
+ items: [{ path: 'count', operator: '$eq', value: '{{ ctx.item.length }}' }],
1051
+ };
1052
+
1053
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
1054
+
1055
+ emitter.emit('formValuesChange', {
1056
+ changedPaths: [['org_o2m']],
1057
+ allValues: {
1058
+ org_o2m: [{ __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } }],
1059
+ },
1060
+ });
1061
+
1062
+ expect(onChange).not.toHaveBeenCalled();
1063
+ });
1064
+
1065
+ it('still clears a row field when data scope explicitly depends on the item length', () => {
1066
+ const emitter = new EventEmitter();
1067
+ const formBlock = {
1068
+ uid: 'form-1',
1069
+ disposed: false,
1070
+ emitter,
1071
+ context: {
1072
+ form: {},
1073
+ formValues: {
1074
+ org_o2m: [
1075
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
1076
+ { __index__: 'row-b', companyName: 'b', user_m2o: { id: 2 } },
1077
+ ],
1078
+ },
1079
+ },
1080
+ };
1081
+
1082
+ const onChange = vi.fn();
1083
+ const model: any = {
1084
+ disposed: false,
1085
+ props: {
1086
+ value: { id: 1 },
1087
+ onChange,
1088
+ },
1089
+ context: {
1090
+ blockModel: formBlock,
1091
+ fieldIndex: ['org_o2m:0'],
1092
+ },
1093
+ };
1094
+
1095
+ const ctx: any = {
1096
+ model,
1097
+ flowKey: 'selectSettings',
1098
+ };
1099
+
1100
+ const filter = {
1101
+ logic: '$and',
1102
+ items: [{ path: 'count', operator: '$eq', value: '{{ ctx.item.length }}' }],
1103
+ };
1104
+
1105
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
1106
+
1107
+ emitter.emit('formValuesChange', {
1108
+ changedPaths: [['org_o2m']],
1109
+ allValues: {
1110
+ org_o2m: [{ __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } }],
1111
+ },
1112
+ });
1113
+
1114
+ expect(onChange).toHaveBeenCalledWith(null);
1115
+ });
1116
+
1117
+ it('does not clear a row field when data scope depends on the item index and another row is removed after it', () => {
1118
+ const emitter = new EventEmitter();
1119
+ const formBlock = {
1120
+ uid: 'form-1',
1121
+ disposed: false,
1122
+ emitter,
1123
+ context: {
1124
+ form: {},
1125
+ formValues: {
1126
+ org_o2m: [
1127
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
1128
+ { __index__: 'row-b', companyName: 'b', user_m2o: { id: 2 } },
1129
+ ],
1130
+ },
1131
+ },
1132
+ };
1133
+
1134
+ const onChange = vi.fn();
1135
+ const model: any = {
1136
+ disposed: false,
1137
+ props: {
1138
+ value: { id: 1 },
1139
+ onChange,
1140
+ },
1141
+ context: {
1142
+ blockModel: formBlock,
1143
+ fieldIndex: ['org_o2m:0'],
1144
+ },
1145
+ };
1146
+
1147
+ const ctx: any = {
1148
+ model,
1149
+ flowKey: 'selectSettings',
1150
+ };
1151
+
1152
+ const filter = {
1153
+ logic: '$and',
1154
+ items: [{ path: 'index', operator: '$eq', value: '{{ ctx.item.index }}' }],
1155
+ };
1156
+
1157
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
1158
+
1159
+ emitter.emit('formValuesChange', {
1160
+ changedPaths: [['org_o2m']],
1161
+ allValues: {
1162
+ org_o2m: [{ __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } }],
1163
+ },
1164
+ });
1165
+
1166
+ expect(onChange).not.toHaveBeenCalled();
1167
+ });
1168
+
1169
+ it('clears a row field when data scope depends on the item index and the row index changes', () => {
1170
+ const emitter = new EventEmitter();
1171
+ const formBlock = {
1172
+ uid: 'form-1',
1173
+ disposed: false,
1174
+ emitter,
1175
+ context: {
1176
+ form: {},
1177
+ formValues: {
1178
+ org_o2m: [
1179
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
1180
+ { __index__: 'row-b', companyName: 'b', user_m2o: { id: 2 } },
1181
+ ],
1182
+ },
1183
+ },
1184
+ };
1185
+
1186
+ const onChange = vi.fn();
1187
+ const model: any = {
1188
+ disposed: false,
1189
+ props: {
1190
+ value: { id: 2 },
1191
+ onChange,
1192
+ },
1193
+ context: {
1194
+ blockModel: formBlock,
1195
+ fieldIndex: ['org_o2m:1'],
1196
+ },
1197
+ };
1198
+
1199
+ const ctx: any = {
1200
+ model,
1201
+ flowKey: 'selectSettings',
1202
+ };
1203
+
1204
+ const filter = {
1205
+ logic: '$and',
1206
+ items: [{ path: 'index', operator: '$eq', value: '{{ ctx.item.index }}' }],
1207
+ };
1208
+
1209
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
1210
+
1211
+ emitter.emit('formValuesChange', {
1212
+ changedPaths: [['org_o2m']],
1213
+ allValues: {
1214
+ org_o2m: [{ __index__: 'row-b', companyName: 'b', user_m2o: { id: 2 } }],
1215
+ },
1216
+ });
1217
+
1218
+ expect(onChange).toHaveBeenCalledWith(null);
1219
+ });
96
1220
  });