visualifyjs 2.5.3-2.dev → 3.0.0

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 (252) hide show
  1. package/.claude/mem/TIMELINE.md +36 -0
  2. package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-external-script-solution.md +24 -0
  3. package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-session-summary.md +43 -0
  4. package/.claude/mem/notes/2026-02-11-cli-fix-editor-command-alias.md +26 -0
  5. package/.claude/mem/notes/2026-02-11-phase-3-developer-experience-completed.md +51 -0
  6. package/.claude/mem/notes/2026-02-11-phase-4-web-workers-implementation-complete.md +59 -0
  7. package/.claude/mem/notes/2026-02-11-visualify-phase-2-3d-visualization-complete.md +50 -0
  8. package/.claude/mem/notes/2026-02-11-visualify-phase-2-committed-ready-for-phase-3.md +33 -0
  9. package/.claude/mem/notes/2026-02-11-visualify-phase-3-complete-developer-experience.md +52 -0
  10. package/.claude/mem/notes/2026-02-11-visualify-repository-cleanup-complete.md +28 -0
  11. package/.claude/mem/notes/2026-02-18-codebase-cleanup-docsify-plugin-documentation.md +37 -0
  12. package/.claude/mem/notes/2026-02-19-css-grid-layout-fix-displaycontents-on-vcontroller.md +18 -0
  13. package/.claude/mem/notes/2026-02-19-docsify-plugin-fixes-latex-and-visualify-code-bloc.md +26 -0
  14. package/.claude/mem/notes/2026-02-19-page-mode-docs-update-decisions.md +23 -0
  15. package/.claude/mem/notes/2026-02-19-react-context-infinite-re-render-loop-fix-pattern.md +31 -0
  16. package/.claude/mem/notes/2026-02-19-version-300-bump-and-build-fixes.md +32 -0
  17. package/.claude/mem/notes/2026-02-19-visualify-build-deployment-architecture-bug-fixes.md +25 -0
  18. package/.claude/mem/notes/2026-02-19-visualify-dist-iife-self-contained-build-config.md +30 -0
  19. package/.claude/mem/notes/2026-02-19-visualify-infinite-loop-i18n-fixes.md +31 -0
  20. package/.claude/mem/notes/2026-02-19-visualify-v3-bundle-splitting-docs-restructuring.md +32 -0
  21. package/.claude/mem/notes/2026-02-20-bundle-externalization-final-architecture.md +29 -0
  22. package/.claude/mem/notes/2026-02-20-chromium-page-fix-unstable-keys-and-double-event-b.md +27 -0
  23. package/.claude/mem/notes/2026-02-20-console-cleanup-bundle-optimization-commit.md +20 -0
  24. package/.claude/mem/notes/2026-02-20-dotbio-dot-plot-fix-useeffect-dependency.md +21 -0
  25. package/.claude/mem/notes/2026-02-20-public-folder-cleanup-and-readme-rewrite.md +25 -0
  26. package/.claude/mem/notes/2026-02-20-v300-release-and-beta-channel-strategy.md +29 -0
  27. package/.claude/mem/notes/2026-02-20-visium-background-image-unknown-legend-fix.md +19 -0
  28. package/.claude/mem/notes/2026-02-20-visualify-cdn-loader-bundle-externalization.md +34 -0
  29. package/.claude/mem/sessions/session-2026-02-20-031524.md +54 -0
  30. package/.claude/settings.local.json +21 -0
  31. package/.github/workflows/static.yml.bak +51 -51
  32. package/.sisyphus/boulder.json +65 -0
  33. package/.sisyphus/plans/phase-4-advanced-optimizations.md +217 -0
  34. package/LICENSE +674 -674
  35. package/README.md +94 -59
  36. package/config-overrides.js +31 -31
  37. package/dist/stats.html +4949 -0
  38. package/dist/visualify-3d.esm.js +1 -0
  39. package/dist/visualify-3d.js +1 -0
  40. package/dist/visualify-core.esm.js +1 -0
  41. package/dist/visualify-core.js +1 -0
  42. package/dist/visualify-docs.esm.js +1 -0
  43. package/dist/visualify-docs.js +1 -0
  44. package/dist/visualify-loader.js +1 -0
  45. package/dist/visualify-pages.esm.js +1 -0
  46. package/dist/visualify-pages.js +1 -0
  47. package/dist/visualify-portal.esm.js +1 -0
  48. package/dist/visualify-portal.js +1 -0
  49. package/dist/visualify-shared.js +26571 -0
  50. package/dist/visualify.js +1 -188
  51. package/docs/CHANGELOG.md +148 -0
  52. package/docs/cli/commands.md +513 -0
  53. package/docs/configuration/visualify-json.md +474 -0
  54. package/docs/docs/3d-visualization.md +374 -0
  55. package/docs/docs/CLI.md +303 -34
  56. package/docs/docs/README.md +65 -65
  57. package/docs/docs/Rechart/bar.md +190 -190
  58. package/docs/docs/Rechart/funnel.md +241 -241
  59. package/docs/docs/Rechart/line.md +355 -355
  60. package/docs/docs/Rechart/pie.md +225 -225
  61. package/docs/docs/Rechart/radar.md +253 -253
  62. package/docs/docs/Rechart/scatter.md +298 -298
  63. package/docs/docs/_404.md +51 -51
  64. package/docs/docs/_coverpage.md +11 -11
  65. package/docs/docs/_sidebar.md +54 -44
  66. package/docs/docs/components/dotBio.md +87 -34
  67. package/docs/docs/components/echart.md +171 -82
  68. package/docs/docs/components/html.md +61 -34
  69. package/docs/docs/components/macaron.md +156 -145
  70. package/docs/docs/components/markdown.md +42 -0
  71. package/docs/docs/components/more.md +183 -142
  72. package/docs/docs/components/plotly.md +132 -62
  73. package/docs/docs/components/scatterL.md +171 -70
  74. package/docs/docs/components/visium.md +112 -57
  75. package/docs/docs/configuration.md +121 -121
  76. package/docs/docs/deploy.md +31 -31
  77. package/docs/docs/docsify-plugin.md +655 -0
  78. package/docs/docs/hmr.md +165 -0
  79. package/docs/docs/i18n.md +332 -0
  80. package/docs/docs/log.md +30 -9
  81. package/docs/docs/more-pages.md +23 -23
  82. package/docs/docs/quickstart.md +148 -124
  83. package/docs/docs/rechart-attributes.md +74 -74
  84. package/docs/docs/rechart-basic-usage.md +160 -162
  85. package/docs/docs/theme.md +5 -5
  86. package/docs/docs/typescript.md +306 -0
  87. package/docs/docs/visual-editor.md +359 -0
  88. package/docs/index.html +85 -71
  89. package/docs/manifest.json +23 -23
  90. package/docs/migration/v3-migration.md +392 -0
  91. package/docs/static/css/fluff-stuff.css +169 -169
  92. package/docs/static/css/font-awesome.min.css +4 -4
  93. package/docs/static/css/visualify.css +6 -25
  94. package/docs/static/js/3d-viz-examples.js +181 -0
  95. package/docs/static/js/configuration.js +630 -448
  96. package/docs/static/js/visualify.js +1 -188
  97. package/package.json +106 -84
  98. package/rollup.config.mjs +766 -76
  99. package/src/_css/404.css +115 -115
  100. package/src/_css/App.css +37 -37
  101. package/src/_css/autoSuggestion.css +26 -26
  102. package/src/_css/circular-progress.css +32 -32
  103. package/src/_css/index.css +36 -36
  104. package/src/_css/modern.css +350 -25
  105. package/src/_media/corner.svg +8 -8
  106. package/src/_media/download.svg +3 -3
  107. package/src/_media/logo.svg +14 -14
  108. package/src/_test/App.test.js +15 -15
  109. package/src/_utils/reportWebVitals.js +13 -13
  110. package/src/a11y/README.md +177 -0
  111. package/src/a11y/aria-labels.js +339 -0
  112. package/src/a11y/color-contrast.js +535 -0
  113. package/src/a11y/index.js +197 -0
  114. package/src/a11y/keyboard-nav.js +523 -0
  115. package/src/a11y/styles.css +165 -0
  116. package/src/cli/commands/dev.js +214 -0
  117. package/src/cli/commands/docs.js +521 -0
  118. package/src/cli/commands/edit.js +379 -0
  119. package/src/cli/commands/init.js +213 -0
  120. package/src/cli/commands/portal.js +236 -0
  121. package/src/cli/dev-server.js +530 -0
  122. package/src/cli/hmr.js +456 -0
  123. package/src/cli/index.js +180 -0
  124. package/src/cli/utils/config.js +207 -0
  125. package/src/cli/utils/logger.js +241 -0
  126. package/src/config/defaults.ts +122 -0
  127. package/src/config/index.ts +72 -0
  128. package/src/config/loader.ts +478 -0
  129. package/src/config/schema.ts +227 -0
  130. package/src/config/validator.ts +337 -0
  131. package/src/core/appContext.js +34 -27
  132. package/src/core/components/Bar.js +383 -0
  133. package/src/core/components/Bar3D.js +473 -0
  134. package/src/core/components/LargeDatasetChart.js +296 -0
  135. package/src/core/components/Line3D.js +310 -0
  136. package/src/core/components/Scatter.js +392 -188
  137. package/src/core/components/Scatter3D.js +455 -0
  138. package/src/core/components/ScatterBio.js +601 -572
  139. package/src/core/components/Surface3D.js +326 -0
  140. package/src/core/components/ThreeCustom.js +648 -0
  141. package/src/core/components/ThreeScene.js +459 -0
  142. package/src/core/components/VisiumPlot.js +191 -165
  143. package/src/core/components/browser.js +42 -42
  144. package/src/core/components/dotplot.js +413 -413
  145. package/src/core/components/html.js +29 -29
  146. package/src/core/components/list.js +178 -178
  147. package/src/core/components/macaron.js +206 -201
  148. package/src/core/components/markdown.js +56 -56
  149. package/src/core/components/parser.scatterBio.js +582 -587
  150. package/src/core/components/ratio.js +82 -80
  151. package/src/core/components/scatterL.js +206 -173
  152. package/src/core/components/searchbar.js +156 -131
  153. package/src/core/components/selection.js +310 -193
  154. package/src/core/components/timeline.js +236 -281
  155. package/src/core/components/visium.js +114 -97
  156. package/src/core/data-processor.js +413 -0
  157. package/src/core/fetch/condfetch.js +82 -82
  158. package/src/core/fetch/fetch.js +92 -92
  159. package/src/core/fetch/json.js +29 -29
  160. package/src/core/fetch/vfetch.js +42 -42
  161. package/src/core/hmr-client.js +724 -0
  162. package/src/core/liveEditor.js +44 -44
  163. package/src/core/modules/codeEditorWithPreview.js +104 -104
  164. package/src/core/modules/echarts/common.js +20 -20
  165. package/src/core/modules/echarts/gl.js +228 -0
  166. package/src/core/modules/echarts/presetHandler.js +41 -41
  167. package/src/core/modules/echarts/presets/esodev.chromium.js +172 -172
  168. package/src/core/modules/echarts/presets/esodev.codex.js +130 -130
  169. package/src/core/modules/echarts/presets/esodev.visium.js +123 -123
  170. package/src/core/modules/echarts/presets/mmtrbc.js +186 -186
  171. package/src/core/modules/echarts.js +70 -71
  172. package/src/core/modules/echartsUtils.js +43 -43
  173. package/src/core/modules/echartswitcher.js +227 -152
  174. package/src/core/modules/replotly/presetHandler.js +24 -24
  175. package/src/core/modules/replotly/presets/minimum.js +18 -18
  176. package/src/core/modules/replotly/presets/mmtrbc.dot.js +114 -114
  177. package/src/core/modules/replotly/presets/mmtrbc.violin.js +100 -100
  178. package/src/core/modules/replotly.js +74 -71
  179. package/src/core/modules/threejs/Camera.js +373 -0
  180. package/src/core/modules/threejs/Lighting.js +459 -0
  181. package/src/core/modules/threejs/Renderer.js +364 -0
  182. package/src/core/modules/threejs/Scene.js +266 -0
  183. package/src/core/modules/threejs/index.js +155 -0
  184. package/src/core/pages/404.js +50 -50
  185. package/src/core/pages/error.js +27 -27
  186. package/src/core/pages/jsonPage.js +62 -62
  187. package/src/core/pages/loading.js +44 -44
  188. package/src/core/parser/echart.data.js +204 -183
  189. package/src/core/parser/echart.features.js +125 -125
  190. package/src/core/parser/echart.general.js +147 -147
  191. package/src/core/parser/echart.hilbert.js +57 -57
  192. package/src/core/parser/echart.parser.js +210 -210
  193. package/src/core/parser/echart.series.js +67 -67
  194. package/src/core/parser/echart.types.js +76 -76
  195. package/src/core/parser/plotly.config.js +10 -10
  196. package/src/core/parser/plotly.data.js +132 -132
  197. package/src/core/parser/plotly.layout.js +9 -9
  198. package/src/core/parser/plotly.violin.js +18 -18
  199. package/src/core/recharts.js +361 -62
  200. package/src/core/router/alias.js +49 -49
  201. package/src/core/router/jsonRouter.js +31 -31
  202. package/src/core/themes/modern.js +32 -32
  203. package/src/core/themes/themeSelector.js +33 -33
  204. package/src/core/visualify.js +213 -47
  205. package/src/core/widgets/circularProgress.js +23 -23
  206. package/src/core/widgets/controller.js +116 -83
  207. package/src/core/widgets/errorBoundary.js +36 -36
  208. package/src/core/widgets/footer.js +185 -177
  209. package/src/core/widgets/header.js +238 -234
  210. package/src/core/widgets/layout/Grid.js +31 -31
  211. package/src/core/widgets/layout.js +36 -36
  212. package/src/core/widgets/mapping.js +56 -42
  213. package/src/core/workers/data-worker.js +349 -0
  214. package/src/core/workers/worker-pool.js +396 -0
  215. package/src/docsify/bundle.js +215 -0
  216. package/src/docsify/markdown.js +271 -0
  217. package/src/docsify/plugin.js +268 -0
  218. package/src/editor/README.md +172 -0
  219. package/src/editor/components/ChartBuilder.jsx +341 -0
  220. package/src/editor/components/ChartTypeSidebar.jsx +91 -0
  221. package/src/editor/components/Editor.jsx +367 -0
  222. package/src/editor/components/Preview.jsx +446 -0
  223. package/src/editor/components/PropertyPanel.jsx +468 -0
  224. package/src/editor/components/StatusBar.jsx +85 -0
  225. package/src/editor/context/EditorContext.js +248 -0
  226. package/src/editor/hooks/useDebounce.js +32 -0
  227. package/src/editor/index.js +315 -0
  228. package/src/editor/styles/editor.css +637 -0
  229. package/src/editor/utils/chartValidator.js +263 -0
  230. package/src/entries/charts3d.js +70 -0
  231. package/src/entries/core.js +78 -0
  232. package/src/entries/docs.js +154 -0
  233. package/src/entries/pages.js +93 -0
  234. package/src/entries/portal.js +204 -0
  235. package/src/entries/shared.js +50 -0
  236. package/src/i18n/formatters.js +455 -0
  237. package/src/i18n/index.js +169 -0
  238. package/src/i18n/locales/ar.json +137 -0
  239. package/src/i18n/locales/de.json +137 -0
  240. package/src/i18n/locales/en.json +137 -0
  241. package/src/i18n/locales/es.json +137 -0
  242. package/src/i18n/locales/he.json +137 -0
  243. package/src/i18n/locales/zh.json +137 -0
  244. package/src/i18n/rtl.css +183 -0
  245. package/src/index.js +82 -62
  246. package/src/loader.js +103 -0
  247. package/src/setupTests.js +5 -5
  248. package/tsconfig.json +51 -0
  249. package/types/charts.d.ts +569 -0
  250. package/types/components.d.ts +441 -0
  251. package/types/config.d.ts +199 -0
  252. package/types/index.d.ts +353 -0
