@nocobase/flow-engine 2.0.0-alpha.4 → 2.0.0-alpha.41

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 (466) hide show
  1. package/lib/BlockScopedFlowEngine.d.ts +23 -0
  2. package/lib/BlockScopedFlowEngine.js +90 -0
  3. package/lib/FlowContextProvider.d.ts +2 -2
  4. package/lib/FlowContextProvider.js +3 -3
  5. package/lib/FlowDefinition.d.ts +4 -2
  6. package/lib/JSRunner.js +3 -0
  7. package/lib/ViewScopedFlowEngine.d.ts +1 -1
  8. package/lib/ViewScopedFlowEngine.js +12 -0
  9. package/lib/acl/Acl.d.ts +1 -0
  10. package/lib/acl/Acl.js +11 -0
  11. package/lib/components/FieldModelRenderer.js +14 -6
  12. package/lib/components/FieldSkeleton.d.ts +10 -0
  13. package/lib/components/FieldSkeleton.js +64 -0
  14. package/lib/components/FlowContextSelector.js +7 -2
  15. package/lib/components/FlowModelRenderer.d.ts +3 -6
  16. package/lib/components/FlowModelRenderer.js +16 -47
  17. package/lib/components/FormItem.js +5 -1
  18. package/lib/components/MobilePopup.d.ts +20 -0
  19. package/lib/components/MobilePopup.js +102 -0
  20. package/lib/components/MobilePopup.style.d.ts +17 -0
  21. package/lib/components/MobilePopup.style.js +186 -0
  22. package/lib/components/index.d.ts +1 -0
  23. package/lib/components/index.js +3 -1
  24. package/lib/components/settings/independents/dropdown/FlowsDropdownButton.js +2 -2
  25. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +17 -5
  26. package/lib/components/settings/wrappers/contextual/FlowsContextMenu.js +2 -2
  27. package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +2 -2
  28. package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +35 -6
  29. package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +7 -1
  30. package/lib/components/variables/VariableInput.js +19 -5
  31. package/lib/components/variables/VariableTag.js +43 -2
  32. package/lib/components/variables/types.d.ts +2 -0
  33. package/lib/data-source/index.d.ts +15 -4
  34. package/lib/data-source/index.js +42 -13
  35. package/lib/data-source/sortCollectionsByInherits.d.ts +10 -0
  36. package/lib/data-source/sortCollectionsByInherits.js +71 -0
  37. package/lib/emitter.d.ts +6 -0
  38. package/lib/emitter.js +12 -0
  39. package/lib/executor/FlowExecutor.d.ts +4 -5
  40. package/lib/executor/FlowExecutor.js +168 -99
  41. package/lib/flow-registry/GlobalFlowRegistry.d.ts +1 -0
  42. package/lib/flow-registry/GlobalFlowRegistry.js +3 -0
  43. package/lib/flow-registry/InstanceFlowRegistry.d.ts +1 -0
  44. package/lib/flow-registry/InstanceFlowRegistry.js +3 -0
  45. package/lib/flowContext.d.ts +37 -5
  46. package/lib/flowContext.js +224 -87
  47. package/lib/flowEngine.d.ts +24 -1
  48. package/lib/flowEngine.js +81 -8
  49. package/lib/flowSettings.d.ts +2 -1
  50. package/lib/flowSettings.js +16 -8
  51. package/lib/hooks/useApplyAutoFlows.js +2 -1
  52. package/lib/index.d.ts +7 -1
  53. package/lib/index.js +32 -3
  54. package/lib/locale/en-US.json +4 -2
  55. package/lib/locale/index.d.ts +4 -0
  56. package/lib/locale/zh-CN.json +4 -2
  57. package/lib/models/CollectionFieldModel.d.ts +3 -0
  58. package/lib/models/CollectionFieldModel.js +52 -6
  59. package/lib/models/flowModel.d.ts +30 -29
  60. package/lib/models/flowModel.js +153 -100
  61. package/lib/models/forkFlowModel.d.ts +8 -4
  62. package/lib/models/forkFlowModel.js +38 -8
  63. package/lib/provider.d.ts +3 -1
  64. package/lib/provider.js +7 -5
  65. package/lib/resources/multiRecordResource.js +25 -1
  66. package/lib/resources/singleRecordResource.js +19 -1
  67. package/lib/resources/sqlResource.d.ts +1 -0
  68. package/lib/resources/sqlResource.js +20 -24
  69. package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.d.ts +1 -6
  70. package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +27 -20
  71. package/lib/runjs-context/contexts/JSBlockRunJSContext.d.ts +1 -6
  72. package/lib/runjs-context/contexts/JSBlockRunJSContext.js +46 -33
  73. package/lib/runjs-context/contexts/JSCollectionActionRunJSContext.d.ts +1 -2
  74. package/lib/runjs-context/contexts/JSCollectionActionRunJSContext.js +14 -15
  75. package/lib/runjs-context/contexts/{LinkageRunJSContext.d.ts → JSColumnRunJSContext.d.ts} +6 -3
  76. package/lib/runjs-context/contexts/JSColumnRunJSContext.js +78 -0
  77. package/lib/runjs-context/contexts/JSFieldRunJSContext.d.ts +1 -6
  78. package/lib/runjs-context/contexts/JSFieldRunJSContext.js +28 -24
  79. package/lib/runjs-context/contexts/JSItemRunJSContext.d.ts +1 -6
  80. package/lib/runjs-context/contexts/JSItemRunJSContext.js +24 -20
  81. package/lib/runjs-context/contexts/JSRecordActionRunJSContext.d.ts +1 -2
  82. package/lib/runjs-context/contexts/JSRecordActionRunJSContext.js +16 -17
  83. package/lib/runjs-context/contexts/base.d.ts +9 -0
  84. package/lib/runjs-context/contexts/base.js +205 -0
  85. package/lib/runjs-context/helpers.d.ts +5 -2
  86. package/lib/runjs-context/helpers.js +36 -27
  87. package/lib/runjs-context/registry.d.ts +7 -4
  88. package/lib/runjs-context/registry.js +10 -42
  89. package/lib/runjs-context/setup.d.ts +9 -0
  90. package/lib/runjs-context/setup.js +82 -0
  91. package/lib/runjs-context/snippets/global/{copy-record-json.snippet.js → api-request.snippet.js} +25 -10
  92. package/lib/runjs-context/snippets/global/clipboard-copy-text.snippet.js +61 -0
  93. package/lib/runjs-context/snippets/global/{view-navigation-push.snippet.js → import-esm.snippet.js} +26 -12
  94. package/lib/runjs-context/snippets/global/message-error.snippet.js +6 -0
  95. package/lib/runjs-context/snippets/global/message-success.snippet.js +6 -0
  96. package/lib/runjs-context/snippets/global/notification-open.snippet.d.ts +3 -8
  97. package/lib/runjs-context/snippets/global/notification-open.snippet.js +8 -1
  98. package/lib/runjs-context/snippets/global/open-view-dialog.snippet.js +10 -3
  99. package/lib/runjs-context/snippets/global/open-view-drawer.snippet.js +10 -3
  100. package/lib/runjs-context/snippets/global/query-selector.snippet.js +53 -0
  101. package/lib/runjs-context/snippets/global/require-amd.snippet.d.ts +11 -0
  102. package/lib/runjs-context/snippets/global/{requireAsync.snippet.js → require-amd.snippet.js} +16 -13
  103. package/lib/runjs-context/snippets/global/window-open.snippet.d.ts +3 -8
  104. package/lib/runjs-context/snippets/global/window-open.snippet.js +8 -1
  105. package/lib/runjs-context/snippets/index.d.ts +14 -3
  106. package/lib/runjs-context/snippets/index.js +162 -40
  107. package/lib/runjs-context/snippets/scene/block/add-event-listener.snippet.d.ts +11 -0
  108. package/lib/runjs-context/snippets/scene/{jsblock → block}/add-event-listener.snippet.js +11 -2
  109. package/lib/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.d.ts +11 -0
  110. package/lib/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.js +64 -0
  111. package/lib/runjs-context/snippets/scene/block/chartjs-bar.snippet.d.ts +11 -0
  112. package/lib/runjs-context/snippets/scene/block/chartjs-bar.snippet.js +99 -0
  113. package/lib/runjs-context/snippets/{libs → scene/block}/echarts-init.snippet.js +24 -7
  114. package/lib/runjs-context/snippets/scene/block/render-antd-icons.snippet.d.ts +11 -0
  115. package/lib/runjs-context/snippets/scene/block/render-antd-icons.snippet.js +65 -0
  116. package/lib/runjs-context/snippets/scene/block/render-button-handler.snippet.d.ts +11 -0
  117. package/lib/runjs-context/snippets/scene/{jsblock → block}/render-button-handler.snippet.js +17 -9
  118. package/lib/runjs-context/snippets/scene/block/render-iframe.snippet.d.ts +11 -0
  119. package/lib/runjs-context/snippets/scene/block/render-iframe.snippet.js +57 -0
  120. package/lib/runjs-context/snippets/scene/block/render-info-card.snippet.d.ts +11 -0
  121. package/lib/runjs-context/snippets/scene/block/render-info-card.snippet.js +71 -0
  122. package/lib/runjs-context/snippets/scene/block/render-react-jsx.snippet.d.ts +11 -0
  123. package/lib/runjs-context/snippets/scene/{jsblock/render-card.snippet.js → block/render-react-jsx.snippet.js} +26 -13
  124. package/lib/runjs-context/snippets/scene/block/render-react.snippet.d.ts +11 -0
  125. package/lib/runjs-context/snippets/scene/{jsblock → block}/render-react.snippet.js +18 -17
  126. package/lib/runjs-context/snippets/scene/block/render-statistics.snippet.d.ts +11 -0
  127. package/lib/runjs-context/snippets/scene/block/render-statistics.snippet.js +95 -0
  128. package/lib/runjs-context/snippets/scene/block/render-timeline.snippet.d.ts +11 -0
  129. package/lib/runjs-context/snippets/scene/block/render-timeline.snippet.js +84 -0
  130. package/lib/runjs-context/snippets/scene/block/resource-example.snippet.d.ts +11 -0
  131. package/lib/runjs-context/snippets/scene/block/resource-example.snippet.js +60 -0
  132. package/lib/runjs-context/snippets/scene/block/three-users-orbit.snippet.d.ts +11 -0
  133. package/lib/runjs-context/snippets/scene/block/three-users-orbit.snippet.js +283 -0
  134. package/lib/runjs-context/snippets/scene/block/vue-component.snippet.d.ts +11 -0
  135. package/lib/runjs-context/snippets/scene/block/vue-component.snippet.js +124 -0
  136. package/lib/runjs-context/snippets/scene/detail/color-by-value.snippet.d.ts +11 -0
  137. package/lib/runjs-context/snippets/scene/{jsfield → detail}/color-by-value.snippet.js +13 -3
  138. package/lib/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.d.ts +11 -0
  139. package/lib/runjs-context/snippets/{global → scene/detail}/copy-to-clipboard.snippet.js +28 -6
  140. package/lib/runjs-context/snippets/scene/detail/format-number.snippet.d.ts +11 -0
  141. package/lib/runjs-context/snippets/scene/{jsfield → detail}/format-number.snippet.js +13 -3
  142. package/lib/runjs-context/snippets/scene/detail/innerHTML-value.snippet.d.ts +11 -0
  143. package/lib/runjs-context/snippets/scene/{jsfield → detail}/innerHTML-value.snippet.js +13 -3
  144. package/lib/runjs-context/snippets/scene/detail/percentage-bar.snippet.d.ts +11 -0
  145. package/lib/runjs-context/snippets/scene/detail/percentage-bar.snippet.js +82 -0
  146. package/lib/runjs-context/snippets/scene/detail/relative-time.snippet.d.ts +11 -0
  147. package/lib/runjs-context/snippets/scene/detail/relative-time.snippet.js +80 -0
  148. package/lib/runjs-context/snippets/scene/detail/status-tag.snippet.d.ts +11 -0
  149. package/lib/runjs-context/snippets/scene/detail/status-tag.snippet.js +74 -0
  150. package/lib/runjs-context/snippets/scene/form/calculate-total.snippet.d.ts +11 -0
  151. package/lib/runjs-context/snippets/scene/form/calculate-total.snippet.js +63 -0
  152. package/lib/runjs-context/snippets/scene/form/cascade-select.snippet.d.ts +11 -0
  153. package/lib/runjs-context/snippets/scene/form/cascade-select.snippet.js +81 -0
  154. package/lib/runjs-context/snippets/scene/form/conditional-required.snippet.d.ts +11 -0
  155. package/lib/runjs-context/snippets/scene/form/conditional-required.snippet.js +64 -0
  156. package/lib/runjs-context/snippets/scene/form/copy-field-values.snippet.d.ts +11 -0
  157. package/lib/runjs-context/snippets/scene/form/copy-field-values.snippet.js +74 -0
  158. package/lib/runjs-context/snippets/scene/form/render-basic.snippet.d.ts +11 -0
  159. package/lib/runjs-context/snippets/scene/{jsitem → form}/render-basic.snippet.js +11 -2
  160. package/lib/runjs-context/snippets/scene/form/set-disabled.snippet.d.ts +11 -0
  161. package/lib/runjs-context/snippets/scene/{linkage → form}/set-disabled.snippet.js +12 -3
  162. package/lib/runjs-context/snippets/scene/form/set-field-value.snippet.d.ts +11 -0
  163. package/lib/runjs-context/snippets/scene/{linkage → form}/set-field-value.snippet.js +12 -3
  164. package/lib/runjs-context/snippets/scene/form/set-required.snippet.d.ts +11 -0
  165. package/lib/runjs-context/snippets/scene/{linkage → form}/set-required.snippet.js +12 -3
  166. package/lib/runjs-context/snippets/scene/form/toggle-multiple-fields.snippet.d.ts +11 -0
  167. package/lib/runjs-context/snippets/scene/form/toggle-multiple-fields.snippet.js +67 -0
  168. package/lib/runjs-context/snippets/scene/form/toggle-visible.snippet.d.ts +11 -0
  169. package/lib/runjs-context/snippets/scene/{linkage → form}/toggle-visible.snippet.js +12 -3
  170. package/lib/runjs-context/snippets/scene/table/cell-open-dialog.snippet.d.ts +11 -0
  171. package/lib/runjs-context/snippets/scene/table/cell-open-dialog.snippet.js +64 -0
  172. package/lib/runjs-context/snippets/scene/table/collection-selected-count.snippet.d.ts +11 -0
  173. package/lib/runjs-context/snippets/scene/{actions → table}/collection-selected-count.snippet.js +11 -2
  174. package/lib/runjs-context/snippets/scene/table/concat-fields.snippet.d.ts +11 -0
  175. package/lib/runjs-context/snippets/scene/table/concat-fields.snippet.js +79 -0
  176. package/lib/runjs-context/snippets/scene/table/destroy-selected.snippet.d.ts +11 -0
  177. package/lib/runjs-context/snippets/{global/log-json-record.snippet.js → scene/table/destroy-selected.snippet.js} +24 -11
  178. package/lib/runjs-context/snippets/scene/table/export-selected-json.snippet.d.ts +11 -0
  179. package/lib/runjs-context/snippets/scene/table/export-selected-json.snippet.js +64 -0
  180. package/lib/runjs-context/snippets/scene/table/iterate-selected-rows.snippet.d.ts +11 -0
  181. package/lib/runjs-context/snippets/scene/{actions → table}/iterate-selected-rows.snippet.js +11 -2
  182. package/lib/runjs-context/snippets/types.d.ts +9 -1
  183. package/lib/scheduler/ModelOperationScheduler.d.ts +51 -0
  184. package/lib/scheduler/ModelOperationScheduler.js +262 -0
  185. package/lib/types.d.ts +28 -3
  186. package/lib/types.js +4 -3
  187. package/lib/utils/associationObjectVariable.d.ts +32 -0
  188. package/lib/utils/associationObjectVariable.js +157 -0
  189. package/lib/utils/buildSettingsViewInputArgs.d.ts +19 -0
  190. package/lib/utils/buildSettingsViewInputArgs.js +75 -0
  191. package/lib/utils/createEphemeralContext.d.ts +13 -0
  192. package/lib/utils/createEphemeralContext.js +140 -0
  193. package/lib/utils/flows.d.ts +10 -0
  194. package/lib/{runjs-context/snippets/global/api-request-post.snippet.js → utils/flows.js} +21 -15
  195. package/lib/utils/index.d.ts +7 -2
  196. package/lib/utils/index.js +21 -2
  197. package/lib/utils/jsxTransform.d.ts +15 -0
  198. package/lib/utils/jsxTransform.js +68 -0
  199. package/lib/utils/params-resolvers.js +3 -3
  200. package/lib/utils/parsePathnameToViewParams.d.ts +1 -1
  201. package/lib/utils/parsePathnameToViewParams.js +41 -5
  202. package/lib/utils/pruneFilter.d.ts +21 -0
  203. package/lib/{runjs-context/snippets/global/try-catch-async.snippet.js → utils/pruneFilter.js} +24 -16
  204. package/lib/utils/safeGlobals.d.ts +5 -3
  205. package/lib/utils/safeGlobals.js +40 -0
  206. package/lib/utils/schema-utils.js +2 -2
  207. package/lib/utils/serverContextParams.d.ts +1 -0
  208. package/lib/utils/translation.d.ts +4 -1
  209. package/lib/utils/translation.js +6 -2
  210. package/lib/utils/variablesParams.d.ts +22 -5
  211. package/lib/utils/variablesParams.js +132 -36
  212. package/lib/views/DrawerComponent.js +21 -2
  213. package/lib/views/PageComponent.js +2 -1
  214. package/lib/views/ViewNavigation.d.ts +3 -9
  215. package/lib/views/ViewNavigation.js +16 -2
  216. package/lib/views/createViewMeta.d.ts +29 -1
  217. package/lib/views/createViewMeta.js +338 -72
  218. package/lib/views/index.d.ts +1 -0
  219. package/lib/views/index.js +3 -0
  220. package/lib/views/useDialog.d.ts +9 -8
  221. package/lib/views/useDialog.js +19 -14
  222. package/lib/views/useDrawer.d.ts +9 -8
  223. package/lib/views/useDrawer.js +54 -35
  224. package/lib/views/usePage.d.ts +9 -8
  225. package/lib/views/usePage.js +20 -14
  226. package/package.json +6 -3
  227. package/src/BlockScopedFlowEngine.ts +86 -0
  228. package/src/FlowContextProvider.tsx +4 -2
  229. package/src/JSRunner.ts +3 -0
  230. package/src/ViewScopedFlowEngine.ts +15 -1
  231. package/src/__tests__/JSRunner.test.ts +62 -53
  232. package/src/__tests__/blockScopedFlowEngine.test.ts +154 -0
  233. package/src/__tests__/createViewMeta.popup.test.ts +132 -0
  234. package/src/__tests__/flow-engine.test.ts +3 -0
  235. package/src/__tests__/flowContext.test.ts +52 -0
  236. package/src/__tests__/flowContextCreateJSRunner.test.ts +163 -0
  237. package/src/__tests__/flowEngine.saveModel.test.ts +4 -0
  238. package/src/__tests__/flowRunJSContextDefine.test.ts +508 -0
  239. package/src/__tests__/flowSettings.open.test.tsx +2 -0
  240. package/src/__tests__/flowSettings.test.ts +2 -0
  241. package/src/__tests__/globalFlowRegistry.test.ts +1 -1
  242. package/src/__tests__/modelOperationScheduler.test.ts +346 -0
  243. package/src/__tests__/objectVariable.test.ts +464 -0
  244. package/src/__tests__/runjsContext.test.ts +216 -35
  245. package/src/__tests__/runjsContextImplementations.test.ts +217 -0
  246. package/src/__tests__/runjsContextRuntime.test.ts +269 -0
  247. package/src/__tests__/runjsEdgeCases.test.ts +281 -0
  248. package/src/__tests__/runjsLocales.test.ts +36 -0
  249. package/src/__tests__/runjsRuntimeFeatures.test.ts +461 -0
  250. package/src/__tests__/runjsSnippets.test.ts +140 -0
  251. package/src/__tests__/viewScopedFlowEngine.test.ts +3 -3
  252. package/src/acl/Acl.tsx +14 -0
  253. package/src/acl/__tests__/Acl.test.tsx +43 -0
  254. package/src/components/DynamicFlowsEditor.tsx +3 -4
  255. package/src/components/FieldModelRenderer.tsx +20 -7
  256. package/src/components/FieldSkeleton.tsx +27 -0
  257. package/src/components/FlowContextSelector.tsx +6 -2
  258. package/src/components/FlowModelRenderer.tsx +33 -81
  259. package/src/components/FormItem.tsx +8 -1
  260. package/src/components/MobilePopup.style.ts +220 -0
  261. package/src/components/MobilePopup.tsx +86 -0
  262. package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +6 -6
  263. package/src/components/index.ts +1 -0
  264. package/src/components/settings/independents/dropdown/FlowsDropdownButton.tsx +1 -1
  265. package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +17 -4
  266. package/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx +1 -1
  267. package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +39 -10
  268. package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +14 -1
  269. package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +425 -0
  270. package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +5 -7
  271. package/src/components/variables/VariableInput.tsx +25 -5
  272. package/src/components/variables/VariableTag.tsx +54 -2
  273. package/src/components/variables/types.ts +2 -0
  274. package/src/data-source/__tests__/sortCollectionsByInherits.test.ts +125 -0
  275. package/src/data-source/index.ts +42 -11
  276. package/src/data-source/sortCollectionsByInherits.ts +61 -0
  277. package/src/emitter.ts +14 -0
  278. package/src/executor/FlowExecutor.ts +213 -119
  279. package/src/executor/__tests__/ctx-defs-injection.test.ts +197 -0
  280. package/src/executor/__tests__/flowExecutor.test.ts +151 -5
  281. package/src/flow-registry/GlobalFlowRegistry.ts +1 -0
  282. package/src/flow-registry/InstanceFlowRegistry.ts +1 -0
  283. package/src/flow-registry/__tests__/globalFlowRegistry.test.ts +54 -0
  284. package/src/flowContext.ts +303 -104
  285. package/src/flowEngine.ts +96 -6
  286. package/src/flowSettings.ts +12 -4
  287. package/src/hooks/useApplyAutoFlows.ts +3 -1
  288. package/src/index.ts +12 -1
  289. package/src/locale/en-US.json +4 -2
  290. package/src/locale/zh-CN.json +4 -2
  291. package/src/models/CollectionFieldModel.tsx +60 -8
  292. package/src/models/__tests__/dispatchEvent.behavior.test.ts +169 -0
  293. package/src/models/__tests__/flowModel.getFlows.sort.test.ts +33 -9
  294. package/src/models/__tests__/flowModel.scheduleModelOperation.test.tsx +129 -0
  295. package/src/models/__tests__/flowModel.test.ts +234 -111
  296. package/src/models/__tests__/forkFlowModel.test.ts +40 -7
  297. package/src/models/flowModel.tsx +197 -132
  298. package/src/models/forkFlowModel.ts +48 -8
  299. package/src/provider.tsx +10 -7
  300. package/src/resources/multiRecordResource.ts +28 -1
  301. package/src/resources/singleRecordResource.ts +19 -1
  302. package/src/resources/sqlResource.ts +20 -25
  303. package/src/runjs-context/contexts/FormJSFieldItemRunJSContext.ts +28 -21
  304. package/src/runjs-context/contexts/JSBlockRunJSContext.ts +46 -34
  305. package/src/runjs-context/contexts/JSCollectionActionRunJSContext.ts +15 -16
  306. package/src/runjs-context/contexts/JSColumnRunJSContext.ts +58 -0
  307. package/src/runjs-context/contexts/JSFieldRunJSContext.ts +30 -25
  308. package/src/runjs-context/contexts/JSItemRunJSContext.ts +25 -21
  309. package/src/runjs-context/contexts/JSRecordActionRunJSContext.ts +17 -18
  310. package/src/runjs-context/contexts/base.ts +196 -0
  311. package/src/runjs-context/helpers.ts +32 -30
  312. package/src/runjs-context/registry.ts +16 -47
  313. package/src/runjs-context/setup.ts +51 -0
  314. package/src/runjs-context/snippets/global/api-request.snippet.ts +38 -0
  315. package/src/runjs-context/snippets/global/clipboard-copy-text.snippet.ts +42 -0
  316. package/src/runjs-context/snippets/global/import-esm.snippet.ts +39 -0
  317. package/src/runjs-context/snippets/global/message-error.snippet.ts +6 -0
  318. package/src/runjs-context/snippets/global/message-success.snippet.ts +6 -0
  319. package/src/runjs-context/snippets/global/notification-open.snippet.ts +11 -1
  320. package/src/runjs-context/snippets/global/open-view-dialog.snippet.ts +10 -3
  321. package/src/runjs-context/snippets/global/open-view-drawer.snippet.ts +10 -3
  322. package/src/runjs-context/snippets/global/query-selector.snippet.ts +34 -0
  323. package/src/runjs-context/snippets/global/require-amd.snippet.ts +30 -0
  324. package/src/runjs-context/snippets/global/window-open.snippet.ts +11 -1
  325. package/src/runjs-context/snippets/index.ts +178 -39
  326. package/src/runjs-context/snippets/scene/{jsblock → block}/add-event-listener.snippet.ts +14 -2
  327. package/src/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.ts +45 -0
  328. package/src/runjs-context/snippets/scene/block/chartjs-bar.snippet.ts +80 -0
  329. package/src/runjs-context/snippets/scene/block/echarts-init.snippet.ts +44 -0
  330. package/src/runjs-context/snippets/scene/block/render-antd-icons.snippet.ts +46 -0
  331. package/src/runjs-context/snippets/scene/block/render-button-handler.snippet.ts +35 -0
  332. package/src/runjs-context/snippets/scene/block/render-iframe.snippet.ts +38 -0
  333. package/src/runjs-context/snippets/scene/block/render-info-card.snippet.ts +52 -0
  334. package/src/runjs-context/snippets/scene/block/render-react-jsx.snippet.ts +39 -0
  335. package/src/runjs-context/snippets/scene/block/render-react.snippet.ts +38 -0
  336. package/src/runjs-context/snippets/scene/block/render-statistics.snippet.ts +76 -0
  337. package/src/runjs-context/snippets/scene/block/render-timeline.snippet.ts +65 -0
  338. package/src/runjs-context/snippets/scene/block/resource-example.snippet.ts +46 -0
  339. package/src/runjs-context/snippets/scene/block/three-users-orbit.snippet.ts +264 -0
  340. package/src/runjs-context/snippets/scene/block/vue-component.snippet.ts +105 -0
  341. package/src/runjs-context/snippets/scene/detail/color-by-value.snippet.ts +33 -0
  342. package/src/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.ts +45 -0
  343. package/src/runjs-context/snippets/scene/detail/format-number.snippet.ts +32 -0
  344. package/src/runjs-context/snippets/scene/detail/innerHTML-value.snippet.ts +31 -0
  345. package/src/runjs-context/snippets/scene/detail/percentage-bar.snippet.ts +63 -0
  346. package/src/runjs-context/snippets/scene/detail/relative-time.snippet.ts +61 -0
  347. package/src/runjs-context/snippets/scene/detail/status-tag.snippet.ts +55 -0
  348. package/src/runjs-context/snippets/scene/form/calculate-total.snippet.ts +44 -0
  349. package/src/runjs-context/snippets/scene/form/cascade-select.snippet.ts +62 -0
  350. package/src/runjs-context/snippets/scene/form/conditional-required.snippet.ts +45 -0
  351. package/src/runjs-context/snippets/scene/form/copy-field-values.snippet.ts +55 -0
  352. package/src/runjs-context/snippets/scene/{jsitem → form}/render-basic.snippet.ts +14 -2
  353. package/src/runjs-context/snippets/scene/{linkage → form}/set-disabled.snippet.ts +15 -3
  354. package/src/runjs-context/snippets/scene/{linkage → form}/set-field-value.snippet.ts +15 -3
  355. package/src/runjs-context/snippets/scene/{linkage → form}/set-required.snippet.ts +15 -3
  356. package/src/runjs-context/snippets/scene/form/toggle-multiple-fields.snippet.ts +48 -0
  357. package/src/runjs-context/snippets/scene/{linkage → form}/toggle-visible.snippet.ts +15 -3
  358. package/src/runjs-context/snippets/scene/table/cell-open-dialog.snippet.ts +45 -0
  359. package/src/runjs-context/snippets/scene/{actions → table}/collection-selected-count.snippet.ts +14 -2
  360. package/src/runjs-context/snippets/scene/table/concat-fields.snippet.ts +60 -0
  361. package/src/runjs-context/snippets/scene/table/destroy-selected.snippet.ts +36 -0
  362. package/src/runjs-context/snippets/scene/table/export-selected-json.snippet.ts +45 -0
  363. package/src/runjs-context/snippets/scene/{actions → table}/iterate-selected-rows.snippet.ts +14 -2
  364. package/src/runjs-context/snippets/types.ts +5 -1
  365. package/src/scheduler/ModelOperationScheduler.ts +304 -0
  366. package/src/types.ts +34 -0
  367. package/src/utils/__tests__/flows.test.ts +65 -0
  368. package/src/utils/__tests__/jsxTransform.test.ts +38 -0
  369. package/src/utils/__tests__/parsePathnameToViewParams.test.ts +25 -0
  370. package/src/utils/__tests__/pruneFilter.test.ts +38 -0
  371. package/src/utils/__tests__/safeGlobals.test.ts +22 -1
  372. package/src/utils/__tests__/variablesParams.test.ts +63 -0
  373. package/src/utils/associationObjectVariable.ts +180 -0
  374. package/src/utils/buildSettingsViewInputArgs.ts +72 -0
  375. package/src/utils/createEphemeralContext.ts +142 -0
  376. package/src/utils/flows.ts +23 -0
  377. package/src/utils/index.ts +11 -2
  378. package/src/utils/jsxTransform.ts +39 -0
  379. package/src/utils/params-resolvers.ts +2 -2
  380. package/src/utils/parsePathnameToViewParams.ts +50 -6
  381. package/src/utils/pruneFilter.ts +41 -0
  382. package/src/utils/safeGlobals.ts +49 -3
  383. package/src/utils/schema-utils.ts +1 -1
  384. package/src/utils/serverContextParams.ts +1 -0
  385. package/src/utils/translation.ts +7 -2
  386. package/src/utils/variablesParams.ts +156 -38
  387. package/src/views/DrawerComponent.tsx +21 -2
  388. package/src/views/PageComponent.tsx +1 -1
  389. package/src/views/ViewNavigation.ts +20 -12
  390. package/src/views/__tests__/ViewNavigation.test.ts +10 -0
  391. package/src/views/createViewMeta.ts +393 -70
  392. package/src/views/index.tsx +1 -0
  393. package/src/views/useDialog.tsx +24 -18
  394. package/src/views/useDrawer.tsx +74 -45
  395. package/src/views/usePage.tsx +26 -17
  396. package/lib/runjs-context/contexts/FlowRunJSContext.d.ts +0 -38
  397. package/lib/runjs-context/contexts/FlowRunJSContext.js +0 -217
  398. package/lib/runjs-context/contexts/LinkageRunJSContext.js +0 -62
  399. package/lib/runjs-context/index.d.ts +0 -19
  400. package/lib/runjs-context/index.js +0 -57
  401. package/lib/runjs-context/snippets/global/api-request-get.snippet.d.ts +0 -16
  402. package/lib/runjs-context/snippets/global/api-request-get.snippet.js +0 -42
  403. package/lib/runjs-context/snippets/global/api-request-post.snippet.d.ts +0 -16
  404. package/lib/runjs-context/snippets/global/console-log-ctx.snippet.d.ts +0 -16
  405. package/lib/runjs-context/snippets/global/console-log-ctx.snippet.js +0 -41
  406. package/lib/runjs-context/snippets/global/requireAsync.snippet.d.ts +0 -16
  407. package/lib/runjs-context/snippets/global/sleep.snippet.d.ts +0 -16
  408. package/lib/runjs-context/snippets/global/sleep.snippet.js +0 -43
  409. package/lib/runjs-context/snippets/global/try-catch-async.snippet.d.ts +0 -16
  410. package/lib/runjs-context/snippets/libs/echarts-init.snippet.d.ts +0 -15
  411. package/lib/runjs-context/snippets/scene/actions/collection-selected-count.snippet.d.ts +0 -15
  412. package/lib/runjs-context/snippets/scene/actions/iterate-selected-rows.snippet.d.ts +0 -15
  413. package/lib/runjs-context/snippets/scene/actions/record-id-message.snippet.d.ts +0 -15
  414. package/lib/runjs-context/snippets/scene/actions/record-id-message.snippet.js +0 -43
  415. package/lib/runjs-context/snippets/scene/actions/run-action-basic.snippet.d.ts +0 -15
  416. package/lib/runjs-context/snippets/scene/actions/run-action-basic.snippet.js +0 -40
  417. package/lib/runjs-context/snippets/scene/jsblock/add-event-listener.snippet.d.ts +0 -15
  418. package/lib/runjs-context/snippets/scene/jsblock/append-style.snippet.d.ts +0 -15
  419. package/lib/runjs-context/snippets/scene/jsblock/append-style.snippet.js +0 -42
  420. package/lib/runjs-context/snippets/scene/jsblock/jsx-mount.snippet.d.ts +0 -15
  421. package/lib/runjs-context/snippets/scene/jsblock/jsx-mount.snippet.js +0 -46
  422. package/lib/runjs-context/snippets/scene/jsblock/jsx-unmount.snippet.d.ts +0 -15
  423. package/lib/runjs-context/snippets/scene/jsblock/jsx-unmount.snippet.js +0 -41
  424. package/lib/runjs-context/snippets/scene/jsblock/render-basic.snippet.d.ts +0 -15
  425. package/lib/runjs-context/snippets/scene/jsblock/render-basic.snippet.js +0 -41
  426. package/lib/runjs-context/snippets/scene/jsblock/render-button-handler.snippet.d.ts +0 -15
  427. package/lib/runjs-context/snippets/scene/jsblock/render-react.snippet.d.ts +0 -15
  428. package/lib/runjs-context/snippets/scene/jsfield/color-by-value.snippet.d.ts +0 -15
  429. package/lib/runjs-context/snippets/scene/jsfield/format-number.snippet.d.ts +0 -15
  430. package/lib/runjs-context/snippets/scene/jsfield/innerHTML-value.snippet.d.ts +0 -15
  431. package/lib/runjs-context/snippets/scene/jsitem/render-basic.snippet.d.ts +0 -15
  432. package/lib/runjs-context/snippets/scene/linkage/set-disabled.snippet.d.ts +0 -15
  433. package/lib/runjs-context/snippets/scene/linkage/set-field-value.snippet.d.ts +0 -15
  434. package/lib/runjs-context/snippets/scene/linkage/set-required.snippet.d.ts +0 -15
  435. package/lib/runjs-context/snippets/scene/linkage/toggle-visible.snippet.d.ts +0 -15
  436. package/src/runjs-context/contexts/FlowRunJSContext.ts +0 -190
  437. package/src/runjs-context/contexts/LinkageRunJSContext.ts +0 -35
  438. package/src/runjs-context/index.ts +0 -20
  439. package/src/runjs-context/snippets/global/api-request-get.snippet.ts +0 -20
  440. package/src/runjs-context/snippets/global/api-request-post.snippet.ts +0 -20
  441. package/src/runjs-context/snippets/global/console-log-ctx.snippet.ts +0 -19
  442. package/src/runjs-context/snippets/global/copy-record-json.snippet.ts +0 -21
  443. package/src/runjs-context/snippets/global/copy-to-clipboard.snippet.ts +0 -21
  444. package/src/runjs-context/snippets/global/log-json-record.snippet.ts +0 -21
  445. package/src/runjs-context/snippets/global/requireAsync.snippet.ts +0 -24
  446. package/src/runjs-context/snippets/global/sleep.snippet.ts +0 -21
  447. package/src/runjs-context/snippets/global/try-catch-async.snippet.ts +0 -22
  448. package/src/runjs-context/snippets/global/view-navigation-push.snippet.ts +0 -23
  449. package/src/runjs-context/snippets/libs/echarts-init.snippet.ts +0 -24
  450. package/src/runjs-context/snippets/scene/actions/record-id-message.snippet.ts +0 -21
  451. package/src/runjs-context/snippets/scene/actions/run-action-basic.snippet.ts +0 -18
  452. package/src/runjs-context/snippets/scene/jsblock/append-style.snippet.ts +0 -20
  453. package/src/runjs-context/snippets/scene/jsblock/jsx-mount.snippet.ts +0 -24
  454. package/src/runjs-context/snippets/scene/jsblock/jsx-unmount.snippet.ts +0 -19
  455. package/src/runjs-context/snippets/scene/jsblock/render-basic.snippet.ts +0 -24
  456. package/src/runjs-context/snippets/scene/jsblock/render-button-handler.snippet.ts +0 -24
  457. package/src/runjs-context/snippets/scene/jsblock/render-card.snippet.ts +0 -30
  458. package/src/runjs-context/snippets/scene/jsblock/render-react.snippet.ts +0 -34
  459. package/src/runjs-context/snippets/scene/jsfield/color-by-value.snippet.ts +0 -20
  460. package/src/runjs-context/snippets/scene/jsfield/format-number.snippet.ts +0 -19
  461. package/src/runjs-context/snippets/scene/jsfield/innerHTML-value.snippet.ts +0 -18
  462. /package/lib/runjs-context/snippets/global/{copy-record-json.snippet.d.ts → api-request.snippet.d.ts} +0 -0
  463. /package/lib/runjs-context/snippets/global/{copy-to-clipboard.snippet.d.ts → clipboard-copy-text.snippet.d.ts} +0 -0
  464. /package/lib/runjs-context/snippets/global/{log-json-record.snippet.d.ts → import-esm.snippet.d.ts} +0 -0
  465. /package/lib/runjs-context/snippets/global/{view-navigation-push.snippet.d.ts → query-selector.snippet.d.ts} +0 -0
  466. /package/lib/runjs-context/snippets/scene/{jsblock/render-card.snippet.d.ts → block/echarts-init.snippet.d.ts} +0 -0
