@363045841yyt/klinechart-core 0.9.0-alpha.0 → 0.9.0-alpha.2

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 (372) hide show
  1. package/dist/controllers/createChartController.d.ts.map +1 -1
  2. package/dist/controllers/createChartController.js +31 -13
  3. package/dist/controllers/createChartController.js.map +1 -1
  4. package/dist/controllers/index.d.ts +1 -0
  5. package/dist/controllers/index.d.ts.map +1 -1
  6. package/dist/controllers/index.js.map +1 -1
  7. package/dist/controllers/types.d.ts +12 -0
  8. package/dist/controllers/types.d.ts.map +1 -1
  9. package/dist/engine/chart.d.ts +29 -11
  10. package/dist/engine/chart.d.ts.map +1 -1
  11. package/dist/engine/chart.js +147 -25
  12. package/dist/engine/chart.js.map +1 -1
  13. package/dist/engine/controller/interaction.d.ts +29 -4
  14. package/dist/engine/controller/interaction.d.ts.map +1 -1
  15. package/dist/engine/controller/interaction.js +78 -31
  16. package/dist/engine/controller/interaction.js.map +1 -1
  17. package/dist/engine/data/incrementalLoadHint.d.ts.map +1 -1
  18. package/dist/engine/data/incrementalLoadHint.js +5 -1
  19. package/dist/engine/data/incrementalLoadHint.js.map +1 -1
  20. package/dist/engine/drawing/DrawingState.d.ts +19 -34
  21. package/dist/engine/drawing/DrawingState.d.ts.map +1 -1
  22. package/dist/engine/drawing/DrawingState.js +110 -92
  23. package/dist/engine/drawing/DrawingState.js.map +1 -1
  24. package/dist/engine/drawing/index.d.ts +4 -1
  25. package/dist/engine/drawing/index.d.ts.map +1 -1
  26. package/dist/engine/drawing/index.js +9 -4
  27. package/dist/engine/drawing/index.js.map +1 -1
  28. package/dist/engine/drawing/interaction.d.ts +9 -29
  29. package/dist/engine/drawing/interaction.d.ts.map +1 -1
  30. package/dist/engine/drawing/interaction.js +23 -40
  31. package/dist/engine/drawing/interaction.js.map +1 -1
  32. package/dist/engine/indicators/chartIndicatorManager.d.ts.map +1 -1
  33. package/dist/engine/indicators/chartIndicatorManager.js +4 -12
  34. package/dist/engine/indicators/chartIndicatorManager.js.map +1 -1
  35. package/dist/engine/layout/chartPaneLayout.d.ts +0 -2
  36. package/dist/engine/layout/chartPaneLayout.d.ts.map +1 -1
  37. package/dist/engine/layout/chartPaneLayout.js +5 -9
  38. package/dist/engine/layout/chartPaneLayout.js.map +1 -1
  39. package/dist/engine/layout/pane.d.ts +0 -39
  40. package/dist/engine/layout/pane.d.ts.map +1 -1
  41. package/dist/engine/layout/pane.js +0 -9
  42. package/dist/engine/layout/pane.js.map +1 -1
  43. package/dist/engine/paneRenderer.d.ts +1 -10
  44. package/dist/engine/paneRenderer.d.ts.map +1 -1
  45. package/dist/engine/paneRenderer.js +2 -19
  46. package/dist/engine/paneRenderer.js.map +1 -1
  47. package/dist/engine/render/chartRenderer.d.ts +49 -8
  48. package/dist/engine/render/chartRenderer.d.ts.map +1 -1
  49. package/dist/engine/render/chartRenderer.js +143 -72
  50. package/dist/engine/render/chartRenderer.js.map +1 -1
  51. package/dist/engine/renderers/Indicator/atr.js +3 -13
  52. package/dist/engine/renderers/Indicator/atr.js.map +1 -1
  53. package/dist/engine/renderers/Indicator/boll.d.ts.map +1 -1
  54. package/dist/engine/renderers/Indicator/boll.js +12 -24
  55. package/dist/engine/renderers/Indicator/boll.js.map +1 -1
  56. package/dist/engine/renderers/Indicator/cci.js +6 -13
  57. package/dist/engine/renderers/Indicator/cci.js.map +1 -1
  58. package/dist/engine/renderers/Indicator/chaikinVol.js +3 -11
  59. package/dist/engine/renderers/Indicator/chaikinVol.js.map +1 -1
  60. package/dist/engine/renderers/Indicator/cmf.js +3 -11
  61. package/dist/engine/renderers/Indicator/cmf.js.map +1 -1
  62. package/dist/engine/renderers/Indicator/dema.js +3 -11
  63. package/dist/engine/renderers/Indicator/dema.js.map +1 -1
  64. package/dist/engine/renderers/Indicator/donchian.js +3 -11
  65. package/dist/engine/renderers/Indicator/donchian.js.map +1 -1
  66. package/dist/engine/renderers/Indicator/ene.d.ts.map +1 -1
  67. package/dist/engine/renderers/Indicator/ene.js +12 -24
  68. package/dist/engine/renderers/Indicator/ene.js.map +1 -1
  69. package/dist/engine/renderers/Indicator/expma.d.ts.map +1 -1
  70. package/dist/engine/renderers/Indicator/expma.js +5 -11
  71. package/dist/engine/renderers/Indicator/expma.js.map +1 -1
  72. package/dist/engine/renderers/Indicator/fastk.js +6 -13
  73. package/dist/engine/renderers/Indicator/fastk.js.map +1 -1
  74. package/dist/engine/renderers/Indicator/fib.js +3 -11
  75. package/dist/engine/renderers/Indicator/fib.js.map +1 -1
  76. package/dist/engine/renderers/Indicator/hma.js +3 -11
  77. package/dist/engine/renderers/Indicator/hma.js.map +1 -1
  78. package/dist/engine/renderers/Indicator/hv.js +3 -11
  79. package/dist/engine/renderers/Indicator/hv.js.map +1 -1
  80. package/dist/engine/renderers/Indicator/ichimoku.js +24 -18
  81. package/dist/engine/renderers/Indicator/ichimoku.js.map +1 -1
  82. package/dist/engine/renderers/Indicator/kama.js +3 -11
  83. package/dist/engine/renderers/Indicator/kama.js.map +1 -1
  84. package/dist/engine/renderers/Indicator/keltner.js +3 -11
  85. package/dist/engine/renderers/Indicator/keltner.js.map +1 -1
  86. package/dist/engine/renderers/Indicator/kst.js +9 -17
  87. package/dist/engine/renderers/Indicator/kst.js.map +1 -1
  88. package/dist/engine/renderers/Indicator/ma.d.ts.map +1 -1
  89. package/dist/engine/renderers/Indicator/ma.js +10 -21
  90. package/dist/engine/renderers/Indicator/ma.js.map +1 -1
  91. package/dist/engine/renderers/Indicator/macd.js +14 -44
  92. package/dist/engine/renderers/Indicator/macd.js.map +1 -1
  93. package/dist/engine/renderers/Indicator/mfi.js +3 -11
  94. package/dist/engine/renderers/Indicator/mfi.js.map +1 -1
  95. package/dist/engine/renderers/Indicator/mom.js +6 -13
  96. package/dist/engine/renderers/Indicator/mom.js.map +1 -1
  97. package/dist/engine/renderers/Indicator/obv.js +3 -11
  98. package/dist/engine/renderers/Indicator/obv.js.map +1 -1
  99. package/dist/engine/renderers/Indicator/parkinson.js +3 -11
  100. package/dist/engine/renderers/Indicator/parkinson.js.map +1 -1
  101. package/dist/engine/renderers/Indicator/pvt.js +3 -11
  102. package/dist/engine/renderers/Indicator/pvt.js.map +1 -1
  103. package/dist/engine/renderers/Indicator/roc.js +3 -11
  104. package/dist/engine/renderers/Indicator/roc.js.map +1 -1
  105. package/dist/engine/renderers/Indicator/rsi.js +12 -20
  106. package/dist/engine/renderers/Indicator/rsi.js.map +1 -1
  107. package/dist/engine/renderers/Indicator/shared/webglBand.d.ts +0 -2
  108. package/dist/engine/renderers/Indicator/shared/webglBand.d.ts.map +1 -1
  109. package/dist/engine/renderers/Indicator/shared/webglBand.js +0 -6
  110. package/dist/engine/renderers/Indicator/shared/webglBand.js.map +1 -1
  111. package/dist/engine/renderers/Indicator/stoch.js +9 -17
  112. package/dist/engine/renderers/Indicator/stoch.js.map +1 -1
  113. package/dist/engine/renderers/Indicator/tema.js +3 -11
  114. package/dist/engine/renderers/Indicator/tema.js.map +1 -1
  115. package/dist/engine/renderers/Indicator/trix.js +3 -11
  116. package/dist/engine/renderers/Indicator/trix.js.map +1 -1
  117. package/dist/engine/renderers/Indicator/vma.js +3 -11
  118. package/dist/engine/renderers/Indicator/vma.js.map +1 -1
  119. package/dist/engine/renderers/Indicator/vwap.js +3 -11
  120. package/dist/engine/renderers/Indicator/vwap.js.map +1 -1
  121. package/dist/engine/renderers/Indicator/wma.js +3 -11
  122. package/dist/engine/renderers/Indicator/wma.js.map +1 -1
  123. package/dist/engine/renderers/Indicator/wmsr.js +6 -13
  124. package/dist/engine/renderers/Indicator/wmsr.js.map +1 -1
  125. package/dist/engine/renderers/candle.d.ts.map +1 -1
  126. package/dist/engine/renderers/candle.js +21 -28
  127. package/dist/engine/renderers/candle.js.map +1 -1
  128. package/dist/engine/renderers/candleViaRenderer.d.ts +19 -0
  129. package/dist/engine/renderers/candleViaRenderer.d.ts.map +1 -0
  130. package/dist/engine/renderers/candleViaRenderer.js +15 -0
  131. package/dist/engine/renderers/candleViaRenderer.js.map +1 -0
  132. package/dist/engine/renderers/linesViaRenderer.d.ts +55 -0
  133. package/dist/engine/renderers/linesViaRenderer.d.ts.map +1 -0
  134. package/dist/engine/renderers/linesViaRenderer.js +169 -0
  135. package/dist/engine/renderers/linesViaRenderer.js.map +1 -0
  136. package/dist/engine/renderers/rectsViaRenderer.d.ts +18 -0
  137. package/dist/engine/renderers/rectsViaRenderer.d.ts.map +1 -0
  138. package/dist/engine/renderers/rectsViaRenderer.js +63 -0
  139. package/dist/engine/renderers/rectsViaRenderer.js.map +1 -0
  140. package/dist/engine/renderers/subVolume.js +7 -26
  141. package/dist/engine/renderers/subVolume.js.map +1 -1
  142. package/dist/engine/renderers/timeAxis.js +1 -1
  143. package/dist/engine/renderers/timeAxis.js.map +1 -1
  144. package/dist/engine/state/chartStateKernel.d.ts +11 -2
  145. package/dist/engine/state/chartStateKernel.d.ts.map +1 -1
  146. package/dist/engine/state/chartStateKernel.js +28 -12
  147. package/dist/engine/state/chartStateKernel.js.map +1 -1
  148. package/dist/engine/state/index.d.ts +1 -1
  149. package/dist/engine/state/index.d.ts.map +1 -1
  150. package/dist/engine/state/index.js +1 -1
  151. package/dist/engine/state/index.js.map +1 -1
  152. package/dist/engine/state/interactionState.d.ts +15 -7
  153. package/dist/engine/state/interactionState.d.ts.map +1 -1
  154. package/dist/engine/state/interactionState.js +55 -60
  155. package/dist/engine/state/interactionState.js.map +1 -1
  156. package/dist/engine/state/rendererState.d.ts +14 -0
  157. package/dist/engine/state/rendererState.d.ts.map +1 -0
  158. package/dist/engine/state/rendererState.js +25 -0
  159. package/dist/engine/state/rendererState.js.map +1 -0
  160. package/dist/engine/state/themeState.d.ts +11 -4
  161. package/dist/engine/state/themeState.d.ts.map +1 -1
  162. package/dist/engine/state/themeState.js +13 -5
  163. package/dist/engine/state/themeState.js.map +1 -1
  164. package/dist/engine/state/viewportState.d.ts.map +1 -1
  165. package/dist/engine/state/viewportState.js +7 -3
  166. package/dist/engine/state/viewportState.js.map +1 -1
  167. package/dist/engine/subPaneManager.d.ts.map +1 -1
  168. package/dist/engine/subPaneManager.js +2 -10
  169. package/dist/engine/subPaneManager.js.map +1 -1
  170. package/dist/foundation/config/chartSettings.d.ts +16 -4
  171. package/dist/foundation/config/chartSettings.d.ts.map +1 -1
  172. package/dist/foundation/config/chartSettings.js +21 -6
  173. package/dist/foundation/config/chartSettings.js.map +1 -1
  174. package/dist/foundation/plugin/index.d.ts +0 -1
  175. package/dist/foundation/plugin/index.d.ts.map +1 -1
  176. package/dist/foundation/plugin/rendererPluginManager.d.ts +1 -22
  177. package/dist/foundation/plugin/rendererPluginManager.d.ts.map +1 -1
  178. package/dist/foundation/plugin/rendererPluginManager.js +0 -84
  179. package/dist/foundation/plugin/rendererPluginManager.js.map +1 -1
  180. package/dist/foundation/plugin/types.d.ts +11 -17
  181. package/dist/foundation/plugin/types.d.ts.map +1 -1
  182. package/dist/foundation/plugin/types.js.map +1 -1
  183. package/dist/foundation/reactivity/frameTransaction.d.ts +71 -0
  184. package/dist/foundation/reactivity/frameTransaction.d.ts.map +1 -0
  185. package/dist/foundation/reactivity/frameTransaction.js +170 -0
  186. package/dist/foundation/reactivity/frameTransaction.js.map +1 -0
  187. package/dist/foundation/reactivity/index.d.ts +4 -2
  188. package/dist/foundation/reactivity/index.d.ts.map +1 -1
  189. package/dist/foundation/reactivity/index.js +2 -1
  190. package/dist/foundation/reactivity/index.js.map +1 -1
  191. package/dist/foundation/reactivity/signal.d.ts +20 -0
  192. package/dist/foundation/reactivity/signal.d.ts.map +1 -1
  193. package/dist/foundation/reactivity/signal.js +27 -0
  194. package/dist/foundation/reactivity/signal.js.map +1 -1
  195. package/dist/foundation/tokens/theme-dark.d.ts.map +1 -1
  196. package/dist/foundation/tokens/theme-dark.js +2 -1
  197. package/dist/foundation/tokens/theme-dark.js.map +1 -1
  198. package/dist/foundation/tokens/theme-light.d.ts.map +1 -1
  199. package/dist/foundation/tokens/theme-light.js +2 -1
  200. package/dist/foundation/tokens/theme-light.js.map +1 -1
  201. package/dist/rendering/render/Renderer.d.ts +29 -14
  202. package/dist/rendering/render/Renderer.d.ts.map +1 -1
  203. package/dist/rendering/render/Renderer.js +3 -10
  204. package/dist/rendering/render/Renderer.js.map +1 -1
  205. package/dist/rendering/render/SurfaceBackend.d.ts +5 -7
  206. package/dist/rendering/render/SurfaceBackend.d.ts.map +1 -1
  207. package/dist/rendering/render/SurfaceBackend.js +5 -7
  208. package/dist/rendering/render/SurfaceBackend.js.map +1 -1
  209. package/dist/rendering/render/createCanvas2DRenderer.d.ts +3 -0
  210. package/dist/rendering/render/createCanvas2DRenderer.d.ts.map +1 -0
  211. package/dist/rendering/render/createCanvas2DRenderer.js +74 -0
  212. package/dist/rendering/render/createCanvas2DRenderer.js.map +1 -0
  213. package/dist/rendering/render/createDefaultRendererHost.d.ts +4 -0
  214. package/dist/rendering/render/createDefaultRendererHost.d.ts.map +1 -0
  215. package/dist/rendering/render/createDefaultRendererHost.js +54 -0
  216. package/dist/rendering/render/createDefaultRendererHost.js.map +1 -0
  217. package/dist/rendering/render/createWebGLRenderer.d.ts.map +1 -1
  218. package/dist/rendering/render/createWebGLRenderer.js +42 -80
  219. package/dist/rendering/render/createWebGLRenderer.js.map +1 -1
  220. package/dist/rendering/render/createWebGPURenderer.d.ts +11 -0
  221. package/dist/rendering/render/createWebGPURenderer.d.ts.map +1 -0
  222. package/dist/rendering/render/createWebGPURenderer.js +619 -0
  223. package/dist/rendering/render/createWebGPURenderer.js.map +1 -0
  224. package/dist/rendering/render/createWebGPUSurfaceBackend.d.ts +15 -0
  225. package/dist/rendering/render/createWebGPUSurfaceBackend.d.ts.map +1 -0
  226. package/dist/rendering/render/createWebGPUSurfaceBackend.js +92 -0
  227. package/dist/rendering/render/createWebGPUSurfaceBackend.js.map +1 -0
  228. package/dist/rendering/render/frameMetrics.d.ts +19 -0
  229. package/dist/rendering/render/frameMetrics.d.ts.map +1 -0
  230. package/dist/rendering/render/frameMetrics.js +43 -0
  231. package/dist/rendering/render/frameMetrics.js.map +1 -0
  232. package/dist/rendering/render/index.d.ts +10 -0
  233. package/dist/rendering/render/index.d.ts.map +1 -1
  234. package/dist/rendering/render/index.js +6 -0
  235. package/dist/rendering/render/index.js.map +1 -1
  236. package/dist/rendering/render/rendererHost.d.ts +34 -0
  237. package/dist/rendering/render/rendererHost.d.ts.map +1 -0
  238. package/dist/rendering/render/rendererHost.js +144 -0
  239. package/dist/rendering/render/rendererHost.js.map +1 -0
  240. package/dist/rendering/render/webgpuResourceTable.d.ts +25 -0
  241. package/dist/rendering/render/webgpuResourceTable.d.ts.map +1 -0
  242. package/dist/rendering/render/webgpuResourceTable.js +54 -0
  243. package/dist/rendering/render/webgpuResourceTable.js.map +1 -0
  244. package/dist/rendering/scene/createLayerFromPlugin.d.ts.map +1 -1
  245. package/dist/rendering/scene/createLayerFromPlugin.js +10 -2
  246. package/dist/rendering/scene/createLayerFromPlugin.js.map +1 -1
  247. package/dist/rendering/scene/layerRegistry.d.ts +3 -5
  248. package/dist/rendering/scene/layerRegistry.d.ts.map +1 -1
  249. package/dist/rendering/scene/layerRegistry.js +1 -1
  250. package/dist/rendering/scene/layerRegistry.js.map +1 -1
  251. package/dist/rendering/scene/retainedScene.d.ts +60 -0
  252. package/dist/rendering/scene/retainedScene.d.ts.map +1 -0
  253. package/dist/rendering/scene/retainedScene.js +42 -0
  254. package/dist/rendering/scene/retainedScene.js.map +1 -0
  255. package/dist/version.d.ts +1 -1
  256. package/dist/version.js +1 -1
  257. package/package.json +2 -1
  258. package/src/__tests__/signal.test.ts +47 -1
  259. package/src/controllers/__tests__/createChartController.theme.test.ts +102 -0
  260. package/src/controllers/createChartController.ts +36 -12
  261. package/src/controllers/index.ts +5 -0
  262. package/src/controllers/types.ts +14 -0
  263. package/src/engine/__tests__/chart.dpr.test.ts +64 -0
  264. package/src/engine/__tests__/paneRenderer.resize.test.ts +0 -7
  265. package/src/engine/__tests__/renderSinglePath.test.ts +121 -0
  266. package/src/engine/chart.ts +172 -25
  267. package/src/engine/controller/__tests__/interaction.dpr.test.ts +45 -41
  268. package/src/engine/controller/interaction.ts +81 -30
  269. package/src/engine/data/__tests__/chartDataManager.incrementalLoad.test.ts +1 -0
  270. package/src/engine/data/incrementalLoadHint.ts +6 -1
  271. package/src/engine/drawing/DrawingState.ts +107 -89
  272. package/src/engine/drawing/__tests__/drawingState.workCopy.test.ts +95 -36
  273. package/src/engine/drawing/__tests__/drawingStore.projection.test.ts +22 -0
  274. package/src/engine/drawing/index.ts +12 -4
  275. package/src/engine/drawing/interaction.ts +25 -40
  276. package/src/engine/indicators/chartIndicatorManager.ts +6 -16
  277. package/src/engine/layout/chartPaneLayout.ts +6 -11
  278. package/src/engine/layout/pane.ts +0 -44
  279. package/src/engine/paneRenderer.ts +3 -34
  280. package/src/engine/render/chartRenderer.ts +181 -81
  281. package/src/engine/renderers/Indicator/atr.ts +5 -17
  282. package/src/engine/renderers/Indicator/boll.ts +15 -26
  283. package/src/engine/renderers/Indicator/cci.ts +7 -17
  284. package/src/engine/renderers/Indicator/chaikinVol.ts +3 -15
  285. package/src/engine/renderers/Indicator/cmf.ts +3 -15
  286. package/src/engine/renderers/Indicator/dema.ts +3 -15
  287. package/src/engine/renderers/Indicator/donchian.ts +3 -12
  288. package/src/engine/renderers/Indicator/ene.ts +15 -25
  289. package/src/engine/renderers/Indicator/expma.ts +4 -13
  290. package/src/engine/renderers/Indicator/fastk.ts +7 -17
  291. package/src/engine/renderers/Indicator/fib.ts +3 -12
  292. package/src/engine/renderers/Indicator/hma.ts +3 -15
  293. package/src/engine/renderers/Indicator/hv.ts +3 -15
  294. package/src/engine/renderers/Indicator/ichimoku.ts +21 -24
  295. package/src/engine/renderers/Indicator/kama.ts +3 -15
  296. package/src/engine/renderers/Indicator/keltner.ts +3 -12
  297. package/src/engine/renderers/Indicator/kst.ts +9 -20
  298. package/src/engine/renderers/Indicator/ma.ts +9 -21
  299. package/src/engine/renderers/Indicator/macd.ts +27 -102
  300. package/src/engine/renderers/Indicator/mfi.ts +3 -15
  301. package/src/engine/renderers/Indicator/mom.ts +7 -17
  302. package/src/engine/renderers/Indicator/obv.ts +3 -15
  303. package/src/engine/renderers/Indicator/parkinson.ts +3 -15
  304. package/src/engine/renderers/Indicator/pvt.ts +3 -15
  305. package/src/engine/renderers/Indicator/roc.ts +3 -15
  306. package/src/engine/renderers/Indicator/rsi.ts +12 -23
  307. package/src/engine/renderers/Indicator/shared/webglBand.ts +0 -13
  308. package/src/engine/renderers/Indicator/stoch.ts +9 -20
  309. package/src/engine/renderers/Indicator/tema.ts +3 -15
  310. package/src/engine/renderers/Indicator/trix.ts +3 -12
  311. package/src/engine/renderers/Indicator/vma.ts +3 -15
  312. package/src/engine/renderers/Indicator/vwap.ts +3 -15
  313. package/src/engine/renderers/Indicator/wma.ts +3 -15
  314. package/src/engine/renderers/Indicator/wmsr.ts +7 -17
  315. package/src/engine/renderers/__tests__/candle.sceneRenderer.test.ts +208 -0
  316. package/src/engine/renderers/__tests__/candleViaRenderer.test.ts +100 -0
  317. package/src/engine/renderers/__tests__/linesViaRenderer.test.ts +263 -0
  318. package/src/engine/renderers/__tests__/rectsViaRenderer.test.ts +87 -0
  319. package/src/engine/renderers/candle.ts +26 -69
  320. package/src/engine/renderers/candleViaRenderer.ts +38 -0
  321. package/src/engine/renderers/linesViaRenderer.ts +226 -0
  322. package/src/engine/renderers/rectsViaRenderer.ts +83 -0
  323. package/src/engine/renderers/subVolume.ts +9 -61
  324. package/src/engine/renderers/timeAxis.ts +1 -1
  325. package/src/engine/state/__tests__/effectiveTheme.test.ts +49 -0
  326. package/src/engine/state/__tests__/rendererState.test.ts +36 -0
  327. package/src/engine/state/__tests__/settingsState.test.ts +1 -1
  328. package/src/engine/state/chartStateKernel.ts +38 -17
  329. package/src/engine/state/index.ts +6 -1
  330. package/src/engine/state/interactionState.ts +73 -102
  331. package/src/engine/state/rendererState.ts +32 -0
  332. package/src/engine/state/themeState.ts +15 -8
  333. package/src/engine/state/viewportState.ts +6 -3
  334. package/src/engine/subPaneManager.ts +2 -16
  335. package/src/foundation/config/__tests__/chartSettings.test.ts +25 -0
  336. package/src/foundation/config/chartSettings.ts +24 -7
  337. package/src/foundation/plugin/index.ts +0 -1
  338. package/src/foundation/plugin/rendererPluginManager.ts +0 -99
  339. package/src/foundation/plugin/types.ts +11 -18
  340. package/src/foundation/reactivity/__tests__/frameTransaction.test.ts +306 -0
  341. package/src/foundation/reactivity/frameTransaction.ts +239 -0
  342. package/src/foundation/reactivity/index.ts +8 -0
  343. package/src/foundation/reactivity/signal.ts +39 -0
  344. package/src/foundation/tokens/__tests__/__snapshots__/baseline.test.ts.snap +2 -2
  345. package/src/foundation/tokens/theme-dark.ts +2 -1
  346. package/src/foundation/tokens/theme-light.ts +2 -1
  347. package/src/rendering/render/Renderer.ts +27 -14
  348. package/src/rendering/render/SurfaceBackend.ts +5 -7
  349. package/src/rendering/render/__tests__/canvas2DRenderer.test.ts +34 -0
  350. package/src/rendering/render/__tests__/contract.test.ts +2 -2
  351. package/src/rendering/render/__tests__/frameMetrics.test.ts +24 -0
  352. package/src/rendering/render/__tests__/rendererHost.test.ts +157 -0
  353. package/src/rendering/render/__tests__/webglRenderer.fallback.test.ts +14 -74
  354. package/src/rendering/render/__tests__/webglRenderer.test.ts +64 -6
  355. package/src/rendering/render/__tests__/webgpuRenderer.test.ts +370 -0
  356. package/src/rendering/render/__tests__/webgpuResourceTable.test.ts +62 -0
  357. package/src/rendering/render/__tests__/webgpuSurfaceBackend.test.ts +120 -0
  358. package/src/rendering/render/createCanvas2DRenderer.ts +83 -0
  359. package/src/rendering/render/createDefaultRendererHost.ts +74 -0
  360. package/src/rendering/render/createWebGLRenderer.ts +41 -87
  361. package/src/rendering/render/createWebGPURenderer.ts +677 -0
  362. package/src/rendering/render/createWebGPUSurfaceBackend.ts +106 -0
  363. package/src/rendering/render/frameMetrics.ts +54 -0
  364. package/src/rendering/render/index.ts +30 -0
  365. package/src/rendering/render/rendererHost.ts +210 -0
  366. package/src/rendering/render/webgpuResourceTable.ts +87 -0
  367. package/src/rendering/scene/__tests__/layerFromPlugin.test.ts +29 -2
  368. package/src/rendering/scene/__tests__/retainedScene.test.ts +59 -0
  369. package/src/rendering/scene/createLayerFromPlugin.ts +9 -2
  370. package/src/rendering/scene/layerRegistry.ts +4 -6
  371. package/src/rendering/scene/retainedScene.ts +101 -0
  372. package/src/version.ts +1 -1
