matterviz 0.3.6 → 0.4.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 (926) hide show
  1. package/dist/EmptyState.svelte.d.ts +9 -0
  2. package/dist/FilePicker.svelte +360 -0
  3. package/dist/FilePicker.svelte.d.ts +17 -0
  4. package/dist/Icon.svelte +44 -0
  5. package/dist/Icon.svelte.d.ts +13 -0
  6. package/dist/MillerIndexInput.svelte +66 -0
  7. package/dist/MillerIndexInput.svelte.d.ts +7 -0
  8. package/dist/api/mp.d.ts +6 -0
  9. package/dist/api/mp.js +22 -0
  10. package/dist/api/optimade.d.ts +45 -0
  11. package/dist/api/optimade.js +141 -0
  12. package/dist/app.css +244 -0
  13. package/dist/brillouin/BrillouinZone.svelte +554 -0
  14. package/dist/brillouin/BrillouinZone.svelte.d.ts +84 -0
  15. package/dist/brillouin/BrillouinZoneControls.svelte +144 -0
  16. package/dist/brillouin/BrillouinZoneControls.svelte.d.ts +17 -0
  17. package/dist/brillouin/BrillouinZoneExportPane.svelte +146 -0
  18. package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +15 -0
  19. package/dist/brillouin/BrillouinZoneInfoPane.svelte +146 -0
  20. package/dist/brillouin/BrillouinZoneInfoPane.svelte.d.ts +13 -0
  21. package/dist/brillouin/BrillouinZoneScene.svelte +522 -0
  22. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +49 -0
  23. package/dist/brillouin/BrillouinZoneTooltip.svelte +83 -0
  24. package/dist/brillouin/BrillouinZoneTooltip.svelte.d.ts +8 -0
  25. package/dist/brillouin/compute.d.ts +17 -0
  26. package/dist/brillouin/compute.js +422 -0
  27. package/dist/brillouin/index.d.ts +8 -0
  28. package/dist/brillouin/index.js +7 -0
  29. package/dist/brillouin/types.d.ts +43 -0
  30. package/dist/brillouin/types.js +1 -0
  31. package/dist/chempot-diagram/ChemPotDiagram.svelte +328 -0
  32. package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
  33. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +843 -0
  34. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
  35. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +3191 -0
  36. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
  37. package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +7 -0
  38. package/dist/chempot-diagram/async-compute.svelte.d.ts +3 -0
  39. package/dist/chempot-diagram/async-compute.svelte.js +80 -0
  40. package/dist/chempot-diagram/chempot-worker.d.ts +1 -0
  41. package/dist/chempot-diagram/chempot-worker.js +12 -0
  42. package/dist/chempot-diagram/color.d.ts +10 -0
  43. package/dist/chempot-diagram/color.js +32 -0
  44. package/dist/chempot-diagram/compute.d.ts +48 -0
  45. package/dist/chempot-diagram/compute.js +804 -0
  46. package/dist/chempot-diagram/index.d.ts +6 -0
  47. package/dist/chempot-diagram/index.js +6 -0
  48. package/dist/chempot-diagram/pointer.d.ts +16 -0
  49. package/dist/chempot-diagram/pointer.js +40 -0
  50. package/dist/chempot-diagram/temperature.d.ts +15 -0
  51. package/dist/chempot-diagram/temperature.js +34 -0
  52. package/dist/chempot-diagram/types.d.ts +81 -0
  53. package/dist/chempot-diagram/types.js +28 -0
  54. package/dist/colors/index.d.ts +47 -0
  55. package/dist/colors/index.js +204 -0
  56. package/dist/composition/BarChart.svelte +297 -0
  57. package/dist/composition/BarChart.svelte.d.ts +39 -0
  58. package/dist/composition/BubbleChart.svelte +218 -0
  59. package/dist/composition/BubbleChart.svelte.d.ts +28 -0
  60. package/dist/composition/Composition.svelte +165 -0
  61. package/dist/composition/Composition.svelte.d.ts +15 -0
  62. package/dist/composition/Formula.svelte +268 -0
  63. package/dist/composition/Formula.svelte.d.ts +19 -0
  64. package/dist/composition/FormulaFilter.svelte +1263 -0
  65. package/dist/composition/FormulaFilter.svelte.d.ts +51 -0
  66. package/dist/composition/PieChart.svelte +324 -0
  67. package/dist/composition/PieChart.svelte.d.ts +37 -0
  68. package/dist/composition/chem-sys.d.ts +8 -0
  69. package/dist/composition/chem-sys.js +85 -0
  70. package/dist/composition/format.d.ts +15 -0
  71. package/dist/composition/format.js +111 -0
  72. package/dist/composition/index.d.ts +21 -0
  73. package/dist/composition/index.js +15 -0
  74. package/dist/composition/parse.d.ts +56 -0
  75. package/dist/composition/parse.js +486 -0
  76. package/dist/constants.d.ts +29 -0
  77. package/dist/constants.js +99 -0
  78. package/dist/controls.d.ts +14 -0
  79. package/dist/controls.js +30 -0
  80. package/dist/convex-hull/ConvexHull.svelte +157 -0
  81. package/dist/convex-hull/ConvexHull.svelte.d.ts +13 -0
  82. package/dist/convex-hull/ConvexHull2D.svelte +827 -0
  83. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +11 -0
  84. package/dist/convex-hull/ConvexHull3D.svelte +1801 -0
  85. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +8 -0
  86. package/dist/convex-hull/ConvexHull4D.svelte +1394 -0
  87. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +8 -0
  88. package/dist/convex-hull/ConvexHullControls.svelte +535 -0
  89. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +48 -0
  90. package/dist/convex-hull/ConvexHullInfoPane.svelte +125 -0
  91. package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +20 -0
  92. package/dist/convex-hull/ConvexHullStats.svelte +929 -0
  93. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +17 -0
  94. package/dist/convex-hull/ConvexHullTooltip.svelte +131 -0
  95. package/dist/convex-hull/ConvexHullTooltip.svelte.d.ts +33 -0
  96. package/dist/convex-hull/GasPressureControls.svelte +247 -0
  97. package/dist/convex-hull/GasPressureControls.svelte.d.ts +11 -0
  98. package/dist/convex-hull/StructurePopup.svelte +151 -0
  99. package/dist/convex-hull/StructurePopup.svelte.d.ts +18 -0
  100. package/dist/convex-hull/TemperatureSlider.svelte +140 -0
  101. package/dist/convex-hull/TemperatureSlider.svelte.d.ts +8 -0
  102. package/dist/convex-hull/barycentric-coords.d.ts +18 -0
  103. package/dist/convex-hull/barycentric-coords.js +182 -0
  104. package/dist/convex-hull/demo-temperature.d.ts +6 -0
  105. package/dist/convex-hull/demo-temperature.js +38 -0
  106. package/dist/convex-hull/gas-thermodynamics.d.ts +16 -0
  107. package/dist/convex-hull/gas-thermodynamics.js +306 -0
  108. package/dist/convex-hull/helpers.d.ts +117 -0
  109. package/dist/convex-hull/helpers.js +718 -0
  110. package/dist/convex-hull/index.d.ts +119 -0
  111. package/dist/convex-hull/index.js +58 -0
  112. package/dist/convex-hull/thermodynamics.d.ts +67 -0
  113. package/dist/convex-hull/thermodynamics.js +1757 -0
  114. package/dist/convex-hull/types.d.ts +162 -0
  115. package/dist/convex-hull/types.js +36 -0
  116. package/dist/coordination/CoordinationBarPlot.svelte +311 -0
  117. package/dist/coordination/CoordinationBarPlot.svelte.d.ts +30 -0
  118. package/dist/coordination/calc-coordination.d.ts +15 -0
  119. package/dist/coordination/calc-coordination.js +63 -0
  120. package/dist/coordination/index.d.ts +8 -0
  121. package/dist/coordination/index.js +7 -0
  122. package/dist/effects.svelte.d.ts +12 -0
  123. package/dist/effects.svelte.js +37 -0
  124. package/dist/element/BohrAtom.svelte.d.ts +20 -0
  125. package/dist/element/ElementHeading.svelte +26 -0
  126. package/dist/element/ElementHeading.svelte.d.ts +8 -0
  127. package/dist/element/ElementPhoto.svelte +57 -0
  128. package/dist/element/ElementPhoto.svelte.d.ts +9 -0
  129. package/dist/element/ElementStats.svelte +80 -0
  130. package/dist/element/ElementStats.svelte.d.ts +8 -0
  131. package/dist/element/ElementTile.svelte +484 -0
  132. package/dist/element/ElementTile.svelte.d.ts +29 -0
  133. package/dist/element/Nucleus.svelte.d.ts +17 -0
  134. package/dist/element/data.d.ts +2 -0
  135. package/dist/element/data.js +2 -0
  136. package/dist/element/index.d.ts +8 -0
  137. package/dist/element/index.js +7 -0
  138. package/dist/element/types.d.ts +57 -0
  139. package/dist/element/types.js +1 -0
  140. package/dist/feedback/ClickFeedback.svelte +58 -0
  141. package/dist/feedback/ClickFeedback.svelte.d.ts +12 -0
  142. package/dist/feedback/DragOverlay.svelte +42 -0
  143. package/dist/feedback/DragOverlay.svelte.d.ts +7 -0
  144. package/dist/feedback/Spinner.svelte.d.ts +7 -0
  145. package/dist/feedback/StatusMessage.svelte.d.ts +9 -0
  146. package/dist/feedback/index.d.ts +4 -0
  147. package/dist/feedback/index.js +4 -0
  148. package/dist/fermi-surface/FermiSlice.svelte +197 -0
  149. package/dist/fermi-surface/FermiSlice.svelte.d.ts +24 -0
  150. package/dist/fermi-surface/FermiSurface.svelte +606 -0
  151. package/dist/fermi-surface/FermiSurface.svelte.d.ts +83 -0
  152. package/dist/fermi-surface/FermiSurfaceControls.svelte +448 -0
  153. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +35 -0
  154. package/dist/fermi-surface/FermiSurfaceScene.svelte +797 -0
  155. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +50 -0
  156. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +85 -0
  157. package/dist/fermi-surface/FermiSurfaceTooltip.svelte.d.ts +8 -0
  158. package/dist/fermi-surface/compute.d.ts +5 -0
  159. package/dist/fermi-surface/compute.js +538 -0
  160. package/dist/fermi-surface/constants.d.ts +9 -0
  161. package/dist/fermi-surface/constants.js +27 -0
  162. package/dist/fermi-surface/export.d.ts +5 -0
  163. package/dist/fermi-surface/export.js +51 -0
  164. package/dist/fermi-surface/index.d.ts +12 -0
  165. package/dist/fermi-surface/index.js +13 -0
  166. package/dist/fermi-surface/marching-cubes.d.ts +2 -0
  167. package/dist/fermi-surface/marching-cubes.js +2 -0
  168. package/dist/fermi-surface/parse.d.ts +2 -0
  169. package/dist/fermi-surface/parse.js +494 -0
  170. package/dist/fermi-surface/symmetry.d.ts +3 -0
  171. package/dist/fermi-surface/symmetry.js +46 -0
  172. package/dist/fermi-surface/types.d.ts +111 -0
  173. package/dist/fermi-surface/types.js +4 -0
  174. package/dist/heatmap-matrix/HeatmapMatrix.svelte +1547 -0
  175. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +110 -0
  176. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +225 -0
  177. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +30 -0
  178. package/dist/heatmap-matrix/index.d.ts +53 -0
  179. package/dist/heatmap-matrix/index.js +100 -0
  180. package/dist/heatmap-matrix/shared.d.ts +2 -0
  181. package/dist/heatmap-matrix/shared.js +4 -0
  182. package/dist/icons.d.ts +569 -0
  183. package/dist/icons.js +648 -0
  184. package/dist/index.d.ts +39 -0
  185. package/dist/index.js +39 -0
  186. package/dist/io/decompress.d.ts +11 -0
  187. package/dist/io/decompress.js +76 -0
  188. package/dist/io/export.d.ts +16 -0
  189. package/dist/io/export.js +338 -0
  190. package/dist/io/fetch.d.ts +5 -0
  191. package/dist/io/fetch.js +43 -0
  192. package/dist/io/file-drop.d.ts +7 -0
  193. package/dist/io/file-drop.js +42 -0
  194. package/dist/io/index.d.ts +7 -0
  195. package/dist/io/index.js +6 -0
  196. package/dist/io/is-binary.d.ts +1 -0
  197. package/dist/io/is-binary.js +20 -0
  198. package/dist/io/types.d.ts +8 -0
  199. package/dist/io/types.js +1 -0
  200. package/dist/io/url-drop.d.ts +2 -0
  201. package/dist/io/url-drop.js +154 -0
  202. package/dist/isosurface/Isosurface.svelte +285 -0
  203. package/dist/isosurface/Isosurface.svelte.d.ts +8 -0
  204. package/dist/isosurface/IsosurfaceControls.svelte +277 -0
  205. package/dist/isosurface/IsosurfaceControls.svelte.d.ts +9 -0
  206. package/dist/isosurface/index.d.ts +5 -0
  207. package/dist/isosurface/index.js +6 -0
  208. package/dist/isosurface/parse.d.ts +6 -0
  209. package/dist/isosurface/parse.js +552 -0
  210. package/dist/isosurface/slice.d.ts +11 -0
  211. package/dist/isosurface/slice.js +141 -0
  212. package/dist/isosurface/types.d.ts +56 -0
  213. package/dist/isosurface/types.js +227 -0
  214. package/dist/keyboard.d.ts +3 -0
  215. package/dist/keyboard.js +23 -0
  216. package/dist/labels.d.ts +53 -0
  217. package/dist/labels.js +278 -0
  218. package/dist/layout/FullscreenToggle.svelte +50 -0
  219. package/dist/layout/FullscreenToggle.svelte.d.ts +7 -0
  220. package/dist/layout/InfoCard.svelte +120 -0
  221. package/dist/layout/InfoCard.svelte.d.ts +21 -0
  222. package/dist/layout/InfoTag.svelte +185 -0
  223. package/dist/layout/InfoTag.svelte.d.ts +19 -0
  224. package/dist/layout/PropertyFilter.svelte +247 -0
  225. package/dist/layout/PropertyFilter.svelte.d.ts +24 -0
  226. package/dist/layout/SettingsSection.svelte +148 -0
  227. package/dist/layout/SettingsSection.svelte.d.ts +17 -0
  228. package/dist/layout/SubpageGrid.svelte +82 -0
  229. package/dist/layout/SubpageGrid.svelte.d.ts +14 -0
  230. package/dist/layout/fullscreen.d.ts +9 -0
  231. package/dist/layout/fullscreen.js +53 -0
  232. package/dist/layout/index.d.ts +10 -0
  233. package/dist/layout/index.js +8 -0
  234. package/dist/layout/json-tree/JsonNode.svelte +548 -0
  235. package/dist/layout/json-tree/JsonNode.svelte.d.ts +11 -0
  236. package/dist/layout/json-tree/JsonTree.svelte +1230 -0
  237. package/dist/layout/json-tree/JsonTree.svelte.d.ts +6 -0
  238. package/dist/layout/json-tree/JsonValue.svelte.d.ts +9 -0
  239. package/dist/layout/json-tree/index.d.ts +3 -0
  240. package/dist/layout/json-tree/index.js +3 -0
  241. package/dist/layout/json-tree/types.d.ts +74 -0
  242. package/dist/layout/json-tree/types.js +2 -0
  243. package/dist/layout/json-tree/utils.d.ts +29 -0
  244. package/dist/layout/json-tree/utils.js +642 -0
  245. package/dist/marching-cubes.d.ts +14 -0
  246. package/dist/marching-cubes.js +535 -0
  247. package/dist/math.d.ts +105 -0
  248. package/dist/math.js +920 -0
  249. package/dist/overlays/ContextMenu.svelte +162 -0
  250. package/dist/overlays/ContextMenu.svelte.d.ts +25 -0
  251. package/dist/overlays/CopyButton.svelte +45 -0
  252. package/dist/overlays/CopyButton.svelte.d.ts +8 -0
  253. package/dist/overlays/DragControlTab.svelte +98 -0
  254. package/dist/overlays/DragControlTab.svelte.d.ts +8 -0
  255. package/dist/overlays/DraggablePane.svelte +487 -0
  256. package/dist/overlays/DraggablePane.svelte.d.ts +36 -0
  257. package/dist/overlays/InfoPaneCards.svelte +149 -0
  258. package/dist/overlays/InfoPaneCards.svelte.d.ts +22 -0
  259. package/dist/overlays/index.d.ts +3 -0
  260. package/dist/overlays/index.js +3 -0
  261. package/dist/periodic-table/PeriodicTable.svelte +480 -0
  262. package/dist/periodic-table/PeriodicTable.svelte.d.ts +55 -0
  263. package/dist/periodic-table/PeriodicTableControls.svelte +557 -0
  264. package/dist/periodic-table/PeriodicTableControls.svelte.d.ts +24 -0
  265. package/dist/periodic-table/PropertySelect.svelte +38 -0
  266. package/dist/periodic-table/PropertySelect.svelte.d.ts +13 -0
  267. package/dist/periodic-table/TableInset.svelte.d.ts +9 -0
  268. package/dist/periodic-table/index.d.ts +10 -0
  269. package/dist/periodic-table/index.js +4 -0
  270. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +1092 -0
  271. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +44 -0
  272. package/dist/phase-diagram/PhaseDiagramControls.svelte +444 -0
  273. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +30 -0
  274. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +127 -0
  275. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
  276. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +184 -0
  277. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +19 -0
  278. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +391 -0
  279. package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +16 -0
  280. package/dist/phase-diagram/TdbInfoPanel.svelte +203 -0
  281. package/dist/phase-diagram/TdbInfoPanel.svelte.d.ts +12 -0
  282. package/dist/phase-diagram/build-diagram.d.ts +11 -0
  283. package/dist/phase-diagram/build-diagram.js +160 -0
  284. package/dist/phase-diagram/colors.d.ts +35 -0
  285. package/dist/phase-diagram/colors.js +51 -0
  286. package/dist/phase-diagram/diagram-input.d.ts +29 -0
  287. package/dist/phase-diagram/diagram-input.js +3 -0
  288. package/dist/phase-diagram/index.d.ts +13 -0
  289. package/dist/phase-diagram/index.js +11 -0
  290. package/dist/phase-diagram/parse.d.ts +55 -0
  291. package/dist/phase-diagram/parse.js +273 -0
  292. package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
  293. package/dist/phase-diagram/svg-to-diagram.js +867 -0
  294. package/dist/phase-diagram/types.d.ts +93 -0
  295. package/dist/phase-diagram/types.js +1 -0
  296. package/dist/phase-diagram/utils.d.ts +118 -0
  297. package/dist/phase-diagram/utils.js +600 -0
  298. package/dist/plot/bar/BarPlot.svelte +1755 -0
  299. package/dist/plot/bar/BarPlot.svelte.d.ts +84 -0
  300. package/dist/plot/bar/BarPlotControls.svelte +67 -0
  301. package/dist/plot/bar/BarPlotControls.svelte.d.ts +18 -0
  302. package/dist/plot/bar/SpacegroupBarPlot.svelte +293 -0
  303. package/dist/plot/bar/SpacegroupBarPlot.svelte.d.ts +9 -0
  304. package/dist/plot/bar/data.d.ts +40 -0
  305. package/dist/plot/bar/data.js +154 -0
  306. package/dist/plot/bar/geometry.d.ts +39 -0
  307. package/dist/plot/bar/geometry.js +60 -0
  308. package/dist/plot/bar/index.d.ts +3 -0
  309. package/dist/plot/bar/index.js +3 -0
  310. package/dist/plot/box/BoxPlot.svelte +1462 -0
  311. package/dist/plot/box/BoxPlot.svelte.d.ts +94 -0
  312. package/dist/plot/box/BoxPlotControls.svelte +109 -0
  313. package/dist/plot/box/BoxPlotControls.svelte.d.ts +19 -0
  314. package/dist/plot/box/Violin.svelte +14 -0
  315. package/dist/plot/box/Violin.svelte.d.ts +70 -0
  316. package/dist/plot/box/box-plot.d.ts +55 -0
  317. package/dist/plot/box/box-plot.js +126 -0
  318. package/dist/plot/box/index.d.ts +5 -0
  319. package/dist/plot/box/index.js +5 -0
  320. package/dist/plot/box/kde.d.ts +16 -0
  321. package/dist/plot/box/kde.js +160 -0
  322. package/dist/plot/box/quantile.d.ts +3 -0
  323. package/dist/plot/box/quantile.js +53 -0
  324. package/dist/plot/core/auto-place.d.ts +43 -0
  325. package/dist/plot/core/auto-place.js +122 -0
  326. package/dist/plot/core/axis-utils.d.ts +46 -0
  327. package/dist/plot/core/axis-utils.js +110 -0
  328. package/dist/plot/core/components/AxisLabel.svelte +51 -0
  329. package/dist/plot/core/components/AxisLabel.svelte.d.ts +16 -0
  330. package/dist/plot/core/components/ColorBar.svelte +724 -0
  331. package/dist/plot/core/components/ColorBar.svelte.d.ts +31 -0
  332. package/dist/plot/core/components/ColorScaleSelect.svelte +55 -0
  333. package/dist/plot/core/components/ColorScaleSelect.svelte.d.ts +15 -0
  334. package/dist/plot/core/components/ControlPane.svelte +46 -0
  335. package/dist/plot/core/components/ControlPane.svelte.d.ts +13 -0
  336. package/dist/plot/core/components/FillArea.svelte +234 -0
  337. package/dist/plot/core/components/FillArea.svelte.d.ts +21 -0
  338. package/dist/plot/core/components/InteractiveAxisLabel.svelte +96 -0
  339. package/dist/plot/core/components/InteractiveAxisLabel.svelte.d.ts +14 -0
  340. package/dist/plot/core/components/Line.svelte +101 -0
  341. package/dist/plot/core/components/Line.svelte.d.ts +15 -0
  342. package/dist/plot/core/components/PlotAxis.svelte +171 -0
  343. package/dist/plot/core/components/PlotAxis.svelte.d.ts +25 -0
  344. package/dist/plot/core/components/PlotControls.svelte +525 -0
  345. package/dist/plot/core/components/PlotControls.svelte.d.ts +4 -0
  346. package/dist/plot/core/components/PlotLegend.svelte +580 -0
  347. package/dist/plot/core/components/PlotLegend.svelte.d.ts +30 -0
  348. package/dist/plot/core/components/PlotTooltip.svelte +83 -0
  349. package/dist/plot/core/components/PlotTooltip.svelte.d.ts +25 -0
  350. package/dist/plot/core/components/PortalSelect.svelte +257 -0
  351. package/dist/plot/core/components/PortalSelect.svelte.d.ts +16 -0
  352. package/dist/plot/core/components/ReferenceLine.svelte +204 -0
  353. package/dist/plot/core/components/ReferenceLine.svelte.d.ts +20 -0
  354. package/dist/plot/core/components/ReferenceLine3D.svelte +156 -0
  355. package/dist/plot/core/components/ReferenceLine3D.svelte.d.ts +14 -0
  356. package/dist/plot/core/components/ReferencePlane.svelte +175 -0
  357. package/dist/plot/core/components/ReferencePlane.svelte.d.ts +14 -0
  358. package/dist/plot/core/components/ZeroLines.svelte +97 -0
  359. package/dist/plot/core/components/ZeroLines.svelte.d.ts +33 -0
  360. package/dist/plot/core/components/ZoomRect.svelte +23 -0
  361. package/dist/plot/core/components/ZoomRect.svelte.d.ts +8 -0
  362. package/dist/plot/core/components/index.d.ts +17 -0
  363. package/dist/plot/core/components/index.js +17 -0
  364. package/dist/plot/core/data-cleaning.d.ts +107 -0
  365. package/dist/plot/core/data-cleaning.js +853 -0
  366. package/dist/plot/core/data-transform.d.ts +16 -0
  367. package/dist/plot/core/data-transform.js +45 -0
  368. package/dist/plot/core/fill-utils.d.ts +33 -0
  369. package/dist/plot/core/fill-utils.js +388 -0
  370. package/dist/plot/core/hover-lock.svelte.d.ts +14 -0
  371. package/dist/plot/core/hover-lock.svelte.js +45 -0
  372. package/dist/plot/core/index.d.ts +10 -0
  373. package/dist/plot/core/index.js +11 -0
  374. package/dist/plot/core/interactions.d.ts +35 -0
  375. package/dist/plot/core/interactions.js +195 -0
  376. package/dist/plot/core/layout.d.ts +79 -0
  377. package/dist/plot/core/layout.js +281 -0
  378. package/dist/plot/core/reference-line.d.ts +60 -0
  379. package/dist/plot/core/reference-line.js +301 -0
  380. package/dist/plot/core/scales.d.ts +48 -0
  381. package/dist/plot/core/scales.js +480 -0
  382. package/dist/plot/core/svg.d.ts +2 -0
  383. package/dist/plot/core/svg.js +41 -0
  384. package/dist/plot/core/types.d.ts +771 -0
  385. package/dist/plot/core/types.js +99 -0
  386. package/dist/plot/core/utils/label-placement.d.ts +68 -0
  387. package/dist/plot/core/utils/label-placement.js +326 -0
  388. package/dist/plot/core/utils/series-visibility.d.ts +26 -0
  389. package/dist/plot/core/utils/series-visibility.js +112 -0
  390. package/dist/plot/core/utils.d.ts +11 -0
  391. package/dist/plot/core/utils.js +27 -0
  392. package/dist/plot/histogram/Histogram.svelte +1418 -0
  393. package/dist/plot/histogram/Histogram.svelte.d.ts +50 -0
  394. package/dist/plot/histogram/HistogramControls.svelte +212 -0
  395. package/dist/plot/histogram/HistogramControls.svelte.d.ts +22 -0
  396. package/dist/plot/histogram/index.d.ts +2 -0
  397. package/dist/plot/histogram/index.js +2 -0
  398. package/dist/plot/index.d.ts +8 -0
  399. package/dist/plot/index.js +10 -0
  400. package/dist/plot/sankey/Sankey.svelte +700 -0
  401. package/dist/plot/sankey/Sankey.svelte.d.ts +74 -0
  402. package/dist/plot/sankey/SankeyControls.svelte +98 -0
  403. package/dist/plot/sankey/SankeyControls.svelte.d.ts +19 -0
  404. package/dist/plot/sankey/index.d.ts +4 -0
  405. package/dist/plot/sankey/index.js +3 -0
  406. package/dist/plot/sankey/sankey-types.d.ts +42 -0
  407. package/dist/plot/sankey/sankey-types.js +4 -0
  408. package/dist/plot/sankey/sankey.d.ts +52 -0
  409. package/dist/plot/sankey/sankey.js +187 -0
  410. package/dist/plot/scatter/BinnedScatterPlot.svelte +1116 -0
  411. package/dist/plot/scatter/BinnedScatterPlot.svelte.d.ts +66 -0
  412. package/dist/plot/scatter/ElementScatter.svelte +63 -0
  413. package/dist/plot/scatter/ElementScatter.svelte.d.ts +14 -0
  414. package/dist/plot/scatter/ScatterPlot.svelte +2357 -0
  415. package/dist/plot/scatter/ScatterPlot.svelte.d.ts +96 -0
  416. package/dist/plot/scatter/ScatterPlotControls.svelte +307 -0
  417. package/dist/plot/scatter/ScatterPlotControls.svelte.d.ts +17 -0
  418. package/dist/plot/scatter/ScatterPoint.svelte +182 -0
  419. package/dist/plot/scatter/ScatterPoint.svelte.d.ts +22 -0
  420. package/dist/plot/scatter/adaptive-density.d.ts +79 -0
  421. package/dist/plot/scatter/adaptive-density.js +217 -0
  422. package/dist/plot/scatter/binned-scatter-types.d.ts +59 -0
  423. package/dist/plot/scatter/binned-scatter-types.js +1 -0
  424. package/dist/plot/scatter/index.d.ts +7 -0
  425. package/dist/plot/scatter/index.js +5 -0
  426. package/dist/plot/scatter/scatter-data.d.ts +19 -0
  427. package/dist/plot/scatter/scatter-data.js +212 -0
  428. package/dist/plot/scatter-3d/ScatterPlot3D.svelte +531 -0
  429. package/dist/plot/scatter-3d/ScatterPlot3D.svelte.d.ts +95 -0
  430. package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte +438 -0
  431. package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte.d.ts +20 -0
  432. package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte +912 -0
  433. package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte.d.ts +74 -0
  434. package/dist/plot/scatter-3d/Surface3D.svelte +197 -0
  435. package/dist/plot/scatter-3d/Surface3D.svelte.d.ts +13 -0
  436. package/dist/plot/scatter-3d/index.d.ts +4 -0
  437. package/dist/plot/scatter-3d/index.js +4 -0
  438. package/dist/plot/sunburst/Sunburst.svelte +1045 -0
  439. package/dist/plot/sunburst/Sunburst.svelte.d.ts +96 -0
  440. package/dist/plot/sunburst/SunburstControls.svelte +200 -0
  441. package/dist/plot/sunburst/SunburstControls.svelte.d.ts +26 -0
  442. package/dist/plot/sunburst/index.d.ts +4 -0
  443. package/dist/plot/sunburst/index.js +4 -0
  444. package/dist/plot/sunburst/render.d.ts +34 -0
  445. package/dist/plot/sunburst/render.js +122 -0
  446. package/dist/plot/sunburst/sunburst.d.ts +62 -0
  447. package/dist/plot/sunburst/sunburst.js +266 -0
  448. package/dist/rdf/RdfPlot.svelte +248 -0
  449. package/dist/rdf/RdfPlot.svelte.d.ts +27 -0
  450. package/dist/rdf/calc-rdf.d.ts +4 -0
  451. package/dist/rdf/calc-rdf.js +98 -0
  452. package/dist/rdf/index.d.ts +23 -0
  453. package/dist/rdf/index.js +2 -0
  454. package/dist/sanitize.d.ts +6 -0
  455. package/dist/sanitize.js +116 -0
  456. package/dist/settings.d.ts +319 -0
  457. package/dist/settings.js +1394 -0
  458. package/dist/spectral/Bands.svelte +1050 -0
  459. package/dist/spectral/Bands.svelte.d.ts +39 -0
  460. package/dist/spectral/BandsAndDos.svelte +134 -0
  461. package/dist/spectral/BandsAndDos.svelte.d.ts +18 -0
  462. package/dist/spectral/BrillouinBandsDos.svelte +264 -0
  463. package/dist/spectral/BrillouinBandsDos.svelte.d.ts +20 -0
  464. package/dist/spectral/Dos.svelte +688 -0
  465. package/dist/spectral/Dos.svelte.d.ts +29 -0
  466. package/dist/spectral/helpers.d.ts +121 -0
  467. package/dist/spectral/helpers.js +1098 -0
  468. package/dist/spectral/index.d.ts +6 -0
  469. package/dist/spectral/index.js +6 -0
  470. package/dist/spectral/types.d.ts +84 -0
  471. package/dist/spectral/types.js +2 -0
  472. package/dist/state.svelte.d.ts +25 -0
  473. package/dist/state.svelte.js +45 -0
  474. package/dist/structure/Arrow.svelte +72 -0
  475. package/dist/structure/Arrow.svelte.d.ts +15 -0
  476. package/dist/structure/AtomLegend.svelte +814 -0
  477. package/dist/structure/AtomLegend.svelte.d.ts +35 -0
  478. package/dist/structure/Bond.svelte +140 -0
  479. package/dist/structure/Bond.svelte.d.ts +9 -0
  480. package/dist/structure/CanvasTooltip.svelte +33 -0
  481. package/dist/structure/CanvasTooltip.svelte.d.ts +12 -0
  482. package/dist/structure/CellSelect.svelte +348 -0
  483. package/dist/structure/CellSelect.svelte.d.ts +13 -0
  484. package/dist/structure/Cylinder.svelte +49 -0
  485. package/dist/structure/Cylinder.svelte.d.ts +13 -0
  486. package/dist/structure/Lattice.svelte +196 -0
  487. package/dist/structure/Lattice.svelte.d.ts +17 -0
  488. package/dist/structure/Structure.svelte +2254 -0
  489. package/dist/structure/Structure.svelte.d.ts +89 -0
  490. package/dist/structure/StructureControls.svelte +1273 -0
  491. package/dist/structure/StructureControls.svelte.d.ts +31 -0
  492. package/dist/structure/StructureExportPane.svelte +252 -0
  493. package/dist/structure/StructureExportPane.svelte.d.ts +17 -0
  494. package/dist/structure/StructureInfoPane.svelte +736 -0
  495. package/dist/structure/StructureInfoPane.svelte.d.ts +19 -0
  496. package/dist/structure/StructureScene.svelte +2256 -0
  497. package/dist/structure/StructureScene.svelte.d.ts +111 -0
  498. package/dist/structure/atom-properties.d.ts +37 -0
  499. package/dist/structure/atom-properties.js +200 -0
  500. package/dist/structure/bond-order-perception.d.ts +13 -0
  501. package/dist/structure/bond-order-perception.js +384 -0
  502. package/dist/structure/bonding.d.ts +69 -0
  503. package/dist/structure/bonding.js +724 -0
  504. package/dist/structure/export.d.ts +20 -0
  505. package/dist/structure/export.js +731 -0
  506. package/dist/structure/index.d.ts +124 -0
  507. package/dist/structure/index.js +167 -0
  508. package/dist/structure/label-placement.d.ts +14 -0
  509. package/dist/structure/label-placement.js +72 -0
  510. package/dist/structure/measure.d.ts +7 -0
  511. package/dist/structure/measure.js +30 -0
  512. package/dist/structure/parse.d.ts +66 -0
  513. package/dist/structure/parse.js +1410 -0
  514. package/dist/structure/partial-occupancy.d.ts +25 -0
  515. package/dist/structure/partial-occupancy.js +99 -0
  516. package/dist/structure/pbc.d.ts +9 -0
  517. package/dist/structure/pbc.js +127 -0
  518. package/dist/structure/supercell.d.ts +8 -0
  519. package/dist/structure/supercell.js +170 -0
  520. package/dist/structure/validation.d.ts +2 -0
  521. package/dist/structure/validation.js +10 -0
  522. package/dist/symmetry/SymmetryStats.svelte +226 -0
  523. package/dist/symmetry/SymmetryStats.svelte.d.ts +21 -0
  524. package/dist/symmetry/WyckoffTable.svelte +120 -0
  525. package/dist/symmetry/WyckoffTable.svelte.d.ts +11 -0
  526. package/dist/symmetry/cell-transform.d.ts +12 -0
  527. package/dist/symmetry/cell-transform.js +91 -0
  528. package/dist/symmetry/index.d.ts +43 -0
  529. package/dist/symmetry/index.js +226 -0
  530. package/dist/symmetry/spacegroups.d.ts +16 -0
  531. package/dist/symmetry/spacegroups.js +429 -0
  532. package/dist/table/HeatmapTable.svelte +1885 -0
  533. package/dist/table/HeatmapTable.svelte.d.ts +49 -0
  534. package/dist/table/ToggleMenu.svelte +385 -0
  535. package/dist/table/ToggleMenu.svelte.d.ts +11 -0
  536. package/dist/table/index.d.ts +72 -0
  537. package/dist/table/index.js +38 -0
  538. package/dist/theme/ThemeControl.svelte +53 -0
  539. package/dist/theme/ThemeControl.svelte.d.ts +9 -0
  540. package/dist/theme/index.d.ts +29 -0
  541. package/dist/theme/index.js +79 -0
  542. package/dist/time.d.ts +4 -0
  543. package/dist/time.js +70 -0
  544. package/dist/tooltip/KCoords.svelte +45 -0
  545. package/dist/tooltip/KCoords.svelte.d.ts +8 -0
  546. package/dist/tooltip/TooltipContent.svelte +58 -0
  547. package/dist/tooltip/TooltipContent.svelte.d.ts +31 -0
  548. package/dist/tooltip/index.d.ts +3 -0
  549. package/dist/tooltip/index.js +2 -0
  550. package/dist/tooltip/types.d.ts +8 -0
  551. package/dist/tooltip/types.js +1 -0
  552. package/dist/trajectory/Trajectory.svelte +1571 -0
  553. package/dist/trajectory/Trajectory.svelte.d.ts +78 -0
  554. package/dist/trajectory/TrajectoryError.svelte +128 -0
  555. package/dist/trajectory/TrajectoryError.svelte.d.ts +13 -0
  556. package/dist/trajectory/TrajectoryExportPane.svelte +358 -0
  557. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +17 -0
  558. package/dist/trajectory/TrajectoryInfoPane.svelte +314 -0
  559. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +17 -0
  560. package/dist/trajectory/constants.d.ts +6 -0
  561. package/dist/trajectory/constants.js +7 -0
  562. package/dist/trajectory/extract.d.ts +5 -0
  563. package/dist/trajectory/extract.js +162 -0
  564. package/dist/trajectory/format-detect.d.ts +10 -0
  565. package/dist/trajectory/format-detect.js +90 -0
  566. package/dist/trajectory/frame-reader.d.ts +17 -0
  567. package/dist/trajectory/frame-reader.js +299 -0
  568. package/dist/trajectory/helpers.d.ts +15 -0
  569. package/dist/trajectory/helpers.js +164 -0
  570. package/dist/trajectory/index.d.ts +63 -0
  571. package/dist/trajectory/index.js +126 -0
  572. package/dist/trajectory/parse/ase.d.ts +2 -0
  573. package/dist/trajectory/parse/ase.js +73 -0
  574. package/dist/trajectory/parse/hdf5.d.ts +2 -0
  575. package/dist/trajectory/parse/hdf5.js +127 -0
  576. package/dist/trajectory/parse/index.d.ts +12 -0
  577. package/dist/trajectory/parse/index.js +306 -0
  578. package/dist/trajectory/parse/lammps.d.ts +5 -0
  579. package/dist/trajectory/parse/lammps.js +179 -0
  580. package/dist/trajectory/parse/vasp.d.ts +2 -0
  581. package/dist/trajectory/parse/vasp.js +87 -0
  582. package/dist/trajectory/parse/xyz.d.ts +26 -0
  583. package/dist/trajectory/parse/xyz.js +123 -0
  584. package/dist/trajectory/plotting.d.ts +28 -0
  585. package/dist/trajectory/plotting.js +423 -0
  586. package/dist/trajectory/types.d.ts +11 -0
  587. package/dist/trajectory/types.js +1 -0
  588. package/dist/utils.d.ts +7 -0
  589. package/dist/utils.js +47 -0
  590. package/dist/xrd/XrdPlot.svelte +616 -0
  591. package/dist/xrd/XrdPlot.svelte.d.ts +28 -0
  592. package/dist/xrd/broadening.d.ts +20 -0
  593. package/dist/xrd/broadening.js +97 -0
  594. package/dist/xrd/calc-xrd.d.ts +37 -0
  595. package/dist/xrd/calc-xrd.js +339 -0
  596. package/dist/xrd/index.d.ts +37 -0
  597. package/dist/xrd/index.js +4 -0
  598. package/dist/xrd/parse.d.ts +13 -0
  599. package/dist/xrd/parse.js +749 -0
  600. package/license +1 -1
  601. package/package.json +237 -1458
  602. package/readme.md +98 -171
  603. package/.vscode/launch.json +0 -13
  604. package/.vscodeignore +0 -7
  605. package/dist/assets/STLExporter-BpTH3YHE.js +0 -8
  606. package/dist/assets/browser-DdDecX_W.js +0 -1
  607. package/dist/assets/export-qgn-H9y6.js +0 -2
  608. package/dist/assets/main-DiKYzti2.css +0 -1
  609. package/dist/assets/moyo_wasm_bg-0ocwg7xY.wasm +0 -0
  610. package/dist/extension.js +0 -31293
  611. package/dist/src/lib/FilePicker.svelte +0 -360
  612. package/dist/src/lib/Icon.svelte +0 -41
  613. package/dist/src/lib/MillerIndexInput.svelte +0 -66
  614. package/dist/src/lib/api/mp.ts +0 -26
  615. package/dist/src/lib/api/optimade.ts +0 -204
  616. package/dist/src/lib/app.css +0 -247
  617. package/dist/src/lib/brillouin/BrillouinZone.svelte +0 -549
  618. package/dist/src/lib/brillouin/BrillouinZoneControls.svelte +0 -144
  619. package/dist/src/lib/brillouin/BrillouinZoneExportPane.svelte +0 -146
  620. package/dist/src/lib/brillouin/BrillouinZoneInfoPane.svelte +0 -146
  621. package/dist/src/lib/brillouin/BrillouinZoneScene.svelte +0 -476
  622. package/dist/src/lib/brillouin/BrillouinZoneTooltip.svelte +0 -92
  623. package/dist/src/lib/brillouin/compute.ts +0 -529
  624. package/dist/src/lib/brillouin/index.ts +0 -8
  625. package/dist/src/lib/brillouin/types.ts +0 -51
  626. package/dist/src/lib/chempot-diagram/ChemPotDiagram.svelte +0 -327
  627. package/dist/src/lib/chempot-diagram/ChemPotDiagram2D.svelte +0 -846
  628. package/dist/src/lib/chempot-diagram/ChemPotDiagram3D.svelte +0 -3193
  629. package/dist/src/lib/chempot-diagram/async-compute.svelte.ts +0 -94
  630. package/dist/src/lib/chempot-diagram/chempot-worker.ts +0 -11
  631. package/dist/src/lib/chempot-diagram/color.ts +0 -42
  632. package/dist/src/lib/chempot-diagram/compute.ts +0 -1014
  633. package/dist/src/lib/chempot-diagram/index.ts +0 -6
  634. package/dist/src/lib/chempot-diagram/pointer.ts +0 -56
  635. package/dist/src/lib/chempot-diagram/temperature.ts +0 -77
  636. package/dist/src/lib/chempot-diagram/types.ts +0 -130
  637. package/dist/src/lib/colors/index.ts +0 -249
  638. package/dist/src/lib/composition/BarChart.svelte +0 -297
  639. package/dist/src/lib/composition/BubbleChart.svelte +0 -218
  640. package/dist/src/lib/composition/Composition.svelte +0 -165
  641. package/dist/src/lib/composition/Formula.svelte +0 -268
  642. package/dist/src/lib/composition/FormulaFilter.svelte +0 -1257
  643. package/dist/src/lib/composition/PieChart.svelte +0 -323
  644. package/dist/src/lib/composition/format.ts +0 -155
  645. package/dist/src/lib/composition/index.ts +0 -37
  646. package/dist/src/lib/composition/parse.ts +0 -605
  647. package/dist/src/lib/constants.ts +0 -134
  648. package/dist/src/lib/controls.ts +0 -42
  649. package/dist/src/lib/convex-hull/ConvexHull.svelte +0 -157
  650. package/dist/src/lib/convex-hull/ConvexHull2D.svelte +0 -825
  651. package/dist/src/lib/convex-hull/ConvexHull3D.svelte +0 -1801
  652. package/dist/src/lib/convex-hull/ConvexHull4D.svelte +0 -1398
  653. package/dist/src/lib/convex-hull/ConvexHullControls.svelte +0 -535
  654. package/dist/src/lib/convex-hull/ConvexHullInfoPane.svelte +0 -125
  655. package/dist/src/lib/convex-hull/ConvexHullStats.svelte +0 -929
  656. package/dist/src/lib/convex-hull/ConvexHullTooltip.svelte +0 -131
  657. package/dist/src/lib/convex-hull/GasPressureControls.svelte +0 -247
  658. package/dist/src/lib/convex-hull/StructurePopup.svelte +0 -151
  659. package/dist/src/lib/convex-hull/TemperatureSlider.svelte +0 -140
  660. package/dist/src/lib/convex-hull/barycentric-coords.ts +0 -246
  661. package/dist/src/lib/convex-hull/demo-temperature.ts +0 -63
  662. package/dist/src/lib/convex-hull/gas-thermodynamics.ts +0 -405
  663. package/dist/src/lib/convex-hull/helpers.ts +0 -932
  664. package/dist/src/lib/convex-hull/index.ts +0 -202
  665. package/dist/src/lib/convex-hull/thermodynamics.ts +0 -2192
  666. package/dist/src/lib/convex-hull/types.ts +0 -267
  667. package/dist/src/lib/coordination/CoordinationBarPlot.svelte +0 -311
  668. package/dist/src/lib/coordination/calc-coordination.ts +0 -93
  669. package/dist/src/lib/coordination/index.ts +0 -9
  670. package/dist/src/lib/effects.svelte.ts +0 -48
  671. package/dist/src/lib/element/ElementHeading.svelte +0 -26
  672. package/dist/src/lib/element/ElementPhoto.svelte +0 -57
  673. package/dist/src/lib/element/ElementStats.svelte +0 -80
  674. package/dist/src/lib/element/ElementTile.svelte +0 -484
  675. package/dist/src/lib/element/data.ts +0 -14
  676. package/dist/src/lib/element/index.ts +0 -8
  677. package/dist/src/lib/element/types.ts +0 -62
  678. package/dist/src/lib/feedback/ClickFeedback.svelte +0 -58
  679. package/dist/src/lib/feedback/DragOverlay.svelte +0 -42
  680. package/dist/src/lib/feedback/index.ts +0 -4
  681. package/dist/src/lib/fermi-surface/FermiSlice.svelte +0 -189
  682. package/dist/src/lib/fermi-surface/FermiSurface.svelte +0 -600
  683. package/dist/src/lib/fermi-surface/FermiSurfaceControls.svelte +0 -448
  684. package/dist/src/lib/fermi-surface/FermiSurfaceScene.svelte +0 -794
  685. package/dist/src/lib/fermi-surface/FermiSurfaceTooltip.svelte +0 -111
  686. package/dist/src/lib/fermi-surface/compute.ts +0 -728
  687. package/dist/src/lib/fermi-surface/constants.ts +0 -32
  688. package/dist/src/lib/fermi-surface/export.ts +0 -64
  689. package/dist/src/lib/fermi-surface/index.ts +0 -14
  690. package/dist/src/lib/fermi-surface/marching-cubes.ts +0 -3
  691. package/dist/src/lib/fermi-surface/parse.ts +0 -574
  692. package/dist/src/lib/fermi-surface/symmetry.ts +0 -56
  693. package/dist/src/lib/fermi-surface/types.ts +0 -159
  694. package/dist/src/lib/heatmap-matrix/HeatmapMatrix.svelte +0 -1545
  695. package/dist/src/lib/heatmap-matrix/HeatmapMatrixControls.svelte +0 -225
  696. package/dist/src/lib/heatmap-matrix/index.ts +0 -167
  697. package/dist/src/lib/heatmap-matrix/shared.ts +0 -7
  698. package/dist/src/lib/icons.ts +0 -650
  699. package/dist/src/lib/index.ts +0 -61
  700. package/dist/src/lib/io/decompress.ts +0 -92
  701. package/dist/src/lib/io/export.ts +0 -385
  702. package/dist/src/lib/io/fetch.ts +0 -46
  703. package/dist/src/lib/io/file-drop.ts +0 -51
  704. package/dist/src/lib/io/index.ts +0 -7
  705. package/dist/src/lib/io/is-binary.ts +0 -24
  706. package/dist/src/lib/io/types.ts +0 -8
  707. package/dist/src/lib/io/url-drop.ts +0 -141
  708. package/dist/src/lib/isosurface/Isosurface.svelte +0 -285
  709. package/dist/src/lib/isosurface/IsosurfaceControls.svelte +0 -277
  710. package/dist/src/lib/isosurface/index.ts +0 -7
  711. package/dist/src/lib/isosurface/parse.ts +0 -656
  712. package/dist/src/lib/isosurface/slice.ts +0 -175
  713. package/dist/src/lib/isosurface/types.ts +0 -309
  714. package/dist/src/lib/labels.ts +0 -320
  715. package/dist/src/lib/layout/FullscreenToggle.svelte +0 -50
  716. package/dist/src/lib/layout/InfoCard.svelte +0 -120
  717. package/dist/src/lib/layout/InfoTag.svelte +0 -185
  718. package/dist/src/lib/layout/PropertyFilter.svelte +0 -246
  719. package/dist/src/lib/layout/SettingsSection.svelte +0 -148
  720. package/dist/src/lib/layout/SubpageGrid.svelte +0 -82
  721. package/dist/src/lib/layout/fullscreen.ts +0 -65
  722. package/dist/src/lib/layout/index.ts +0 -11
  723. package/dist/src/lib/layout/json-tree/JsonNode.svelte +0 -548
  724. package/dist/src/lib/layout/json-tree/JsonTree.svelte +0 -1230
  725. package/dist/src/lib/layout/json-tree/index.ts +0 -3
  726. package/dist/src/lib/layout/json-tree/types.ts +0 -126
  727. package/dist/src/lib/layout/json-tree/utils.ts +0 -682
  728. package/dist/src/lib/marching-cubes.ts +0 -614
  729. package/dist/src/lib/math.ts +0 -1081
  730. package/dist/src/lib/overlays/ContextMenu.svelte +0 -162
  731. package/dist/src/lib/overlays/CopyButton.svelte +0 -45
  732. package/dist/src/lib/overlays/DragControlTab.svelte +0 -98
  733. package/dist/src/lib/overlays/DraggablePane.svelte +0 -487
  734. package/dist/src/lib/overlays/InfoPaneCards.svelte +0 -149
  735. package/dist/src/lib/overlays/index.ts +0 -3
  736. package/dist/src/lib/periodic-table/PeriodicTable.svelte +0 -469
  737. package/dist/src/lib/periodic-table/PeriodicTableControls.svelte +0 -557
  738. package/dist/src/lib/periodic-table/PropertySelect.svelte +0 -37
  739. package/dist/src/lib/periodic-table/index.ts +0 -12
  740. package/dist/src/lib/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +0 -1086
  741. package/dist/src/lib/phase-diagram/PhaseDiagramControls.svelte +0 -444
  742. package/dist/src/lib/phase-diagram/PhaseDiagramEditorPane.svelte +0 -126
  743. package/dist/src/lib/phase-diagram/PhaseDiagramExportPane.svelte +0 -184
  744. package/dist/src/lib/phase-diagram/PhaseDiagramTooltip.svelte +0 -391
  745. package/dist/src/lib/phase-diagram/TdbInfoPanel.svelte +0 -203
  746. package/dist/src/lib/phase-diagram/build-diagram.ts +0 -186
  747. package/dist/src/lib/phase-diagram/colors.ts +0 -58
  748. package/dist/src/lib/phase-diagram/diagram-input.ts +0 -40
  749. package/dist/src/lib/phase-diagram/index.ts +0 -13
  750. package/dist/src/lib/phase-diagram/parse.ts +0 -348
  751. package/dist/src/lib/phase-diagram/svg-to-diagram.ts +0 -1023
  752. package/dist/src/lib/phase-diagram/types.ts +0 -144
  753. package/dist/src/lib/phase-diagram/utils.ts +0 -775
  754. package/dist/src/lib/plot/AxisLabel.svelte +0 -51
  755. package/dist/src/lib/plot/BarPlot.svelte +0 -2113
  756. package/dist/src/lib/plot/BarPlotControls.svelte +0 -66
  757. package/dist/src/lib/plot/BinnedScatterPlot.svelte +0 -1114
  758. package/dist/src/lib/plot/ColorBar.svelte +0 -721
  759. package/dist/src/lib/plot/ColorScaleSelect.svelte +0 -54
  760. package/dist/src/lib/plot/ElementScatter.svelte +0 -63
  761. package/dist/src/lib/plot/FillArea.svelte +0 -223
  762. package/dist/src/lib/plot/Histogram.svelte +0 -1558
  763. package/dist/src/lib/plot/HistogramControls.svelte +0 -212
  764. package/dist/src/lib/plot/InteractiveAxisLabel.svelte +0 -96
  765. package/dist/src/lib/plot/Line.svelte +0 -84
  766. package/dist/src/lib/plot/PlotAxis.svelte +0 -169
  767. package/dist/src/lib/plot/PlotControls.svelte +0 -537
  768. package/dist/src/lib/plot/PlotLegend.svelte +0 -569
  769. package/dist/src/lib/plot/PlotTooltip.svelte +0 -67
  770. package/dist/src/lib/plot/PortalSelect.svelte +0 -253
  771. package/dist/src/lib/plot/ReferenceLine.svelte +0 -204
  772. package/dist/src/lib/plot/ReferenceLine3D.svelte +0 -156
  773. package/dist/src/lib/plot/ReferencePlane.svelte +0 -175
  774. package/dist/src/lib/plot/ScatterPlot.svelte +0 -2778
  775. package/dist/src/lib/plot/ScatterPlot3D.svelte +0 -529
  776. package/dist/src/lib/plot/ScatterPlot3DControls.svelte +0 -437
  777. package/dist/src/lib/plot/ScatterPlot3DScene.svelte +0 -912
  778. package/dist/src/lib/plot/ScatterPlotControls.svelte +0 -306
  779. package/dist/src/lib/plot/ScatterPoint.svelte +0 -182
  780. package/dist/src/lib/plot/SpacegroupBarPlot.svelte +0 -293
  781. package/dist/src/lib/plot/Surface3D.svelte +0 -197
  782. package/dist/src/lib/plot/ZeroLines.svelte +0 -97
  783. package/dist/src/lib/plot/ZoomRect.svelte +0 -23
  784. package/dist/src/lib/plot/adaptive-density.ts +0 -316
  785. package/dist/src/lib/plot/auto-place.ts +0 -184
  786. package/dist/src/lib/plot/axis-utils.ts +0 -122
  787. package/dist/src/lib/plot/binned-scatter-types.ts +0 -83
  788. package/dist/src/lib/plot/data-cleaning.ts +0 -1069
  789. package/dist/src/lib/plot/data-transform.ts +0 -69
  790. package/dist/src/lib/plot/defaults.ts +0 -9
  791. package/dist/src/lib/plot/fill-utils.ts +0 -494
  792. package/dist/src/lib/plot/hover-lock.svelte.ts +0 -60
  793. package/dist/src/lib/plot/index.ts +0 -53
  794. package/dist/src/lib/plot/interactions.ts +0 -119
  795. package/dist/src/lib/plot/layout.ts +0 -425
  796. package/dist/src/lib/plot/reference-line.ts +0 -426
  797. package/dist/src/lib/plot/scales.ts +0 -654
  798. package/dist/src/lib/plot/svg.ts +0 -23
  799. package/dist/src/lib/plot/types.ts +0 -1144
  800. package/dist/src/lib/plot/utils/label-placement.ts +0 -541
  801. package/dist/src/lib/plot/utils/series-visibility.ts +0 -140
  802. package/dist/src/lib/plot/utils.ts +0 -11
  803. package/dist/src/lib/rdf/RdfPlot.svelte +0 -247
  804. package/dist/src/lib/rdf/calc-rdf.ts +0 -167
  805. package/dist/src/lib/rdf/index.ts +0 -27
  806. package/dist/src/lib/sanitize.ts +0 -126
  807. package/dist/src/lib/settings.ts +0 -1479
  808. package/dist/src/lib/spectral/Bands.svelte +0 -1040
  809. package/dist/src/lib/spectral/BandsAndDos.svelte +0 -134
  810. package/dist/src/lib/spectral/BrillouinBandsDos.svelte +0 -252
  811. package/dist/src/lib/spectral/Dos.svelte +0 -697
  812. package/dist/src/lib/spectral/helpers.ts +0 -1381
  813. package/dist/src/lib/spectral/index.ts +0 -8
  814. package/dist/src/lib/spectral/types.ts +0 -112
  815. package/dist/src/lib/state.svelte.ts +0 -64
  816. package/dist/src/lib/structure/Arrow.svelte +0 -72
  817. package/dist/src/lib/structure/AtomLegend.svelte +0 -815
  818. package/dist/src/lib/structure/Bond.svelte +0 -140
  819. package/dist/src/lib/structure/CanvasTooltip.svelte +0 -33
  820. package/dist/src/lib/structure/CellSelect.svelte +0 -349
  821. package/dist/src/lib/structure/Cylinder.svelte +0 -45
  822. package/dist/src/lib/structure/Lattice.svelte +0 -196
  823. package/dist/src/lib/structure/Structure.svelte +0 -2248
  824. package/dist/src/lib/structure/StructureControls.svelte +0 -1273
  825. package/dist/src/lib/structure/StructureExportPane.svelte +0 -252
  826. package/dist/src/lib/structure/StructureInfoPane.svelte +0 -737
  827. package/dist/src/lib/structure/StructureScene.svelte +0 -2255
  828. package/dist/src/lib/structure/atom-properties.ts +0 -316
  829. package/dist/src/lib/structure/bond-order-perception.ts +0 -447
  830. package/dist/src/lib/structure/bonding.ts +0 -944
  831. package/dist/src/lib/structure/export.ts +0 -861
  832. package/dist/src/lib/structure/index.ts +0 -291
  833. package/dist/src/lib/structure/label-placement.ts +0 -130
  834. package/dist/src/lib/structure/measure.ts +0 -45
  835. package/dist/src/lib/structure/parse.ts +0 -1705
  836. package/dist/src/lib/structure/partial-occupancy.ts +0 -183
  837. package/dist/src/lib/structure/pbc.ts +0 -164
  838. package/dist/src/lib/structure/supercell.ts +0 -226
  839. package/dist/src/lib/structure/validation.ts +0 -11
  840. package/dist/src/lib/symmetry/SymmetryStats.svelte +0 -226
  841. package/dist/src/lib/symmetry/WyckoffTable.svelte +0 -120
  842. package/dist/src/lib/symmetry/cell-transform.ts +0 -118
  843. package/dist/src/lib/symmetry/index.ts +0 -348
  844. package/dist/src/lib/symmetry/spacegroups.ts +0 -404
  845. package/dist/src/lib/table/HeatmapTable.svelte +0 -1833
  846. package/dist/src/lib/table/ToggleMenu.svelte +0 -385
  847. package/dist/src/lib/table/index.ts +0 -139
  848. package/dist/src/lib/theme/ThemeControl.svelte +0 -53
  849. package/dist/src/lib/theme/index.ts +0 -107
  850. package/dist/src/lib/time.ts +0 -71
  851. package/dist/src/lib/tooltip/TooltipContent.svelte +0 -58
  852. package/dist/src/lib/tooltip/index.ts +0 -2
  853. package/dist/src/lib/tooltip/types.ts +0 -13
  854. package/dist/src/lib/trajectory/Trajectory.svelte +0 -1545
  855. package/dist/src/lib/trajectory/TrajectoryError.svelte +0 -128
  856. package/dist/src/lib/trajectory/TrajectoryExportPane.svelte +0 -357
  857. package/dist/src/lib/trajectory/TrajectoryInfoPane.svelte +0 -313
  858. package/dist/src/lib/trajectory/constants.ts +0 -7
  859. package/dist/src/lib/trajectory/extract.ts +0 -196
  860. package/dist/src/lib/trajectory/format-detect.ts +0 -96
  861. package/dist/src/lib/trajectory/frame-reader.ts +0 -456
  862. package/dist/src/lib/trajectory/helpers.ts +0 -217
  863. package/dist/src/lib/trajectory/index.ts +0 -218
  864. package/dist/src/lib/trajectory/parse/ase.ts +0 -109
  865. package/dist/src/lib/trajectory/parse/hdf5.ts +0 -173
  866. package/dist/src/lib/trajectory/parse/index.ts +0 -411
  867. package/dist/src/lib/trajectory/parse/lammps.ts +0 -215
  868. package/dist/src/lib/trajectory/parse/vasp.ts +0 -102
  869. package/dist/src/lib/trajectory/parse/xyz.ts +0 -143
  870. package/dist/src/lib/trajectory/plotting.ts +0 -599
  871. package/dist/src/lib/trajectory/types.ts +0 -13
  872. package/dist/src/lib/utils.ts +0 -56
  873. package/dist/src/lib/xrd/XrdPlot.svelte +0 -615
  874. package/dist/src/lib/xrd/broadening.ts +0 -130
  875. package/dist/src/lib/xrd/calc-xrd.ts +0 -397
  876. package/dist/src/lib/xrd/index.ts +0 -38
  877. package/dist/src/lib/xrd/parse.ts +0 -858
  878. package/dist/webview.js +0 -29421
  879. package/icon.png +0 -0
  880. package/matterviz-0.3.2.vsix +0 -0
  881. package/matterviz-0.3.4.vsix +0 -0
  882. package/matterviz-0.3.5.vsix +0 -0
  883. package/scripts/sync-config.ts +0 -101
  884. package/src/declarations.d.ts +0 -2
  885. package/src/extension.ts +0 -972
  886. package/src/node-io.ts +0 -65
  887. package/src/types.ts +0 -17
  888. package/src/webview/JsonBrowser.svelte +0 -1079
  889. package/src/webview/PlotPanel.svelte +0 -346
  890. package/src/webview/detect.ts +0 -444
  891. package/src/webview/main.ts +0 -764
  892. package/src/webview/plot-utils.ts +0 -250
  893. package/test-fixtures/all-viz-types.json.gz +0 -0
  894. package/test-fixtures/plot-demo-data.json.gz +0 -0
  895. package/tests/detect.test.ts +0 -604
  896. package/tests/extension.test.ts +0 -2041
  897. package/tests/node-io.test.ts +0 -39
  898. package/tests/plot-utils.test.ts +0 -302
  899. package/tests/vite-plugin-json-gz.test.ts +0 -114
  900. package/tests/vscode-mock.ts +0 -18
  901. package/tests/webview.test.ts +0 -231
  902. package/tsconfig.json +0 -20
  903. package/vite-plugin-json-gz.ts +0 -29
  904. package/vite.config.ts +0 -34
  905. package/vite.extension.config.ts +0 -34
  906. /package/dist/{src/lib/EmptyState.svelte → EmptyState.svelte} +0 -0
  907. /package/dist/{src/lib/chempot-diagram → chempot-diagram}/ChemPotScene3D.svelte +0 -0
  908. /package/dist/{src/lib/colors → colors}/alloy-colors.json +0 -0
  909. /package/dist/{src/lib/colors → colors}/dark-mode-colors.json +0 -0
  910. /package/dist/{src/lib/colors → colors}/jmol-colors.json +0 -0
  911. /package/dist/{src/lib/colors → colors}/muted-colors.json +0 -0
  912. /package/dist/{src/lib/colors → colors}/pastel-colors.json +0 -0
  913. /package/dist/{src/lib/colors → colors}/vesta-colors.json +0 -0
  914. /package/dist/{src/lib/element → element}/BohrAtom.svelte +0 -0
  915. /package/dist/{src/lib/element → element}/Nucleus.svelte +0 -0
  916. /package/dist/{src/lib/element → element}/data.json +0 -0
  917. /package/dist/{src/lib/element → element}/data.json.gz +0 -0
  918. /package/dist/{src/lib/element → element}/data.json.gz.d.ts +0 -0
  919. /package/dist/{src/lib/element → element}/data.schema.json +0 -0
  920. /package/dist/{src/lib/element-image-urls.json → element-image-urls.json} +0 -0
  921. /package/dist/{src/lib/feedback → feedback}/Spinner.svelte +0 -0
  922. /package/dist/{src/lib/feedback → feedback}/StatusMessage.svelte +0 -0
  923. /package/dist/{src/lib/layout → layout}/json-tree/JsonValue.svelte +0 -0
  924. /package/dist/{src/lib/periodic-table → periodic-table}/TableInset.svelte +0 -0
  925. /package/dist/{src/lib/theme → theme}/themes.mjs +0 -0
  926. /package/dist/{src/lib/xrd → xrd}/atomic_scattering_params.json +0 -0