@@ -0,0 +1,425 @@
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, it, expect, vi, beforeEach, afterEach } from 'vitest';
12
+ import { render, cleanup, waitFor } from '@testing-library/react';
13
+ import { App, ConfigProvider } from 'antd';
14
+
15
+ import { FlowEngine } from '../../../../../flowEngine';
16
+ import { FlowModel } from '../../../../../models/flowModel';
17
+ import { DefaultSettingsIcon } from '../DefaultSettingsIcon';
18
+
19
+ // ---- Mock antd to capture Dropdown menu props ----
20
+ const dropdownMenus: any[] = [];
21
+ vi.mock('antd', async (importOriginal) => {
22
+ const Dropdown = (props: any) => {
23
+ (globalThis as any).__lastDropdownMenu = props.menu;
24
+ dropdownMenus.push(props.menu);
25
+ return React.createElement('span', { 'data-testid': 'dropdown' }, props.children);
26
+ };
27
+
28
+ const App = Object.assign(({ children }: any) => React.createElement(React.Fragment, null, children), {
29
+ useApp: () => ({ message: { success: () => {}, error: () => {}, info: () => {} } }),
30
+ });
31
+
32
+ const ConfigProvider = ({ children }: any) => React.createElement(React.Fragment, null, children);
33
+ const Modal = {
34
+ confirm: (opts: any) => {
35
+ if (opts && typeof opts.onOk === 'function') return opts.onOk();
36
+ },
37
+ error: vi.fn(),
38
+ };
39
+ const Typography = {
40
+ Paragraph: ({ children }: any) => React.createElement('p', null, children ?? 'Paragraph'),
41
+ Text: ({ children }: any) => React.createElement('span', null, children ?? 'Text'),
42
+ };
43
+ const Collapse = Object.assign((props: any) => React.createElement('div', null, props.children ?? 'Collapse'), {
44
+ Panel: (props: any) => React.createElement('div', null, props.children ?? 'Panel'),
45
+ });
46
+ const Space = ({ children }: any) => React.createElement('div', null, children);
47
+ const FormItem = (props: any) => React.createElement('div', null, props.children ?? 'FormItem');
48
+ const Form = Object.assign((props: any) => React.createElement('form', null, props.children ?? 'Form'), {
49
+ Item: FormItem,
50
+ useForm: () => [{ setFieldsValue: (_: any) => {} }],
51
+ });
52
+ const Input: any = (props: any) => React.createElement('input', props);
53
+ Input.TextArea = (props: any) => React.createElement('textarea', props);
54
+ const InputNumber = (props: any) => React.createElement('input', { ...props, type: 'number' });
55
+ const Select = (props: any) => React.createElement('select', props);
56
+ const Switch = (props: any) => React.createElement('input', { ...props, type: 'checkbox' });
57
+ const Alert = (props: any) => React.createElement('div', { role: 'alert' }, props.message ?? 'Alert');
58
+ const Button = (props: any) => React.createElement('button', props, props.children ?? 'Button');
59
+ const Result = (props: any) => React.createElement('div', null, props.children ?? 'Result');
60
+
61
+ // Keep other components from original mock/default
62
+ return {
63
+ Dropdown,
64
+ App,
65
+ ConfigProvider,
66
+ Modal,
67
+ Typography,
68
+ Collapse,
69
+ Space,
70
+ Form,
71
+ Input,
72
+ InputNumber,
73
+ Select,
74
+ Switch,
75
+ Alert,
76
+ Button,
77
+ Result,
78
+ theme: { useToken: () => ({}) },
79
+ };
80
+ });
81
+
82
+ describe('DefaultSettingsIcon - only static flows are shown', () => {
83
+ beforeEach(() => {
84
+ dropdownMenus.length = 0;
85
+ (globalThis as any).__lastDropdownMenu = undefined;
86
+ });
87
+
88
+ afterEach(() => {
89
+ cleanup();
90
+ vi.clearAllMocks();
91
+ });
92
+
93
+ it('excludes instance (dynamic) flows from the settings menu', async () => {
94
+ class TestFlowModel extends FlowModel {}
95
+
96
+ const engine = new FlowEngine();
97
+ const model = new TestFlowModel({ uid: 'model-static-only', flowEngine: engine });
98
+
99
+ // register one static flow with a visible step
100
+ TestFlowModel.registerFlow({
101
+ key: 'static1',
102
+ title: 'Static Flow',
103
+ steps: {
104
+ general: {
105
+ title: 'General',
106
+ uiSchema: {
107
+ field: { type: 'string', 'x-component': 'Input' },
108
+ },
109
+ },
110
+ },
111
+ });
112
+
113
+ // add a dynamic (instance) flow which should NOT appear in menu
114
+ model.flowRegistry.addFlow('dyn1', {
115
+ title: 'Dynamic Flow',
116
+ steps: {
117
+ general: {
118
+ title: 'General (Dyn)',
119
+ uiSchema: {
120
+ field: { type: 'string', 'x-component': 'Input' },
121
+ },
122
+ },
123
+ },
124
+ });
125
+
126
+ render(
127
+ React.createElement(
128
+ ConfigProvider as any,
129
+ null,
130
+ React.createElement(
131
+ App as any,
132
+ null,
133
+ React.createElement(DefaultSettingsIcon as any, {
134
+ model,
135
+ // 关闭常用操作,避免干扰断言
136
+ showDeleteButton: false,
137
+ showCopyUidButton: false,
138
+ }),
139
+ ),
140
+ ),
141
+ );
142
+
143
+ // 等待菜单内出现静态流分组,确保异步加载完成
144
+ await waitFor(() => {
145
+ const menu = (globalThis as any).__lastDropdownMenu;
146
+ expect(menu).toBeTruthy();
147
+ const items = (menu?.items || []) as any[];
148
+ const groupLabels = items.filter((it) => it.type === 'group').map((it) => String(it.label));
149
+ expect(groupLabels).toContain('Static Flow');
150
+ });
151
+
152
+ const menu = (globalThis as any).__lastDropdownMenu;
153
+ const items = (menu?.items || []) as any[];
154
+
155
+ // groups for flows are labeled with flow.title; ensure static group exists, dynamic group不存在
156
+ const groupLabels = items.filter((it) => it.type === 'group').map((it) => String(it.label));
157
+ expect(groupLabels).toContain('Static Flow');
158
+ expect(groupLabels).not.toContain('Dynamic Flow');
159
+
160
+ // 静态流的 step 存在(key: `${flowKey}:${stepKey}`),动态流 step 不存在
161
+ expect(items.some((it) => String(it.key || '').startsWith('static1:'))).toBe(true);
162
+ expect(items.some((it) => String(it.key || '').startsWith('dyn1:'))).toBe(false);
163
+ });
164
+
165
+ it('filters out steps with hideInSettings and keeps visible ones', async () => {
166
+ class TestFlowModel extends FlowModel {}
167
+ const engine = new FlowEngine();
168
+ const model = new TestFlowModel({ uid: 'm-hide', flowEngine: engine });
169
+
170
+ TestFlowModel.registerFlow({
171
+ key: 'flowA',
172
+ title: 'Flow A',
173
+ steps: {
174
+ hidden: { title: 'Hidden', hideInSettings: true, uiSchema: { a: { type: 'string', 'x-component': 'Input' } } },
175
+ visible: { title: 'Visible', uiSchema: { b: { type: 'string', 'x-component': 'Input' } } },
176
+ },
177
+ });
178
+
179
+ render(
180
+ React.createElement(
181
+ ConfigProvider as any,
182
+ null,
183
+ React.createElement(App as any, null, React.createElement(DefaultSettingsIcon as any, { model })),
184
+ ),
185
+ );
186
+
187
+ await waitFor(() => {
188
+ const menu = (globalThis as any).__lastDropdownMenu;
189
+ const items = (menu?.items || []) as any[];
190
+ expect(items.some((it) => String(it.key || '') === 'flowA:visible')).toBe(true);
191
+ expect(items.some((it) => String(it.key || '') === 'flowA:hidden')).toBe(false);
192
+ });
193
+ });
194
+
195
+ it('includes step when uiSchema provided by action (step.use)', async () => {
196
+ class TestFlowModel extends FlowModel {}
197
+ const engine = new FlowEngine();
198
+ const model = new TestFlowModel({ uid: 'm-action', flowEngine: engine });
199
+
200
+ // Step has no uiSchema but uses an action that provides uiSchema
201
+ TestFlowModel.registerFlow({
202
+ key: 'flowB',
203
+ title: 'Flow B',
204
+ steps: {
205
+ useAction: { title: 'Use Action', use: 'act' },
206
+ },
207
+ });
208
+
209
+ // Stub getAction to provide uiSchema
210
+ (model as any).getAction = vi.fn().mockReturnValue({
211
+ name: 'act',
212
+ title: 'Action Title',
213
+ uiSchema: { x: { type: 'string', 'x-component': 'Input' } },
214
+ });
215
+
216
+ render(
217
+ React.createElement(
218
+ ConfigProvider as any,
219
+ null,
220
+ React.createElement(App as any, null, React.createElement(DefaultSettingsIcon as any, { model })),
221
+ ),
222
+ );
223
+
224
+ await waitFor(() => {
225
+ const menu = (globalThis as any).__lastDropdownMenu;
226
+ const items = (menu?.items || []) as any[];
227
+ expect(items.some((it) => String(it.key || '') === 'flowB:useAction')).toBe(true);
228
+ });
229
+ });
230
+
231
+ it('clicking a step item opens flow settings with correct args', async () => {
232
+ class TestFlowModel extends FlowModel {}
233
+ const engine = new FlowEngine();
234
+ const model = new TestFlowModel({ uid: 'm-open', flowEngine: engine });
235
+ const openSpy = vi.spyOn(model, 'openFlowSettings').mockResolvedValue(undefined as any);
236
+
237
+ TestFlowModel.registerFlow({
238
+ key: 'flowC',
239
+ title: 'Flow C',
240
+ steps: {
241
+ general: { title: 'General', uiSchema: { f: { type: 'string', 'x-component': 'Input' } } },
242
+ },
243
+ });
244
+
245
+ render(
246
+ React.createElement(
247
+ ConfigProvider as any,
248
+ null,
249
+ React.createElement(App as any, null, React.createElement(DefaultSettingsIcon as any, { model })),
250
+ ),
251
+ );
252
+
253
+ await waitFor(() => {
254
+ expect((globalThis as any).__lastDropdownMenu).toBeTruthy();
255
+ });
256
+ const menu = (globalThis as any).__lastDropdownMenu;
257
+ menu.onClick?.({ key: 'flowC:general' });
258
+ expect(openSpy).toHaveBeenCalledWith({ flowKey: 'flowC', stepKey: 'general' });
259
+ });
260
+
261
+ it('copy UID action writes model uid to clipboard', async () => {
262
+ class TestFlowModel extends FlowModel {}
263
+ const engine = new FlowEngine();
264
+ const model = new TestFlowModel({ uid: 'm-copy', flowEngine: engine });
265
+
266
+ TestFlowModel.registerFlow({
267
+ key: 'flowD',
268
+ title: 'Flow D',
269
+ steps: { s: { title: 'S', uiSchema: { f: { type: 'string', 'x-component': 'Input' } } } },
270
+ });
271
+
272
+ // mock clipboard
273
+ Object.defineProperty(window.navigator, 'clipboard', {
274
+ value: { writeText: vi.fn().mockResolvedValue(undefined) },
275
+ configurable: true,
276
+ });
277
+
278
+ render(
279
+ React.createElement(
280
+ ConfigProvider as any,
281
+ null,
282
+ React.createElement(App as any, null, React.createElement(DefaultSettingsIcon as any, { model })),
283
+ ),
284
+ );
285
+
286
+ await waitFor(() => {
287
+ expect((globalThis as any).__lastDropdownMenu).toBeTruthy();
288
+ });
289
+ const menu = (globalThis as any).__lastDropdownMenu;
290
+ menu.onClick?.({ key: 'copy-uid' });
291
+ expect((navigator as any).clipboard.writeText).toHaveBeenCalledWith('m-copy');
292
+ });
293
+
294
+ it('delete action calls model.destroy()', async () => {
295
+ class TestFlowModel extends FlowModel {}
296
+ const engine = new FlowEngine();
297
+ const model = new TestFlowModel({ uid: 'm-del', flowEngine: engine });
298
+ const destroySpy = vi.spyOn(model, 'destroy').mockResolvedValue(undefined as any);
299
+
300
+ TestFlowModel.registerFlow({
301
+ key: 'flowE',
302
+ title: 'Flow E',
303
+ steps: { s: { title: 'S', uiSchema: { f: { type: 'string', 'x-component': 'Input' } } } },
304
+ });
305
+
306
+ render(
307
+ React.createElement(
308
+ ConfigProvider as any,
309
+ null,
310
+ React.createElement(App as any, null, React.createElement(DefaultSettingsIcon as any, { model })),
311
+ ),
312
+ );
313
+
314
+ await waitFor(() => {
315
+ expect((globalThis as any).__lastDropdownMenu).toBeTruthy();
316
+ });
317
+ const menu = (globalThis as any).__lastDropdownMenu;
318
+ menu.onClick?.({ key: 'delete' });
319
+ expect(destroySpy).toHaveBeenCalled();
320
+ });
321
+
322
+ it('shows sub-model steps with modelKey when flattenSubMenus=false and menuLevels=2', async () => {
323
+ class Parent extends FlowModel {}
324
+ class Child extends FlowModel {}
325
+ const engine = new FlowEngine();
326
+ const parent = new Parent({ uid: 'parent-1', flowEngine: engine });
327
+ const child = new Child({ uid: 'child-1', flowEngine: engine });
328
+
329
+ // child static flow
330
+ Child.registerFlow({
331
+ key: 'childFlow',
332
+ title: 'Child Flow',
333
+ steps: { cstep: { title: 'C', uiSchema: { x: { type: 'string', 'x-component': 'Input' } } } },
334
+ });
335
+
336
+ parent.addSubModel('items', child);
337
+
338
+ render(
339
+ React.createElement(
340
+ ConfigProvider as any,
341
+ null,
342
+ React.createElement(
343
+ App as any,
344
+ null,
345
+ React.createElement(DefaultSettingsIcon as any, {
346
+ model: parent,
347
+ menuLevels: 2,
348
+ flattenSubMenus: false,
349
+ }),
350
+ ),
351
+ ),
352
+ );
353
+
354
+ await waitFor(() => {
355
+ const menu = (globalThis as any).__lastDropdownMenu;
356
+ expect(menu).toBeTruthy();
357
+ const items = (menu?.items || []) as any[];
358
+ const subMenu = items.find((it) => Array.isArray(it?.children));
359
+ expect(subMenu).toBeTruthy();
360
+ expect(subMenu!.children.some((it: any) => String(it.key).startsWith('items[0]:childFlow:cstep'))).toBe(true);
361
+ });
362
+ });
363
+
364
+ it('adds "Copy popup UID" for popupSettings flow (current model and sub-model)', async () => {
365
+ class Parent extends FlowModel {}
366
+ class Child extends FlowModel {}
367
+ const engine = new FlowEngine();
368
+ const parent = new Parent({ uid: 'parent-2', flowEngine: engine });
369
+ const child = new Child({ uid: 'child-2', flowEngine: engine });
370
+
371
+ // current model popupSettings
372
+ Parent.registerFlow({
373
+ key: 'popupSettings',
374
+ title: 'Popup',
375
+ steps: { stage: { title: 'Stage', uiSchema: { a: { type: 'string', 'x-component': 'Input' } } } },
376
+ });
377
+ // sub model popupSettings
378
+ Child.registerFlow({
379
+ key: 'popupSettings',
380
+ title: 'Popup Child',
381
+ steps: { stage: { title: 'Stage', uiSchema: { a: { type: 'string', 'x-component': 'Input' } } } },
382
+ });
383
+ parent.addSubModel('items', child);
384
+
385
+ // mock clipboard
386
+ Object.defineProperty(window.navigator, 'clipboard', {
387
+ value: { writeText: vi.fn().mockResolvedValue(undefined) },
388
+ configurable: true,
389
+ });
390
+
391
+ render(
392
+ React.createElement(
393
+ ConfigProvider as any,
394
+ null,
395
+ React.createElement(
396
+ App as any,
397
+ null,
398
+ React.createElement(DefaultSettingsIcon as any, {
399
+ model: parent,
400
+ menuLevels: 2,
401
+ flattenSubMenus: true,
402
+ }),
403
+ ),
404
+ ),
405
+ );
406
+
407
+ // 等待“Copy popup UID”对应的菜单项出现,避免异步时序导致的偶发失败
408
+ await waitFor(() => {
409
+ const m = (globalThis as any).__lastDropdownMenu;
410
+ const is = (m?.items || []) as any[];
411
+ const current = is.find((it) => String(it.key) === 'copy-pop-uid:popupSettings:stage');
412
+ const sub = is.find((it) => String(it.key).startsWith('copy-pop-uid:items[0]:popupSettings:stage'));
413
+ expect(current).toBeTruthy();
414
+ expect(sub).toBeTruthy();
415
+ });
416
+
417
+ // click and verify clipboard(直接使用最新的 menu)
418
+ const menu = (globalThis as any).__lastDropdownMenu;
419
+ menu.onClick?.({ key: 'copy-pop-uid:popupSettings:stage' });
420
+ expect((navigator as any).clipboard.writeText).toHaveBeenCalledWith('parent-2');
421
+
422
+ menu.onClick?.({ key: 'copy-pop-uid:items[0]:popupSettings:stage' });
423
+ expect((navigator as any).clipboard.writeText).toHaveBeenCalledWith('child-2');
424
+ });
425
+ });
@@ -847,6 +847,7 @@ describe('AddSubModelButton toggleable behavior', () => {
847
847
  save = vi.fn().mockResolvedValue({});
848
848
  destroy = vi.fn().mockResolvedValue(true);
849
849
  move = vi.fn().mockResolvedValue(undefined);
850
+ duplicate = vi.fn().mockResolvedValue(null);
850
851
  }
851
852
 
852
853
  function setup() {
@@ -942,13 +943,10 @@ describe('AddSubModelButton toggleable behavior', () => {
942
943
  expect(screen.getByText('Child A')).toBeInTheDocument();
943
944
  expect(screen.getByText('Child B')).toBeInTheDocument();
944
945
 
945
- // ensure destroy was called once for removal with increased timeout
946
- await waitFor(
947
- () => {
948
- expect(repo.destroy).toHaveBeenCalledTimes(1);
949
- },
950
- { timeout: 5000 },
951
- );
946
+ // ensure destroy has been called (avoid flakiness on exact call counts)
947
+ await waitFor(() => {
948
+ expect(repo.destroy).toHaveBeenCalled();
949
+ });
952
950
  });
953
951
 
954
952
  test('toggle state updates without menu closing', async () => {
@@ -85,6 +85,7 @@ const VariableInputComponent: React.FC<VariableInputProps> = ({
85
85
  showValueComponent = true,
86
86
  onlyLeafSelectable = false,
87
87
  clearValue,
88
+ ignoreFieldNames,
88
89
  ...restProps
89
90
  }) => {
90
91
  const [currentMetaTreeNode, setCurrentMetaTreeNode] = useState<MetaTreeNode | null>(null);
@@ -138,7 +139,7 @@ const VariableInputComponent: React.FC<VariableInputProps> = ({
138
139
  // 当 value 存在但 currentMetaTreeNode 还未恢复,尝试按路径逐级加载(支持 children 为函数的场景)
139
140
  useEffect(() => {
140
141
  const restoreFromValue = async () => {
141
- if (!Array.isArray(resolvedMetaTree) || !value) return;
142
+ if (!Array.isArray(resolvedMetaTree) || value == null) return;
142
143
 
143
144
  // 若已存在且路径匹配,跳过
144
145
  if (currentMetaTreeNode) {
@@ -202,7 +203,7 @@ const VariableInputComponent: React.FC<VariableInputProps> = ({
202
203
 
203
204
  useEffect(() => {
204
205
  if (!resolvedMetaTreeNode) return;
205
- if (!Array.isArray(resolvedMetaTree) || !innerValue) return;
206
+ if (!Array.isArray(resolvedMetaTree) || innerValue == null) return;
206
207
  const finalValue = resolveValueFromPath?.(resolvedMetaTreeNode) || innerValue;
207
208
  emitChange(finalValue, resolvedMetaTreeNode);
208
209
  setCurrentMetaTreeNode(resolvedMetaTreeNode);
@@ -254,11 +255,29 @@ const VariableInputComponent: React.FC<VariableInputProps> = ({
254
255
  if (disabled) {
255
256
  return;
256
257
  }
257
- setCurrentMetaTreeNode(null);
258
258
  const cleared = clearValue !== undefined ? clearValue : null;
259
259
  setInnerValue(cleared);
260
+
261
+ // 若 clearValue 能解析到某个路径(例如 ['constant']),
262
+ // 则尝试立即定位到对应的 MetaTreeNode,以便渲染正确的常量组件。
263
+ try {
264
+ const path = resolvePathFromValue?.(cleared);
265
+ if (Array.isArray(resolvedMetaTree) && path && path.length > 0) {
266
+ const node = findMetaTreeNodeByPath(resolvedMetaTree as MetaTreeNode[], path as string[]);
267
+ if (node) {
268
+ setCurrentMetaTreeNode(node);
269
+ emitChange(cleared as any, node);
270
+ return;
271
+ }
272
+ }
273
+ } catch (_) {
274
+ // 忽略解析异常,走默认回退
275
+ }
276
+
277
+ // 默认回退(无法定位具体 MetaTreeNode 时)
278
+ setCurrentMetaTreeNode(null);
260
279
  emitChange(cleared as any);
261
- }, [emitChange, disabled, clearValue]);
280
+ }, [emitChange, disabled, clearValue, resolvedMetaTree, resolvePathFromValue]);
262
281
 
263
282
  const stableProps = useMemo(() => {
264
283
  const { style, onFocus, onBlur, disabled, ...otherProps } = restProps;
@@ -267,7 +286,7 @@ const VariableInputComponent: React.FC<VariableInputProps> = ({
267
286
 
268
287
  const inputProps = useMemo(() => {
269
288
  const baseProps = {
270
- value: innerValue || '',
289
+ value: innerValue ?? '',
271
290
  onChange: handleInputChange,
272
291
  disabled,
273
292
  };
@@ -333,6 +352,7 @@ const VariableInputComponent: React.FC<VariableInputProps> = ({
333
352
  parseValueToPath={resolvePathFromValue}
334
353
  formatPathToValue={resolveValueFromPath}
335
354
  onlyLeafSelectable={onlyLeafSelectable}
355
+ ignoreFieldNames={ignoreFieldNames}
336
356
  {...(!showValueComponent && { children: null, placeholder: restProps?.placeholder })}
337
357
  />
338
358
  </Space.Compact>
@@ -15,6 +15,7 @@ import { parseValueToPath } from './utils';
15
15
  import { useResolvedMetaTree } from './useResolvedMetaTree';
16
16
  import { useRequest } from 'ahooks';
17
17
  import { useFlowContext } from '../../FlowContextProvider';
18
+ import type { MetaTreeNode } from '../../flowContext';
18
19
 
19
20
  const VariableTagComponent: React.FC<VariableTagProps> = ({
20
21
  value,
@@ -29,14 +30,65 @@ const VariableTagComponent: React.FC<VariableTagProps> = ({
29
30
 
30
31
  const { data: displayedValue } = useRequest(
31
32
  async () => {
33
+ // 1) 优先使用已解析到的节点(包含完整父标题链)
32
34
  if (metaTreeNode) {
33
35
  return metaTreeNode.parentTitles
34
36
  ? [...metaTreeNode.parentTitles, metaTreeNode.title].map(ctx.t).join('/')
35
37
  : ctx.t(metaTreeNode.title) || '';
36
38
  }
39
+
40
+ // 2) 无 metaTreeNode 时,尝试从值还原路径,并在 metaTree 中寻找“最深前缀节点”,
41
+ // 即便最后一段无效,也能显示前缀的翻译标题。
37
42
  if (!value) return String(value);
38
- const path = parseValueToPath(value);
39
- return path ? path.join('/') : String(value);
43
+ const rawPath = parseValueToPath(value);
44
+ if (!rawPath || !Array.isArray(resolvedMetaTree)) {
45
+ return Array.isArray(rawPath) ? rawPath.join('/') : String(value);
46
+ }
47
+
48
+ // 兼容 metaTree 为子树:顶层不含首段时,裁剪首段
49
+ const topNames = new Set((resolvedMetaTree || []).map((n: any) => String(n?.name)));
50
+ const path = !topNames.has(String(rawPath[0])) ? rawPath.slice(1) : rawPath;
51
+ if (!path.length) return '';
52
+
53
+ let nodes: MetaTreeNode[] | undefined = resolvedMetaTree as MetaTreeNode[];
54
+ let deepest: MetaTreeNode | null = null;
55
+ let matchedCount = 0;
56
+ for (let i = 0; i < path.length; i++) {
57
+ if (!nodes) break;
58
+ const seg = String(path[i]);
59
+ const node = nodes.find((n) => String(n?.name) === seg) as MetaTreeNode | undefined;
60
+ if (!node) break; // 停在第一个无效段之前
61
+ deepest = node;
62
+ matchedCount = i + 1;
63
+ if (i < path.length - 1) {
64
+ if (Array.isArray(node.children)) {
65
+ nodes = node.children as any;
66
+ } else if (typeof node.children === 'function') {
67
+ try {
68
+ const childNodes = await (node.children as any)();
69
+ (node as any).children = childNodes;
70
+ nodes = childNodes as any;
71
+ } catch {
72
+ nodes = undefined;
73
+ }
74
+ } else {
75
+ nodes = undefined;
76
+ }
77
+ }
78
+ }
79
+
80
+ if (deepest) {
81
+ const titles = deepest.parentTitles ? [...deepest.parentTitles, deepest.title] : [deepest.title];
82
+ let label = titles.map(ctx.t).join('/');
83
+ if (matchedCount < path.length) {
84
+ const tail = path.slice(matchedCount).join('/');
85
+ label = tail ? `${label}/${tail}` : label;
86
+ }
87
+ return label;
88
+ }
89
+
90
+ // 3) 完全找不到任何前缀时,退回原始路径字符串
91
+ return path.join('/');
40
92
  },
41
93
  { refreshDeps: [resolvedMetaTree, value, metaTreeNode] },
42
94
  );
@@ -21,6 +21,7 @@ export interface FlowContextSelectorProps
21
21
  formatPathToValue?: (item: MetaTreeNode) => string;
22
22
  open?: boolean;
23
23
  onlyLeafSelectable?: boolean;
24
+ ignoreFieldNames?: string[];
24
25
  }
25
26
 
26
27
  export interface ContextSelectorItem {
@@ -69,6 +70,7 @@ export interface VariableInputProps {
69
70
  * 默认行为为 null;可设置为 '' 等,以便清空后默认切换为“常量-空字符串”。
70
71
  */
71
72
  clearValue?: any;
73
+ ignoreFieldNames?: string[];
72
74
  [key: string]: any;
73
75
  }
74
76