@@ -0,0 +1,306 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+
3
+ import { createFrameTransaction } from '../frameTransaction'
4
+
5
+ type Input = { x: number; y: number }
6
+ type Snapshot = { generation: number; x: number; y: number; sum: number }
7
+
8
+ describe('createFrameTransaction', () => {
9
+ it('coalesces many writeInput into one published snapshot on flush', () => {
10
+ const ft = createFrameTransaction<Input, Snapshot>({
11
+ initialInput: { x: 0, y: 0 },
12
+ derive: (input, generation) => ({
13
+ generation,
14
+ x: input.x,
15
+ y: input.y,
16
+ sum: input.x + input.y,
17
+ }),
18
+ })
19
+ const listener = vi.fn()
20
+ ft.published$.subscribe(listener)
21
+
22
+ ft.writeInput({ x: 1 })
23
+ ft.writeInput({ x: 2, y: 3 })
24
+ ft.writeInput({ x: 10 })
25
+ expect(listener).not.toHaveBeenCalled()
26
+
27
+ const snap = ft.flush()
28
+ expect(snap).toEqual({ generation: 1, x: 10, y: 3, sum: 13 })
29
+ expect(ft.published$.peek()).toEqual(snap)
30
+ expect(listener).toHaveBeenCalledTimes(1)
31
+ })
32
+
33
+ it('bumps generation only on successful flush', () => {
34
+ const ft = createFrameTransaction<Input, Snapshot>({
35
+ initialInput: { x: 0, y: 0 },
36
+ derive: (input, generation) => ({
37
+ generation,
38
+ x: input.x,
39
+ y: input.y,
40
+ sum: input.x + input.y,
41
+ }),
42
+ })
43
+ expect(ft.generation).toBe(0)
44
+ ft.writeInput({ x: 1 })
45
+ ft.flush()
46
+ expect(ft.generation).toBe(1)
47
+ ft.writeInput({ y: 2 })
48
+ ft.flush()
49
+ expect(ft.generation).toBe(2)
50
+ })
51
+
52
+ it('does not publish when derive throws; generation stays', () => {
53
+ let fail = false
54
+ const ft = createFrameTransaction<Input, Snapshot>({
55
+ initialInput: { x: 0, y: 0 },
56
+ derive: (input, generation) => {
57
+ if (fail) throw new Error('derive-fail')
58
+ return { generation, x: input.x, y: input.y, sum: input.x + input.y }
59
+ },
60
+ })
61
+ fail = true
62
+ ft.writeInput({ x: 5 })
63
+ expect(() => ft.flush()).toThrow('derive-fail')
64
+ expect(ft.generation).toBe(0)
65
+ expect(ft.published$.peek().generation).toBe(0)
66
+
67
+ fail = false
68
+ // 失败帧应保留 pending,无需再次 write 也能重试
69
+ const snap = ft.flush()
70
+ expect(snap.generation).toBe(1)
71
+ expect(snap.x).toBe(5)
72
+ })
73
+
74
+ it('routes writeInput during render into next generation only', () => {
75
+ const seen: number[] = []
76
+ const ft = createFrameTransaction<Input, Snapshot>({
77
+ initialInput: { x: 0, y: 0 },
78
+ derive: (input, generation) => ({
79
+ generation,
80
+ x: input.x,
81
+ y: input.y,
82
+ sum: input.x + input.y,
83
+ }),
84
+ render: (snapshot) => {
85
+ seen.push(snapshot.x)
86
+ // re-entrant high-frequency write during paint
87
+ ft.writeInput({ x: 99 })
88
+ },
89
+ })
90
+ ft.writeInput({ x: 1 })
91
+ const first = ft.flush()
92
+ expect(first.x).toBe(1)
93
+ expect(seen).toEqual([1])
94
+ // re-entrant write not published yet
95
+ expect(ft.published$.peek().x).toBe(1)
96
+ const second = ft.flush()
97
+ expect(second.x).toBe(99)
98
+ expect(second.generation).toBe(2)
99
+ })
100
+
101
+ it('flush with no pending input returns published snapshot without notify', () => {
102
+ const ft = createFrameTransaction<Input, Snapshot>({
103
+ initialInput: { x: 1, y: 2 },
104
+ derive: (input, generation) => ({
105
+ generation,
106
+ x: input.x,
107
+ y: input.y,
108
+ sum: input.x + input.y,
109
+ }),
110
+ })
111
+ // seed first publish
112
+ ft.writeInput({ x: 1, y: 2 })
113
+ ft.flush()
114
+ const listener = vi.fn()
115
+ ft.published$.subscribe(listener)
116
+ const again = ft.flush()
117
+ expect(again.generation).toBe(1)
118
+ expect(listener).not.toHaveBeenCalled()
119
+ })
120
+
121
+ it('select publishes only when selected value changes by equality', () => {
122
+ const ft = createFrameTransaction<Input, Snapshot>({
123
+ initialInput: { x: 0, y: 0 },
124
+ derive: (input, generation) => ({
125
+ generation,
126
+ x: input.x,
127
+ y: input.y,
128
+ sum: input.x + input.y,
129
+ }),
130
+ })
131
+ const selected = ft.select((s) => s.x)
132
+ const listener = vi.fn()
133
+ selected.subscribe(listener)
134
+
135
+ ft.writeInput({ x: 1, y: 0 })
136
+ ft.flush()
137
+ expect(listener).toHaveBeenCalledTimes(1)
138
+ expect(selected.peek()).toBe(1)
139
+
140
+ ft.writeInput({ x: 1, y: 9 })
141
+ ft.flush()
142
+ // x unchanged
143
+ expect(listener).toHaveBeenCalledTimes(1)
144
+
145
+ ft.writeInput({ x: 2 })
146
+ ft.flush()
147
+ expect(listener).toHaveBeenCalledTimes(2)
148
+ expect(selected.peek()).toBe(2)
149
+ })
150
+
151
+ it('scheduleFlush coalesces to one raf-style runner', () => {
152
+ const runners: Array<() => void> = []
153
+ const schedule = (run: () => void) => {
154
+ runners.push(run)
155
+ return 1
156
+ }
157
+ const ft = createFrameTransaction<Input, Snapshot>({
158
+ initialInput: { x: 0, y: 0 },
159
+ derive: (input, generation) => ({
160
+ generation,
161
+ x: input.x,
162
+ y: input.y,
163
+ sum: input.x + input.y,
164
+ }),
165
+ schedule,
166
+ })
167
+ ft.writeInput({ x: 1 })
168
+ ft.scheduleFlush()
169
+ ft.writeInput({ x: 2 })
170
+ ft.scheduleFlush()
171
+ expect(runners).toHaveLength(1)
172
+ runners[0]!()
173
+ expect(ft.published$.peek().x).toBe(2)
174
+ expect(ft.generation).toBe(1)
175
+ })
176
+
177
+ it('advances generation before notifying published subscribers', () => {
178
+ const ft = createFrameTransaction<Input, Snapshot>({
179
+ initialInput: { x: 0, y: 0 },
180
+ derive: (input, generation) => ({
181
+ generation,
182
+ x: input.x,
183
+ y: input.y,
184
+ sum: input.x + input.y,
185
+ }),
186
+ })
187
+ let seenGeneration = -1
188
+ ft.published$.subscribe(() => {
189
+ seenGeneration = ft.generation
190
+ })
191
+ ft.writeInput({ x: 1 })
192
+ ft.flush()
193
+ expect(seenGeneration).toBe(1)
194
+ expect(ft.published$.peek().generation).toBe(1)
195
+ })
196
+
197
+ it('defers re-entrant flush during publish to next generation only', () => {
198
+ const order: number[] = []
199
+ const ft = createFrameTransaction<Input, Snapshot>({
200
+ initialInput: { x: 0, y: 0 },
201
+ derive: (input, generation) => ({
202
+ generation,
203
+ x: input.x,
204
+ y: input.y,
205
+ sum: input.x + input.y,
206
+ }),
207
+ })
208
+ ft.published$.subscribe(() => {
209
+ const snap = ft.published$.peek()
210
+ order.push(snap.x)
211
+ if (snap.x === 1) {
212
+ ft.writeInput({ x: 2 })
213
+ // re-entrant flush must not publish x=2 before remaining listeners of gen1
214
+ ft.flush()
215
+ }
216
+ })
217
+ const late = vi.fn()
218
+ ft.published$.subscribe(late)
219
+
220
+ ft.writeInput({ x: 1 })
221
+ ft.flush()
222
+ expect(order).toEqual([1])
223
+ expect(late).toHaveBeenCalledTimes(1)
224
+ expect(ft.published$.peek().x).toBe(1)
225
+ expect(ft.generation).toBe(1)
226
+
227
+ // deferred work runs on next idle flush
228
+ const second = ft.flush()
229
+ expect(second.x).toBe(2)
230
+ expect(second.generation).toBe(2)
231
+ })
232
+
233
+ it('freezes generation-0 published snapshot root', () => {
234
+ const ft = createFrameTransaction<Input, Snapshot>({
235
+ initialInput: { x: 0, y: 0 },
236
+ derive: (input, generation) => ({
237
+ generation,
238
+ x: input.x,
239
+ y: input.y,
240
+ sum: input.x + input.y,
241
+ }),
242
+ })
243
+ const snap = ft.published$.peek() as Snapshot & { x: number }
244
+ expect(Object.isFrozen(snap)).toBe(true)
245
+ expect(() => {
246
+ ;(snap as { x: number }).x = 99
247
+ }).toThrow()
248
+ })
249
+
250
+ it('reschedules after scheduled flush throws', () => {
251
+ const runners: Array<() => void> = []
252
+ let failNextRealFrame = false
253
+ const ft = createFrameTransaction<Input, Snapshot>({
254
+ initialInput: { x: 0, y: 0 },
255
+ derive: (input, generation) => {
256
+ // generation 0 是构造占位,不可失败
257
+ if (generation > 0 && failNextRealFrame) {
258
+ failNextRealFrame = false
259
+ throw new Error('boom')
260
+ }
261
+ return {
262
+ generation,
263
+ x: input.x,
264
+ y: input.y,
265
+ sum: input.x + input.y,
266
+ }
267
+ },
268
+ schedule: (run) => {
269
+ runners.push(run)
270
+ return 1
271
+ },
272
+ })
273
+ failNextRealFrame = true
274
+ ft.writeInput({ x: 3 })
275
+ ft.scheduleFlush()
276
+ expect(runners).toHaveLength(1)
277
+ expect(() => runners[0]!()).toThrow('boom')
278
+ // dirty retained → another schedule registered
279
+ expect(runners.length).toBeGreaterThanOrEqual(2)
280
+ runners[runners.length - 1]!()
281
+ expect(ft.published$.peek().x).toBe(3)
282
+ expect(ft.generation).toBe(1)
283
+ })
284
+
285
+ it('resets scheduleQueued when schedule() throws', () => {
286
+ let shouldThrow = true
287
+ const ft = createFrameTransaction<Input, Snapshot>({
288
+ initialInput: { x: 0, y: 0 },
289
+ derive: (input, generation) => ({
290
+ generation,
291
+ x: input.x,
292
+ y: input.y,
293
+ sum: input.x + input.y,
294
+ }),
295
+ schedule: () => {
296
+ if (shouldThrow) throw new Error('no-raf')
297
+ return 1
298
+ },
299
+ })
300
+ ft.writeInput({ x: 1 })
301
+ expect(() => ft.scheduleFlush()).toThrow('no-raf')
302
+ shouldThrow = false
303
+ // must not be permanently stuck
304
+ expect(() => ft.scheduleFlush()).not.toThrow()
305
+ })
306
+ })
@@ -0,0 +1,239 @@
1
+ /**
2
+ * 帧事务:高频输入集中写入,每帧最多发布一次不可变快照。
3
+ *
4
+ * @remarks
5
+ * 解决两类问题:
6
+ * 1. pointermove 等事件若直接写普通 Signal,会按事件频率广播订阅者。
7
+ * 2. latest 与 published 双视图会让不同模块读到不同代际状态。
8
+ *
9
+ * 本原语只暴露 pending 写入与 published 快照,不暴露公开 latest。
10
+ * 一帧固定走 capture → derive → seal → render → publish → complete。
11
+ * render 或 publish 期间的 writeInput 一律进入下一代 pending。
12
+ * 非 idle 时调用 flush 不会嵌套发布,仅保留 dirty 供外层完成后调度。
13
+ */
14
+
15
+ import { createSignal, type ReadonlySignal } from './signal'
16
+
17
+ /** 帧事务所处阶段;用于隔离重入写入 */
18
+ export type FramePhase = 'idle' | 'capturing' | 'deriving' | 'sealing' | 'rendering' | 'publishing'
19
+
20
+ /**
21
+ * 浅合并输入补丁。
22
+ * 仅顶层键覆盖,不递归合并嵌套对象。
23
+ */
24
+ function mergeInput<T extends Record<string, unknown>>(base: T, patch: Partial<T>): T {
25
+ return { ...base, ...patch }
26
+ }
27
+
28
+ /** 浅冻结快照根对象;大数组字段由 derive 侧结构共享,禁止深拷贝 */
29
+ function sealSnapshotRoot<T>(snapshot: T): T {
30
+ if (typeof snapshot === 'object' && snapshot !== null) {
31
+ Object.freeze(snapshot)
32
+ }
33
+ return snapshot
34
+ }
35
+
36
+ export interface FrameTransactionOptions<TInput extends Record<string, unknown>, TSnapshot> {
37
+ /** 初始 pending 输入;也会用于生成 generation 0 的占位 published */
38
+ initialInput: TInput
39
+ /**
40
+ * 由封存输入纯推导快照。
41
+ * 不得写外部 kernel / DOM;失败则本帧不发布。
42
+ */
43
+ derive: (input: Readonly<TInput>, generation: number) => TSnapshot
44
+ /**
45
+ * 可选:使用本帧快照绘制或执行副作用。
46
+ * 此阶段 writeInput 进入下一代,不得假定能改当前快照。
47
+ */
48
+ render?: (snapshot: Readonly<TSnapshot>) => void
49
+ /**
50
+ * 调度 flush 的宿主。默认 requestAnimationFrame;测试可注入同步队列。
51
+ * 返回值可忽略(兼容 rAF handle)。
52
+ */
53
+ schedule?: (run: () => void) => unknown
54
+ }
55
+
56
+ export interface FrameTransaction<TInput extends Record<string, unknown>, TSnapshot> {
57
+ /** 最近一次成功发布的快照(只读 Signal) */
58
+ readonly published$: ReadonlySignal<Readonly<TSnapshot>>
59
+ /** 已成功发布的帧代际;失败 flush 不增加 */
60
+ readonly generation: number
61
+ /** 当前阶段,调试与不变量检查用 */
62
+ readonly phase: FramePhase
63
+ /**
64
+ * 合并高频输入。idle 写入当前 pending;非 idle 写入 nextPending。
65
+ * 不触发订阅通知。
66
+ */
67
+ writeInput(patch: Partial<TInput>): void
68
+ /**
69
+ * 同步执行一帧事务。无 pending 时返回当前 published,且不通知。
70
+ * 非 idle 调用不会嵌套发布,返回当前 published。
71
+ * @returns 本帧使用的快照(成功时等于 published$.peek())
72
+ */
73
+ flush(): Readonly<TSnapshot>
74
+ /**
75
+ * 请求在宿主调度器上合并 flush;多次调用在同一 pending 调度内只注册一次。
76
+ */
77
+ scheduleFlush(): void
78
+ /**
79
+ * 从 published 投影只读 Signal。
80
+ * 仅当 select 结果相对上一值 Object.is 不等时通知。
81
+ */
82
+ select<T>(selector: (snapshot: Readonly<TSnapshot>) => T): ReadonlySignal<T>
83
+ }
84
+
85
+ /**
86
+ * 创建帧事务控制器。
87
+ *
88
+ * @typeParam TInput - 可合并的输入形状(浅层 Partial 合并)
89
+ * @typeParam TSnapshot - derive 产出的不可变快照类型
90
+ */
91
+ export function createFrameTransaction<
92
+ TInput extends Record<string, unknown>,
93
+ TSnapshot,
94
+ >(options: FrameTransactionOptions<TInput, TSnapshot>): FrameTransaction<TInput, TSnapshot> {
95
+ const { derive, render } = options
96
+ const schedule =
97
+ options.schedule ??
98
+ ((run: () => void) => {
99
+ if (typeof requestAnimationFrame === 'function') {
100
+ return requestAnimationFrame(run)
101
+ }
102
+ return setTimeout(run, 0)
103
+ })
104
+
105
+ /** 当前代可写入的输入(仅 idle 时接收 writeInput) */
106
+ let pending: TInput = { ...options.initialInput }
107
+ /** 事务进行中产生的输入,complete 后并入下一代 */
108
+ let nextPending: TInput | null = null
109
+ /** 是否存在尚未 flush 的 pending 变更 */
110
+ let dirty = false
111
+ let generation = 0
112
+ let phase: FramePhase = 'idle'
113
+ let scheduleQueued = false
114
+
115
+ // generation 0:用初始输入 derive 一次,作为 published 占位,避免订阅者读到 undefined
116
+ const initialSnapshot = sealSnapshotRoot(derive({ ...pending }, 0))
117
+ const published = createSignal<TSnapshot>(initialSnapshot)
118
+
119
+ function writeInput(patch: Partial<TInput>): void {
120
+ if (phase === 'idle') {
121
+ pending = mergeInput(pending, patch)
122
+ dirty = true
123
+ return
124
+ }
125
+ // capturing 之后当前输入已封存;重入写入只能进下一代
126
+ const base = nextPending ?? pending
127
+ nextPending = mergeInput(base, patch)
128
+ }
129
+
130
+ function flush(): Readonly<TSnapshot> {
131
+ // 禁止嵌套发布:订阅者 / render 中 flush 只保留 dirty,由外层 complete 后再调度
132
+ if (phase !== 'idle') {
133
+ return published.peek()
134
+ }
135
+
136
+ if (!dirty && nextPending === null) {
137
+ return published.peek()
138
+ }
139
+
140
+ // 若仅有 nextPending(例如上一帧 render 中写入),提升为当前 pending
141
+ if (!dirty && nextPending !== null) {
142
+ pending = nextPending
143
+ nextPending = null
144
+ dirty = true
145
+ }
146
+
147
+ let sealedInput: TInput | undefined
148
+ phase = 'capturing'
149
+ try {
150
+ sealedInput = pending
151
+ dirty = false
152
+ // 本帧 capture 之后的新写入进入 nextPending,不得污染 sealedInput
153
+ pending = { ...sealedInput }
154
+
155
+ phase = 'deriving'
156
+ const nextGeneration = generation + 1
157
+ const snapshot = derive(sealedInput, nextGeneration)
158
+
159
+ phase = 'sealing'
160
+ sealSnapshotRoot(snapshot)
161
+
162
+ phase = 'rendering'
163
+ render?.(snapshot)
164
+
165
+ // 先推进代际再发布,保证订阅者读 generation 与 snapshot.generation 一致
166
+ phase = 'publishing'
167
+ generation = nextGeneration
168
+ published.set(snapshot)
169
+
170
+ return snapshot
171
+ } catch (err) {
172
+ // derive/render 失败:不推进 generation(若已推进则回滚),保留 sealed 输入供重试
173
+ if (sealedInput !== undefined) {
174
+ pending = sealedInput
175
+ dirty = true
176
+ }
177
+ throw err
178
+ } finally {
179
+ phase = 'idle'
180
+ // 事务中累积的 nextPending 与重试 pending 合并
181
+ if (nextPending !== null) {
182
+ pending = mergeInput(pending, nextPending)
183
+ nextPending = null
184
+ dirty = true
185
+ }
186
+ // 有残留 dirty 时挂下一代调度(含 re-entrant write / 失败重试)
187
+ if (dirty) {
188
+ scheduleFlush()
189
+ }
190
+ }
191
+ }
192
+
193
+ function scheduleFlush(): void {
194
+ if (scheduleQueued) return
195
+ if (!dirty && nextPending === null) return
196
+ scheduleQueued = true
197
+ try {
198
+ schedule(() => {
199
+ scheduleQueued = false
200
+ // flush 的 finally 已在失败时保留 dirty 并 scheduleFlush;此处继续抛出供测试/诊断
201
+ flush()
202
+ })
203
+ } catch (err) {
204
+ // schedule 本身失败:复位标志,允许后续 scheduleFlush 重试
205
+ scheduleQueued = false
206
+ throw err
207
+ }
208
+ }
209
+
210
+ function select<T>(selector: (snapshot: Readonly<TSnapshot>) => T): ReadonlySignal<T> {
211
+ const selected = createSignal(selector(published.peek()))
212
+ published.subscribe(() => {
213
+ const next = selector(published.peek())
214
+ selected.set(next)
215
+ })
216
+ const read = (): T => selected()
217
+ return Object.assign(read, {
218
+ peek: selected.peek,
219
+ subscribe: selected.subscribe,
220
+ }) as ReadonlySignal<T>
221
+ }
222
+
223
+ return {
224
+ published$: Object.assign((() => published()) as ReadonlySignal<Readonly<TSnapshot>>, {
225
+ peek: published.peek,
226
+ subscribe: published.subscribe,
227
+ }),
228
+ get generation() {
229
+ return generation
230
+ },
231
+ get phase() {
232
+ return phase
233
+ },
234
+ writeInput,
235
+ flush,
236
+ scheduleFlush,
237
+ select,
238
+ }
239
+ }
@@ -4,6 +4,7 @@ export {
4
4
  computed,
5
5
  effect,
6
6
  batch,
7
+ selectSignal,
7
8
  createStateStore,
8
9
  createSubState,
9
10
  } from './signal'
