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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
@@ -9,18 +9,56 @@
9
9
 
10
10
  import { EditableItemModel, FilterableItemModel, tExpr } from '@nocobase/flow-engine';
11
11
  import { Input } from 'antd';
12
- import React from 'react';
12
+ import type { InputProps } from 'antd';
13
+ import React, { useEffect, useState } from 'react';
13
14
  import { customAlphabet as Alphabet } from 'nanoid';
14
15
  import { FieldModel } from '../base/FieldModel';
15
16
  import { ScanInput } from '../../../components/form/ScanInput';
16
17
 
18
+ function IMESafeInput(props: InputProps) {
19
+ const { value, onChange, onCompositionStart, onCompositionEnd, ...rest } = props;
20
+ const [inputValue, setInputValue] = useState<InputProps['value']>(() => normalizeInputValue(value));
21
+
22
+ useEffect(() => {
23
+ setInputValue(normalizeInputValue(value));
24
+ }, [value]);
25
+
26
+ const getEventValue = (event: React.ChangeEvent<HTMLInputElement> | React.CompositionEvent<HTMLInputElement>) =>
27
+ event.currentTarget.value;
28
+
29
+ return (
30
+ <Input
31
+ {...rest}
32
+ value={inputValue}
33
+ onChange={(event) => {
34
+ setInputValue(getEventValue(event));
35
+ onChange?.(event);
36
+ }}
37
+ onCompositionStart={(event) => {
38
+ setInputValue(getEventValue(event));
39
+ onCompositionStart?.(event);
40
+ }}
41
+ onCompositionEnd={(event) => {
42
+ setInputValue(getEventValue(event));
43
+ onCompositionEnd?.(event);
44
+ }}
45
+ />
46
+ );
47
+ }
48
+
49
+ function normalizeInputValue(nextValue: unknown): InputProps['value'] {
50
+ if (typeof nextValue === 'bigint') return String(nextValue);
51
+ if (nextValue == null) return '';
52
+ return nextValue as InputProps['value'];
53
+ }
54
+
17
55
  export class InputFieldModel extends FieldModel {
18
56
  render() {
19
57
  if (this.props.enableScan) {
20
58
  return <ScanInput {...this.props} />;
21
59
  }
22
60
  const { enableScan, disableManualInput, ...inputProps } = this.props;
23
- return <Input {...inputProps} />;
61
+ return <IMESafeInput {...inputProps} />;
24
62
  }
25
63
  }
26
64
 
@@ -7,7 +7,7 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- import { ElementProxy, tExpr, createSafeDocument, createSafeWindow, createSafeNavigator } from '@nocobase/flow-engine';
10
+ import { ElementProxy, tExpr } from '@nocobase/flow-engine';
11
11
  import React, { useEffect, useMemo, useRef } from 'react';
12
12
  import { Input } from 'antd';
13
13
  import { FieldModel } from '../base/FieldModel';
@@ -398,16 +398,7 @@ JSEditableFieldModel.registerFlow({
398
398
  get: () => isReadOnlyMode(ctx.model),
399
399
  cache: false,
400
400
  });
401
- const navigator = createSafeNavigator();
402
- await ctx.runjs(
403
- code,
404
- {
405
- window: createSafeWindow({ navigator }),
406
- document: createSafeDocument(),
407
- navigator,
408
- },
409
- { version },
410
- );
401
+ await ctx.runjs(code, undefined, { version });
411
402
  });
412
403
  },
413
404
  },
@@ -7,7 +7,7 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- import { ElementProxy, tExpr, createSafeWindow, createSafeDocument, createSafeNavigator } from '@nocobase/flow-engine';
10
+ import { ElementProxy, tExpr } from '@nocobase/flow-engine';
11
11
  import React, { useEffect } from 'react';
12
12
  import { FieldModel } from '../base/FieldModel';
13
13
  import { resolveRunJsParams } from '../utils/resolveRunJsParams';
@@ -211,12 +211,7 @@ JSFieldModel.registerFlow({
211
211
  get: () => ctx.model.props?.value,
212
212
  cache: false,
213
213
  });
214
- const navigator = createSafeNavigator();
215
- await ctx.runjs(
216
- code,
217
- { window: createSafeWindow({ navigator }), document: createSafeDocument(), navigator },
218
- { version },
219
- );
214
+ await ctx.runjs(code, undefined, { version });
220
215
  });
221
216
  },
222
217
  },