@@ -0,0 +1,2254 @@
1
+ <script lang="ts">
2
+ import type { ColorSchemeName } from '../colors'
3
+ import { ELEMENT_COLOR_SCHEMES } from '../colors'
4
+ import type { ShowControlsProp } from '../controls'
5
+ import { normalize_show_controls } from '../controls'
6
+ import type { ElementSymbol } from '../element'
7
+ import { StatusMessage } from '../feedback'
8
+ import Spinner from '../feedback/Spinner.svelte'
9
+ import Icon from '../Icon.svelte'
10
+ import { create_file_drop_handler, load_from_url } from '../io'
11
+ import { forward_window_keydown, handle_and_prevent } from '../keyboard'
12
+ import { parse_volumetric_file } from '../isosurface/parse'
13
+ import type { IsosurfaceSettings, VolumetricData } from '../isosurface/types'
14
+ import {
15
+ auto_isosurface_settings,
16
+ DEFAULT_ISOSURFACE_SETTINGS,
17
+ tile_volumetric_data,
18
+ } from '../isosurface/types'
19
+ import { ELEM_SYMBOLS } from '../labels'
20
+ import { set_fullscreen_bg, toggle_fullscreen } from '../layout'
21
+ import type { Vec3 } from '../math'
22
+ import { create_cart_to_frac, create_frac_to_cart } from '../math'
23
+ import { DEFAULTS } from '../settings'
24
+ import { colors } from '../state.svelte'
25
+ import type {
26
+ AnyStructure,
27
+ BondEditMode,
28
+ BondOrder,
29
+ Crystal,
30
+ MeasureMode,
31
+ StructureBond,
32
+ } from './'
33
+ import {
34
+ default_vector_configs,
35
+ get_element_counts,
36
+ get_pbc_image_sites,
37
+ get_structure_vector_keys,
38
+ } from './'
39
+ import { wrap_to_unit_cell } from './pbc'
40
+ import {
41
+ is_valid_supercell_input,
42
+ make_supercell,
43
+ parse_supercell_scaling,
44
+ } from './supercell'
45
+ import type { CellType, SymmetrySettings } from '../symmetry'
46
+ import * as symmetry from '../symmetry'
47
+ import { transform_cell } from '../symmetry'
48
+ import type { MoyoDataset } from '@spglib/moyo-wasm'
49
+ import { Canvas } from '@threlte/core'
50
+ import type { ComponentProps, Snippet } from 'svelte'
51
+ import { untrack } from 'svelte'
52
+ import { click_outside, tooltip } from 'svelte-multiselect/attachments'
53
+ import type { HTMLAttributes } from 'svelte/elements'
54
+ import { SvelteMap, SvelteSet } from 'svelte/reactivity'
55
+ import type { Camera, OrthographicCamera, Scene } from 'three'
56
+ import type { AtomColorConfig } from './atom-properties'
57
+ import { get_property_colors } from './atom-properties'
58
+ import AtomLegend from './AtomLegend.svelte'
59
+ import CellSelect from './CellSelect.svelte'
60
+ import { BOND_ORDER_OPTIONS, merge_bond_edits, remap_bonds_after_deletion } from './bonding'
61
+ import type { StructureHandlerData } from './index'
62
+ import { MAX_SELECTED_SITES } from './measure'
63
+ import { normalize_fractional_coords, parse_any_structure } from './parse'
64
+ import StructureControls from './StructureControls.svelte'
65
+ import StructureExportPane from './StructureExportPane.svelte'
66
+ import StructureInfoPane from './StructureInfoPane.svelte'
67
+ import StructureScene from './StructureScene.svelte'
68
+ import { to_error } from '../utils'
69
+
70
+ // Type alias for event handlers to reduce verbosity
71
+ type EventHandler = (data: StructureHandlerData) => void
72
+ type BondEditContext = {
73
+ structure_identity: AnyStructure | undefined
74
+ source_bond_signature: string
75
+ cell_type: CellType
76
+ supercell_scaling: string
77
+ show_image_atoms: boolean
78
+ }
79
+ type BondEditSnapshot = {
80
+ bonds: StructureBond[] | undefined
81
+ context: BondEditContext
82
+ }
83
+ type BondEditHistorySnapshot = {
84
+ added_bonds: StructureBond[]
85
+ removed_bonds: StructureBond[]
86
+ bond_order_overrides: StructureBond[]
87
+ bond_edit_mode: BondEditMode
88
+ bond_edit_order: BondOrder
89
+ }
90
+ type SceneProps = ComponentProps<typeof StructureScene> & typeof DEFAULTS.structure
91
+
92
+ // Local reactive state for scene and lattice props. Deeply reactive so nested mutations propagate.
93
+ // Deep-clone to prevent mutations from leaking to global defaults across component instances.
94
+ let scene_props = $state(structuredClone(DEFAULTS.structure) as SceneProps)
95
+ let lattice_props = $state({
96
+ cell_edge_opacity: DEFAULTS.structure.cell_edge_opacity,
97
+ cell_surface_opacity: DEFAULTS.structure.cell_surface_opacity,
98
+ cell_edge_color: DEFAULTS.structure.cell_edge_color,
99
+ cell_surface_color: DEFAULTS.structure.cell_surface_color,
100
+ cell_edge_width: DEFAULTS.structure.cell_edge_width,
101
+ show_cell_vectors: DEFAULTS.structure.show_cell_vectors,
102
+ })
103
+
104
+ let {
105
+ structure = $bindable(),
106
+ bonds = $bindable(),
107
+ scene_props: scene_props_in = $bindable(),
108
+ lattice_props: lattice_props_in = $bindable(),
109
+ controls_open = $bindable(false),
110
+ info_pane_open = $bindable(false),
111
+ enable_measure_mode = $bindable(true),
112
+ measure_mode = $bindable<MeasureMode>(`distance`),
113
+ bond_edit_mode = $bindable<BondEditMode>(`add`),
114
+ bond_edit_order = $bindable<BondOrder>(1),
115
+ background_color = $bindable(),
116
+ background_opacity = $bindable(0.1),
117
+ show_controls,
118
+ fullscreen = $bindable(false),
119
+ wrapper = $bindable(),
120
+ width = $bindable(0),
121
+ height = $bindable(0),
122
+ reset_text = `Reset camera (or double-click)`,
123
+ color_scheme = $bindable(`Vesta`),
124
+ atom_color_config = $bindable({
125
+ mode: DEFAULTS.structure.atom_color_mode,
126
+ scale: DEFAULTS.structure.atom_color_scale,
127
+ scale_type: DEFAULTS.structure.atom_color_scale_type,
128
+ }),
129
+ hovered = $bindable(false),
130
+ dragover = $bindable(false),
131
+ allow_file_drop = true,
132
+ enable_info_pane = true,
133
+ png_dpi = $bindable(150),
134
+ show_image_atoms = $bindable(true),
135
+ supercell_scaling = $bindable(`1x1x1`),
136
+ fullscreen_toggle = DEFAULTS.structure.fullscreen_toggle,
137
+ bottom_left,
138
+ data_url,
139
+ structure_string,
140
+ on_file_drop,
141
+ spinner_props = {},
142
+ loading = $bindable(false),
143
+ error_msg = $bindable(),
144
+ performance_mode = $bindable(`quality`),
145
+ // expose selected site indices for external control/highlighting
146
+ selected_sites = $bindable([]),
147
+ highlighted_sites = $bindable([]),
148
+ hovered_site_idx = $bindable(null),
149
+ // expose measured site indices for overlays/labels
150
+ measured_sites = $bindable([]),
151
+ // expose the displayed structure (with image atoms and supercell) for external use
152
+ displayed_structure = $bindable(),
153
+ // Track hidden elements across component lifecycle
154
+ hidden_elements = $bindable(new SvelteSet<ElementSymbol>()),
155
+ // Track hidden property values (e.g. Wyckoff positions, coordination numbers)
156
+ hidden_prop_vals = $bindable(new SvelteSet<number | string>()),
157
+ // Per-element radius overrides (absolute values in Angstroms)
158
+ element_radius_overrides = $bindable<Partial<Record<ElementSymbol, number>>>({}),
159
+ // Per-site radius overrides (absolute values in Angstroms)
160
+ site_radius_overrides = $bindable<SvelteMap<number, number>>(new SvelteMap()),
161
+ // Symmetry analysis data (bindable for external access)
162
+ sym_data = $bindable(null),
163
+ // Symmetry analysis settings (bindable for external control)
164
+ symmetry_settings = $bindable(symmetry.default_sym_settings),
165
+ // Map element symbols to different elements (e.g. {'H': 'Na', 'He': 'Cl'})
166
+ // Useful for LAMMPS files where atom types are mapped to H, He, Li by default
167
+ element_mapping = $bindable(),
168
+ // Cell type: original, conventional, or primitive (requires symmetry analysis)
169
+ cell_type = $bindable(`original`),
170
+ // Volumetric data for isosurface rendering (parsed from CHGCAR or .cube files)
171
+ volumetric_data = $bindable<VolumetricData[]>(),
172
+ // Isosurface rendering settings
173
+ isosurface_settings = $bindable<IsosurfaceSettings>({
174
+ ...DEFAULT_ISOSURFACE_SETTINGS,
175
+ }),
176
+ // Active volume index when multiple volumes are present
177
+ active_volume_idx = $bindable(0),
178
+ children,
179
+ top_right_controls,
180
+ on_file_load,
181
+ on_error,
182
+ on_fullscreen_change,
183
+ on_camera_move,
184
+ on_camera_reset,
185
+ on_bonds_change,
186
+ ...rest
187
+ }:
188
+ & {
189
+ structure?: AnyStructure
190
+ bonds?: StructureBond[]
191
+ scene_props?: ComponentProps<typeof StructureScene>
192
+ /**
193
+ * Controls visibility configuration.
194
+ * - 'always': controls always visible
195
+ * - 'hover': controls visible on component hover (default)
196
+ * - 'never': controls never visible
197
+ * - object: { mode, hidden, style } for fine-grained control
198
+ *
199
+ * Control names: 'reset-camera', 'fullscreen', 'measure-mode', 'info-pane', 'export-pane', 'controls'
200
+ */
201
+ show_controls?: ShowControlsProp
202
+ fullscreen?: boolean
203
+ // bindable width of the canvas
204
+ width?: number
205
+ // bindable height of the canvas
206
+ height?: number
207
+ // Canvas wrapper element (for export pane)
208
+ wrapper?: HTMLDivElement
209
+ // PNG export DPI setting
210
+ png_dpi?: number
211
+ reset_text?: string
212
+ hovered?: boolean
213
+ dragover?: boolean
214
+ allow_file_drop?: boolean
215
+ enable_info_pane?: boolean
216
+ enable_measure_mode?: boolean
217
+ measure_mode?: MeasureMode
218
+ bond_edit_mode?: BondEditMode
219
+ bond_edit_order?: BondOrder
220
+ info_pane_open?: boolean
221
+ fullscreen_toggle?: Snippet<[{ fullscreen: boolean }]> | boolean
222
+ bottom_left?: Snippet<[{ structure?: AnyStructure }]>
223
+ top_right_controls?: Snippet // Additional controls to render at the end of the control buttons row
224
+ data_url?: string // URL to load structure from (alternative to providing structure directly)
225
+ // Generic callback for when files are dropped - receives raw content and filename
226
+ on_file_drop?: (content: string | ArrayBuffer, filename: string) => void
227
+ // spinner props (passed to Spinner component)
228
+ spinner_props?: ComponentProps<typeof Spinner>
229
+ loading?: boolean
230
+ error_msg?: string
231
+ // Performance mode: 'quality' (default) or 'speed' for large structures
232
+ performance_mode?: `quality` | `speed`
233
+ // allow parent components to control highlighted/selected site indices
234
+ selected_sites?: number[]
235
+ highlighted_sites?: number[]
236
+ hovered_site_idx?: number | null
237
+ // explicit measured sites for distance/angle overlays
238
+ measured_sites?: number[]
239
+ // expose the displayed structure (with image atoms and/or supercell) for external use
240
+ displayed_structure?: AnyStructure
241
+ // Track which elements are hidden (bindable across frames in trajectories)
242
+ hidden_elements?: Set<ElementSymbol>
243
+ // Track which property values are hidden (e.g. Wyckoff positions, coordination numbers)
244
+ hidden_prop_vals?: Set<number | string>
245
+ // Per-element radius overrides (absolute values in Angstroms)
246
+ element_radius_overrides?: Partial<Record<ElementSymbol, number>>
247
+ // Per-site radius overrides (absolute values in Angstroms)
248
+ // Accepts Map or SvelteMap for flexibility with external callers
249
+ site_radius_overrides?: Map<number, number> | SvelteMap<number, number>
250
+ // Symmetry analysis data (bindable for external access)
251
+ sym_data?: MoyoDataset | null
252
+ // Symmetry analysis settings (bindable for external control)
253
+ symmetry_settings?: Partial<SymmetrySettings>
254
+ // Map element symbols to different elements (e.g. {'H': 'Na', 'He': 'Cl'})
255
+ element_mapping?: Partial<Record<ElementSymbol, ElementSymbol>>
256
+ // Cell type: original, conventional, or primitive (requires symmetry analysis)
257
+ cell_type?: CellType
258
+ // Volumetric data for isosurface rendering (parsed from CHGCAR or .cube files)
259
+ volumetric_data?: VolumetricData[]
260
+ // Isosurface rendering settings
261
+ isosurface_settings?: IsosurfaceSettings
262
+ // Active volume index when multiple volumes are present
263
+ active_volume_idx?: number
264
+ // structure content as string (alternative to providing structure directly or via data_url)
265
+ structure_string?: string
266
+ // Atom coloring configuration
267
+ atom_color_config?: Partial<AtomColorConfig>
268
+ children?: Snippet<[{ structure?: AnyStructure; fullscreen: boolean }]>
269
+ on_file_load?: EventHandler
270
+ on_error?: EventHandler
271
+ on_fullscreen_change?: EventHandler
272
+ on_camera_move?: EventHandler
273
+ on_camera_reset?: EventHandler
274
+ on_bonds_change?: (bonds: StructureBond[] | undefined) => void
275
+ }
276
+ & Omit<ComponentProps<typeof StructureControls>, `children` | `onclose`>
277
+ & Omit<HTMLAttributes<HTMLDivElement>, `children`> = $props()
278
+
279
+ // Initialize models from incoming props; mutations come from UI controls; we mirror into local dicts (NOTE only doing shallow merge)
280
+ $effect.pre(() => {
281
+ if (scene_props_in && typeof scene_props_in === `object`) {
282
+ Object.assign(scene_props, scene_props_in)
283
+ }
284
+ if (lattice_props_in && typeof lattice_props_in === `object`) {
285
+ Object.assign(lattice_props, lattice_props_in)
286
+ }
287
+ })
288
+
289
+ // Load structure from URL when data_url is provided
290
+ $effect(() => {
291
+ if (data_url && !structure) {
292
+ loading = true
293
+ error_msg = undefined
294
+
295
+ load_from_url(data_url, (content, filename) => {
296
+ if (on_file_drop) on_file_drop(content, filename)
297
+ else {
298
+ // Parse structure internally when no handler provided
299
+ try {
300
+ const text_content = content instanceof ArrayBuffer
301
+ ? new TextDecoder().decode(content)
302
+ : content
303
+ const parsed = parse_file_content(text_content, filename)
304
+ emit_file_load_event(parsed, filename, content)
305
+ } catch (error) {
306
+ error_msg = `Failed to parse structure: ${
307
+ to_error(error).message
308
+ }`
309
+ on_error?.({ error_msg, filename })
310
+ }
311
+ }
312
+ })
313
+ .then(() => loading = false)
314
+ .catch((error: Error) => {
315
+ console.error(`Failed to load structure from URL:`, error)
316
+ error_msg = `Failed to load structure: ${error.message}`
317
+ loading = false
318
+ on_error?.({ error_msg, filename: data_url })
319
+ })
320
+ }
321
+ })
322
+
323
+ $effect(() => { // Parse structure from string when structure_string is provided
324
+ if (!structure_string || data_url) return
325
+ loading = true
326
+ error_msg = undefined
327
+ clear_camera_state()
328
+ try {
329
+ const parsed = parse_any_structure(structure_string, `string`)
330
+ if (parsed) {
331
+ structure = parsed
332
+ untrack(() => emit_file_load_event(parsed, `string`, structure_string))
333
+ } else {
334
+ throw new Error(`Failed to parse structure from string`)
335
+ }
336
+ } catch (err) {
337
+ error_msg = `Failed to parse structure from string: ${
338
+ to_error(err).message
339
+ }`
340
+ untrack(() => on_error?.({ error_msg, filename: `string` }))
341
+ } finally {
342
+ loading = false
343
+ }
344
+ })
345
+
346
+ // Auto-populate vector_configs when structure has vector data (force, magmom, spin, etc.)
347
+ // Skip if configs were externally provided. Clear auto-generated configs on structure change.
348
+ let vectors_auto_populated_for: AnyStructure | undefined = undefined
349
+ let last_auto_configs: Record<string, unknown> | undefined = undefined
350
+
351
+ $effect(() => {
352
+ if (!structure?.sites || structure === vectors_auto_populated_for) return
353
+ const keys = get_structure_vector_keys(structure)
354
+ // Clear auto-generated configs from previous structure; preserve externally-modified ones
355
+ const existing = scene_props.vector_configs
356
+ if (last_auto_configs && existing === last_auto_configs) {
357
+ scene_props.vector_configs = {}
358
+ last_auto_configs = undefined
359
+ } else if (existing && Object.keys(existing).length > 0) {
360
+ vectors_auto_populated_for = structure
361
+ return
362
+ }
363
+ vectors_auto_populated_for = structure
364
+ if (keys.length === 0) return
365
+ const configs = default_vector_configs(keys)
366
+ scene_props.vector_configs = configs
367
+ // Read back the proxied reference — Svelte 5 $state wraps objects in
368
+ // proxies, so `scene_props.vector_configs !== configs`. Storing the proxy
369
+ // lets the identity check above detect unmodified auto-configs.
370
+ // See https://svelte.dev/e/state_proxy_equality_mismatch
371
+ last_auto_configs = scene_props.vector_configs
372
+ scene_props.vector_scale ??= DEFAULTS.structure.vector_scale
373
+ scene_props.vector_color ??= DEFAULTS.structure.vector_color
374
+ })
375
+
376
+ // Optimize scene props for performance based on structure size and mode
377
+ $effect(() => {
378
+ if (structure?.sites && performance_mode === `speed`) {
379
+ const site_count = structure.sites.length
380
+ const current_sphere_segments = scene_props.sphere_segments || 20
381
+
382
+ // Reduce sphere segments for large structures in speed mode
383
+ if (site_count > 200) {
384
+ scene_props.sphere_segments = Math.min(current_sphere_segments, 12)
385
+ }
386
+ }
387
+ })
388
+
389
+ $effect(() => {
390
+ colors.element = ELEMENT_COLOR_SCHEMES[color_scheme as ColorSchemeName]
391
+ })
392
+
393
+ // Compute property-based colors for legend display
394
+ let property_colors = $derived(
395
+ get_property_colors(
396
+ structure,
397
+ atom_color_config,
398
+ scene_props.bonding_strategy,
399
+ sym_data,
400
+ ),
401
+ )
402
+
403
+ let symmetry_run_id = 0
404
+ let symmetry_error = $state<string>()
405
+ let last_symmetry_structure_ref: AnyStructure | null = null
406
+
407
+ // Trigger symmetry analysis when structure is loaded or settings change.
408
+ // Skip during atom drags — symmetry doesn't change from moving atoms,
409
+ // and WASM analysis on every drag frame causes severe frame drops.
410
+ $effect(() => {
411
+ if (dragging_atoms) return
412
+ if (!structure || !(`lattice` in structure)) {
413
+ untrack(() => {
414
+ sym_data = null
415
+ symmetry_error = undefined
416
+ })
417
+ last_symmetry_structure_ref = null
418
+ return
419
+ }
420
+
421
+ const current_structure = structure
422
+ const structure_changed = current_structure !== last_symmetry_structure_ref
423
+ if (structure_changed) {
424
+ untrack(() => {
425
+ sym_data = null
426
+ symmetry_error = undefined
427
+ })
428
+ last_symmetry_structure_ref = current_structure
429
+ } else {
430
+ // Keep previous symmetry data while recomputing so bound consumers
431
+ // (e.g. SymmetryStats inputs) do not unmount and lose focus.
432
+ untrack(() => symmetry_error = undefined)
433
+ }
434
+ const run_id = ++symmetry_run_id
435
+ // Destructure symmetry_settings to ensure Svelte tracks changes to symprec and algo
436
+ // (reading just the object reference isn't sufficient for fine-grained reactivity)
437
+ const { symprec, algo } = symmetry_settings ?? symmetry.default_sym_settings
438
+ const current_settings = { symprec, algo }
439
+ // Skip symmetry auto-analysis in unit tests; happy-dom can't fetch WASM assets
440
+ if (typeof process !== `undefined` && process.env?.VITEST) return
441
+
442
+ symmetry.ensure_moyo_wasm_ready()
443
+ .then(() =>
444
+ run_id === symmetry_run_id
445
+ ? symmetry.analyze_structure_symmetry(current_structure, current_settings)
446
+ : null
447
+ )
448
+ .then((data) => {
449
+ if (data && run_id === symmetry_run_id) {
450
+ untrack(() => sym_data = data)
451
+ }
452
+ })
453
+ .catch((err) => {
454
+ if (run_id === symmetry_run_id) {
455
+ untrack(() => sym_data = null)
456
+ symmetry_error = `Symmetry analysis failed: ${err?.message || err}`
457
+ console.error(`Symmetry analysis failed:`, err)
458
+ }
459
+ })
460
+ })
461
+
462
+ let measure_menu_open = $state(false)
463
+ let export_pane_open = $state(false)
464
+ let focused = $state(false)
465
+
466
+ // Bond customization state
467
+ let added_bonds = $state<StructureBond[]>([])
468
+ let removed_bonds = $state<StructureBond[]>([])
469
+ let bond_order_overrides = $state<StructureBond[]>([])
470
+ let bond_undo_stack = $state<BondEditHistorySnapshot[]>([])
471
+ let bond_redo_stack = $state<BondEditHistorySnapshot[]>([])
472
+ // These hold object-identity tokens (structure_identity) compared with ===/!==,
473
+ // so they must stay raw — proxying them via $state would break identity comparisons
474
+ // (state_proxy_equality_mismatch) against the raw `structure` prop.
475
+ let bond_history_context = $state.raw<BondEditContext>()
476
+ let last_bond_structure_identity = $state.raw(structure)
477
+ let last_emitted_bond_signature = $state<string>()
478
+ let bond_edit_snapshot = $state.raw<BondEditSnapshot>()
479
+ let has_bond_edits = $derived(
480
+ added_bonds.length > 0 || removed_bonds.length > 0 ||
481
+ bond_order_overrides.length > 0,
482
+ )
483
+
484
+ const clone_bonds = (edit_bonds: StructureBond[]): StructureBond[] =>
485
+ edit_bonds.map((bond) => ({
486
+ ...bond,
487
+ cell_shift: bond.cell_shift && ([...bond.cell_shift] as Vec3),
488
+ }))
489
+
490
+ const snapshot_bond_edits = (): BondEditHistorySnapshot => ({
491
+ added_bonds: clone_bonds(added_bonds),
492
+ removed_bonds: clone_bonds(removed_bonds),
493
+ bond_order_overrides: clone_bonds(bond_order_overrides),
494
+ bond_edit_mode,
495
+ bond_edit_order,
496
+ })
497
+
498
+ function restore_bond_edit_snapshot(snapshot: BondEditHistorySnapshot) {
499
+ added_bonds = clone_bonds(snapshot.added_bonds)
500
+ removed_bonds = clone_bonds(snapshot.removed_bonds)
501
+ bond_order_overrides = clone_bonds(snapshot.bond_order_overrides)
502
+ bond_edit_mode = snapshot.bond_edit_mode
503
+ bond_edit_order = snapshot.bond_edit_order
504
+ clear_selection()
505
+ }
506
+
507
+ function clear_bond_history() {
508
+ bond_undo_stack = []
509
+ bond_redo_stack = []
510
+ bond_history_context = undefined
511
+ }
512
+
513
+ function push_bond_undo() {
514
+ if (bond_undo_stack.length >= MAX_HISTORY) {
515
+ bond_undo_stack.splice(0, bond_undo_stack.length - MAX_HISTORY + 1)
516
+ }
517
+ bond_history_context ??= current_bond_edit_context()
518
+ bond_undo_stack.push(snapshot_bond_edits())
519
+ bond_redo_stack = []
520
+ }
521
+
522
+ function undo_bond_edit() {
523
+ if (bond_undo_stack.length === 0) return
524
+ const restored = bond_undo_stack.pop()
525
+ if (!restored) return
526
+ bond_redo_stack.push(snapshot_bond_edits())
527
+ restore_bond_edit_snapshot(restored)
528
+ }
529
+
530
+ function redo_bond_edit() {
531
+ if (bond_redo_stack.length === 0) return
532
+ const restored = bond_redo_stack.pop()
533
+ if (!restored) return
534
+ bond_undo_stack.push(snapshot_bond_edits())
535
+ restore_bond_edit_snapshot(restored)
536
+ }
537
+
538
+ function clear_bond_edits() {
539
+ added_bonds = []
540
+ removed_bonds = []
541
+ bond_order_overrides = []
542
+ clear_bond_history()
543
+ }
544
+
545
+ function emit_bonds(next_bonds: StructureBond[] | undefined) {
546
+ const signature = bond_signature(next_bonds)
547
+ if (signature === last_emitted_bond_signature) return
548
+ last_emitted_bond_signature = signature
549
+ bonds = next_bonds
550
+ on_bonds_change?.(next_bonds)
551
+ }
552
+
553
+ const bond_signature = (edit_bonds: StructureBond[] | undefined): string =>
554
+ edit_bonds === undefined ? `undefined` : JSON.stringify(edit_bonds)
555
+
556
+ const current_source_bonds = (): StructureBond[] | undefined =>
557
+ bonds ?? structure?.properties?.bonds
558
+
559
+ const current_source_bond_signature = (): string => {
560
+ const raw_signature = bond_signature(current_source_bonds())
561
+ if (raw_signature !== last_emitted_bond_signature) return raw_signature
562
+ return bond_history_context?.source_bond_signature ??
563
+ (bond_edit_snapshot
564
+ ? bond_signature(bond_edit_snapshot.bonds)
565
+ : raw_signature)
566
+ }
567
+
568
+ const current_bond_edit_context = (): BondEditContext => ({
569
+ structure_identity: structure,
570
+ source_bond_signature: current_source_bond_signature(),
571
+ cell_type,
572
+ supercell_scaling,
573
+ show_image_atoms,
574
+ })
575
+
576
+ const bond_edit_context_changed = (
577
+ previous: BondEditContext,
578
+ current: BondEditContext,
579
+ ): boolean =>
580
+ previous.structure_identity !== current.structure_identity ||
581
+ previous.source_bond_signature !== current.source_bond_signature ||
582
+ previous.cell_type !== current.cell_type ||
583
+ previous.supercell_scaling !== current.supercell_scaling ||
584
+ previous.show_image_atoms !== current.show_image_atoms
585
+
586
+ const resolve_bond_edit_reset_bonds = (
587
+ snapshot: BondEditSnapshot,
588
+ ): StructureBond[] | undefined =>
589
+ snapshot.context.structure_identity === structure
590
+ ? snapshot.bonds
591
+ : structure?.properties?.bonds
592
+
593
+ $effect(() => {
594
+ const next_structure_identity = structure
595
+ untrack(() => {
596
+ if (
597
+ last_bond_structure_identity !== next_structure_identity &&
598
+ bond_signature(bonds) === last_emitted_bond_signature
599
+ ) {
600
+ emit_bonds(structure?.properties?.bonds)
601
+ }
602
+ last_bond_structure_identity = next_structure_identity
603
+ })
604
+ })
605
+
606
+ $effect(() => {
607
+ const history_context = bond_history_context
608
+ if (history_context === undefined) return
609
+ if (bond_edit_context_changed(history_context, current_bond_edit_context())) {
610
+ untrack(clear_bond_history)
611
+ }
612
+ })
613
+
614
+ $effect(() => {
615
+ const snapshot = bond_edit_snapshot
616
+ if (snapshot === undefined) return
617
+ const context = current_bond_edit_context()
618
+ if (!bond_edit_context_changed(snapshot.context, context)) return
619
+ untrack(() => {
620
+ emit_bonds(resolve_bond_edit_reset_bonds(snapshot))
621
+ clear_bond_edits()
622
+ bond_edit_snapshot = undefined
623
+ })
624
+ })
625
+
626
+ $effect(() => {
627
+ if (!has_bond_edits) {
628
+ if (bond_edit_snapshot === undefined) return
629
+ emit_bonds(resolve_bond_edit_reset_bonds(bond_edit_snapshot))
630
+ bond_edit_snapshot = undefined
631
+ return
632
+ }
633
+ bond_edit_snapshot ??= {
634
+ bonds: current_source_bonds(),
635
+ context: current_bond_edit_context(),
636
+ }
637
+ const edited_bonds = merge_bond_edits(
638
+ bond_edit_snapshot.bonds ?? [],
639
+ added_bonds,
640
+ removed_bonds,
641
+ bond_order_overrides,
642
+ )
643
+ emit_bonds(edited_bonds)
644
+ })
645
+
646
+ // === Edit-atoms mode state ===
647
+ let dragging_atoms = $state(false)
648
+ let undo_stack = $state<AnyStructure[]>([])
649
+ let redo_stack = $state<AnyStructure[]>([])
650
+ const MAX_HISTORY = 20
651
+ // Flag set before internal edits (undo/redo/delete/add/move) to distinguish
652
+ // them from external structure changes (file load, trajectory step, etc.)
653
+ let is_internal_edit = false
654
+ // Add-atom sub-mode state (bound to StructureScene)
655
+ let add_atom_mode = $state(false)
656
+ let add_element = $state<ElementSymbol>(`C` as ElementSymbol)
657
+ let canvas_cursor = $state(`default`)
658
+ let is_measure_selection_mode = $derived(
659
+ measure_mode === `distance` || measure_mode === `angle`,
660
+ )
661
+ let show_measure_selection_limit = $derived(
662
+ is_measure_selection_mode && measured_sites.length >= MAX_SELECTED_SITES,
663
+ )
664
+ let show_selection_reset = $derived(
665
+ has_bond_edits ||
666
+ (is_measure_selection_mode && measured_sites.length > 0) ||
667
+ (measure_mode === `edit-atoms` && selected_sites.length > 0),
668
+ )
669
+ let atom_legend_selected_sites = $derived(
670
+ measure_mode === `edit-atoms` ? selected_sites : [],
671
+ )
672
+ let change_element_mode = $state(false)
673
+ let change_element_value = $state(``)
674
+ // Ephemeral toast message for edit operations
675
+ let toast_msg = $state<string | null>(null)
676
+ let toast_timer: ReturnType<typeof setTimeout> | undefined
677
+ function show_toast(msg: string, duration_ms = 2000) {
678
+ clearTimeout(toast_timer)
679
+ toast_msg = msg
680
+ toast_timer = setTimeout(() => (toast_msg = null), duration_ms)
681
+ }
682
+
683
+ // Normalize and validate element symbol (e.g. "fe" → "Fe", "Xx" → null)
684
+ function normalize_element(input: string): ElementSymbol | null {
685
+ const normalized = (input.charAt(0).toUpperCase() +
686
+ input.slice(1).toLowerCase()) as ElementSymbol
687
+ return ELEM_SYMBOLS.includes(normalized) ? normalized : null
688
+ }
689
+
690
+ function clear_selection() {
691
+ selected_sites = []
692
+ measured_sites = []
693
+ dragging_atoms = false
694
+ }
695
+
696
+ function push_undo() {
697
+ if (!structure) return
698
+ if (undo_stack.length >= MAX_HISTORY) {
699
+ undo_stack.splice(0, undo_stack.length - MAX_HISTORY + 1)
700
+ }
701
+ undo_stack.push($state.snapshot(structure) as AnyStructure)
702
+ redo_stack.length = 0
703
+ }
704
+
705
+ // Shared undo/redo: pop from `source`, push current state onto `target`
706
+ function apply_history(source: AnyStructure[], target: AnyStructure[]) {
707
+ if (source.length === 0 || !structure) return
708
+ const restored = source.pop()
709
+ if (!restored) return
710
+ is_internal_edit = true
711
+ target.push($state.snapshot(structure) as AnyStructure)
712
+ structure = restored
713
+ clear_selection()
714
+ }
715
+
716
+ const undo = () => apply_history(undo_stack, redo_stack)
717
+ const redo = () => apply_history(redo_stack, undo_stack)
718
+
719
+ // Clear undo/redo stacks when structure changes externally (file load, etc.)
720
+ // Internal edits set is_internal_edit=true before modifying structure.
721
+ // This $effect runs after microtask, so the flag is still set from the edit.
722
+ $effect(() => {
723
+ // Track structure to re-run when it changes
724
+ void structure
725
+ if (is_internal_edit) {
726
+ is_internal_edit = false
727
+ return
728
+ }
729
+ // External change — clear history and stale edit-atoms state
730
+ untrack(() => {
731
+ if (undo_stack.length > 0 || redo_stack.length > 0) {
732
+ undo_stack = []
733
+ redo_stack = []
734
+ }
735
+ if (highlighted_sites.length > 0) highlighted_sites = []
736
+ if (measure_mode === `edit-atoms`) {
737
+ if (selected_sites.length > 0 || measured_sites.length > 0) clear_selection()
738
+ if (site_radius_overrides?.size > 0) site_radius_overrides.clear()
739
+ }
740
+ })
741
+ })
742
+
743
+ // Clear selection when switching measure/edit mode so stale state doesn't carry over
744
+ let mode_first_run = true
745
+ $effect(() => {
746
+ void measure_mode // track reactively
747
+ if (mode_first_run) {
748
+ mode_first_run = false
749
+ return
750
+ }
751
+ untrack(() => {
752
+ if (selected_sites.length > 0 || measured_sites.length > 0) clear_selection()
753
+ if (measure_mode === `edit-bonds`) bond_edit_mode = `add`
754
+ })
755
+ })
756
+
757
+ $effect(() => {
758
+ void bond_edit_mode
759
+ untrack(() => {
760
+ if (measure_mode === `edit-bonds` && (selected_sites.length > 0 || measured_sites.length > 0)) {
761
+ clear_selection()
762
+ }
763
+ })
764
+ })
765
+
766
+ // Auto-bake cell type transform and clear stale state when entering edit-atoms mode
767
+ $effect(() => {
768
+ if (measure_mode !== `edit-atoms`) return
769
+ untrack(() => {
770
+ // Clear bond edits from edit-bonds mode to avoid stale state
771
+ if (has_bond_edits) clear_bond_edits()
772
+ else clear_bond_history()
773
+ if (cell_type !== `original` && cell_transformed_structure && structure) {
774
+ // Bake the transformed cell: push original to undo, replace structure
775
+ is_internal_edit = true
776
+ push_undo()
777
+ structure = $state.snapshot(cell_transformed_structure) as AnyStructure
778
+ cell_type = `original`
779
+ }
780
+ })
781
+ })
782
+
783
+ let controls_config = $derived(normalize_show_controls(show_controls))
784
+ // $effect instead of `$derived(hovered || focused)`: the $derived reading the $bindable
785
+ // `hovered` prop went stale after the first hover/leave cycle, so the gizmo + mode toggle only
786
+ // appeared on the first mouseenter until reload.
787
+ let viewer_active = $state(false)
788
+ $effect(() => {
789
+ viewer_active = hovered || focused
790
+ })
791
+ let scene_gizmo = $derived(viewer_active && (scene_props.gizmo ?? scene_props.show_gizmo))
792
+ let active_scene_sites = $derived([
793
+ ...new SvelteSet([...(scene_props.active_sites ?? []), ...highlighted_sites]),
794
+ ])
795
+
796
+ // Normalize structure coordinates: wrap fractional coords to [0,1) and recompute Cartesian
797
+ // This ensures atoms are rendered inside the unit cell regardless of data source
798
+ let normalized_structure = $derived.by(() => {
799
+ if (!structure || !(`lattice` in structure)) return structure
800
+ return normalize_fractional_coords(structure) as AnyStructure
801
+ })
802
+
803
+ let structure_with_bonds = $derived.by(() => {
804
+ if (!normalized_structure || bonds === undefined) return normalized_structure
805
+ return {
806
+ ...normalized_structure,
807
+ properties: { ...normalized_structure.properties, bonds },
808
+ } as AnyStructure
809
+ })
810
+
811
+ // Apply cell type transformation (original, conventional, or primitive)
812
+ // This must happen BEFORE supercell transformation
813
+ let cell_transformed_structure = $derived.by(() => {
814
+ if (
815
+ !structure_with_bonds || !(`lattice` in structure_with_bonds) ||
816
+ cell_type === `original`
817
+ ) {
818
+ return structure_with_bonds
819
+ }
820
+ // Cell type transformation requires symmetry data
821
+ if (!sym_data) {
822
+ return structure_with_bonds
823
+ }
824
+ try {
825
+ return transform_cell(structure_with_bonds as Crystal, cell_type, sym_data)
826
+ } catch (error) {
827
+ console.error(`Failed to transform cell to ${cell_type}:`, error)
828
+ return structure_with_bonds
829
+ }
830
+ })
831
+
832
+ // Create supercell if needed (uses cell_transformed_structure as base)
833
+ let supercell_structure = $state(structure)
834
+ let supercell_loading = $state(false)
835
+ let has_supercell = $derived(
836
+ Boolean(supercell_scaling) && ![``, `1x1x1`, `1`].includes(supercell_scaling),
837
+ )
838
+ let bond_edits_enabled = $derived(
839
+ cell_type === `original` && !has_supercell && !supercell_loading,
840
+ )
841
+
842
+ $effect(() => {
843
+ if (measure_mode !== `edit-bonds` || bond_edits_enabled) return
844
+ untrack(() => {
845
+ clear_selection()
846
+ clear_bond_edits()
847
+ measure_mode = `distance`
848
+ show_toast(`Bond editing is only available for the original 1x1x1 cell`)
849
+ })
850
+ })
851
+
852
+ // Tile volumetric data to match supercell when active.
853
+ // Gate on !supercell_loading so the tiled volume and supercell structure update
854
+ // in the same frame (large supercells defer structure via setTimeout).
855
+ let supercell_volume = $derived.by(() => {
856
+ const vol = volumetric_data?.[active_volume_idx]
857
+ if (!vol || !has_supercell || supercell_loading) return vol
858
+ try {
859
+ return tile_volumetric_data(vol, parse_supercell_scaling(supercell_scaling))
860
+ } catch {
861
+ return vol
862
+ }
863
+ })
864
+
865
+ let supercell_timeout: ReturnType<typeof setTimeout> | undefined
866
+ $effect(() => {
867
+ const base_structure = cell_transformed_structure
868
+ clearTimeout(supercell_timeout)
869
+ if (!base_structure || !(`lattice` in base_structure) || !has_supercell) {
870
+ supercell_structure = base_structure
871
+ supercell_loading = false
872
+ } else if (!is_valid_supercell_input(supercell_scaling)) {
873
+ supercell_structure = base_structure
874
+ supercell_loading = false
875
+ } else {
876
+ // For large supercells, show loading state and use async generation
877
+ const sites_count = base_structure.sites?.length || 0
878
+ const [nx_str, ny_str, nz_str] = supercell_scaling.split(/[x×]/)
879
+ const scaling_mult = (parseInt(nx_str, 10) || 1) * (parseInt(ny_str, 10) || 1) *
880
+ (parseInt(nz_str, 10) || 1)
881
+ const estimated_sites = sites_count * scaling_mult
882
+
883
+ // Show spinner for supercells with >1000 estimated sites or scaling >8
884
+ const show_loading = estimated_sites > 1000 || scaling_mult > 8
885
+
886
+ if (show_loading) {
887
+ supercell_loading = true
888
+ // Use setTimeout to allow UI to update before heavy computation
889
+ supercell_timeout = setTimeout(() => {
890
+ try {
891
+ if (base_structure && `lattice` in base_structure) {
892
+ supercell_structure = make_supercell(
893
+ base_structure as Crystal,
894
+ supercell_scaling,
895
+ )
896
+ }
897
+ } catch (error) {
898
+ console.error(`Failed to create supercell:`, error)
899
+ supercell_structure = base_structure
900
+ } finally {
901
+ supercell_loading = false
902
+ }
903
+ }, 10)
904
+ } else {
905
+ if (base_structure && `lattice` in base_structure) {
906
+ supercell_structure = make_supercell(
907
+ base_structure as Crystal,
908
+ supercell_scaling,
909
+ )
910
+ }
911
+ supercell_loading = false
912
+ }
913
+ }
914
+ })
915
+
916
+ // Clear selections, site overrides, and stale camera target when transformations
917
+ // change site indices (skip first run to preserve parent-provided selections)
918
+ let first_run = true
919
+ $effect(() => {
920
+ void [supercell_scaling, show_image_atoms, structure, cell_type] // track reactively
921
+ if (first_run) {
922
+ first_run = false
923
+ return
924
+ }
925
+ untrack(() => {
926
+ // In edit-atoms mode, structure changes are intentional user edits
927
+ // (move/add/delete) — preserve the selection so TransformControls stays active
928
+ if (measure_mode === `edit-atoms`) return
929
+ if (selected_sites.length > 0 || measured_sites.length > 0) clear_selection()
930
+ // Clear site radius overrides since site indices are no longer valid
931
+ if (site_radius_overrides?.size > 0) site_radius_overrides.clear()
932
+ // Clear stale camera target so orbit controls re-center on the new cell
933
+ scene_props.camera_target = undefined
934
+ })
935
+ })
936
+
937
+ // Apply element mapping then image atoms to the supercell structure.
938
+ // Skip get_pbc_image_sites during atom drags — the vector math + doubled site
939
+ // count causes frame drops. Image atoms reappear instantly on drag release.
940
+ $effect(() => {
941
+ let struct = supercell_structure
942
+ if (struct && element_mapping && Object.keys(element_mapping).length > 0) {
943
+ const mapping = element_mapping // capture for TypeScript narrowing
944
+ struct = {
945
+ ...struct,
946
+ sites: struct.sites.map((site) => ({
947
+ ...site,
948
+ species: site.species.map((sp) => ({
949
+ ...sp,
950
+ element: mapping[sp.element as ElementSymbol] ?? sp.element,
951
+ })),
952
+ label: mapping[site.label as ElementSymbol] ?? site.label,
953
+ })),
954
+ }
955
+ }
956
+ displayed_structure =
957
+ !dragging_atoms && show_image_atoms && struct && `lattice` in struct &&
958
+ struct.lattice
959
+ ? get_pbc_image_sites(struct)
960
+ : struct
961
+ })
962
+
963
+ // Track if camera has ever been moved from initial position
964
+ let camera_has_moved = $state(false)
965
+ let camera_is_moving = $state(false)
966
+ let scene = $state<Scene | undefined>(undefined)
967
+ let camera = $state<Camera | undefined>(undefined)
968
+ let orbit_controls = $state<
969
+ ComponentProps<typeof StructureScene>[`orbit_controls`]
970
+ >(undefined)
971
+ let rotation_target_ref = $state<Vec3 | undefined>(undefined)
972
+ let initial_computed_zoom = $state<number | undefined>(undefined)
973
+
974
+ // Mutual exclusion: opening one pane closes others
975
+ $effect(() => {
976
+ if (info_pane_open) {
977
+ untrack(() => [controls_open, export_pane_open] = [false, false])
978
+ }
979
+ })
980
+ $effect(() => {
981
+ if (controls_open) {
982
+ untrack(() => [info_pane_open, export_pane_open] = [false, false])
983
+ }
984
+ })
985
+ $effect(() => {
986
+ if (export_pane_open) {
987
+ untrack(() => [info_pane_open, controls_open] = [false, false])
988
+ }
989
+ })
990
+
991
+ // Reset tracking when structure changes
992
+ $effect(() => {
993
+ if (structure) camera_has_moved = false
994
+ })
995
+
996
+ // Clear stale camera target and position so StructureScene uses the new
997
+ // structure's rotation_target (unit cell center) and auto-positions the camera.
998
+ function clear_camera_state() {
999
+ scene_props.camera_target = undefined
1000
+ scene_props.camera_position = [0, 0, 0]
1001
+ }
1002
+
1003
+ const read_orbit_target = (): Vec3 | undefined => {
1004
+ if (!orbit_controls?.target) return
1005
+ const { x, y, z } = orbit_controls.target
1006
+ return [x, y, z]
1007
+ }
1008
+
1009
+ const read_camera_position = (): Vec3 | undefined =>
1010
+ camera
1011
+ ? [camera.position.x, camera.position.y, camera.position.z]
1012
+ : scene_props.camera_position
1013
+
1014
+ // Emit debounced camera updates while controls are active.
1015
+ $effect(() => {
1016
+ if (!camera_is_moving) return
1017
+ camera_has_moved = true
1018
+
1019
+ const emit_camera_move = () => {
1020
+ const camera_position = read_camera_position()
1021
+ if (camera_position === undefined) return
1022
+ const camera_target = read_orbit_target()
1023
+ scene_props.camera_position = camera_position
1024
+ scene_props.camera_target = camera_target
1025
+ on_camera_move?.({
1026
+ structure,
1027
+ camera_has_moved,
1028
+ camera_position,
1029
+ camera_target,
1030
+ })
1031
+ }
1032
+
1033
+ emit_camera_move()
1034
+ const emit_interval = setInterval(emit_camera_move, 200)
1035
+ return () => clearInterval(emit_interval)
1036
+ })
1037
+
1038
+ function reset_camera() {
1039
+ // Reset camera position to trigger automatic positioning.
1040
+ scene_props.camera_position = [0, 0, 0]
1041
+ scene_props.camera_target = rotation_target_ref
1042
+ camera_has_moved = false
1043
+
1044
+ let camera_position: Vec3 = [0, 0, 0]
1045
+ let camera_target: Vec3 | undefined = rotation_target_ref
1046
+
1047
+ // Reset pan/zoom and ensure controls target returns to structure center.
1048
+ if (orbit_controls && camera) {
1049
+ if (
1050
+ `reset` in orbit_controls &&
1051
+ typeof orbit_controls.reset === `function`
1052
+ ) orbit_controls.reset()
1053
+ if (orbit_controls.target && rotation_target_ref) {
1054
+ const [target_x, target_y, target_z] = rotation_target_ref
1055
+ orbit_controls.target.set(target_x, target_y, target_z)
1056
+ }
1057
+
1058
+ // Reset zoom for orthographic camera
1059
+ if (`zoom` in camera && initial_computed_zoom !== undefined) {
1060
+ const ortho_camera = camera as OrthographicCamera
1061
+ ortho_camera.zoom = initial_computed_zoom
1062
+ ortho_camera.updateProjectionMatrix()
1063
+ }
1064
+
1065
+ // Call update to apply changes immediately
1066
+ if (typeof orbit_controls.update === `function`) orbit_controls.update()
1067
+ camera_position = read_camera_position() ?? camera_position
1068
+ camera_target = read_orbit_target()
1069
+ }
1070
+
1071
+ scene_props.camera_position = camera_position
1072
+ scene_props.camera_target = camera_target
1073
+ on_camera_reset?.({ structure, camera_has_moved, camera_position, camera_target })
1074
+ }
1075
+
1076
+ const emit_file_load_event = (
1077
+ loaded_structure: AnyStructure,
1078
+ filename: string,
1079
+ content: string | ArrayBuffer,
1080
+ ) =>
1081
+ on_file_load?.({
1082
+ structure: loaded_structure,
1083
+ filename,
1084
+ file_size: typeof content === `string`
1085
+ ? new Blob([content]).size
1086
+ : content.byteLength,
1087
+ total_atoms: loaded_structure.sites?.length || 0,
1088
+ })
1089
+
1090
+ // Try to parse content as a volumetric file, setting both structure and volumetric data.
1091
+ // Delegates format detection entirely to parse_volumetric_file (filename + content sniffing).
1092
+ // Returns the parsed structure on success, or null if the file isn't a volumetric format.
1093
+ function try_parse_volumetric(
1094
+ text_content: string,
1095
+ filename: string,
1096
+ ): AnyStructure | null {
1097
+ const vol_result = parse_volumetric_file(text_content, filename)
1098
+ if (!vol_result) return null
1099
+ // parse_volumetric_file extracts structure from file header;
1100
+ // parsers set pbc so the lattice conforms to Crystal's LatticeType
1101
+ structure = vol_result.structure as AnyStructure
1102
+ volumetric_data = vol_result.volumes
1103
+ // Auto-compute reasonable isosurface settings from data range
1104
+ const vol = vol_result.volumes[0]
1105
+ if (vol) {
1106
+ isosurface_settings = auto_isosurface_settings(vol.data_range)
1107
+ active_volume_idx = 0
1108
+ }
1109
+ return structure
1110
+ }
1111
+
1112
+ // Parse file content, trying volumetric format first then falling back to plain structure.
1113
+ // Returns the parsed structure on success, throws on failure.
1114
+ function parse_file_content(text_content: string, filename: string): AnyStructure {
1115
+ clear_camera_state()
1116
+ const vol_struct = try_parse_volumetric(text_content, filename)
1117
+ if (vol_struct) return vol_struct
1118
+ // Clear stale volumetric data when loading a non-volumetric file
1119
+ volumetric_data = []
1120
+ const parsed = parse_any_structure(text_content, filename)
1121
+ if (!parsed) throw new Error(`Failed to parse structure from ${filename}`)
1122
+ structure = parsed
1123
+ return parsed
1124
+ }
1125
+
1126
+ const handle_file_drop = create_file_drop_handler({
1127
+ allow: () => allow_file_drop,
1128
+ on_drop: (content, filename) => {
1129
+ if (on_file_drop) return on_file_drop(content, filename)
1130
+ try {
1131
+ const text_content = content instanceof ArrayBuffer
1132
+ ? new TextDecoder().decode(content)
1133
+ : content
1134
+ const parsed = parse_file_content(text_content, filename)
1135
+ emit_file_load_event(parsed, filename, content)
1136
+ } catch (err) {
1137
+ error_msg = `Failed to parse structure: ${
1138
+ to_error(err).message
1139
+ }`
1140
+ on_error?.({ error_msg, filename })
1141
+ }
1142
+ },
1143
+ on_error: (msg) => {
1144
+ error_msg = msg
1145
+ on_error?.({ error_msg: msg })
1146
+ },
1147
+ set_loading: (val) => {
1148
+ loading = val
1149
+ if (val) [error_msg, dragover] = [undefined, false]
1150
+ },
1151
+ })
1152
+
1153
+ // Handle keyboard shortcuts. Returns true if the key was handled, so the caller
1154
+ // (handle_and_prevent / forward_window_keydown) can suppress the browser default.
1155
+ function handle_keydown(event: KeyboardEvent): boolean {
1156
+ // Don't handle shortcuts if user is typing in an input field
1157
+ const target = event.target
1158
+ const is_input_focused =
1159
+ target instanceof HTMLElement &&
1160
+ (target.tagName === `INPUT` ||
1161
+ target.tagName === `TEXTAREA` ||
1162
+ target.tagName === `SELECT` ||
1163
+ target.isContentEditable)
1164
+
1165
+ // Allow Escape to cancel add-atom mode even when the element input is focused
1166
+ if (event.key === `Escape` && measure_mode === `edit-atoms` && add_atom_mode) {
1167
+ add_atom_mode = false
1168
+ return true
1169
+ }
1170
+
1171
+ if (is_input_focused) return false
1172
+
1173
+ if (measure_mode === `edit-bonds`) {
1174
+ const key = event.key.toLowerCase()
1175
+ const plain = !event.ctrlKey && !event.metaKey && !event.altKey
1176
+ if (event.ctrlKey || event.metaKey) {
1177
+ if (key === `z` && !event.shiftKey) {
1178
+ if (bond_undo_stack.length === 0) return false
1179
+ undo_bond_edit()
1180
+ show_toast(`Undo bond edit (${bond_undo_stack.length} left)`)
1181
+ return true
1182
+ } else if (key === `y` || (key === `z` && event.shiftKey)) {
1183
+ if (bond_redo_stack.length === 0) return false
1184
+ redo_bond_edit()
1185
+ show_toast(`Redo bond edit (${bond_redo_stack.length} left)`)
1186
+ return true
1187
+ }
1188
+ }
1189
+ if (key === `a` && plain) {
1190
+ bond_edit_mode = `add`
1191
+ return true
1192
+ }
1193
+ if (key === `d` && plain) {
1194
+ bond_edit_mode = `delete`
1195
+ return true
1196
+ }
1197
+ if (event.key === `Escape` && selected_sites.length > 0) {
1198
+ clear_selection()
1199
+ return true
1200
+ }
1201
+ }
1202
+
1203
+ // Edit-atoms mode shortcuts (including undo/redo)
1204
+ if (measure_mode === `edit-atoms`) {
1205
+ // Undo/redo shortcuts (Ctrl/Cmd + Z/Y) — only active in edit-atoms mode
1206
+ if (event.ctrlKey || event.metaKey) {
1207
+ const key = event.key.toLowerCase()
1208
+ if (key === `z` && !event.shiftKey) {
1209
+ if (undo_stack.length === 0) return false
1210
+ undo()
1211
+ show_toast(`Undo (${undo_stack.length} left)`)
1212
+ return true
1213
+ } else if (key === `y` || (key === `z` && event.shiftKey)) {
1214
+ if (redo_stack.length === 0) return false
1215
+ redo()
1216
+ show_toast(`Redo (${redo_stack.length} left)`)
1217
+ return true
1218
+ }
1219
+ }
1220
+
1221
+ if (event.key === `Delete` || event.key === `Backspace`) {
1222
+ // Delete selected atoms
1223
+ if (selected_sites.length > 0 && structure?.sites) {
1224
+ is_internal_edit = true
1225
+ push_undo()
1226
+ const to_delete = scene_to_structure_indices(selected_sites, true)
1227
+ const n_deleted = to_delete.size
1228
+ clear_selection()
1229
+ // Remap explicit bond metadata so surviving bonds track shifted site indices.
1230
+ // structure_with_bonds prefers the bindable `bonds` prop, so remap that too.
1231
+ if (bonds !== undefined) bonds = remap_bonds_after_deletion(bonds, to_delete)
1232
+ const old_bonds = structure.properties?.bonds
1233
+ structure = {
1234
+ ...structure,
1235
+ sites: structure.sites.filter((_, idx) => !to_delete.has(idx)),
1236
+ ...(old_bonds && {
1237
+ properties: {
1238
+ ...structure.properties,
1239
+ bonds: remap_bonds_after_deletion(old_bonds, to_delete),
1240
+ },
1241
+ }),
1242
+ }
1243
+ // Clear per-site overrides since indices shifted after deletion
1244
+ if (site_radius_overrides?.size > 0) site_radius_overrides.clear()
1245
+ clear_bond_edits()
1246
+ show_toast(`Deleted ${n_deleted} site${n_deleted > 1 ? `s` : ``}`)
1247
+ return true
1248
+ }
1249
+ return false
1250
+ }
1251
+ const key = event.key.toLowerCase()
1252
+ const plain = !event.ctrlKey && !event.metaKey && !event.altKey
1253
+
1254
+ if (key === `a` && plain) {
1255
+ // Enter add-atom sub-mode (plain 'a' only, not Ctrl+A/Cmd+A/Alt+A)
1256
+ add_atom_mode = !add_atom_mode
1257
+ return true
1258
+ }
1259
+ // Change element of selected atoms
1260
+ if (key === `e` && plain && selected_sites.length > 0) {
1261
+ change_element_mode = !change_element_mode
1262
+ return true
1263
+ }
1264
+ // Duplicate selected atoms at a small offset
1265
+ if (
1266
+ key === `d` && (event.ctrlKey || event.metaKey) &&
1267
+ selected_sites.length > 0 && structure?.sites
1268
+ ) {
1269
+ is_internal_edit = true
1270
+ push_undo()
1271
+ const orig_indices = scene_to_structure_indices(selected_sites)
1272
+ const cart_to_frac = get_cart_to_frac()
1273
+ const new_sites = structure.sites
1274
+ .filter((_, idx) => orig_indices.has(idx))
1275
+ .map((site) => {
1276
+ const new_xyz: Vec3 = [
1277
+ site.xyz[0] + 0.5,
1278
+ site.xyz[1] + 0.5,
1279
+ site.xyz[2] + 0.5,
1280
+ ]
1281
+ return {
1282
+ ...site,
1283
+ xyz: new_xyz,
1284
+ abc: cart_to_frac?.(new_xyz) ?? new_xyz,
1285
+ properties: { ...site.properties },
1286
+ }
1287
+ })
1288
+ const base_idx = structure.sites.length
1289
+ structure = {
1290
+ ...structure,
1291
+ sites: [...structure.sites, ...new_sites],
1292
+ }
1293
+ // Select the newly duplicated atoms
1294
+ selected_sites = new_sites.map((_, idx) => base_idx + idx)
1295
+ measured_sites = [...selected_sites]
1296
+ show_toast(
1297
+ `Duplicated ${new_sites.length} site${new_sites.length > 1 ? `s` : ``}`,
1298
+ )
1299
+ return true
1300
+ }
1301
+
1302
+ // add_atom_mode Escape is already handled above (before is_input_focused guard)
1303
+ if (event.key === `Escape`) {
1304
+ if (change_element_mode) {
1305
+ change_element_mode = false
1306
+ return true
1307
+ }
1308
+ if (selected_sites.length > 0) {
1309
+ clear_selection()
1310
+ return true
1311
+ }
1312
+ }
1313
+ }
1314
+
1315
+ // Interface shortcuts (require Ctrl/Cmd modifier to avoid accidental triggers)
1316
+ const has_modifier = event.ctrlKey || event.metaKey
1317
+ if (event.key === `f` && has_modifier && fullscreen_toggle) {
1318
+ toggle_fullscreen(wrapper)
1319
+ return true
1320
+ } else if (event.key === `i` && has_modifier && enable_info_pane) {
1321
+ info_pane_open = !info_pane_open
1322
+ return true
1323
+ } else if (event.key === `Escape`) {
1324
+ // Prioritize closing panes, then exit edit modes, then exit fullscreen
1325
+ if (info_pane_open) info_pane_open = false
1326
+ else if (controls_open) controls_open = false
1327
+ else if (export_pane_open) export_pane_open = false
1328
+ else if (measure_mode === `edit-bonds` || measure_mode === `edit-atoms`) {
1329
+ measure_mode = `distance`
1330
+ } else return false
1331
+ return true
1332
+ }
1333
+ return false
1334
+ }
1335
+
1336
+ // Hover (window) path: skip edit-mode mutations so destructive keys (delete/undo)
1337
+ // require focus, not just a hovering mouse.
1338
+ const handle_hover_keydown = (event: KeyboardEvent): boolean =>
1339
+ measure_mode === `edit-atoms` || measure_mode === `edit-bonds`
1340
+ ? false
1341
+ : handle_keydown(event)
1342
+
1343
+ // === Edit-atoms mode helpers ===
1344
+
1345
+ // Map scene indices (into displayed_structure) back to raw structure indices.
1346
+ // Handles supercell atoms via orig_unit_cell_idx property.
1347
+ // skip_image_atoms: when true, image atoms (PBC ghosts) are excluded from the result.
1348
+ function scene_to_structure_indices(
1349
+ scene_indices: number[],
1350
+ skip_image_atoms = false,
1351
+ ): SvelteSet<number> {
1352
+ const result = new SvelteSet<number>()
1353
+ for (const scene_idx of scene_indices) {
1354
+ const displayed_site = displayed_structure?.sites?.[scene_idx]
1355
+ if (!displayed_site) continue
1356
+ if (skip_image_atoms && displayed_site.properties?.orig_site_idx != null) {
1357
+ continue
1358
+ }
1359
+
1360
+ if (has_supercell && displayed_site.properties?.orig_unit_cell_idx != null) {
1361
+ result.add(displayed_site.properties.orig_unit_cell_idx as number)
1362
+ } else if (displayed_site.properties?.orig_site_idx != null) {
1363
+ // Image atom (PBC ghost) — map back to its original site index
1364
+ result.add(displayed_site.properties.orig_site_idx as number)
1365
+ } else {
1366
+ result.add(scene_idx)
1367
+ }
1368
+ }
1369
+ return result
1370
+ }
1371
+
1372
+ // Try to create a Cartesian→fractional converter for the current structure's lattice
1373
+ function get_cart_to_frac(): ((xyz: Vec3) => Vec3) | undefined {
1374
+ if (!structure || !(`lattice` in structure)) return undefined
1375
+ try {
1376
+ return create_cart_to_frac((structure as Crystal).lattice.matrix)
1377
+ } catch {
1378
+ console.warn(`Failed to compute lattice inverse for fractional coordinates`)
1379
+ return undefined
1380
+ }
1381
+ }
1382
+
1383
+ // Handle atom moves from TransformControls. Applies Cartesian delta and wraps
1384
+ // fractional coords inline so normalize_fractional_coords hits its fast path.
1385
+ function handle_sites_moved(scene_indices: number[], delta: Vec3) {
1386
+ if (!structure?.sites) return
1387
+ is_internal_edit = true
1388
+
1389
+ const orig_indices = scene_to_structure_indices(scene_indices)
1390
+ // For crystals, wrap to [0,1) inline so normalize_fractional_coords fast-paths.
1391
+ // For molecules (no lattice), just apply the Cartesian delta directly.
1392
+ const lattice = `lattice` in structure
1393
+ ? (structure as Crystal).lattice.matrix
1394
+ : null
1395
+ const cart_to_frac = lattice ? create_cart_to_frac(lattice) : null
1396
+ const frac_to_cart = lattice ? create_frac_to_cart(lattice) : null
1397
+ structure = {
1398
+ ...structure,
1399
+ sites: structure.sites.map((site, idx) => {
1400
+ if (!orig_indices.has(idx)) return site
1401
+ const new_xyz: Vec3 = [
1402
+ site.xyz[0] + delta[0],
1403
+ site.xyz[1] + delta[1],
1404
+ site.xyz[2] + delta[2],
1405
+ ]
1406
+ if (!cart_to_frac || !frac_to_cart) {
1407
+ return { ...site, xyz: new_xyz, abc: new_xyz }
1408
+ }
1409
+ const wrapped_abc = wrap_to_unit_cell(cart_to_frac(new_xyz))
1410
+ return { ...site, xyz: frac_to_cart(wrapped_abc), abc: wrapped_abc }
1411
+ }),
1412
+ }
1413
+ }
1414
+
1415
+ // Change element symbol of selected atoms
1416
+ function handle_change_element(new_element: string) {
1417
+ if (!structure?.sites || selected_sites.length === 0) return
1418
+ const elem = normalize_element(new_element)
1419
+ if (!elem) return
1420
+ is_internal_edit = true
1421
+ push_undo()
1422
+ const orig_indices = scene_to_structure_indices(selected_sites)
1423
+ structure = {
1424
+ ...structure,
1425
+ sites: structure.sites.map((site, idx) => {
1426
+ if (!orig_indices.has(idx)) return site
1427
+ return {
1428
+ ...site,
1429
+ species: [{ element: elem, occu: 1, oxidation_state: 0 }],
1430
+ label: elem,
1431
+ }
1432
+ }),
1433
+ }
1434
+ change_element_mode = false
1435
+ change_element_value = ``
1436
+ show_toast(
1437
+ `Changed ${orig_indices.size} site${
1438
+ orig_indices.size > 1 ? `s` : ``
1439
+ } to ${elem}`,
1440
+ )
1441
+ }
1442
+
1443
+ // Handle add-atom from StructureScene click-to-place
1444
+ function handle_add_atom(xyz: Vec3, element: ElementSymbol) {
1445
+ if (!structure) return
1446
+ const elem = normalize_element(element)
1447
+ if (!elem) {
1448
+ return console.warn(`Invalid element symbol "${element}", ignoring add-atom`)
1449
+ }
1450
+ is_internal_edit = true
1451
+ push_undo()
1452
+ structure = {
1453
+ ...structure,
1454
+ sites: [...structure.sites, {
1455
+ species: [{ element: elem, occu: 1, oxidation_state: 0 }],
1456
+ xyz,
1457
+ abc: get_cart_to_frac()?.(xyz) ?? xyz,
1458
+ label: elem,
1459
+ properties: {},
1460
+ }],
1461
+ }
1462
+ show_toast(`Added ${elem} at (${xyz.map((coord) => coord.toFixed(2)).join(`, `)})`)
1463
+ }
1464
+
1465
+ // Only set background override when background_color is explicitly provided
1466
+ $effect(() => {
1467
+ if (typeof window !== `undefined` && wrapper && background_color) {
1468
+ // Convert opacity (0-1) to hex alpha value (00-FF)
1469
+ const alpha_hex = Math.round(background_opacity * 255)
1470
+ .toString(16)
1471
+ .padStart(2, `0`)
1472
+ wrapper.style.setProperty(
1473
+ `--struct-bg-override`,
1474
+ `${background_color}${alpha_hex}`,
1475
+ )
1476
+ } else if (typeof window !== `undefined` && wrapper) {
1477
+ // Remove override to use theme system
1478
+ wrapper.style.removeProperty(`--struct-bg-override`)
1479
+ }
1480
+ })
1481
+
1482
+ $effect(() => { // fullscreen and background
1483
+ if (typeof window !== `undefined`) {
1484
+ if (fullscreen && !document.fullscreenElement && wrapper) {
1485
+ wrapper.requestFullscreen().catch(console.error)
1486
+ } else if (!fullscreen && document.fullscreenElement) {
1487
+ document.exitFullscreen()
1488
+ }
1489
+ }
1490
+ set_fullscreen_bg(wrapper, fullscreen, `--struct-bg-fullscreen`)
1491
+ })
1492
+ </script>
1493
+
1494
+ <svelte:document
1495
+ onfullscreenchange={() => {
1496
+ fullscreen = Boolean(document.fullscreenElement)
1497
+ on_fullscreen_change?.({ structure, fullscreen })
1498
+ }}
1499
+ />
1500
+
1501
+ <!-- Forward shortcuts to the hovered viewer when focus is on <body> (see
1502
+ forward_window_keydown). Edit modes are excluded so destructive keys
1503
+ (delete/undo) still require focus, not just a hovering mouse. -->
1504
+ <svelte:window onkeydown={forward_window_keydown(() => hovered, handle_hover_keydown)} />
1505
+
1506
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
1507
+ <div
1508
+ class:dragover
1509
+ class:active={info_pane_open || controls_open || export_pane_open}
1510
+ class:gizmo-visible={Boolean(scene_gizmo)}
1511
+ role="application"
1512
+ tabindex="0"
1513
+ style:--canvas-cursor={canvas_cursor}
1514
+ aria-label="Structure viewer"
1515
+ bind:this={wrapper}
1516
+ bind:clientWidth={width}
1517
+ bind:clientHeight={height}
1518
+ onmouseenter={() => (hovered = true)}
1519
+ onmouseleave={() => (hovered = false)}
1520
+ onfocusin={() => (focused = true)}
1521
+ onfocusout={(event) => {
1522
+ if (!(event.relatedTarget instanceof Node) || !wrapper?.contains(event.relatedTarget)) {
1523
+ focused = false
1524
+ }
1525
+ }}
1526
+ ondblclick={(event) => {
1527
+ const target = event.target
1528
+ if (!(target instanceof HTMLElement)) return
1529
+ // Don't reset if double-click was on UI controls/panes/legend
1530
+ if (
1531
+ [`.control-buttons`, `.structure-legend`, `.atom-legend`, `.info-pane`, `.export-pane`, `.controls-pane`].some((selector) => target.closest(selector))
1532
+ || target.tagName === `BUTTON` || target.tagName === `INPUT` || target.tagName === `SELECT`
1533
+ ) return
1534
+ // Reset camera for double-clicks on the 3D scene
1535
+ reset_camera()
1536
+ }}
1537
+ ondrop={handle_file_drop}
1538
+ ondragover={(event) => {
1539
+ event.preventDefault()
1540
+ if (!allow_file_drop) return
1541
+ dragover = true
1542
+ }}
1543
+ ondragleave={(event) => {
1544
+ event.preventDefault()
1545
+ dragover = false
1546
+ }}
1547
+ onkeydown={handle_and_prevent(handle_keydown)}
1548
+ {...rest}
1549
+ class="structure {rest.class ?? ``}"
1550
+ >
1551
+ {@render children?.({ structure, fullscreen })}
1552
+ {#if loading}
1553
+ <Spinner
1554
+ text="Loading structure..."
1555
+ {...spinner_props}
1556
+ style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%)"
1557
+ />
1558
+ {:else if error_msg}
1559
+ <StatusMessage bind:message={error_msg} type="error" dismissible />
1560
+ {:else if (structure?.sites?.length ?? 0) > 0}
1561
+ <section
1562
+ class="control-buttons {controls_config.class}"
1563
+ style={controls_config.style}
1564
+ >
1565
+ {#if controls_config.mode !== `never`}
1566
+ {#if camera_has_moved && controls_config.visible(`reset-camera`)}
1567
+ <button class="reset-camera" onclick={reset_camera} title={reset_text}>
1568
+ <!-- Target/Focus icon for reset camera -->
1569
+ <Icon icon="Reset" />
1570
+ </button>
1571
+ {/if}
1572
+ {#if fullscreen_toggle && controls_config.visible(`fullscreen`)}
1573
+ <button
1574
+ type="button"
1575
+ onclick={() => fullscreen_toggle && toggle_fullscreen(wrapper)}
1576
+ title="{fullscreen ? `Exit` : `Enter`} fullscreen"
1577
+ aria-pressed={fullscreen}
1578
+ class="fullscreen-toggle"
1579
+ style="padding: 0 3px"
1580
+ {@attach tooltip()}
1581
+ >
1582
+ {#if typeof fullscreen_toggle === `function`}
1583
+ {@render fullscreen_toggle({ fullscreen })}
1584
+ {:else}
1585
+ <Icon icon="{fullscreen ? `Exit` : ``}Fullscreen" />
1586
+ {/if}
1587
+ </button>
1588
+ {/if}
1589
+
1590
+ {#if enable_measure_mode && controls_config.visible(`measure-mode`)}
1591
+ <div
1592
+ class="measure-mode-dropdown"
1593
+ {@attach click_outside({ callback: () => measure_menu_open = false })}
1594
+ >
1595
+ <button
1596
+ onclick={() => (measure_menu_open = !measure_menu_open)}
1597
+ title="Measure / Edit"
1598
+ class="view-mode-button"
1599
+ class:active={measure_menu_open}
1600
+ aria-expanded={measure_menu_open}
1601
+ style="transform: scale(1.2)"
1602
+ >
1603
+ {#if show_measure_selection_limit}
1604
+ <span class="selection-limit-text">
1605
+ {measured_sites.length}/{MAX_SELECTED_SITES}
1606
+ </span>
1607
+ {:else}
1608
+ <Icon
1609
+ icon={({
1610
+ distance: `Ruler`,
1611
+ angle: `Angle`,
1612
+ 'edit-bonds': `Link`,
1613
+ 'edit-atoms': `Edit`,
1614
+ } as const)[measure_mode]}
1615
+ />
1616
+ {/if}
1617
+ <Icon
1618
+ icon="Arrow{measure_menu_open ? `Up` : `Down`}"
1619
+ style="margin-left: -2px"
1620
+ />
1621
+ </button>
1622
+ {#if show_selection_reset}
1623
+ <button
1624
+ type="button"
1625
+ aria-label="Reset selection and bond edits"
1626
+ onclick={() => {
1627
+ clear_selection()
1628
+ clear_bond_edits()
1629
+ }}
1630
+ >
1631
+ <Icon icon="Reset" style="margin-left: -4px" />
1632
+ </button>
1633
+ {/if}
1634
+ {#if measure_menu_open}
1635
+ <div class="view-mode-dropdown">
1636
+ {#each [
1637
+ { mode: `distance`, icon: `Ruler`, label: `Distance`, scale: 1.1 },
1638
+ { mode: `angle`, icon: `Angle`, label: `Angle`, scale: 1.3 },
1639
+ { mode: `edit-atoms`, icon: `Edit`, label: `Edit Atoms`, scale: 1.0 },
1640
+ { mode: `edit-bonds`, icon: `Link`, label: `Edit Bonds`, scale: 1.0 },
1641
+ ] as const as { mode, icon, label, scale } (mode)}
1642
+ <button
1643
+ class="view-mode-option"
1644
+ class:selected={measure_mode === mode}
1645
+ disabled={mode === `edit-bonds` && !bond_edits_enabled}
1646
+ title={mode === `edit-bonds` && !bond_edits_enabled
1647
+ ? `Bond editing is only available for the original 1x1x1 cell`
1648
+ : label}
1649
+ onclick={() => {
1650
+ if (mode === `edit-bonds` && !bond_edits_enabled) return
1651
+ ;[measure_mode, measure_menu_open] = [mode, false]
1652
+ }}
1653
+ >
1654
+ <Icon {icon} style="transform: scale({scale})" />
1655
+ <span>{label}</span>
1656
+ </button>
1657
+ {/each}
1658
+ </div>
1659
+ {/if}
1660
+ </div>
1661
+
1662
+ <!-- Undo/redo buttons (only in edit-atoms mode) -->
1663
+ {#if measure_mode === `edit-atoms`}
1664
+ <div class="undo-redo-container">
1665
+ <button
1666
+ type="button"
1667
+ aria-label="Undo (Cmd/Ctrl+Z)"
1668
+ disabled={undo_stack.length === 0}
1669
+ onclick={undo}
1670
+ title="Undo (Cmd/Ctrl+Z)"
1671
+ class="undo-redo-btn"
1672
+ >
1673
+ <Icon icon="Undo" />
1674
+ {#if undo_stack.length > 0}
1675
+ <span class="history-count">{undo_stack.length}</span>
1676
+ {/if}
1677
+ </button>
1678
+ <button
1679
+ type="button"
1680
+ aria-label="Redo (Cmd/Ctrl+Y or Cmd+Shift+Z)"
1681
+ disabled={redo_stack.length === 0}
1682
+ onclick={redo}
1683
+ title="Redo (Cmd/Ctrl+Y or Cmd+Shift+Z)"
1684
+ class="undo-redo-btn"
1685
+ >
1686
+ <Icon icon="Redo" />
1687
+ {#if redo_stack.length > 0}
1688
+ <span class="history-count">{redo_stack.length}</span>
1689
+ {/if}
1690
+ </button>
1691
+ </div>
1692
+ {/if}
1693
+
1694
+ {#if measure_mode === `edit-bonds`}
1695
+ <div class="bond-edit-toolbar" aria-label="Bond editing controls">
1696
+ {#if bond_edit_mode === `add`}
1697
+ <label>
1698
+ <span>Bond order</span>
1699
+ <select bind:value={bond_edit_order}>
1700
+ {#each BOND_ORDER_OPTIONS as { order, label } (label)}
1701
+ <option value={order}>{label}</option>
1702
+ {/each}
1703
+ </select>
1704
+ </label>
1705
+ {/if}
1706
+ <div class="bond-edit-mode-toggle">
1707
+ {#each [
1708
+ { mode: `add`, label: `Add`, title: `Add: click two atoms` },
1709
+ { mode: `delete`, label: `Delete`, title: `Delete: click a bond` },
1710
+ ] as const as { mode, label, title } (mode)}
1711
+ <button
1712
+ type="button"
1713
+ class:selected={bond_edit_mode === mode}
1714
+ aria-pressed={bond_edit_mode === mode}
1715
+ title="{title} ({label[0]})"
1716
+ onclick={() => (bond_edit_mode = mode)}
1717
+ >
1718
+ {label}
1719
+ </button>
1720
+ {/each}
1721
+ </div>
1722
+ </div>
1723
+ <div class="undo-redo-container">
1724
+ <button
1725
+ type="button"
1726
+ aria-label="Undo bond edit (Cmd/Ctrl+Z)"
1727
+ disabled={bond_undo_stack.length === 0}
1728
+ onclick={undo_bond_edit}
1729
+ title="Undo bond edit (Cmd/Ctrl+Z)"
1730
+ class="undo-redo-btn"
1731
+ >
1732
+ <Icon icon="Undo" />
1733
+ {#if bond_undo_stack.length > 0}
1734
+ <span class="history-count">{bond_undo_stack.length}</span>
1735
+ {/if}
1736
+ </button>
1737
+ <button
1738
+ type="button"
1739
+ aria-label="Redo bond edit (Cmd/Ctrl+Y or Cmd+Shift+Z)"
1740
+ disabled={bond_redo_stack.length === 0}
1741
+ onclick={redo_bond_edit}
1742
+ title="Redo bond edit (Cmd/Ctrl+Y or Cmd+Shift+Z)"
1743
+ class="undo-redo-btn"
1744
+ >
1745
+ <Icon icon="Redo" />
1746
+ {#if bond_redo_stack.length > 0}
1747
+ <span class="history-count">{bond_redo_stack.length}</span>
1748
+ {/if}
1749
+ </button>
1750
+ </div>
1751
+ {/if}
1752
+
1753
+ <!-- Add-atom element input (shown when add_atom_mode is active) -->
1754
+ {#if measure_mode === `edit-atoms` && add_atom_mode}
1755
+ <div class="add-atom-input">
1756
+ <label>
1757
+ <span>Element:</span>
1758
+ <input
1759
+ type="text"
1760
+ bind:value={add_element}
1761
+ maxlength="2"
1762
+ placeholder="C"
1763
+ style="width: 3em; text-align: center"
1764
+ />
1765
+ </label>
1766
+ <span style="font-size: 0.75em; opacity: 0.7">Click to place</span>
1767
+ </div>
1768
+ {/if}
1769
+
1770
+ <!-- Change-element input (shown when 'e' pressed with selection) -->
1771
+ {#if measure_mode === `edit-atoms` && change_element_mode &&
1772
+ selected_sites.length > 0}
1773
+ <div class="add-atom-input">
1774
+ <label>
1775
+ <span>New element:</span>
1776
+ <input
1777
+ type="text"
1778
+ bind:value={change_element_value}
1779
+ maxlength="2"
1780
+ placeholder="Fe"
1781
+ style="width: 3em; text-align: center"
1782
+ onkeydown={(event: KeyboardEvent) => {
1783
+ if (event.key === `Enter`) {
1784
+ handle_change_element(change_element_value)
1785
+ } else if (event.key === `Escape`) {
1786
+ change_element_mode = false
1787
+ }
1788
+ event.stopPropagation()
1789
+ }}
1790
+ {@attach (node: HTMLInputElement) => {
1791
+ node.focus()
1792
+ }}
1793
+ />
1794
+ </label>
1795
+ <span style="font-size: 0.75em; opacity: 0.7">Enter to apply</span>
1796
+ </div>
1797
+ {/if}
1798
+ {/if}
1799
+
1800
+ {#if enable_info_pane && normalized_structure &&
1801
+ controls_config.visible(`info-pane`)}
1802
+ <StructureInfoPane
1803
+ structure={normalized_structure}
1804
+ bind:pane_open={info_pane_open}
1805
+ bind:highlighted_sites
1806
+ bind:hovered_site_idx
1807
+ bind:selected_sites
1808
+ {sym_data}
1809
+ {@attach tooltip({ content: `Structure info pane` })}
1810
+ />
1811
+ {/if}
1812
+
1813
+ {#if controls_config.visible(`export-pane`)}
1814
+ <StructureExportPane
1815
+ bind:export_pane_open
1816
+ structure={normalized_structure}
1817
+ {wrapper}
1818
+ {scene}
1819
+ {camera}
1820
+ bind:png_dpi
1821
+ pane_props={{ style: `max-height: calc(${height}px - 50px)` }}
1822
+ />
1823
+ {/if}
1824
+
1825
+ {#if controls_config.visible(`controls`)}
1826
+ <StructureControls
1827
+ bind:controls_open
1828
+ bind:scene_props
1829
+ bind:lattice_props
1830
+ bind:show_image_atoms
1831
+ bind:supercell_scaling
1832
+ bind:background_color
1833
+ bind:background_opacity
1834
+ bind:color_scheme
1835
+ bind:atom_color_config
1836
+ bind:cell_type
1837
+ bind:volumetric_data
1838
+ bind:isosurface_settings
1839
+ bind:active_volume_idx
1840
+ {structure}
1841
+ {supercell_loading}
1842
+ {sym_data}
1843
+ />
1844
+ {/if}
1845
+
1846
+ {@render top_right_controls?.()}
1847
+ {/if}
1848
+ </section>
1849
+
1850
+ <AtomLegend
1851
+ bind:atom_color_config
1852
+ {property_colors}
1853
+ elements={get_element_counts(supercell_structure ?? structure!)}
1854
+ bind:hidden_elements
1855
+ bind:hidden_prop_vals
1856
+ bind:element_mapping
1857
+ bind:element_radius_overrides
1858
+ bind:site_radius_overrides
1859
+ selected_sites={atom_legend_selected_sites}
1860
+ structure={displayed_structure}
1861
+ show_mode_toggle={viewer_active}
1862
+ {sym_data}
1863
+ >
1864
+ {#if structure && `lattice` in structure}
1865
+ <CellSelect
1866
+ bind:supercell_scaling
1867
+ bind:cell_type
1868
+ {sym_data}
1869
+ loading={supercell_loading}
1870
+ direction="up"
1871
+ />
1872
+ {/if}
1873
+ </AtomLegend>
1874
+
1875
+ <!-- prevent from rendering in vitest runner since WebGLRenderingContext not available -->
1876
+ {#if typeof WebGLRenderingContext !== `undefined`}
1877
+ <!-- prevent HTML labels from rendering outside of the canvas -->
1878
+ <div style="overflow: hidden; height: 100%; width: 100%">
1879
+ <Canvas>
1880
+ <StructureScene
1881
+ structure={displayed_structure}
1882
+ base_structure={cell_transformed_structure}
1883
+ {...scene_props}
1884
+ gizmo={scene_gizmo}
1885
+ {lattice_props}
1886
+ volumetric_data={supercell_volume}
1887
+ {isosurface_settings}
1888
+ bind:camera_is_moving
1889
+ bind:selected_sites
1890
+ active_sites={active_scene_sites}
1891
+ bind:hovered_idx={hovered_site_idx}
1892
+ bind:measured_sites
1893
+ bind:scene
1894
+ bind:camera
1895
+ bind:orbit_controls
1896
+ bind:rotation_target_ref
1897
+ bind:initial_computed_zoom
1898
+ bind:hidden_elements
1899
+ bind:hidden_prop_vals
1900
+ bind:element_radius_overrides
1901
+ bind:site_radius_overrides
1902
+ bind:added_bonds
1903
+ bind:removed_bonds
1904
+ bind:bond_order_overrides
1905
+ {bond_edits_enabled}
1906
+ bind:bond_edit_mode
1907
+ {bond_edit_order}
1908
+ {measure_mode}
1909
+ {width}
1910
+ {height}
1911
+ {atom_color_config}
1912
+ {sym_data}
1913
+ on_sites_moved={handle_sites_moved}
1914
+ on_operation_start={push_undo}
1915
+ on_bond_edit_start={push_bond_undo}
1916
+ on_add_atom={handle_add_atom}
1917
+ bind:add_atom_mode
1918
+ bind:add_element
1919
+ bind:cursor={canvas_cursor}
1920
+ bind:dragging_atoms
1921
+ />
1922
+ </Canvas>
1923
+ </div>
1924
+ {/if}
1925
+
1926
+ <div class="bottom-left">
1927
+ {@render bottom_left?.({ structure: displayed_structure })}
1928
+ </div>
1929
+
1930
+ {#if toast_msg}
1931
+ <div class="edit-toast">{toast_msg}</div>
1932
+ {/if}
1933
+
1934
+ {#if symmetry_error}
1935
+ <div class="symmetry-error">
1936
+ <span>{symmetry_error}</span>
1937
+ <button onclick={() => (symmetry_error = undefined)} aria-label="Dismiss">
1938
+ ×
1939
+ </button>
1940
+ </div>
1941
+ {/if}
1942
+ {:else if structure}
1943
+ <p class="warn">No sites found in structure</p>
1944
+ {:else}
1945
+ <p class="warn">No structure provided</p>
1946
+ {/if}
1947
+ </div>
1948
+
1949
+ <style>
1950
+ .structure {
1951
+ position: relative;
1952
+ container-type: size; /* enable cqh/cqw for internal panes */
1953
+ height: var(--struct-height, 500px);
1954
+ width: var(--struct-width, 100%);
1955
+ max-width: var(--struct-max-width, 100%);
1956
+ min-width: var(--struct-min-width, 300px);
1957
+ border-radius: var(--struct-border-radius, var(--border-radius, 3pt));
1958
+ background: var(--struct-bg-override, var(--struct-bg));
1959
+ color: var(--struct-text-color);
1960
+ display: flex;
1961
+ }
1962
+ .structure.active {
1963
+ z-index: var(--struct-active-z-index, 2);
1964
+ }
1965
+ .structure:fullscreen {
1966
+ background: var(--struct-bg-fullscreen, var(--struct-bg));
1967
+ overflow: hidden;
1968
+ }
1969
+ .structure:fullscreen :global(canvas) {
1970
+ height: 100vh !important;
1971
+ width: 100vw !important;
1972
+ }
1973
+ .structure.dragover {
1974
+ background: var(--struct-dragover-bg, var(--dragover-bg));
1975
+ border: var(--struct-dragover-border, var(--dragover-border));
1976
+ }
1977
+ /* Ensure canvas is transparent so the themed --struct-bg shows through */
1978
+ .structure :global(canvas) {
1979
+ background: transparent;
1980
+ cursor: var(--canvas-cursor, default);
1981
+ }
1982
+ .structure:not(.gizmo-visible) :global(.responsive-gizmo) {
1983
+ opacity: 0;
1984
+ pointer-events: none;
1985
+ visibility: hidden;
1986
+ }
1987
+ /* Avoid accidental text selection while interacting with the viewer */
1988
+ .structure :global(canvas),
1989
+ .structure section.control-buttons,
1990
+ .structure .bottom-left {
1991
+ user-select: none;
1992
+ }
1993
+ div.bottom-left {
1994
+ position: absolute;
1995
+ bottom: 0;
1996
+ left: 0;
1997
+ font-size: var(--struct-bottom-left-font-size, 1.2em);
1998
+ padding: var(--struct-bottom-left-padding, 1pt 5pt);
1999
+ }
2000
+ section.control-buttons {
2001
+ position: absolute;
2002
+ display: flex;
2003
+ top: var(--struct-buttons-top, var(--ctrl-btn-top, 1ex));
2004
+ right: var(--struct-buttons-right, var(--ctrl-btn-right, 1ex));
2005
+ gap: 4pt;
2006
+ /* buttons need higher z-index than AtomLegend to make info/controls panes occlude legend */
2007
+ /* we also need crazy high z-index to make info/control pane occlude threlte/extras' <HTML> elements for site labels */
2008
+ z-index: var(
2009
+ --struct-buttons-z-index,
2010
+ var(--z-index-overlay-controls, 100000000)
2011
+ );
2012
+ opacity: 0;
2013
+ pointer-events: none;
2014
+ transition: opacity 0.2s ease;
2015
+ }
2016
+ /* Mode: always - controls always visible */
2017
+ section.control-buttons.always-visible {
2018
+ opacity: 1;
2019
+ pointer-events: auto;
2020
+ }
2021
+ /* Mode: hover - controls visible on component hover */
2022
+ .structure:hover section.control-buttons.hover-visible,
2023
+ .structure:focus-within section.control-buttons.hover-visible {
2024
+ opacity: 1;
2025
+ pointer-events: auto;
2026
+ }
2027
+ /* Mode: never - stays hidden (default state, no additional CSS needed) */
2028
+ section.control-buttons > :global(button) {
2029
+ background-color: transparent;
2030
+ display: flex;
2031
+ padding: 1px 6px;
2032
+ border-radius: var(--border-radius, 3pt);
2033
+ font-size: clamp(0.85em, 2cqmin, 1.3em);
2034
+ }
2035
+ section.control-buttons :global(button:hover) {
2036
+ background-color: color-mix(in srgb, currentColor 8%, transparent);
2037
+ }
2038
+ /* Match Trajectory dropdown UI */
2039
+ .view-mode-dropdown {
2040
+ position: absolute;
2041
+ top: 115%;
2042
+ right: 0;
2043
+ background: var(--surface-bg);
2044
+ border-radius: var(--border-radius, 3pt);
2045
+ box-shadow: 0 8px 16px -4px rgba(0, 0, 0, 0.3), 0 4px 8px -2px rgba(0, 0, 0, 0.1);
2046
+ display: flex;
2047
+ flex-direction: column;
2048
+ }
2049
+ .view-mode-option {
2050
+ display: flex;
2051
+ align-items: center;
2052
+ gap: 1ex;
2053
+ width: 100%;
2054
+ padding: var(--trajectory-view-mode-option-padding, 5pt);
2055
+ box-sizing: border-box;
2056
+ background: transparent;
2057
+ border-radius: 0;
2058
+ text-align: left;
2059
+ transition: background-color 0.15s ease;
2060
+ }
2061
+ .view-mode-option:first-child {
2062
+ border-top-left-radius: 3px;
2063
+ border-top-right-radius: 3px;
2064
+ }
2065
+ .view-mode-option.selected {
2066
+ color: var(--accent-color);
2067
+ }
2068
+ .view-mode-option span {
2069
+ font-weight: 500;
2070
+ white-space: nowrap;
2071
+ overflow: hidden;
2072
+ text-overflow: ellipsis;
2073
+ flex: 1;
2074
+ }
2075
+ .measure-mode-dropdown {
2076
+ display: flex;
2077
+ position: relative;
2078
+ height: fit-content;
2079
+ place-self: center;
2080
+ }
2081
+ .measure-mode-dropdown > button {
2082
+ background: transparent;
2083
+ padding: 1px 6px;
2084
+ font-size: clamp(0.85em, 2cqmin, 1.3em);
2085
+ }
2086
+ .selection-limit-text {
2087
+ font-weight: bold;
2088
+ font-size: 0.9em;
2089
+ color: var(--accent-color, #ff6b6b);
2090
+ min-width: 2.5em;
2091
+ text-align: center;
2092
+ }
2093
+ p.warn {
2094
+ position: absolute;
2095
+ inset: 0;
2096
+ display: grid;
2097
+ place-content: center;
2098
+ }
2099
+ .symmetry-error {
2100
+ position: absolute;
2101
+ bottom: 1rem;
2102
+ right: 1rem;
2103
+ background: rgba(255, 165, 0, 0.95);
2104
+ color: #000;
2105
+ padding: 0.75rem 1rem;
2106
+ border-radius: var(--border-radius, 3pt);
2107
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
2108
+ display: flex;
2109
+ gap: 1rem;
2110
+ max-width: min(90%, 400px);
2111
+ font-size: 0.9rem;
2112
+ z-index: 1000;
2113
+ }
2114
+ .symmetry-error span {
2115
+ flex: 1;
2116
+ }
2117
+ .symmetry-error button {
2118
+ background: transparent;
2119
+ border: none;
2120
+ font-size: 1.5rem;
2121
+ line-height: 1;
2122
+ padding: 0;
2123
+ cursor: pointer;
2124
+ opacity: 0.7;
2125
+ }
2126
+ .symmetry-error button:hover {
2127
+ opacity: 1;
2128
+ }
2129
+ .edit-toast {
2130
+ position: absolute;
2131
+ bottom: 3rem;
2132
+ left: 50%;
2133
+ transform: translateX(-50%);
2134
+ background: color-mix(in srgb, var(--page-bg, Canvas) 85%, currentColor);
2135
+ color: var(--text-color, currentColor);
2136
+ padding: 0.4rem 0.8rem;
2137
+ border-radius: var(--border-radius, 3pt);
2138
+ font-size: 0.8rem;
2139
+ z-index: 100;
2140
+ pointer-events: none;
2141
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
2142
+ animation: toast-fade 2s ease-in-out;
2143
+ opacity: 0;
2144
+ }
2145
+ @keyframes toast-fade {
2146
+ 0%, 70% {
2147
+ opacity: 1;
2148
+ }
2149
+ 100% {
2150
+ opacity: 0;
2151
+ }
2152
+ }
2153
+ /* CellSelect: position at left of legend, show on hover */
2154
+ .structure :global(.cell-select) {
2155
+ order: -1; /* Move to left side of AtomLegend flex container */
2156
+ opacity: 0;
2157
+ pointer-events: none;
2158
+ transition: opacity 0.3s ease;
2159
+ }
2160
+ .structure:hover :global(.cell-select) {
2161
+ opacity: 1;
2162
+ pointer-events: auto;
2163
+ }
2164
+ .undo-redo-container {
2165
+ display: flex;
2166
+ }
2167
+ .undo-redo-btn {
2168
+ position: relative;
2169
+ display: flex;
2170
+ align-items: center;
2171
+ justify-content: center;
2172
+ }
2173
+ .bond-edit-toolbar {
2174
+ --bond-edit-control-height: 1.8em;
2175
+ display: flex;
2176
+ align-items: center;
2177
+ gap: 0.4em;
2178
+ font-size: 0.8em;
2179
+ }
2180
+ .bond-edit-mode-toggle,
2181
+ .bond-edit-toolbar label {
2182
+ display: flex;
2183
+ align-items: center;
2184
+ }
2185
+ .bond-edit-mode-toggle {
2186
+ gap: 0.35em;
2187
+ }
2188
+ .bond-edit-mode-toggle button,
2189
+ .bond-edit-toolbar label,
2190
+ .bond-edit-toolbar select {
2191
+ height: var(--bond-edit-control-height);
2192
+ line-height: 1;
2193
+ }
2194
+ .bond-edit-mode-toggle button {
2195
+ min-width: 3.5em;
2196
+ font: inherit;
2197
+ }
2198
+ .bond-edit-mode-toggle button.selected {
2199
+ background: var(--accent-color, #007acc);
2200
+ color: white;
2201
+ }
2202
+ .bond-edit-mode-toggle button.selected:hover {
2203
+ background-color: color-mix(in srgb, var(--accent-color, #007acc) 70%, black);
2204
+ }
2205
+ .bond-edit-toolbar label {
2206
+ gap: 0.25em;
2207
+ }
2208
+ .bond-edit-toolbar select {
2209
+ max-width: 8em;
2210
+ font: inherit;
2211
+ }
2212
+ .history-count {
2213
+ position: absolute;
2214
+ bottom: -2px;
2215
+ right: -2px;
2216
+ background: var(--accent-color, #007acc);
2217
+ color: white;
2218
+ border-radius: 50%;
2219
+ width: 12px;
2220
+ height: 12px;
2221
+ font-size: 8px;
2222
+ font-weight: bold;
2223
+ display: flex;
2224
+ align-items: center;
2225
+ justify-content: center;
2226
+ line-height: 1;
2227
+ pointer-events: none;
2228
+ z-index: 1;
2229
+ }
2230
+ .add-atom-input {
2231
+ display: flex;
2232
+ align-items: center;
2233
+ gap: 0.5em;
2234
+ background: color-mix(in srgb, var(--page-bg, Canvas) 85%, currentColor);
2235
+ color: var(--text-color, currentColor);
2236
+ padding: 0.3em 0.6em;
2237
+ border-radius: var(--border-radius, 3pt);
2238
+ font-size: 0.8rem;
2239
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
2240
+ label {
2241
+ display: flex;
2242
+ align-items: center;
2243
+ gap: 0.3em;
2244
+ }
2245
+ input {
2246
+ background: color-mix(in srgb, currentColor 10%, transparent);
2247
+ border: 1px solid color-mix(in srgb, currentColor 20%, transparent);
2248
+ border-radius: 3px;
2249
+ color: inherit;
2250
+ font-size: 0.85rem;
2251
+ padding: 0.1em 0.3em;
2252
+ }
2253
+ }
2254
+ </style>