@@ -14,4 +15,11 @@ export type {
14
15
  WritableRef,
15
16
  ReadonlyRef,
16
17
  Computed,
18
+ SelectedSignal,
17
19
  } from './signal'
20
+ export { createFrameTransaction } from './frameTransaction'
21
+ export type {
22
+ FramePhase,
23
+ FrameTransaction,
24
+ FrameTransactionOptions,
25
+ } from './frameTransaction'
@@ -233,6 +233,45 @@ export function batch<T>(fn: () => T): T {
233
233
  }
234
234
  }
235
235
 
236
+ /**
237
+ * selectSignal 返回值:只读投影 + 解除对 source 的订阅。
238
+ */
239
+ export type SelectedSignal<U> = ReadonlySignal<U> & {
240
+ /** 取消对 source 的订阅;之后不再随 source 更新 */
241
+ dispose: () => void
242
+ }
243
+
244
+ /**
245
+ * 从源 Signal 投影只读 Signal。
246
+ * 仅当 selector 结果相对上一值 equal 判定为不等时通知。
247
+ *
248
+ * @remarks
249
+ * 用于框架桥接:避免整包 snapshot 变化时无关字段也触发 UI 更新。
250
+ * 默认 equal 为 Object.is;对 {x,y} 等可传入结构相等函数。
251
+ * 短生命周期投影必须调用 dispose,否则会永久订阅 source。
252
+ *
253
+ * @typeParam T - 源快照类型
254
+ * @typeParam U - 投影结果类型
255
+ */
256
+ export function selectSignal<T, U>(
257
+ source: ReadonlySignal<T>,
258
+ selector: (value: T) => U,
259
+ equal: (a: U, b: U) => boolean = Object.is,
260
+ ): SelectedSignal<U> {
261
+ const selected = createSignal(selector(source.peek()))
262
+ const unsubSource = source.subscribe(() => {
263
+ const next = selector(source.peek())
264
+ if (equal(selected.peek(), next)) return
265
+ selected.set(next)
266
+ })
267
+ const read = (): U => selected()
268
+ return Object.assign(read, {
269
+ peek: selected.peek,
270
+ subscribe: selected.subscribe,
271
+ dispose: unsubSource,
272
+ }) as SelectedSignal<U>
273
+ }
274
+
236
275
  /**
237
276
  * 从初始状态对象创建一组相关 signal。
238
277
  *
@@ -71,7 +71,7 @@ exports[`theme baseline — dark > CSS declaration block (snapshot) 1`] = `
71
71
  --klc-color-tag-bg-transparent: transparent;
72
72
  --klc-color-tag-bg-active: #1890ff;
73
73
  --klc-color-tag-bg-active-hover: #40a9ff;
74
- --klc-color-tag-bg-hover: #262C36;
74
+ --klc-color-tag-bg-hover: #2D3544;
75
75
  --klc-color-border-dark: rgba(255, 255, 255, 0.15);
76
76
  --klc-color-border-medium: rgba(255, 255, 255, 0.12);
77
77
  --klc-color-border-light: rgba(255, 255, 255, 0.08);
@@ -271,7 +271,7 @@ exports[`theme baseline — light > CSS declaration block (snapshot) 1`] = `
271
271
  --klc-color-tag-bg-transparent: transparent;
272
272
  --klc-color-tag-bg-active: #1890ff;
273
273
  --klc-color-tag-bg-active-hover: #40a9ff;
274
- --klc-color-tag-bg-hover: #f0f0f0;
274
+ --klc-color-tag-bg-hover: #E5E7EB;
275
275
  --klc-color-border-dark: rgba(0, 0, 0, 0.12);
276
276
  --klc-color-border-medium: rgba(0, 0, 0, 0.10);
277
277
  --klc-color-border-light: rgba(0, 0, 0, 0.08);
@@ -117,7 +117,8 @@ export const darkTheme: Theme = {
117
117
  transparent: 'transparent',
118
118
  active: '#1890ff',
119
119
  activeHover: '#40a9ff',
120
- hover: '#262C36',
120
+ // 相对 background #111827 提高悬停对比,列表项 hover 更易辨认
121
+ hover: '#2D3544',
121
122
  },
122
123
  border: {
123
124
  dark: 'rgba(255, 255, 255, 0.15)',
@@ -123,7 +123,8 @@ export const lightTheme: Theme = {
123
123
  transparent: 'transparent',
124
124
  active: '#1890ff',
125
125
  activeHover: '#40a9ff',
126
- hover: '#f0f0f0',
126
+ // 相对 background #FAFAFA 提高悬停对比,避免与底色糊成一片
127
+ hover: '#E5E7EB',
127
128
  },
128
129
  border: {
129
130
  dark: 'rgba(0, 0, 0, 0.12)',