@@ -0,0 +1,724 @@
1
+ /**
2
+ * @fileoverview Client-side Hot Module Replacement (HMR) handler for Visualify
3
+ * @module core/hmr-client
4
+ *
5
+ * Provides WebSocket client functionality for HMR, chart update logic,
6
+ * state preservation, and error display capabilities.
7
+ */
8
+
9
+ import { useEffect, useRef, useCallback } from 'react';
10
+
11
+ /**
12
+ * Default HMR client configuration
13
+ * @readonly
14
+ * @type {Object}
15
+ */
16
+ const DEFAULT_CONFIG = {
17
+ wsUrl: 'ws://localhost:3000/__hmr',
18
+ reconnectDelay: 1000,
19
+ maxReconnectAttempts: 10,
20
+ heartbeatInterval: 30000,
21
+ debug: false,
22
+ };
23
+
24
+ /**
25
+ * HMR Client class - manages WebSocket connection and updates
26
+ */
27
+ class HMRClient {
28
+ /**
29
+ * Create an HMR client instance
30
+ * @param {Object} options - Configuration options
31
+ */
32
+ constructor(options = {}) {
33
+ this.config = { ...DEFAULT_CONFIG, ...options };
34
+ this.ws = null;
35
+ this.reconnectAttempts = 0;
36
+ this.reconnectTimer = null;
37
+ this.heartbeatTimer = null;
38
+ this.isConnected = false;
39
+ this.updateHandlers = new Map();
40
+ this.errorHandlers = new Set();
41
+ this.statePreservers = new Map();
42
+ this.errorOverlay = null;
43
+
44
+ // Bound methods
45
+ this.handleOpen = this.handleOpen.bind(this);
46
+ this.handleMessage = this.handleMessage.bind(this);
47
+ this.handleClose = this.handleClose.bind(this);
48
+ this.handleError = this.handleError.bind(this);
49
+ this.reconnect = this.reconnect.bind(this);
50
+ this.sendHeartbeat = this.sendHeartbeat.bind(this);
51
+ }
52
+
53
+ /**
54
+ * Initialize the HMR client and connect to WebSocket
55
+ * @returns {void}
56
+ */
57
+ init() {
58
+ if (this.ws) {
59
+ this.log('HMR client already initialized');
60
+ return;
61
+ }
62
+
63
+ this.log('Initializing HMR client...');
64
+ this.connect();
65
+ this.createErrorOverlay();
66
+ }
67
+
68
+ /**
69
+ * Connect to the WebSocket server
70
+ * @private
71
+ * @returns {void}
72
+ */
73
+ connect() {
74
+ try {
75
+ this.log(`Connecting to ${this.config.wsUrl}...`);
76
+ this.ws = new WebSocket(this.config.wsUrl);
77
+
78
+ this.ws.addEventListener('open', this.handleOpen);
79
+ this.ws.addEventListener('message', this.handleMessage);
80
+ this.ws.addEventListener('close', this.handleClose);
81
+ this.ws.addEventListener('error', this.handleError);
82
+ } catch (err) {
83
+ this.error('Failed to create WebSocket:', err);
84
+ this.scheduleReconnect();
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Disconnect from the WebSocket server
90
+ * @returns {void}
91
+ */
92
+ disconnect() {
93
+ this.log('Disconnecting HMR client...');
94
+
95
+ // Clear timers
96
+ if (this.reconnectTimer) {
97
+ clearTimeout(this.reconnectTimer);
98
+ this.reconnectTimer = null;
99
+ }
100
+
101
+ if (this.heartbeatTimer) {
102
+ clearInterval(this.heartbeatTimer);
103
+ this.heartbeatTimer = null;
104
+ }
105
+
106
+ // Close WebSocket
107
+ if (this.ws) {
108
+ this.ws.removeEventListener('open', this.handleOpen);
109
+ this.ws.removeEventListener('message', this.handleMessage);
110
+ this.ws.removeEventListener('close', this.handleClose);
111
+ this.ws.removeEventListener('error', this.handleError);
112
+
113
+ if (this.ws.readyState === WebSocket.OPEN) {
114
+ this.ws.close();
115
+ }
116
+
117
+ this.ws = null;
118
+ }
119
+
120
+ this.isConnected = false;
121
+ }
122
+
123
+ /**
124
+ * Handle WebSocket open event
125
+ * @private
126
+ * @returns {void}
127
+ */
128
+ handleOpen() {
129
+ this.log('Connected to HMR server');
130
+ this.isConnected = true;
131
+ this.reconnectAttempts = 0;
132
+
133
+ // Start heartbeat
134
+ this.heartbeatTimer = setInterval(
135
+ this.sendHeartbeat,
136
+ this.config.heartbeatInterval,
137
+ );
138
+
139
+ // Clear any error overlay on successful connection
140
+ this.clearErrorOverlay();
141
+
142
+ // Send ready message
143
+ this.send({ type: 'ready' });
144
+ }
145
+
146
+ /**
147
+ * Handle WebSocket message event
148
+ * @private
149
+ * @param {MessageEvent} event - The message event
150
+ * @returns {void}
151
+ */
152
+ handleMessage(event) {
153
+ try {
154
+ const message = JSON.parse(event.data);
155
+ this.log('Received message:', message.type);
156
+
157
+ switch (message.type) {
158
+ case 'connected':
159
+ this.log('HMR connection established');
160
+ break;
161
+
162
+ case 'update':
163
+ this.handleUpdate(message);
164
+ break;
165
+
166
+ case 'error':
167
+ this.handleServerError(message);
168
+ break;
169
+
170
+ case 'ping':
171
+ this.send({ type: 'pong' });
172
+ break;
173
+
174
+ default:
175
+ this.log('Unknown message type:', message.type);
176
+ }
177
+ } catch (err) {
178
+ this.error('Failed to parse message:', err);
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Handle WebSocket close event
184
+ * @private
185
+ * @returns {void}
186
+ */
187
+ handleClose() {
188
+ this.log('WebSocket connection closed');
189
+ this.isConnected = false;
190
+
191
+ if (this.heartbeatTimer) {
192
+ clearInterval(this.heartbeatTimer);
193
+ this.heartbeatTimer = null;
194
+ }
195
+
196
+ // Attempt to reconnect
197
+ this.scheduleReconnect();
198
+ }
199
+
200
+ /**
201
+ * Handle WebSocket error event
202
+ * @private
203
+ * @param {Event} event - The error event
204
+ * @returns {void}
205
+ */
206
+ handleError(event) {
207
+ this.error('WebSocket error:', event);
208
+ }
209
+
210
+ /**
211
+ * Schedule a reconnection attempt
212
+ * @private
213
+ * @returns {void}
214
+ */
215
+ scheduleReconnect() {
216
+ if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {
217
+ this.error('Max reconnection attempts reached');
218
+ this.showErrorOverlay({
219
+ message: 'Lost connection to HMR server. Please refresh the page.',
220
+ });
221
+ return;
222
+ }
223
+
224
+ this.reconnectAttempts++;
225
+ const delay = this.config.reconnectDelay * Math.min(this.reconnectAttempts, 5);
226
+
227
+ this.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})...`);
228
+
229
+ this.reconnectTimer = setTimeout(() => {
230
+ this.reconnect();
231
+ }, delay);
232
+ }
233
+
234
+ /**
235
+ * Attempt to reconnect to the server
236
+ * @private
237
+ * @returns {void}
238
+ */
239
+ reconnect() {
240
+ if (this.isConnected) {
241
+ return;
242
+ }
243
+
244
+ this.disconnect();
245
+ this.connect();
246
+ }
247
+
248
+ /**
249
+ * Send a heartbeat ping to keep connection alive
250
+ * @private
251
+ * @returns {void}
252
+ */
253
+ sendHeartbeat() {
254
+ if (this.isConnected) {
255
+ this.send({ type: 'ping' });
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Send a message to the server
261
+ * @param {Object} message - The message to send
262
+ * @returns {void}
263
+ */
264
+ send(message) {
265
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
266
+ try {
267
+ this.ws.send(JSON.stringify(message));
268
+ } catch (err) {
269
+ this.error('Failed to send message:', err);
270
+ }
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Handle update message from server
276
+ * @private
277
+ * @param {Object} message - The update message
278
+ * @returns {void}
279
+ */
280
+ handleUpdate(message) {
281
+ const { configType, file, config, changeType } = message;
282
+
283
+ this.log(`Processing ${configType} update:`, file);
284
+
285
+ // Clear error overlay on successful update
286
+ this.clearErrorOverlay();
287
+
288
+ // Get state preserver for this config type
289
+ const preserveState = this.statePreservers.get(configType);
290
+ let preservedState = null;
291
+
292
+ if (preserveState && changeType !== 'removed') {
293
+ preservedState = preserveState();
294
+ this.log('State preserved for update');
295
+ }
296
+
297
+ // Call registered update handlers
298
+ const handlers = this.updateHandlers.get(configType);
299
+ if (handlers) {
300
+ handlers.forEach((handler) => {
301
+ try {
302
+ handler({
303
+ file,
304
+ config,
305
+ changeType,
306
+ preservedState,
307
+ });
308
+ } catch (err) {
309
+ this.error('Update handler failed:', err);
310
+ }
311
+ });
312
+ }
313
+
314
+ // Notify general update handlers
315
+ const generalHandlers = this.updateHandlers.get('*');
316
+ if (generalHandlers) {
317
+ generalHandlers.forEach((handler) => {
318
+ try {
319
+ handler({
320
+ type: configType,
321
+ file,
322
+ config,
323
+ changeType,
324
+ preservedState,
325
+ });
326
+ } catch (err) {
327
+ this.error('General update handler failed:', err);
328
+ }
329
+ });
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Handle error message from server
335
+ * @private
336
+ * @param {Object} message - The error message
337
+ * @returns {void}
338
+ */
339
+ handleServerError(message) {
340
+ this.error('Server error:', message.message);
341
+
342
+ // Show error overlay
343
+ this.showErrorOverlay({
344
+ file: message.file,
345
+ message: message.message,
346
+ details: message.details,
347
+ isSyntaxError: message.isSyntaxError,
348
+ });
349
+
350
+ // Notify error handlers
351
+ this.errorHandlers.forEach((handler) => {
352
+ try {
353
+ handler(message);
354
+ } catch (err) {
355
+ this.error('Error handler failed:', err);
356
+ }
357
+ });
358
+ }
359
+
360
+ /**
361
+ * Create the error overlay element
362
+ * @private
363
+ * @returns {void}
364
+ */
365
+ createErrorOverlay() {
366
+ if (typeof document === 'undefined') return;
367
+
368
+ // Remove existing overlay if any
369
+ const existing = document.getElementById('visualify-hmr-error');
370
+ if (existing) {
371
+ existing.remove();
372
+ }
373
+
374
+ const overlay = document.createElement('div');
375
+ overlay.id = 'visualify-hmr-error';
376
+ overlay.style.cssText = `
377
+ position: fixed;
378
+ top: 0;
379
+ left: 0;
380
+ right: 0;
381
+ bottom: 0;
382
+ background: rgba(0, 0, 0, 0.85);
383
+ z-index: 99999;
384
+ display: none;
385
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
386
+ padding: 20px;
387
+ overflow: auto;
388
+ `;
389
+
390
+ const content = document.createElement('div');
391
+ content.style.cssText = `
392
+ background: #1e1e1e;
393
+ border-radius: 8px;
394
+ max-width: 800px;
395
+ margin: 40px auto;
396
+ padding: 24px;
397
+ color: #fff;
398
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
399
+ `;
400
+
401
+ content.innerHTML = `
402
+ <div style="display: flex; align-items: center; margin-bottom: 16px;">
403
+ <span style="
404
+ background: #ff5555;
405
+ color: white;
406
+ padding: 4px 12px;
407
+ border-radius: 4px;
408
+ font-size: 12px;
409
+ font-weight: bold;
410
+ margin-right: 12px;
411
+ ">ERROR</span>
412
+ <h2 style="margin: 0; font-size: 18px;">Hot Module Replacement Failed</h2>
413
+ </div>
414
+ <div id="visualify-hmr-error-message" style="
415
+ background: #2d2d2d;
416
+ padding: 16px;
417
+ border-radius: 4px;
418
+ font-family: monospace;
419
+ font-size: 14px;
420
+ margin-bottom: 16px;
421
+ white-space: pre-wrap;
422
+ word-break: break-word;
423
+ "></div>
424
+ <div id="visualify-hmr-error-file" style="
425
+ color: #888;
426
+ font-size: 12px;
427
+ margin-bottom: 16px;
428
+ "></div>
429
+ <button onclick="window.__VISUALIFY_HMR_CLIENT__.clearErrorOverlay()" style="
430
+ background: #4a9eff;
431
+ color: white;
432
+ border: none;
433
+ padding: 8px 16px;
434
+ border-radius: 4px;
435
+ cursor: pointer;
436
+ font-size: 14px;
437
+ ">Dismiss</button>
438
+ `;
439
+
440
+ overlay.appendChild(content);
441
+ document.body.appendChild(overlay);
442
+
443
+ this.errorOverlay = overlay;
444
+ }
445
+
446
+ /**
447
+ * Show the error overlay
448
+ * @private
449
+ * @param {Object} error - The error details
450
+ * @returns {void}
451
+ */
452
+ showErrorOverlay(error) {
453
+ if (!this.errorOverlay) return;
454
+
455
+ const messageEl = document.getElementById('visualify-hmr-error-message');
456
+ const fileEl = document.getElementById('visualify-hmr-error-file');
457
+
458
+ if (messageEl) {
459
+ messageEl.textContent = error.message || 'Unknown error';
460
+ }
461
+
462
+ if (fileEl && error.file) {
463
+ fileEl.textContent = `File: ${error.file}`;
464
+ }
465
+
466
+ this.errorOverlay.style.display = 'block';
467
+ }
468
+
469
+ /**
470
+ * Clear the error overlay
471
+ * @returns {void}
472
+ */
473
+ clearErrorOverlay() {
474
+ if (this.errorOverlay) {
475
+ this.errorOverlay.style.display = 'none';
476
+ }
477
+ }
478
+
479
+ /**
480
+ * Register an update handler
481
+ * @param {string} configType - The config type to handle (e.g., 'main', 'component', '*')
482
+ * @param {Function} handler - The handler function
483
+ * @returns {Function} Unregister function
484
+ */
485
+ onUpdate(configType, handler) {
486
+ if (!this.updateHandlers.has(configType)) {
487
+ this.updateHandlers.set(configType, new Set());
488
+ }
489
+
490
+ this.updateHandlers.get(configType).add(handler);
491
+
492
+ // Return unregister function
493
+ return () => {
494
+ this.updateHandlers.get(configType)?.delete(handler);
495
+ };
496
+ }
497
+
498
+ /**
499
+ * Register an error handler
500
+ * @param {Function} handler - The error handler function
501
+ * @returns {Function} Unregister function
502
+ */
503
+ onError(handler) {
504
+ this.errorHandlers.add(handler);
505
+
506
+ return () => {
507
+ this.errorHandlers.delete(handler);
508
+ };
509
+ }
510
+
511
+ /**
512
+ * Register a state preserver function
513
+ * @param {string} configType - The config type
514
+ * @param {Function} preserver - Function that returns state to preserve
515
+ * @returns {Function} Unregister function
516
+ */
517
+ registerStatePreserver(configType, preserver) {
518
+ this.statePreservers.set(configType, preserver);
519
+
520
+ return () => {
521
+ this.statePreservers.delete(configType);
522
+ };
523
+ }
524
+
525
+ /**
526
+ * Log a message (if debug is enabled)
527
+ * @private
528
+ * @param {...any} args - Messages to log
529
+ * @returns {void}
530
+ */
531
+ log(...args) {
532
+ if (this.config.debug) {
533
+ console.log('[HMR]', ...args);
534
+ }
535
+ }
536
+
537
+ /**
538
+ * Log an error
539
+ * @private
540
+ * @param {...any} args - Error messages
541
+ * @returns {void}
542
+ */
543
+ error(...args) {
544
+ console.error('[HMR]', ...args);
545
+ }
546
+ }
547
+
548
+ // Global HMR client instance
549
+ let globalClient = null;
550
+
551
+ /**
552
+ * Initialize the global HMR client
553
+ * @param {Object} options - Configuration options
554
+ * @returns {HMRClient} The HMR client instance
555
+ */
556
+ export function initHMR(options = {}) {
557
+ if (typeof window === 'undefined') {
558
+ return null;
559
+ }
560
+ if (window.__VISUALIFY_HMR__?.enabled !== true) {
561
+ return null;
562
+ }
563
+
564
+ if (!globalClient) {
565
+ // Try to get URL from global config
566
+ const wsUrl = window.__VISUALIFY_HMR__?.wsUrl || DEFAULT_CONFIG.wsUrl;
567
+
568
+ globalClient = new HMRClient({
569
+ ...options,
570
+ wsUrl,
571
+ });
572
+
573
+ globalClient.init();
574
+
575
+ // Store globally for access
576
+ window.__VISUALIFY_HMR_CLIENT__ = globalClient;
577
+ }
578
+
579
+ return globalClient;
580
+ }
581
+
582
+ /**
583
+ * Get the global HMR client instance
584
+ * @returns {HMRClient|null} The HMR client instance
585
+ */
586
+ export function getHMRClient() {
587
+ return globalClient || window?.__VISUALIFY_HMR_CLIENT__ || null;
588
+ }
589
+
590
+ /**
591
+ * React hook for HMR integration
592
+ * @param {Object} options - Hook options
593
+ * @param {string} options.configType - The config type to listen for
594
+ * @param {Function} options.onUpdate - Callback when update is received
595
+ * @param {Function} options.onError - Callback when error is received
596
+ * @param {Function} options.preserveState - Function to preserve state
597
+ * @returns {Object} HMR status and utilities
598
+ */
599
+ export function useHMR(options = {}) {
600
+ const { configType, onUpdate, onError, preserveState } = options;
601
+ const clientRef = useRef(null);
602
+
603
+ useEffect(() => {
604
+ if (typeof window === 'undefined') return;
605
+ if (window.__VISUALIFY_HMR__?.enabled !== true) return;
606
+
607
+ // Initialize HMR client
608
+ const client = initHMR();
609
+ clientRef.current = client;
610
+
611
+ if (!client) return;
612
+
613
+ const unsubscribers = [];
614
+
615
+ // Register update handler
616
+ if (onUpdate && configType) {
617
+ const unsubscribe = client.onUpdate(configType, onUpdate);
618
+ unsubscribers.push(unsubscribe);
619
+ }
620
+
621
+ // Register error handler
622
+ if (onError) {
623
+ const unsubscribe = client.onError(onError);
624
+ unsubscribers.push(unsubscribe);
625
+ }
626
+
627
+ // Register state preserver
628
+ if (preserveState && configType) {
629
+ const unsubscribe = client.registerStatePreserver(configType, preserveState);
630
+ unsubscribers.push(unsubscribe);
631
+ }
632
+
633
+ // Cleanup
634
+ return () => {
635
+ unsubscribers.forEach((unsubscribe) => unsubscribe());
636
+ };
637
+ }, [configType, onUpdate, onError, preserveState]);
638
+
639
+ return {
640
+ isConnected: clientRef.current?.isConnected || false,
641
+ client: clientRef.current,
642
+ clearError: () => clientRef.current?.clearErrorOverlay(),
643
+ };
644
+ }
645
+
646
+ /**
647
+ * Preserve ECharts instance state
648
+ * @param {Object} chartInstance - The ECharts instance
649
+ * @returns {Object} Preserved state
650
+ */
651
+ export function preserveChartState(chartInstance) {
652
+ if (!chartInstance) return null;
653
+
654
+ try {
655
+ const option = chartInstance.getOption();
656
+
657
+ return {
658
+ // Preserve zoom state for dataZoom components
659
+ dataZoom: option.dataZoom?.map((dz) => ({
660
+ start: dz.start,
661
+ end: dz.end,
662
+ startValue: dz.startValue,
663
+ endValue: dz.endValue,
664
+ })),
665
+
666
+ // Preserve legend selection
667
+ legend: option.legend?.[0]?.selected,
668
+
669
+ // Preserve tooltip state
670
+ tooltip: option.tooltip?.[0],
671
+
672
+ // Preserve brush selection if any
673
+ brush: option.brush,
674
+
675
+ // Preserve current data view
676
+ series: option.series?.map((s) => ({
677
+ name: s.name,
678
+ data: s.data,
679
+ })),
680
+ };
681
+ } catch (err) {
682
+ console.error('[HMR] Failed to preserve chart state:', err);
683
+ return null;
684
+ }
685
+ }
686
+
687
+ /**
688
+ * Restore ECharts instance state
689
+ * @param {Object} chartInstance - The ECharts instance
690
+ * @param {Object} state - The state to restore
691
+ */
692
+ export function restoreChartState(chartInstance, state) {
693
+ if (!chartInstance || !state) return;
694
+
695
+ try {
696
+ // Restore dataZoom state
697
+ if (state.dataZoom) {
698
+ chartInstance.setOption({
699
+ dataZoom: state.dataZoom,
700
+ });
701
+ }
702
+
703
+ // Restore legend selection
704
+ if (state.legend) {
705
+ chartInstance.setOption({
706
+ legend: { selected: state.legend },
707
+ });
708
+ }
709
+
710
+ // Restore brush if any
711
+ if (state.brush) {
712
+ chartInstance.setOption({ brush: state.brush });
713
+ }
714
+ } catch (err) {
715
+ console.error('[HMR] Failed to restore chart state:', err);
716
+ }
717
+ }
718
+
719
+ // Auto-initialize if in browser and enabled
720
+ if (typeof window !== 'undefined' && window.__VISUALIFY_HMR__?.enabled) {
721
+ initHMR();
722
+ }
723
+
724
+ export default HMRClient;