visualifyjs 2.5.3 → 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 -193
  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 -0
  63. package/docs/docs/_404.md +51 -51
  64. package/docs/docs/_coverpage.md +11 -11
  65. package/docs/docs/_sidebar.md +54 -43
  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 -123
  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 -1
  81. package/docs/docs/more-pages.md +23 -23
  82. package/docs/docs/quickstart.md +148 -119
  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 -579
  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 -143
  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,459 @@
1
+ /**
2
+ * ThreeScene.js - React component for Three.js integration
3
+ * Uses @react-three/fiber for React integration
4
+ */
5
+
6
+ import React, {
7
+ useRef,
8
+ useMemo,
9
+ useEffect,
10
+ useState,
11
+ useCallback,
12
+ forwardRef,
13
+ useImperativeHandle,
14
+ } from 'react';
15
+ import { Canvas, useFrame, useThree } from '@react-three/fiber';
16
+ import { OrbitControls, TrackballControls, FlyControls } from '@react-three/drei';
17
+ import * as THREE from 'three';
18
+
19
+ /**
20
+ * Default camera configuration
21
+ */
22
+ const DEFAULT_CAMERA = {
23
+ position: [0, 0, 100],
24
+ fov: 75,
25
+ near: 0.1,
26
+ far: 1000,
27
+ };
28
+
29
+ /**
30
+ * Default lighting configuration
31
+ */
32
+ const DEFAULT_LIGHTS = [
33
+ { type: 'ambient', color: '#ffffff', intensity: 0.5 },
34
+ { type: 'directional', position: [10, 10, 10], intensity: 1 },
35
+ ];
36
+
37
+ /**
38
+ * Light component that creates different light types
39
+ */
40
+ function Light({ config }) {
41
+ const lightRef = useRef();
42
+
43
+ const light = useMemo(() => {
44
+ switch (config.type) {
45
+ case 'ambient':
46
+ return new THREE.AmbientLight(config.color, config.intensity)
47
+ case 'directional':
48
+ const dirLight = new THREE.DirectionalLight(config.color, config.intensity)
49
+ dirLight.position.set(...(config.position || [10, 10, 10]))
50
+ dirLight.castShadow = config.castShadow || false
51
+ return dirLight
52
+ case 'point':
53
+ const pointLight = new THREE.PointLight(
54
+ config.color,
55
+ config.intensity,
56
+ config.distance || 0,
57
+ config.decay || 2
58
+ )
59
+ pointLight.position.set(...(config.position || [0, 0, 0]))
60
+ pointLight.castShadow = config.castShadow || false
61
+ return pointLight
62
+ case 'spot':
63
+ const spotLight = new THREE.SpotLight(
64
+ config.color,
65
+ config.intensity,
66
+ config.distance || 0,
67
+ config.angle || Math.PI / 6,
68
+ config.penumbra || 0,
69
+ config.decay || 2
70
+ )
71
+ spotLight.position.set(...(config.position || [0, 10, 0]))
72
+ spotLight.castShadow = config.castShadow || false
73
+ return spotLight
74
+ case 'hemisphere':
75
+ const hemiLight = new THREE.HemisphereLight(
76
+ config.skyColor || '#ffffff',
77
+ config.groundColor || '#444444',
78
+ config.intensity || 1
79
+ )
80
+ hemiLight.position.set(...(config.position || [0, 10, 0]))
81
+ return hemiLight
82
+ default:
83
+ return new THREE.AmbientLight('#ffffff', 0.5)
84
+ }
85
+ }, [config])
86
+
87
+ useEffect(() => {
88
+ if (lightRef.current) {
89
+ // Update light properties if config changes
90
+ if (config.intensity !== undefined) {
91
+ lightRef.current.intensity = config.intensity
92
+ }
93
+ if (config.color !== undefined) {
94
+ lightRef.current.color.set(config.color)
95
+ }
96
+ }
97
+ }, [config.intensity, config.color])
98
+
99
+ return <primitive ref={lightRef} object={light} />
100
+ }
101
+
102
+ /**
103
+ * Mesh component that creates different geometry types
104
+ */
105
+ function MeshObject({ config }) {
106
+ const meshRef = useRef()
107
+
108
+ const geometry = useMemo(() => {
109
+ if (!config.geometry) return new THREE.BoxGeometry(1, 1, 1)
110
+
111
+ const geo = config.geometry
112
+ switch (geo.type) {
113
+ case 'box':
114
+ return new THREE.BoxGeometry(
115
+ geo.width || 1,
116
+ geo.height || 1,
117
+ geo.depth || 1
118
+ )
119
+ case 'sphere':
120
+ return new THREE.SphereGeometry(
121
+ geo.radius || 1,
122
+ geo.widthSegments || 32,
123
+ geo.heightSegments || 16
124
+ )
125
+ case 'cylinder':
126
+ return new THREE.CylinderGeometry(
127
+ geo.radiusTop || 1,
128
+ geo.radiusBottom || 1,
129
+ geo.height || 1,
130
+ geo.radialSegments || 32
131
+ )
132
+ case 'plane':
133
+ return new THREE.PlaneGeometry(
134
+ geo.width || 1,
135
+ geo.height || 1
136
+ )
137
+ case 'torus':
138
+ return new THREE.TorusGeometry(
139
+ geo.radius || 1,
140
+ geo.tube || 0.4,
141
+ geo.radialSegments || 16,
142
+ geo.tubularSegments || 100
143
+ )
144
+ case 'cone':
145
+ return new THREE.ConeGeometry(
146
+ geo.radius || 1,
147
+ geo.height || 1,
148
+ geo.radialSegments || 32
149
+ )
150
+ default:
151
+ return new THREE.BoxGeometry(1, 1, 1)
152
+ }
153
+ }, [config.geometry])
154
+
155
+ const material = useMemo(() => {
156
+ if (!config.material) return new THREE.MeshStandardMaterial({ color: '#888888' })
157
+
158
+ const mat = config.material
159
+ switch (mat.type) {
160
+ case 'basic':
161
+ return new THREE.MeshBasicMaterial({
162
+ color: mat.color || '#888888',
163
+ transparent: mat.transparent || false,
164
+ opacity: mat.opacity !== undefined ? mat.opacity : 1,
165
+ wireframe: mat.wireframe || false,
166
+ })
167
+ case 'standard':
168
+ return new THREE.MeshStandardMaterial({
169
+ color: mat.color || '#888888',
170
+ roughness: mat.roughness !== undefined ? mat.roughness : 0.5,
171
+ metalness: mat.metalness !== undefined ? mat.metalness : 0.5,
172
+ transparent: mat.transparent || false,
173
+ opacity: mat.opacity !== undefined ? mat.opacity : 1,
174
+ wireframe: mat.wireframe || false,
175
+ })
176
+ case 'phong':
177
+ return new THREE.MeshPhongMaterial({
178
+ color: mat.color || '#888888',
179
+ shininess: mat.shininess || 30,
180
+ transparent: mat.transparent || false,
181
+ opacity: mat.opacity !== undefined ? mat.opacity : 1,
182
+ })
183
+ case 'lambert':
184
+ return new THREE.MeshLambertMaterial({
185
+ color: mat.color || '#888888',
186
+ transparent: mat.transparent || false,
187
+ opacity: mat.opacity !== undefined ? mat.opacity : 1,
188
+ })
189
+ default:
190
+ return new THREE.MeshStandardMaterial({ color: mat.color || '#888888' })
191
+ }
192
+ }, [config.material])
193
+
194
+ // Handle animation
195
+ useFrame((state) => {
196
+ if (config.animation && meshRef.current) {
197
+ const { rotation, position } = config.animation
198
+ if (rotation) {
199
+ if (rotation[0]) meshRef.current.rotation.x += rotation[0]
200
+ if (rotation[1]) meshRef.current.rotation.y += rotation[1]
201
+ if (rotation[2]) meshRef.current.rotation.z += rotation[2]
202
+ }
203
+ if (position) {
204
+ if (position.speed) {
205
+ meshRef.current.position.y = Math.sin(state.clock.elapsedTime * position.speed) * (position.amplitude || 1)
206
+ }
207
+ }
208
+ }
209
+ })
210
+
211
+ // Cleanup
212
+ useEffect(() => {
213
+ return () => {
214
+ geometry.dispose()
215
+ material.dispose()
216
+ }
217
+ }, [geometry, material])
218
+
219
+ return (
220
+ <mesh
221
+ ref={meshRef}
222
+ geometry={geometry}
223
+ material={material}
224
+ position={config.position || [0, 0, 0]}
225
+ rotation={config.rotation || [0, 0, 0]}
226
+ scale={config.scale || [1, 1, 1]}
227
+ castShadow={config.castShadow !== false}
228
+ receiveShadow={config.receiveShadow !== false}
229
+ />
230
+ )
231
+ }
232
+
233
+ /**
234
+ * Controls component for camera manipulation
235
+ */
236
+ function Controls({ type, config = {} }) {
237
+ const { camera, gl } = useThree()
238
+
239
+ const controlsProps = useMemo(() => ({
240
+ enableDamping: config.enableDamping !== false,
241
+ dampingFactor: config.dampingFactor || 0.05,
242
+ enableZoom: config.enableZoom !== false,
243
+ enablePan: config.enablePan !== false,
244
+ enableRotate: config.enableRotate !== false,
245
+ autoRotate: config.autoRotate || false,
246
+ autoRotateSpeed: config.autoRotateSpeed || 1,
247
+ minDistance: config.minDistance || 0,
248
+ maxDistance: config.maxDistance || Infinity,
249
+ ...config,
250
+ }), [config])
251
+
252
+ switch (type) {
253
+ case 'orbit':
254
+ return <OrbitControls {...controlsProps} args={[camera, gl.domElement]} />
255
+ case 'trackball':
256
+ return <TrackballControls {...controlsProps} args={[camera, gl.domElement]} />
257
+ case 'fly':
258
+ return <FlyControls {...controlsProps} args={[camera, gl.domElement]} />
259
+ default:
260
+ return <OrbitControls {...controlsProps} args={[camera, gl.domElement]} />
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Animation handler for auto-rotation
266
+ */
267
+ function AnimationHandler({ config }) {
268
+ useFrame((state, delta) => {
269
+ if (config?.autoRotate) {
270
+ // Auto-rotation is handled by controls, but we can add custom animations here
271
+ }
272
+ })
273
+ return null
274
+ }
275
+
276
+ /**
277
+ * Scene content component
278
+ */
279
+ function SceneContent({ sceneConfig, controls, animation }) {
280
+ const { scene } = useThree()
281
+
282
+ // Set scene background
283
+ useEffect(() => {
284
+ if (sceneConfig?.backgroundColor) {
285
+ scene.background = new THREE.Color(sceneConfig.backgroundColor)
286
+ }
287
+ if (sceneConfig?.fog) {
288
+ scene.fog = new THREE.Fog(
289
+ sceneConfig.fog.color || 0x000000,
290
+ sceneConfig.fog.near || 1,
291
+ sceneConfig.fog.far || 1000
292
+ )
293
+ }
294
+ }, [scene, sceneConfig])
295
+
296
+ // Memoize lights to prevent unnecessary re-renders
297
+ const lights = useMemo(() => {
298
+ return (sceneConfig?.lights || DEFAULT_LIGHTS).map((light, index) => (
299
+ <Light key={`light-${index}`} config={light} />
300
+ ))
301
+ }, [sceneConfig?.lights])
302
+
303
+ // Memoize objects to prevent unnecessary re-renders
304
+ const objects = useMemo(() => {
305
+ if (!sceneConfig?.objects) return null
306
+ return sceneConfig.objects.map((obj, index) => {
307
+ if (obj.type === 'mesh') {
308
+ return <MeshObject key={`mesh-${index}`} config={obj} />
309
+ }
310
+ return null
311
+ })
312
+ }, [sceneConfig?.objects])
313
+
314
+ return (
315
+ <>
316
+ {lights}
317
+ {objects}
318
+ <Controls type={controls} config={animation} />
319
+ <AnimationHandler config={animation} />
320
+ </>
321
+ )
322
+ }
323
+
324
+ /**
325
+ * Error boundary for WebGL failures
326
+ */
327
+ class ErrorBoundary extends React.Component {
328
+ constructor(props) {
329
+ super(props)
330
+ this.state = { hasError: false, error: null }
331
+ }
332
+
333
+ static getDerivedStateFromError(error) {
334
+ return { hasError: true, error }
335
+ }
336
+
337
+ componentDidCatch(error, errorInfo) {
338
+ console.error('ThreeScene error:', error, errorInfo)
339
+ }
340
+
341
+ render() {
342
+ if (this.state.hasError) {
343
+ return (
344
+ <div style={{ padding: '20px', color: 'red', textAlign: 'center' }}>
345
+ <h3>WebGL Error</h3>
346
+ <p>Failed to initialize 3D scene. Please check your browser supports WebGL.</p>
347
+ <p style={{ fontSize: '12px', color: '#666' }}>{this.state.error?.message}</p>
348
+ </div>
349
+ )
350
+ }
351
+ return this.props.children
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Main ThreeScene component
357
+ */
358
+ const ThreeScene = forwardRef(({ props, style }, ref) => {
359
+ const { config } = props || {}
360
+ const [isLoading, setIsLoading] = useState(true)
361
+ const [error, setError] = useState(null)
362
+ const canvasRef = useRef()
363
+
364
+ // Extract configuration
365
+ const sceneConfig = config?.scene || {}
366
+ const cameraConfig = sceneConfig?.camera || DEFAULT_CAMERA
367
+ const controls = config?.controls || 'orbit'
368
+ const animation = config?.animation || {}
369
+
370
+ // Memoize camera settings
371
+ const cameraSettings = useMemo(() => ({
372
+ position: cameraConfig.position || DEFAULT_CAMERA.position,
373
+ fov: cameraConfig.fov || DEFAULT_CAMERA.fov,
374
+ near: cameraConfig.near || DEFAULT_CAMERA.near,
375
+ far: cameraConfig.far || DEFAULT_CAMERA.far,
376
+ }), [cameraConfig])
377
+
378
+ // Memoize canvas props
379
+ const canvasProps = useMemo(() => ({
380
+ gl: {
381
+ antialias: true,
382
+ alpha: true,
383
+ powerPreference: 'high-performance',
384
+ },
385
+ camera: cameraSettings,
386
+ onCreated: () => setIsLoading(false),
387
+ onError: (err) => {
388
+ console.error('Canvas error:', err)
389
+ setError(err)
390
+ setIsLoading(false)
391
+ },
392
+ }), [cameraSettings])
393
+
394
+ // Expose imperative handle
395
+ useImperativeHandle(ref, () => ({
396
+ getCanvas: () => canvasRef.current,
397
+ getScene: () => canvasRef.current?.scene,
398
+ getCamera: () => canvasRef.current?.camera,
399
+ getRenderer: () => canvasRef.current?.gl,
400
+ screenshot: (mimeType = 'image/png') => {
401
+ const renderer = canvasRef.current?.gl
402
+ if (renderer) {
403
+ return renderer.domElement.toDataURL(mimeType)
404
+ }
405
+ return null
406
+ },
407
+ }), [])
408
+
409
+ if (error) {
410
+ return (
411
+ <div style={{ ...style, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
412
+ <div style={{ color: 'red', textAlign: 'center' }}>
413
+ <p>Failed to load 3D scene</p>
414
+ <p style={{ fontSize: '12px' }}>{error.message}</p>
415
+ </div>
416
+ </div>
417
+ )
418
+ }
419
+
420
+ return (
421
+ <div id={props?.id} style={{ ...style, position: 'relative' }}>
422
+ {isLoading && (
423
+ <div
424
+ style={{
425
+ position: 'absolute',
426
+ top: 0,
427
+ left: 0,
428
+ right: 0,
429
+ bottom: 0,
430
+ display: 'flex',
431
+ alignItems: 'center',
432
+ justifyContent: 'center',
433
+ background: '#f5f5f5',
434
+ zIndex: 1,
435
+ }}
436
+ >
437
+ Loading 3D Scene...
438
+ </div>
439
+ )}
440
+ <ErrorBoundary>
441
+ <Canvas
442
+ ref={canvasRef}
443
+ {...canvasProps}
444
+ style={{ width: '100%', height: '100%' }}
445
+ >
446
+ <SceneContent
447
+ sceneConfig={sceneConfig}
448
+ controls={controls}
449
+ animation={animation}
450
+ />
451
+ </Canvas>
452
+ </ErrorBoundary>
453
+ </div>
454
+ )
455
+ })
456
+
457
+ ThreeScene.displayName = 'ThreeScene'
458
+
459
+ export default ThreeScene