@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
@@ -15,9 +15,10 @@ import {
15
15
  DndProvider,
16
16
  DragHandler,
17
17
  Droppable,
18
+ isCtxDateExpression,
18
19
  isRunJSValue,
19
20
  normalizeRunJSValue,
20
- runjsWithSafeGlobals,
21
+ parseCtxDateExpression,
21
22
  tExpr,
22
23
  FlowModelRenderer,
23
24
  FlowSettingsButton,
@@ -45,6 +46,15 @@ import { normalizeFilterValueByOperator } from './valueNormalization';
45
46
 
46
47
  const RELATION_FIELD_TYPES = ['belongsTo', 'hasOne', 'hasMany', 'belongsToMany', 'belongsToArray'];
47
48
  const NUMERIC_FIELD_TYPES = ['integer', 'float', 'double', 'decimal'];
49
+ const DATE_FILTER_OPERATORS = new Set([
50
+ '$dateOn',
51
+ '$dateNotOn',
52
+ '$dateBefore',
53
+ '$dateAfter',
54
+ '$dateNotBefore',
55
+ '$dateNotAfter',
56
+ '$dateBetween',
57
+ ]);
48
58
 
49
59
  function getFilterFormFieldMetaType(field: CollectionField) {
50
60
  if (RELATION_FIELD_TYPES.includes(field.type)) {
@@ -67,6 +77,14 @@ function getFilterFormFieldMetaType(field: CollectionField) {
67
77
  }
68
78
  }
69
79
 
80
+ function parseDateFilterDefaultValue(operator: string | undefined, rawValue: unknown) {
81
+ if (!operator || !DATE_FILTER_OPERATORS.has(operator) || !isCtxDateExpression(rawValue)) {
82
+ return undefined;
83
+ }
84
+
85
+ return parseCtxDateExpression(rawValue);
86
+ }
87
+
70
88
  function shouldShowFilterFormFieldMeta(field: CollectionField) {
71
89
  return Boolean(field?.interface);
72
90
  }
@@ -231,6 +249,7 @@ export class FilterFormBlockModel extends FilterBlockModel<{
231
249
  private initialDefaultsPromise?: Promise<void>;
232
250
  private initialRefreshHandledTargetIds = new Set<string>();
233
251
  private lastDefaultValueByFieldName = new Map<string, any>();
252
+ private userEditedFieldNames = new Set<string>();
234
253
  private defaultValuesRefreshSeq = 0;
235
254
 
236
255
  get form() {
@@ -431,11 +450,34 @@ export class FilterFormBlockModel extends FilterBlockModel<{
431
450
 
432
451
  private canApplyFormDefaultValue(name: string, current: any, force?: boolean) {
433
452
  if (force) return true;
453
+ if (this.userEditedFieldNames.has(name)) return false;
434
454
  if (isEmptyValue(current)) return true;
435
455
  if (!this.lastDefaultValueByFieldName.has(name)) return false;
436
456
  return isEqual(current, this.lastDefaultValueByFieldName.get(name));
437
457
  }
438
458
 
459
+ private canApplyFormOverrideValue(name: string, force?: boolean) {
460
+ if (force) return true;
461
+ return !this.userEditedFieldNames.has(name);
462
+ }
463
+
464
+ private normalizeFieldValueMode(mode: unknown): 'default' | 'assign' | 'override' {
465
+ if (mode === 'assign') return 'assign';
466
+ if (mode === 'override') return 'override';
467
+ return 'default';
468
+ }
469
+
470
+ private markFilterFormUserEditedFields(changedValues: any) {
471
+ if (!changedValues || typeof changedValues !== 'object' || Array.isArray(changedValues)) return;
472
+ for (const name of Object.keys(changedValues)) {
473
+ this.userEditedFieldNames.add(String(name));
474
+ }
475
+ }
476
+
477
+ private resetFilterFormUserEditedFields() {
478
+ this.userEditedFieldNames.clear();
479
+ }
480
+
439
481
  private async matchDefaultValueCondition(condition: any) {
440
482
  if (!condition) return true;
441
483
 
@@ -458,18 +500,27 @@ export class FilterFormBlockModel extends FilterBlockModel<{
458
500
  if (!form) return appliedValues;
459
501
 
460
502
  const force = options?.force === true;
503
+ if (force) {
504
+ this.resetFilterFormUserEditedFields();
505
+ }
506
+
461
507
  const params = this.getStepParams?.('formFilterBlockModelSettings', 'defaultValues');
462
508
  const rules = (params?.value || []) as any[];
463
509
  if (!Array.isArray(rules) || rules.length === 0) return appliedValues;
464
510
 
465
- const resolveValue = async (raw: any) => {
511
+ const resolveValue = async (raw: any, operator?: string) => {
466
512
  // RunJS support
467
513
  if (isRunJSValue(raw)) {
468
514
  const { code, version } = normalizeRunJSValue(raw);
469
- const ret = await runjsWithSafeGlobals(this.context, code, { version });
515
+ const ret = await this.context.runjs(code, undefined, { version });
470
516
  return ret?.success ? ret.value : undefined;
471
517
  }
472
518
 
519
+ const parsedDateFilterValue = parseDateFilterDefaultValue(operator, raw);
520
+ if (typeof parsedDateFilterValue !== 'undefined') {
521
+ return parsedDateFilterValue;
522
+ }
523
+
473
524
  return await (this.context as any).resolveJsonTemplate?.(raw);
474
525
  };
475
526
 
@@ -493,14 +544,15 @@ export class FilterFormBlockModel extends FilterBlockModel<{
493
544
 
494
545
  const current = (form as any).getFieldValue?.(name);
495
546
 
496
- const resolved = await resolveValue(rule.value);
547
+ const operator = getDefaultOperator(itemModel as any);
548
+ const resolved = await resolveValue(rule.value, operator);
497
549
  if (options?.refreshSeq && options.refreshSeq !== this.defaultValuesRefreshSeq) return appliedValues;
498
550
  if (typeof resolved === 'undefined') continue;
499
551
 
500
- const operator = getDefaultOperator(itemModel as any);
501
552
  const normalized = normalizeFilterValueByOperator(operator, resolved);
502
- const mode = String(rule.mode || 'default') === 'assign' ? 'assign' : 'default';
553
+ const mode = this.normalizeFieldValueMode(rule.mode);
503
554
  if (mode === 'default' && !this.canApplyFormDefaultValue(String(name), current, force)) continue;
555
+ if (mode === 'override' && !this.canApplyFormOverrideValue(String(name), force)) continue;
504
556
  if (isEqual(current, normalized)) {
505
557
  if (mode === 'default') {
506
558
  this.lastDefaultValueByFieldName.set(String(name), normalized);
@@ -527,6 +579,7 @@ export class FilterFormBlockModel extends FilterBlockModel<{
527
579
  }
528
580
 
529
581
  private handleFilterFormValuesChange(changedValues: any, allValues: any) {
582
+ this.markFilterFormUserEditedFields(changedValues);
530
583
  const refreshSeq = ++this.defaultValuesRefreshSeq;
531
584
  void (async () => {
532
585
  const appliedValues = await this.applyFormDefaultValues({ refreshSeq });
@@ -62,9 +62,25 @@ function isRecord(value: unknown): value is Record<string, unknown> {
62
62
  return !!value && typeof value === 'object' && !Array.isArray(value);
63
63
  }
64
64
 
65
+ interface CollectionFieldWithAssociationMarker {
66
+ isAssociationField?: () => boolean;
67
+ target?: unknown;
68
+ }
69
+
70
+ function hasAssociationMarker(value: unknown): value is CollectionFieldWithAssociationMarker {
71
+ return isRecord(value) && (typeof value.isAssociationField === 'function' || 'target' in value);
72
+ }
73
+
74
+ const isAssociationCollectionField = (collectionField: unknown) => {
75
+ if (!hasAssociationMarker(collectionField)) {
76
+ return false;
77
+ }
78
+ return collectionField.isAssociationField ? collectionField.isAssociationField() : Boolean(collectionField.target);
79
+ };
80
+
65
81
  const normalizeAssociationDefaultFilterValue = (value: any, fieldModel: any) => {
66
82
  const collectionField = fieldModel?.context?.collectionField;
67
- if (!collectionField?.isAssociationField?.()) {
83
+ if (!isAssociationCollectionField(collectionField)) {
68
84
  return value;
69
85
  }
70
86
 
@@ -171,8 +187,7 @@ const buildFilterFormFieldItem = ({
171
187
  if (!binding) {
172
188
  return;
173
189
  }
174
- const isAssociation =
175
- typeof field?.isAssociationField === 'function' ? field.isAssociationField() : Boolean(field?.target);
190
+ const isAssociation = isAssociationCollectionField(field);
176
191
  const fieldModel =
177
192
  isAssociation && ctxWithFlags.engine?.getModelClass?.('FilterFormRecordSelectFieldModel')
178
193
  ? 'FilterFormRecordSelectFieldModel'
@@ -507,10 +522,7 @@ export class FilterFormItemModel extends FilterableItemModel<{
507
522
  const formValue = this.context.form?.getFieldValue(this.props.name);
508
523
  const modelValue = fieldModel.getFilterValue ? fieldModel.getFilterValue() : formValue;
509
524
  const collectionField = (fieldModel as any)?.context?.collectionField;
510
- const isAssociationField =
511
- typeof collectionField?.isAssociationField === 'function'
512
- ? collectionField.isAssociationField()
513
- : !!collectionField?.target;
525
+ const isAssociationField = isAssociationCollectionField(collectionField);
514
526
  const shouldUseFormValue =
515
527
  isAssociationField &&
516
528
  !this.mounted &&
@@ -547,11 +559,7 @@ export class FilterFormItemModel extends FilterableItemModel<{
547
559
  return value;
548
560
  }
549
561
  const collectionField = (fieldModel as any)?.context?.collectionField;
550
- const isAssociation =
551
- typeof collectionField?.isAssociationField === 'function'
552
- ? collectionField.isAssociationField()
553
- : !!collectionField?.target;
554
- if (!isAssociation) {
562
+ if (!isAssociationCollectionField(collectionField)) {
555
563
  return value;
556
564
  }
557
565
  const normalizedFieldNames = normalizeAssociationFieldNames(
@@ -798,6 +806,13 @@ FilterFormItemModel.registerFlow({
798
806
  },
799
807
  defaultOperator: {
800
808
  use: 'defaultOperator',
809
+ hideInSettings(ctx) {
810
+ const collectionField =
811
+ ctx.collectionField ||
812
+ ctx.model?.context?.collectionField ||
813
+ ctx.model?.subModels?.field?.context?.collectionField;
814
+ return isAssociationCollectionField(collectionField);
815
+ },
801
816
  },
802
817
  operatorComponentProps: {
803
818
  use: 'operatorComponentProps',
@@ -7,7 +7,7 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- import { createSafeDocument, createSafeWindow, createSafeNavigator, tExpr } from '@nocobase/flow-engine';
10
+ import { tExpr } from '@nocobase/flow-engine';
11
11
  import { CodeEditor } from '../../../components/code-editor';
12
12
  import { FilterFormActionModel } from './FilterFormActionModel';
13
13
  import { resolveRunJsParams } from '../../utils/resolveRunJsParams';
@@ -60,12 +60,7 @@ FilterFormJSActionModel.registerFlow({
60
60
  },
61
61
  async handler(ctx, params) {
62
62
  const { code, version } = resolveRunJsParams(ctx, params);
63
- const navigator = createSafeNavigator();
64
- await ctx.runjs(
65
- code,
66
- { window: createSafeWindow({ navigator }), document: createSafeDocument(), navigator },
67
- { version },
68
- );
63
+ await ctx.runjs(code, undefined, { version });
69
64
  },
70
65
  },
71
66
  },
@@ -33,6 +33,42 @@ class DummyCollectionBlockModel extends CollectionBlockModel {
33
33
  }
34
34
 
35
35
  describe('FilterFormItemModel defineChildren association fields', () => {
36
+ it('hides default operator setting for association filter fields', () => {
37
+ const engine = new FlowEngine();
38
+ engine.registerModels({
39
+ FilterFormItemModel,
40
+ });
41
+
42
+ const filterItem = engine.createModel<FilterFormItemModel>({
43
+ uid: 'association-filter-item-settings',
44
+ use: 'FilterFormItemModel',
45
+ });
46
+
47
+ const defaultOperatorStep = filterItem.getFlow('filterFormItemSettings')?.steps?.defaultOperator as {
48
+ hideInSettings?: (ctx: {
49
+ collectionField?: unknown;
50
+ model?: {
51
+ subModels?: {
52
+ field?: {
53
+ context?: {
54
+ collectionField?: unknown;
55
+ };
56
+ };
57
+ };
58
+ };
59
+ }) => boolean;
60
+ };
61
+ expect(defaultOperatorStep?.hideInSettings?.({ collectionField: { isAssociationField: () => true } })).toBe(true);
62
+ expect(
63
+ defaultOperatorStep?.hideInSettings?.({
64
+ model: { subModels: { field: { context: { collectionField: { target: 'departments' } } } } },
65
+ }),
66
+ ).toBe(true);
67
+ expect(defaultOperatorStep?.hideInSettings?.({ collectionField: { interface: 'input', type: 'string' } })).toBe(
68
+ false,
69
+ );
70
+ });
71
+
36
72
  it('groups association target fields and supports recursive paths', async () => {
37
73
  const engine = new FlowEngine();
38
74
  engine.registerModels({
@@ -17,7 +17,15 @@ import { FilterFormBlockModel } from '../FilterFormBlockModel';
17
17
  function resolveTemplateValue(raw: any, values: Record<string, any>): any {
18
18
  if (typeof raw === 'string') {
19
19
  const matched = raw.match(/^\{\{\s*ctx\.formValues\.([^}]+?)\s*\}\}$/);
20
- return matched ? values[matched[1]] : raw;
20
+ if (matched) {
21
+ return values[matched[1]];
22
+ }
23
+
24
+ if (/^\{\{\s*ctx\.date\.relative\.past\.day\.n7\s*\}\}$/.test(raw)) {
25
+ return '2026-06-15';
26
+ }
27
+
28
+ return raw;
21
29
  }
22
30
  if (Array.isArray(raw)) {
23
31
  return raw.map((item) => resolveTemplateValue(item, values));
@@ -30,7 +38,7 @@ function resolveTemplateValue(raw: any, values: Record<string, any>): any {
30
38
 
31
39
  function createFilterFormDefaultValuesModel(rules: any[], initialValues: Record<string, any> = {}) {
32
40
  const values = { ...initialValues };
33
- const createItem = (fieldPath: string, uid: string) => ({
41
+ const createItem = (fieldPath: string, uid: string, operator?: string) => ({
34
42
  uid,
35
43
  fieldPath,
36
44
  props: { name: `${fieldPath}_${uid}` },
@@ -44,12 +52,13 @@ function createFilterFormDefaultValuesModel(rules: any[], initialValues: Record<
44
52
  return undefined;
45
53
  },
46
54
  subModels: {
47
- field: {},
55
+ field: operator ? { operator } : {},
48
56
  },
49
57
  });
50
58
  const model = {
51
59
  defaultValuesRefreshSeq: 0,
52
60
  lastDefaultValueByFieldName: new Map<string, any>(),
61
+ userEditedFieldNames: new Set<string>(),
53
62
  form: {
54
63
  getFieldsValue: () => ({ ...values }),
55
64
  getFieldValue: (name: string) => values[name],
@@ -75,7 +84,11 @@ function createFilterFormDefaultValuesModel(rules: any[], initialValues: Record<
75
84
  subModels: {
76
85
  grid: {
77
86
  subModels: {
78
- items: [createItem('nickname', 'nick'), createItem('username', 'user')],
87
+ items: [
88
+ createItem('nickname', 'nick'),
89
+ createItem('username', 'user'),
90
+ createItem('birthdate_tz', 'birthdate', '$dateOn'),
91
+ ],
79
92
  },
80
93
  },
81
94
  },
@@ -86,6 +99,10 @@ function createFilterFormDefaultValuesModel(rules: any[], initialValues: Record<
86
99
  return undefined;
87
100
  }),
88
101
  canApplyFormDefaultValue: (FilterFormBlockModel.prototype as any).canApplyFormDefaultValue,
102
+ canApplyFormOverrideValue: (FilterFormBlockModel.prototype as any).canApplyFormOverrideValue,
103
+ normalizeFieldValueMode: (FilterFormBlockModel.prototype as any).normalizeFieldValueMode,
104
+ markFilterFormUserEditedFields: (FilterFormBlockModel.prototype as any).markFilterFormUserEditedFields,
105
+ resetFilterFormUserEditedFields: (FilterFormBlockModel.prototype as any).resetFilterFormUserEditedFields,
89
106
  matchDefaultValueCondition: (FilterFormBlockModel.prototype as any).matchDefaultValueCondition,
90
107
  applyFormDefaultValues: FilterFormBlockModel.prototype.applyFormDefaultValues,
91
108
  handleFilterFormValuesChange: (FilterFormBlockModel.prototype as any).handleFilterFormValuesChange,
@@ -296,6 +313,40 @@ describe('filter-form defaultValues wiring', () => {
296
313
  expect(values.username_user).toBe('Manual');
297
314
  });
298
315
 
316
+ it('does not reapply a filter form default value after user clears the field', async () => {
317
+ const { model, values } = createFilterFormDefaultValuesModel([
318
+ {
319
+ key: 'username-default',
320
+ enable: true,
321
+ targetPath: 'username',
322
+ mode: 'default',
323
+ value: 'admin',
324
+ },
325
+ ]);
326
+
327
+ await FilterFormBlockModel.prototype.applyFormDefaultValues.call(model as any);
328
+ expect(values.username_user).toBe('admin');
329
+
330
+ values.username_user = undefined;
331
+ (model as any).handleFilterFormValuesChange({ username_user: undefined }, { username_user: undefined });
332
+
333
+ await waitFor(() => {
334
+ expect(model.dispatchEvent).toHaveBeenCalledWith(
335
+ 'formValuesChange',
336
+ {
337
+ changedValues: {
338
+ username_user: undefined,
339
+ },
340
+ allValues: {
341
+ username_user: undefined,
342
+ },
343
+ },
344
+ { debounce: true },
345
+ );
346
+ });
347
+ expect(values.username_user).toBeUndefined();
348
+ });
349
+
299
350
  it('applies fixed values even when the target filter field already has a value', async () => {
300
351
  const { model, values } = createFilterFormDefaultValuesModel(
301
352
  [
@@ -315,6 +366,94 @@ describe('filter-form defaultValues wiring', () => {
315
366
  expect(values.username_user).toBe('Bob');
316
367
  });
317
368
 
369
+ it('preserves relative date descriptors for date filter default values', async () => {
370
+ const { model, values } = createFilterFormDefaultValuesModel([
371
+ {
372
+ key: 'birthdate-default',
373
+ enable: true,
374
+ targetPath: 'birthdate_tz',
375
+ mode: 'assign',
376
+ value: '{{ ctx.date.relative.past.day.n7 }}',
377
+ },
378
+ ]);
379
+
380
+ await FilterFormBlockModel.prototype.applyFormDefaultValues.call(model as any);
381
+
382
+ expect(values.birthdate_tz_birthdate).toEqual({ type: 'past', unit: 'day', number: 7 });
383
+ });
384
+
385
+ it('applies override values until the target filter field is changed by user', async () => {
386
+ const { model, values } = createFilterFormDefaultValuesModel(
387
+ [
388
+ {
389
+ key: 'username-override',
390
+ enable: true,
391
+ targetPath: 'username',
392
+ mode: 'override',
393
+ value: '{{ ctx.formValues.nickname_nick }}',
394
+ },
395
+ ],
396
+ { nickname_nick: 'Bob', username_user: 'Manual' },
397
+ );
398
+
399
+ await FilterFormBlockModel.prototype.applyFormDefaultValues.call(model as any);
400
+ expect(values.username_user).toBe('Bob');
401
+
402
+ values.nickname_nick = 'Carol';
403
+ model.defaultValuesRefreshSeq += 1;
404
+ await FilterFormBlockModel.prototype.applyFormDefaultValues.call(model as any, {
405
+ refreshSeq: model.defaultValuesRefreshSeq,
406
+ });
407
+ expect(values.username_user).toBe('Carol');
408
+
409
+ values.username_user = 'User value';
410
+ (model as any).handleFilterFormValuesChange(
411
+ { username_user: 'User value' },
412
+ { nickname_nick: 'Carol', username_user: 'User value' },
413
+ );
414
+
415
+ await waitFor(() => {
416
+ expect(values.username_user).toBe('User value');
417
+ });
418
+
419
+ values.nickname_nick = 'Dora';
420
+ model.defaultValuesRefreshSeq += 1;
421
+ await FilterFormBlockModel.prototype.applyFormDefaultValues.call(model as any, {
422
+ refreshSeq: model.defaultValuesRefreshSeq,
423
+ });
424
+ expect(values.username_user).toBe('User value');
425
+ });
426
+
427
+ it('clears filter form user-edited override state on forced apply', async () => {
428
+ const { model, values } = createFilterFormDefaultValuesModel(
429
+ [
430
+ {
431
+ key: 'username-override',
432
+ enable: true,
433
+ targetPath: 'username',
434
+ mode: 'override',
435
+ condition: {
436
+ logic: '$and',
437
+ items: [{ path: '{{ ctx.formValues.nickname_nick }}', operator: '$eq', value: 'allow' }],
438
+ },
439
+ value: '{{ ctx.formValues.nickname_nick }}',
440
+ },
441
+ ],
442
+ { nickname_nick: 'deny', username_user: 'User value' },
443
+ );
444
+
445
+ model.userEditedFieldNames.add('username_user');
446
+
447
+ await FilterFormBlockModel.prototype.applyFormDefaultValues.call(model as any, { force: true });
448
+ expect(values.username_user).toBe('User value');
449
+ expect(model.userEditedFieldNames.has('username_user')).toBe(false);
450
+
451
+ values.nickname_nick = 'allow';
452
+ await FilterFormBlockModel.prototype.applyFormDefaultValues.call(model as any);
453
+
454
+ expect(values.username_user).toBe('allow');
455
+ });
456
+
318
457
  it('skips filter form field values when the rule condition does not match', async () => {
319
458
  const { model, values } = createFilterFormDefaultValuesModel(
320
459
  [
@@ -210,7 +210,7 @@ FilterFormCustomFieldModel.registerFlow({
210
210
  'x-decorator': 'FormItem',
211
211
  required: true,
212
212
  description:
213
- '{{t("Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.")}}',
213
+ '{{t("Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.")}}',
214
214
  },
215
215
  source: {
216
216
  type: 'array',
@@ -18,12 +18,23 @@ type TestFieldModel = {
18
18
  setupReactiveRender: ReturnType<typeof vi.fn>;
19
19
  _reactiveWrapperCache?: unknown;
20
20
  __originalRender?: () => React.ReactNode;
21
+ translate?: (text: string) => string;
21
22
  };
22
23
 
23
24
  const MultipleKeywordsInput = (props: Record<string, unknown>) => {
24
25
  return <div data-testid="multiple-keywords-input" {...props} />;
25
26
  };
26
27
 
28
+ const Select = (props: Record<string, unknown>) => {
29
+ return <div data-testid="select" {...props} />;
30
+ };
31
+
32
+ function mockT(text: string) {
33
+ if (text === '{{t("Yes")}}') return '是';
34
+ if (text === '{{t("No")}}') return '否';
35
+ return text;
36
+ }
37
+
27
38
  function createFieldModel() {
28
39
  return {
29
40
  props: {
@@ -52,7 +63,10 @@ function createFilterItemModel({
52
63
  collectionField,
53
64
  context: {
54
65
  app: {
55
- getComponent: (name: string) => (name === 'MultipleKeywordsInput' ? MultipleKeywordsInput : undefined),
66
+ getComponent: (name: string) => {
67
+ if (name === 'MultipleKeywordsInput') return MultipleKeywordsInput;
68
+ if (name === 'Select') return Select;
69
+ },
56
70
  },
57
71
  collectionField,
58
72
  },
@@ -112,6 +126,47 @@ describe('customizeFilterRender action', () => {
112
126
  expect((rerenderedElement as React.ReactElement).props.placeholder).toBe('updated runtime placeholder');
113
127
  });
114
128
 
129
+ it('translates operator schema select options', () => {
130
+ const fieldModel = {
131
+ ...createFieldModel(),
132
+ translate: mockT,
133
+ };
134
+ const model = createFilterItemModel({
135
+ fieldModel,
136
+ operator: '$isTruly',
137
+ collectionField: {
138
+ interface: 'checkbox',
139
+ type: 'boolean',
140
+ filterable: {
141
+ operators: [
142
+ {
143
+ label: '{{t("Yes")}}',
144
+ value: '$isTruly',
145
+ schema: {
146
+ 'x-component': 'Select',
147
+ 'x-component-props': {
148
+ options: [
149
+ { label: '{{t("Yes")}}', value: true },
150
+ { label: '{{t("No")}}', value: false },
151
+ ],
152
+ },
153
+ },
154
+ },
155
+ ],
156
+ },
157
+ },
158
+ });
159
+
160
+ customizeFilterRender.handler?.({ model } as any);
161
+
162
+ const element = fieldModel.render();
163
+ expect((element as React.ReactElement).type).toBe(Select);
164
+ expect((element as React.ReactElement).props.options).toEqual([
165
+ { label: '是', value: true },
166
+ { label: '否', value: false },
167
+ ]);
168
+ });
169
+
115
170
  it('restores original render when switching to an operator without a schema component', () => {
116
171
  const fieldModel = createFieldModel();
117
172
  const originalRender = fieldModel.render;
@@ -239,6 +239,7 @@ EditFormModel.registerFlow({
239
239
  // 切换记录(翻页等)时,清理“用户改动标记”,避免把上一条记录的编辑状态带到下一条
240
240
  if (recordChanged) {
241
241
  ctx.model.resetUserModifiedFields?.();
242
+ ctx.model.resetRuntimeUserEditedState?.();
242
243
  }
243
244
 
244
245
  ctx.form.setFieldsValue(currentRecord);
@@ -117,7 +117,7 @@ FormSubmitActionModel.registerFlow({
117
117
  try {
118
118
  ctx.model.setProps('loading', true);
119
119
  const { submitHandler } = await import('./submitHandler');
120
- await submitHandler(ctx, params);
120
+ return await submitHandler(ctx, params);
121
121
  } catch (error) {
122
122
  ctx.model.setProps('loading', false);
123
123
  if (error instanceof FlowExitAllException) {
@@ -166,6 +166,13 @@ export class FormBlockModel<
166
166
  this.userModifiedTopLevelFields.clear();
167
167
  }
168
168
 
169
+ /**
170
+ * @internal
171
+ */
172
+ resetRuntimeUserEditedState(): void {
173
+ this.formValueRuntime?.resetUserEditedState?.();
174
+ }
175
+
169
176
  public async onDispatchEventStart(eventName: string, options?: any, inputArgs?: Record<string, any>): Promise<void> {
170
177
  if (eventName === 'beforeRender') {
171
178
  const grid = this.subModels.grid;
@@ -10,7 +10,7 @@
10
10
  /**
11
11
  * JS Form Action:表单工具栏按钮点击执行 JS。
12
12
  */
13
- import { tExpr, createSafeWindow, createSafeDocument, createSafeNavigator } from '@nocobase/flow-engine';
13
+ import { tExpr } from '@nocobase/flow-engine';
14
14
  import { CodeEditor } from '../../../components/code-editor';
15
15
  import { FormActionModel } from './FormActionModel';
16
16
  import { resolveRunJsParams } from '../../utils/resolveRunJsParams';
@@ -73,12 +73,7 @@ ctx.message.success('Current form values: ' + JSON.stringify(values));
73
73
  await ctx.resource.refresh();
74
74
  }
75
75
  });
76
- const navigator = createSafeNavigator();
77
- await ctx.runjs(
78
- code,
79
- { window: createSafeWindow({ navigator }), document: createSafeDocument(), navigator },
80
- { version },
81
- );
76
+ await ctx.runjs(code, undefined, { version });
82
77
  },
83
78
  },
84
79
  },