@@ -7,14 +7,7 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- import {
11
- ElementProxy,
12
- FormItem,
13
- createSafeDocument,
14
- createSafeWindow,
15
- createSafeNavigator,
16
- tExpr,
17
- } from '@nocobase/flow-engine';
10
+ import { ElementProxy, FormItem, tExpr } from '@nocobase/flow-engine';
18
11
  import React from 'react';
19
12
  import { CodeEditor } from '../../components/code-editor';
20
13
  import { CommonItemModel } from '../base/CommonItemModel';
@@ -150,12 +143,7 @@ ctx.render(<JsItem />);
150
143
  ctx.defineProperty('element', {
151
144
  get: () => new ElementProxy(element),
152
145
  });
153
- const navigator = createSafeNavigator();
154
- await ctx.runjs(
155
- code,
156
- { window: createSafeWindow({ navigator }), document: createSafeDocument(), navigator },
157
- { version },
158
- );
146
+ await ctx.runjs(code, undefined, { version });
159
147
  });
160
148
  },
161
149
  },
@@ -15,17 +15,20 @@ import { MobileSelect } from './mobile-components/MobileSelect';
15
15
  import { enumToOptions, getSelectedEnumLabels, translateOptionLabel } from '../../internal/utils/enumOptionsUtils';
16
16
 
17
17
  const getOriginalEnumOptions = (model: SelectFieldModel) => {
18
- const fromEnum = enumToOptions(model.context.collectionField?.uiSchema?.enum, (text) => text) || [];
18
+ const fromEnum = enumToOptions(model.context.collectionField?.uiSchema?.enum, model.translate) || [];
19
19
  if (fromEnum.length > 0) {
20
20
  return fromEnum.map((option) => ({ ...option }));
21
21
  }
22
22
  const current = Array.isArray(model.props.options) ? model.props.options : [];
23
- return current.map((option) => ({ ...option }));
23
+ return current.map((option) => ({
24
+ ...option,
25
+ label: translateOptionLabel(option.label, model.translate),
26
+ }));
24
27
  };
25
-
26
28
  export class SelectFieldModel extends FieldModel {
27
29
  render() {
28
30
  const fallbackOptions = getOriginalEnumOptions(this);
31
+
29
32
  const options = this.props.options?.map((v) => {
30
33
  return {
31
34
  ...v,
@@ -46,7 +49,6 @@ export class SelectFieldModel extends FieldModel {
46
49
  if (this.context.isMobileLayout) {
47
50
  return <MobileSelect {...this.props} options={options} displayValue={value} />;
48
51
  }
49
-
50
52
  return (
51
53
  <Select
52
54
  {...this.props}
@@ -8,15 +8,84 @@
8
8
  */
9
9
 
10
10
  import { Input } from 'antd';
11
- import React from 'react';
12
- import _ from 'lodash';
11
+ import type { TextAreaProps } from 'antd/es/input';
12
+ import React, { useEffect, useRef, useState } from 'react';
13
13
  import { largeField, EditableItemModel } from '@nocobase/flow-engine';
14
14
  import { FieldModel } from '../base/FieldModel';
15
15
 
16
+ function IMESafeTextArea(props: TextAreaProps) {
17
+ const { value, onChange, onCompositionStart, onCompositionEnd, ...rest } = props;
18
+ const [textAreaValue, setTextAreaValue] = useState<TextAreaProps['value']>(() => normalizeTextAreaValue(value));
19
+ const textAreaValueRef = useRef<TextAreaProps['value']>(textAreaValue);
20
+ const pendingLocalValuesRef = useRef<TextAreaProps['value'][]>([]);
21
+
22
+ useEffect(() => {
23
+ const nextValue = normalizeTextAreaValue(value);
24
+ const pendingValues = pendingLocalValuesRef.current;
25
+ const pendingIndex = pendingValues.findIndex((item) => Object.is(item, nextValue));
26
+
27
+ if (pendingIndex >= 0) {
28
+ pendingValues.splice(0, pendingIndex + 1);
29
+ if (!Object.is(textAreaValueRef.current, nextValue)) {
30
+ return;
31
+ }
32
+ }
33
+
34
+ pendingValues.length = 0;
35
+ if (Object.is(textAreaValueRef.current, nextValue)) {
36
+ return;
37
+ }
38
+
39
+ textAreaValueRef.current = nextValue;
40
+ setTextAreaValue(nextValue);
41
+ }, [value]);
42
+
43
+ const getEventValue = (event: React.ChangeEvent<HTMLTextAreaElement> | React.CompositionEvent<HTMLTextAreaElement>) =>
44
+ event.currentTarget.value;
45
+
46
+ const recordLocalValue = (
47
+ event: React.ChangeEvent<HTMLTextAreaElement> | React.CompositionEvent<HTMLTextAreaElement>,
48
+ ) => {
49
+ const nextValue = getEventValue(event);
50
+ const pendingValues = pendingLocalValuesRef.current;
51
+ pendingValues.push(nextValue);
52
+ if (pendingValues.length > 20) {
53
+ pendingValues.shift();
54
+ }
55
+ textAreaValueRef.current = nextValue;
56
+ setTextAreaValue(nextValue);
57
+ };
58
+
59
+ return (
60
+ <Input.TextArea
61
+ {...rest}
62
+ value={textAreaValue}
63
+ onChange={(event) => {
64
+ recordLocalValue(event);
65
+ onChange?.(event);
66
+ }}
67
+ onCompositionStart={(event) => {
68
+ recordLocalValue(event);
69
+ onCompositionStart?.(event);
70
+ }}
71
+ onCompositionEnd={(event) => {
72
+ recordLocalValue(event);
73
+ onCompositionEnd?.(event);
74
+ }}
75
+ />
76
+ );
77
+ }
78
+
79
+ function normalizeTextAreaValue(nextValue: unknown): TextAreaProps['value'] {
80
+ if (typeof nextValue === 'bigint') return String(nextValue);
81
+ if (nextValue == null) return '';
82
+ return nextValue as TextAreaProps['value'];
83
+ }
84
+
16
85
  @largeField()
17
86
  export class TextareaFieldModel extends FieldModel {
18
87
  render() {
19
- return <Input.TextArea {...this.props} />;
88
+ return <IMESafeTextArea {...this.props} />;
20
89
  }
21
90
  }
22
91
 
@@ -48,12 +48,12 @@ export class VariableFieldFormModel extends FlowModel {
48
48
  <FormProvider form={this.form}>
49
49
  <FormLayout layout={'vertical'}>
50
50
  {this.mapSubModels('fields', (field) => {
51
- // 确保字段模型具备稳定的 id/name,便于依赖路径的组件(如公式字段)正确解析
51
+ // Avoid conflicting with the DOM nodeName property when rendering the temporary input.
52
52
  const init = field?.getStepParams?.('fieldSettings', 'init') || {};
53
53
  const fp = init?.fieldPath as string | undefined;
54
54
  if (fp) {
55
- const namePath = fp.includes('.') ? fp.split('.') : [fp];
56
- const toSet: any = {};
55
+ const namePath = (fp === 'nodeName' ? '__nb_variable_field_nodeName' : fp).split('.');
56
+ const toSet: Record<string, unknown> = {};
57
57
  if (!field?.props?.id) toSet.id = namePath;
58
58
  if (!field?.props?.name) toSet.name = namePath;
59
59
  if (Object.keys(toSet).length) field?.setProps?.(toSet);
@@ -0,0 +1,96 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import { FlowEngine } from '@nocobase/flow-engine';
11
+ import { render, screen } from '@testing-library/react';
12
+ import { describe, expect, it } from 'vitest';
13
+ import { DisplayDateTimeFieldModel } from '../DisplayDateTimeFieldModel';
14
+
15
+ describe('DisplayDateTimeFieldModel', () => {
16
+ it('uses dateFormat, showTime, and timeFormat when rendering read pretty datetime text', () => {
17
+ const engine = new FlowEngine();
18
+ engine.registerModels({ DisplayDateTimeFieldModel });
19
+
20
+ const model = engine.createModel<DisplayDateTimeFieldModel>({
21
+ use: DisplayDateTimeFieldModel,
22
+ uid: 'display-datetime-field-format',
23
+ props: {
24
+ value: '2026-06-15 13:05:06',
25
+ dateFormat: 'YYYY-MM-DD',
26
+ showTime: true,
27
+ timeFormat: 'HH:mm:ss',
28
+ },
29
+ });
30
+
31
+ render(model.render());
32
+
33
+ expect(screen.getByText('2026-06-15 13:05:06')).toBeInTheDocument();
34
+ });
35
+
36
+ it('does not reuse a time-only format when rendering datetime text', () => {
37
+ const engine = new FlowEngine();
38
+ engine.registerModels({ DisplayDateTimeFieldModel });
39
+
40
+ const model = engine.createModel<DisplayDateTimeFieldModel>({
41
+ use: DisplayDateTimeFieldModel,
42
+ uid: 'display-datetime-field-stale-time-format',
43
+ props: {
44
+ value: '2026-06-15 13:05:06',
45
+ format: 'HH:mm:ss',
46
+ showTime: true,
47
+ timeFormat: 'HH:mm:ss',
48
+ },
49
+ });
50
+
51
+ render(model.render());
52
+
53
+ expect(screen.getByText('2026-06-15 13:05:06')).toBeInTheDocument();
54
+ expect(screen.queryByText('13:05:06')).not.toBeInTheDocument();
55
+ });
56
+
57
+ it('keeps an existing complete datetime format when no split format props are configured', () => {
58
+ const engine = new FlowEngine();
59
+ engine.registerModels({ DisplayDateTimeFieldModel });
60
+
61
+ const model = engine.createModel<DisplayDateTimeFieldModel>({
62
+ use: DisplayDateTimeFieldModel,
63
+ uid: 'display-datetime-field-complete-format',
64
+ props: {
65
+ value: '2026-06-15 13:05:06',
66
+ format: 'YYYY/MM/DD HH:mm:ss',
67
+ },
68
+ });
69
+
70
+ render(model.render());
71
+
72
+ expect(screen.getByText('2026/06/15 13:05:06')).toBeInTheDocument();
73
+ });
74
+
75
+ it('renders only the date for date-only fields even when a datetime format remains', () => {
76
+ const engine = new FlowEngine();
77
+ engine.registerModels({ DisplayDateTimeFieldModel });
78
+
79
+ const model = engine.createModel<DisplayDateTimeFieldModel>({
80
+ use: DisplayDateTimeFieldModel,
81
+ uid: 'display-datetime-field-date-only',
82
+ props: {
83
+ value: '2026-06-15 13:05:06',
84
+ dateOnly: true,
85
+ format: 'YYYY-MM-DD HH:mm:ss',
86
+ showTime: false,
87
+ timeFormat: 'HH:mm:ss',
88
+ },
89
+ });
90
+
91
+ render(model.render());
92
+
93
+ expect(screen.getByText('2026-06-15')).toBeInTheDocument();
94
+ expect(screen.queryByText('2026-06-15 13:05:06')).not.toBeInTheDocument();
95
+ });
96
+ });
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { FlowEngine } from '@nocobase/flow-engine';
11
- import { render, screen } from '@testing-library/react';
11
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
12
12
  import React from 'react';
13
13
  import { describe, expect, it, vi } from 'vitest';
14
14
  import { InputFieldModel } from '../InputFieldModel';
@@ -43,6 +43,122 @@ describe('InputFieldModel', () => {
43
43
  consoleError.mockRestore();
44
44
  });
45
45
 
46
+ it('keeps IME composition text visible before the parent value is committed', () => {
47
+ const onChange = vi.fn();
48
+ const onCompositionStart = vi.fn();
49
+ const onCompositionEnd = vi.fn();
50
+ const model = createInputFieldModel({
51
+ value: '',
52
+ onChange,
53
+ onCompositionStart,
54
+ onCompositionEnd,
55
+ });
56
+
57
+ render(<>{model.render()}</>);
58
+
59
+ const input = screen.getByRole('textbox') as HTMLInputElement;
60
+
61
+ fireEvent.compositionStart(input);
62
+ fireEvent.change(input, { target: { value: 'ni' }, nativeEvent: { isComposing: true } });
63
+
64
+ expect(input.value).toBe('ni');
65
+ expect(onChange).toHaveBeenCalledTimes(1);
66
+ expect(onCompositionStart).toHaveBeenCalledTimes(1);
67
+
68
+ fireEvent.compositionEnd(input, { target: { value: '你' } });
69
+
70
+ expect(input.value).toBe('你');
71
+ expect(onCompositionEnd).toHaveBeenCalledTimes(1);
72
+ });
73
+
74
+ it('returns to the committed parent value after IME composition is submitted', () => {
75
+ const onChange = vi.fn();
76
+ const onCompositionEnd = vi.fn();
77
+ const createModel = (value: string) =>
78
+ createInputFieldModel({
79
+ value,
80
+ onChange,
81
+ onCompositionEnd,
82
+ });
83
+
84
+ const { rerender } = render(<>{createModel('').render()}</>);
85
+ const input = screen.getByRole('textbox') as HTMLInputElement;
86
+
87
+ fireEvent.compositionStart(input);
88
+ fireEvent.change(input, { target: { value: 'ni' }, nativeEvent: { isComposing: true } });
89
+ fireEvent.compositionEnd(input, { target: { value: '你' } });
90
+
91
+ expect(input.value).toBe('你');
92
+
93
+ rerender(<>{createModel('你').render()}</>);
94
+
95
+ expect(input.value).toBe('你');
96
+ });
97
+
98
+ it('does not overwrite in-progress DOM input with a stale parent value', async () => {
99
+ const onChange = vi.fn();
100
+ const createModel = (value: string) =>
101
+ createInputFieldModel({
102
+ value,
103
+ onChange,
104
+ });
105
+
106
+ const { rerender } = render(<>{createModel('').render()}</>);
107
+ const input = screen.getByRole('textbox') as HTMLInputElement;
108
+
109
+ fireEvent.change(input, { target: { value: 'té' } });
110
+ rerender(<>{createModel('').render()}</>);
111
+
112
+ expect(input.value).toBe('té');
113
+
114
+ rerender(<>{createModel('reset').render()}</>);
115
+
116
+ await waitFor(() => {
117
+ expect((screen.getByRole('textbox') as HTMLInputElement).value).toBe('reset');
118
+ });
119
+ });
120
+
121
+ it('keeps replacement-style Vietnamese IME edits in the DOM', () => {
122
+ const onChange = vi.fn();
123
+ const createModel = (value: string) =>
124
+ createInputFieldModel({
125
+ value,
126
+ onChange,
127
+ });
128
+
129
+ const { rerender } = render(<>{createModel('').render()}</>);
130
+ const input = screen.getByRole('textbox') as HTMLInputElement;
131
+
132
+ fireEvent.change(input, { target: { value: 'te' } });
133
+ rerender(<>{createModel('te').render()}</>);
134
+ expect(input.value).toBe('te');
135
+
136
+ fireEvent.change(input, { target: { value: 'té' } });
137
+ rerender(<>{createModel('te').render()}</>);
138
+ expect(input.value).toBe('té');
139
+
140
+ fireEvent.change(input, { target: { value: 'tét' } });
141
+ rerender(<>{createModel('té').render()}</>);
142
+ expect(input.value).toBe('tét');
143
+ });
144
+
145
+ it('does not overwrite local replacement edit when parent advances to an older value', () => {
146
+ const onChange = vi.fn();
147
+ const createModel = (value: string) =>
148
+ createInputFieldModel({
149
+ value,
150
+ onChange,
151
+ });
152
+
153
+ const { rerender } = render(<>{createModel('').render()}</>);
154
+ const input = screen.getByRole('textbox') as HTMLInputElement;
155
+
156
+ fireEvent.change(input, { target: { value: 'té' } });
157
+ rerender(<>{createModel('t').render()}</>);
158
+
159
+ expect(input.value).toBe('té');
160
+ });
161
+
46
162
  it('renders ScanInput when scan input is enabled', () => {
47
163
  const model = createInputFieldModel({ enableScan: true });
48
164
 
@@ -0,0 +1,43 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import React from 'react';
11
+ import { describe, expect, it } from 'vitest';
12
+ import { SelectFieldModel } from '../SelectFieldModel';
13
+
14
+ function mockT(text: string) {
15
+ if (text === '{{t("Yes")}}') return '是';
16
+ if (text === '{{t("No")}}') return '否';
17
+ return text;
18
+ }
19
+
20
+ describe('SelectFieldModel', () => {
21
+ it('translates enum fallback labels for selected values', () => {
22
+ const model = {
23
+ props: {
24
+ value: true,
25
+ },
26
+ context: {
27
+ collectionField: {
28
+ uiSchema: {
29
+ enum: [
30
+ { label: '{{t("Yes")}}', value: true },
31
+ { label: '{{t("No")}}', value: false },
32
+ ],
33
+ },
34
+ },
35
+ },
36
+ translate: mockT,
37
+ } as unknown as SelectFieldModel;
38
+
39
+ const element = SelectFieldModel.prototype.render.call(model) as React.ReactElement;
40
+
41
+ expect(element.props.value).toEqual({ label: '是', value: true });
42
+ });
43
+ });
@@ -0,0 +1,131 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import { FieldModelRenderer, FlowEngine, FlowEngineProvider } from '@nocobase/flow-engine';
11
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
12
+ import { Form } from 'antd';
13
+ import React from 'react';
14
+ import { describe, expect, it, vi } from 'vitest';
15
+ import { TextareaFieldModel } from '../TextareaFieldModel';
16
+
17
+ function createTextareaFieldModel(props?: Record<string, unknown>) {
18
+ const engine = new FlowEngine();
19
+ return new TextareaFieldModel({
20
+ uid: 'textarea-field-model-test',
21
+ flowEngine: engine,
22
+ props,
23
+ });
24
+ }
25
+
26
+ describe('TextareaFieldModel', () => {
27
+ it('syncs multiline values assigned through the form renderer', async () => {
28
+ const flowEngine = new FlowEngine();
29
+ const model = createTextareaFieldModel();
30
+ model.dispatchEvent = vi.fn().mockResolvedValue([]);
31
+
32
+ function FormHost() {
33
+ const [form] = Form.useForm();
34
+
35
+ React.useEffect(() => {
36
+ form.setFieldsValue({ address: 'aaaaaa\nbbbbbb' });
37
+ }, [form]);
38
+
39
+ return (
40
+ <FlowEngineProvider engine={flowEngine}>
41
+ <Form form={form}>
42
+ <Form.Item name="address">
43
+ <FieldModelRenderer model={model} />
44
+ </Form.Item>
45
+ </Form>
46
+ </FlowEngineProvider>
47
+ );
48
+ }
49
+
50
+ render(<FormHost />);
51
+
52
+ await waitFor(() => {
53
+ expect((screen.getByRole('textbox') as HTMLTextAreaElement).value).toBe('aaaaaa\nbbbbbb');
54
+ });
55
+ });
56
+
57
+ it('keeps IME composition text visible before the parent value is committed', () => {
58
+ const onChange = vi.fn();
59
+ const onCompositionStart = vi.fn();
60
+ const onCompositionEnd = vi.fn();
61
+ const model = createTextareaFieldModel({
62
+ value: '',
63
+ onChange,
64
+ onCompositionStart,
65
+ onCompositionEnd,
66
+ });
67
+
68
+ render(<>{model.render()}</>);
69
+
70
+ const textarea = screen.getByRole('textbox') as HTMLTextAreaElement;
71
+
72
+ fireEvent.compositionStart(textarea);
73
+ fireEvent.change(textarea, { target: { value: 'ni' }, nativeEvent: { isComposing: true } });
74
+
75
+ expect(textarea.value).toBe('ni');
76
+ expect(onChange).toHaveBeenCalledTimes(1);
77
+ expect(onCompositionStart).toHaveBeenCalledTimes(1);
78
+
79
+ fireEvent.compositionEnd(textarea, { target: { value: '你' } });
80
+
81
+ expect(textarea.value).toBe('你');
82
+ expect(onCompositionEnd).toHaveBeenCalledTimes(1);
83
+ });
84
+
85
+ it('does not overwrite in-progress DOM input with a stale parent value', async () => {
86
+ const onChange = vi.fn();
87
+ const createModel = (value: string) =>
88
+ createTextareaFieldModel({
89
+ value,
90
+ onChange,
91
+ });
92
+
93
+ const { rerender } = render(<>{createModel('').render()}</>);
94
+ const textarea = screen.getByRole('textbox') as HTMLTextAreaElement;
95
+
96
+ fireEvent.change(textarea, { target: { value: 'té' } });
97
+ rerender(<>{createModel('').render()}</>);
98
+
99
+ expect(textarea.value).toBe('té');
100
+
101
+ rerender(<>{createModel('reset').render()}</>);
102
+
103
+ await waitFor(() => {
104
+ expect((screen.getByRole('textbox') as HTMLTextAreaElement).value).toBe('reset');
105
+ });
106
+ });
107
+
108
+ it('keeps replacement-style Vietnamese IME edits in the DOM', () => {
109
+ const onChange = vi.fn();
110
+ const createModel = (value: string) =>
111
+ createTextareaFieldModel({
112
+ value,
113
+ onChange,
114
+ });
115
+
116
+ const { rerender } = render(<>{createModel('').render()}</>);
117
+ const textarea = screen.getByRole('textbox') as HTMLTextAreaElement;
118
+
119
+ fireEvent.change(textarea, { target: { value: 'te' } });
120
+ rerender(<>{createModel('te').render()}</>);
121
+ expect(textarea.value).toBe('te');
122
+
123
+ fireEvent.change(textarea, { target: { value: 'té' } });
124
+ rerender(<>{createModel('te').render()}</>);
125
+ expect(textarea.value).toBe('té');
126
+
127
+ fireEvent.change(textarea, { target: { value: 'tét' } });
128
+ rerender(<>{createModel('té').render()}</>);
129
+ expect(textarea.value).toBe('tét');
130
+ });
131
+ });