matterviz 0.3.7 → 0.4.1

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 (486) hide show
  1. package/dist/Icon.svelte +7 -4
  2. package/dist/MillerIndexInput.svelte +1 -1
  3. package/dist/api/optimade.js +32 -26
  4. package/dist/app.css +0 -3
  5. package/dist/brillouin/BrillouinZone.svelte +76 -148
  6. package/dist/brillouin/BrillouinZone.svelte.d.ts +6 -14
  7. package/dist/brillouin/BrillouinZoneExportPane.svelte +43 -96
  8. package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +1 -1
  9. package/dist/brillouin/BrillouinZoneInfoPane.svelte +9 -32
  10. package/dist/brillouin/BrillouinZoneInfoPane.svelte.d.ts +2 -3
  11. package/dist/brillouin/BrillouinZoneScene.svelte +97 -205
  12. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +4 -23
  13. package/dist/brillouin/BrillouinZoneTooltip.svelte +16 -25
  14. package/dist/brillouin/ReciprocalVectors.svelte +39 -0
  15. package/dist/brillouin/ReciprocalVectors.svelte.d.ts +9 -0
  16. package/dist/brillouin/compute.d.ts +2 -0
  17. package/dist/brillouin/compute.js +89 -90
  18. package/dist/brillouin/geometry.d.ts +8 -0
  19. package/dist/brillouin/geometry.js +57 -0
  20. package/dist/brillouin/index.d.ts +2 -0
  21. package/dist/brillouin/index.js +2 -0
  22. package/dist/brillouin/types.d.ts +2 -2
  23. package/dist/chempot-diagram/ChemPotDiagram.svelte +14 -13
  24. package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +1 -1
  25. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +109 -203
  26. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +4 -1
  27. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +180 -470
  28. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +7 -1
  29. package/dist/chempot-diagram/async-compute.svelte.js +3 -1
  30. package/dist/chempot-diagram/chempot-worker.js +2 -1
  31. package/dist/chempot-diagram/color.d.ts +3 -6
  32. package/dist/chempot-diagram/color.js +5 -5
  33. package/dist/chempot-diagram/compute.d.ts +4 -4
  34. package/dist/chempot-diagram/compute.js +20 -20
  35. package/dist/chempot-diagram/controls-state.svelte.d.ts +10 -0
  36. package/dist/chempot-diagram/controls-state.svelte.js +42 -0
  37. package/dist/chempot-diagram/export.d.ts +47 -0
  38. package/dist/chempot-diagram/export.js +133 -0
  39. package/dist/chempot-diagram/index.d.ts +1 -0
  40. package/dist/chempot-diagram/index.js +1 -0
  41. package/dist/chempot-diagram/pointer.d.ts +0 -10
  42. package/dist/chempot-diagram/pointer.js +4 -4
  43. package/dist/chempot-diagram/types.d.ts +3 -3
  44. package/dist/colors/index.js +8 -7
  45. package/dist/composition/FormulaFilter.svelte +18 -11
  46. package/dist/composition/PieChart.svelte +11 -10
  47. package/dist/composition/chem-sys.d.ts +8 -0
  48. package/dist/composition/chem-sys.js +86 -0
  49. package/dist/composition/format.js +7 -4
  50. package/dist/composition/index.d.ts +1 -0
  51. package/dist/composition/index.js +1 -0
  52. package/dist/composition/parse.d.ts +0 -1
  53. package/dist/composition/parse.js +41 -31
  54. package/dist/controls.d.ts +1 -0
  55. package/dist/controls.js +0 -1
  56. package/dist/convex-hull/ConvexHull.svelte +8 -10
  57. package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -4
  58. package/dist/convex-hull/ConvexHull2D.svelte +106 -185
  59. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
  60. package/dist/convex-hull/ConvexHull3D.svelte +179 -683
  61. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
  62. package/dist/convex-hull/ConvexHull4D.svelte +183 -687
  63. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
  64. package/dist/convex-hull/ConvexHullChrome.svelte +268 -0
  65. package/dist/convex-hull/ConvexHullChrome.svelte.d.ts +30 -0
  66. package/dist/convex-hull/ConvexHullControls.svelte +88 -7
  67. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +7 -6
  68. package/dist/convex-hull/ConvexHullInfoPane.svelte +18 -5
  69. package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +6 -5
  70. package/dist/convex-hull/ConvexHullStats.svelte +36 -175
  71. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +3 -1
  72. package/dist/convex-hull/ConvexHullTooltip.svelte +11 -2
  73. package/dist/convex-hull/ConvexHullTooltip.svelte.d.ts +2 -1
  74. package/dist/convex-hull/GasPressureControls.svelte +4 -4
  75. package/dist/convex-hull/TemperatureSlider.svelte +2 -2
  76. package/dist/convex-hull/barycentric-coords.d.ts +2 -4
  77. package/dist/convex-hull/barycentric-coords.js +6 -33
  78. package/dist/convex-hull/canvas-interactions.svelte.d.ts +79 -0
  79. package/dist/convex-hull/canvas-interactions.svelte.js +278 -0
  80. package/dist/convex-hull/demo-temperature.d.ts +1 -1
  81. package/dist/convex-hull/demo-temperature.js +20 -22
  82. package/dist/convex-hull/gas-thermodynamics.d.ts +2 -2
  83. package/dist/convex-hull/gas-thermodynamics.js +22 -30
  84. package/dist/convex-hull/helpers.d.ts +42 -7
  85. package/dist/convex-hull/helpers.js +171 -78
  86. package/dist/convex-hull/hull-state.svelte.d.ts +44 -0
  87. package/dist/convex-hull/hull-state.svelte.js +124 -0
  88. package/dist/convex-hull/index.d.ts +10 -8
  89. package/dist/convex-hull/index.js +7 -2
  90. package/dist/convex-hull/thermodynamics.js +136 -960
  91. package/dist/convex-hull/types.d.ts +13 -5
  92. package/dist/convex-hull/types.js +12 -0
  93. package/dist/coordination/CoordinationBarPlot.svelte +27 -34
  94. package/dist/coordination/CoordinationBarPlot.svelte.d.ts +1 -1
  95. package/dist/element/BohrAtom.svelte +2 -1
  96. package/dist/element/index.d.ts +4 -0
  97. package/dist/element/index.js +18 -0
  98. package/dist/feedback/DragOverlay.svelte +3 -1
  99. package/dist/feedback/DragOverlay.svelte.d.ts +1 -0
  100. package/dist/feedback/StatusMessage.svelte +13 -3
  101. package/dist/fermi-surface/FermiSlice.svelte +13 -5
  102. package/dist/fermi-surface/FermiSurface.svelte +78 -151
  103. package/dist/fermi-surface/FermiSurface.svelte.d.ts +5 -14
  104. package/dist/fermi-surface/FermiSurfaceControls.svelte +1 -1
  105. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  106. package/dist/fermi-surface/FermiSurfaceScene.svelte +72 -221
  107. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +3 -23
  108. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +8 -34
  109. package/dist/fermi-surface/compute.js +67 -66
  110. package/dist/fermi-surface/export.js +6 -16
  111. package/dist/fermi-surface/index.d.ts +0 -1
  112. package/dist/fermi-surface/index.js +0 -1
  113. package/dist/fermi-surface/parse.d.ts +1 -1
  114. package/dist/fermi-surface/parse.js +71 -79
  115. package/dist/fermi-surface/types.d.ts +3 -2
  116. package/dist/heatmap-matrix/HeatmapMatrix.svelte +69 -52
  117. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +4 -3
  118. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +3 -2
  119. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +5 -5
  120. package/dist/heatmap-matrix/index.d.ts +3 -2
  121. package/dist/heatmap-matrix/index.js +1 -1
  122. package/dist/index.d.ts +1 -0
  123. package/dist/index.js +1 -0
  124. package/dist/io/ExportPane.svelte +166 -0
  125. package/dist/io/ExportPane.svelte.d.ts +17 -0
  126. package/dist/io/decompress.js +5 -4
  127. package/dist/io/export.d.ts +9 -5
  128. package/dist/io/export.js +77 -51
  129. package/dist/io/fetch.d.ts +2 -1
  130. package/dist/io/fetch.js +5 -1
  131. package/dist/io/file-drop.d.ts +8 -1
  132. package/dist/io/file-drop.js +48 -36
  133. package/dist/io/index.d.ts +2 -0
  134. package/dist/io/index.js +10 -0
  135. package/dist/io/types.d.ts +13 -0
  136. package/dist/io/url-drop.js +64 -33
  137. package/dist/isosurface/parse.js +52 -51
  138. package/dist/isosurface/slice.js +5 -4
  139. package/dist/isosurface/types.js +1 -1
  140. package/dist/keyboard.d.ts +3 -0
  141. package/dist/keyboard.js +23 -0
  142. package/dist/labels.d.ts +1 -1
  143. package/dist/labels.js +9 -8
  144. package/dist/layout/FullscreenButton.svelte +33 -0
  145. package/dist/layout/FullscreenButton.svelte.d.ts +10 -0
  146. package/dist/layout/FullscreenToggle.svelte +8 -14
  147. package/dist/layout/PropertyFilter.svelte +3 -2
  148. package/dist/layout/SettingsSection.svelte +1 -1
  149. package/dist/layout/ViewerChrome.svelte +116 -0
  150. package/dist/layout/ViewerChrome.svelte.d.ts +17 -0
  151. package/dist/layout/fullscreen.d.ts +4 -0
  152. package/dist/layout/fullscreen.svelte.d.ts +8 -0
  153. package/dist/layout/fullscreen.svelte.js +37 -0
  154. package/dist/layout/index.d.ts +3 -0
  155. package/dist/layout/index.js +3 -0
  156. package/dist/layout/json-tree/JsonNode.svelte +1 -1
  157. package/dist/layout/json-tree/JsonTree.svelte +2 -2
  158. package/dist/layout/json-tree/utils.js +5 -4
  159. package/dist/marching-cubes.js +8 -13
  160. package/dist/math.d.ts +12 -4
  161. package/dist/math.js +42 -30
  162. package/dist/overlays/DraggablePane.svelte +4 -4
  163. package/dist/overlays/index.d.ts +4 -0
  164. package/dist/periodic-table/PeriodicTable.svelte +27 -15
  165. package/dist/periodic-table/PropertySelect.svelte +1 -0
  166. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +9 -3
  167. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
  168. package/dist/phase-diagram/PhaseDiagramControls.svelte +3 -2
  169. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +4 -3
  170. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +4 -2
  171. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +2 -3
  172. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +47 -132
  173. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +3 -4
  174. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +1 -1
  175. package/dist/phase-diagram/build-diagram.js +2 -2
  176. package/dist/phase-diagram/colors.js +1 -1
  177. package/dist/phase-diagram/parse.d.ts +2 -1
  178. package/dist/phase-diagram/parse.js +6 -5
  179. package/dist/phase-diagram/types.d.ts +1 -1
  180. package/dist/phase-diagram/utils.d.ts +3 -3
  181. package/dist/phase-diagram/utils.js +8 -12
  182. package/dist/plot/{BarPlot.svelte → bar/BarPlot.svelte} +246 -841
  183. package/dist/plot/{BarPlot.svelte.d.ts → bar/BarPlot.svelte.d.ts} +8 -16
  184. package/dist/plot/{BarPlotControls.svelte → bar/BarPlotControls.svelte} +6 -5
  185. package/dist/plot/{BarPlotControls.svelte.d.ts → bar/BarPlotControls.svelte.d.ts} +3 -3
  186. package/dist/plot/{SpacegroupBarPlot.svelte → bar/SpacegroupBarPlot.svelte} +8 -7
  187. package/dist/plot/{SpacegroupBarPlot.svelte.d.ts → bar/SpacegroupBarPlot.svelte.d.ts} +1 -1
  188. package/dist/plot/bar/data.d.ts +40 -0
  189. package/dist/plot/bar/data.js +154 -0
  190. package/dist/plot/bar/geometry.d.ts +39 -0
  191. package/dist/plot/bar/geometry.js +60 -0
  192. package/dist/plot/bar/index.d.ts +3 -0
  193. package/dist/plot/bar/index.js +3 -0
  194. package/dist/plot/box/BoxPlot.svelte +1292 -0
  195. package/dist/plot/box/BoxPlot.svelte.d.ts +95 -0
  196. package/dist/plot/box/BoxPlotControls.svelte +109 -0
  197. package/dist/plot/box/BoxPlotControls.svelte.d.ts +19 -0
  198. package/dist/plot/box/Violin.svelte +14 -0
  199. package/dist/plot/box/Violin.svelte.d.ts +70 -0
  200. package/dist/plot/box/box-plot.d.ts +56 -0
  201. package/dist/plot/box/box-plot.js +129 -0
  202. package/dist/plot/box/index.d.ts +5 -0
  203. package/dist/plot/box/index.js +5 -0
  204. package/dist/plot/box/kde.d.ts +17 -0
  205. package/dist/plot/box/kde.js +160 -0
  206. package/dist/plot/box/quantile.d.ts +3 -0
  207. package/dist/plot/box/quantile.js +53 -0
  208. package/dist/plot/{auto-place.d.ts → core/auto-place.d.ts} +1 -1
  209. package/dist/plot/{auto-place.js → core/auto-place.js} +6 -3
  210. package/dist/plot/core/axis-utils.d.ts +46 -0
  211. package/dist/plot/core/axis-utils.js +110 -0
  212. package/dist/plot/{AxisLabel.svelte → core/components/AxisLabel.svelte} +2 -2
  213. package/dist/plot/{AxisLabel.svelte.d.ts → core/components/AxisLabel.svelte.d.ts} +1 -1
  214. package/dist/plot/{ColorBar.svelte → core/components/ColorBar.svelte} +41 -38
  215. package/dist/plot/{ColorBar.svelte.d.ts → core/components/ColorBar.svelte.d.ts} +7 -6
  216. package/dist/plot/{ColorScaleSelect.svelte → core/components/ColorScaleSelect.svelte} +4 -3
  217. package/dist/plot/{ColorScaleSelect.svelte.d.ts → core/components/ColorScaleSelect.svelte.d.ts} +2 -2
  218. package/dist/plot/core/components/ControlPane.svelte +46 -0
  219. package/dist/plot/core/components/ControlPane.svelte.d.ts +13 -0
  220. package/dist/plot/{FillArea.svelte → core/components/FillArea.svelte} +17 -6
  221. package/dist/plot/{FillArea.svelte.d.ts → core/components/FillArea.svelte.d.ts} +1 -1
  222. package/dist/plot/{InteractiveAxisLabel.svelte → core/components/InteractiveAxisLabel.svelte} +3 -3
  223. package/dist/plot/{InteractiveAxisLabel.svelte.d.ts → core/components/InteractiveAxisLabel.svelte.d.ts} +2 -2
  224. package/dist/plot/{Line.svelte → core/components/Line.svelte} +33 -15
  225. package/dist/plot/{Line.svelte.d.ts → core/components/Line.svelte.d.ts} +3 -2
  226. package/dist/plot/{PlotAxis.svelte → core/components/PlotAxis.svelte} +9 -6
  227. package/dist/plot/{PlotAxis.svelte.d.ts → core/components/PlotAxis.svelte.d.ts} +5 -3
  228. package/dist/plot/{PlotControls.svelte → core/components/PlotControls.svelte} +17 -29
  229. package/dist/plot/core/components/PlotControls.svelte.d.ts +4 -0
  230. package/dist/plot/{PlotLegend.svelte → core/components/PlotLegend.svelte} +21 -10
  231. package/dist/plot/{PlotLegend.svelte.d.ts → core/components/PlotLegend.svelte.d.ts} +3 -2
  232. package/dist/plot/{PlotTooltip.svelte → core/components/PlotTooltip.svelte} +17 -1
  233. package/dist/plot/{PlotTooltip.svelte.d.ts → core/components/PlotTooltip.svelte.d.ts} +8 -0
  234. package/dist/plot/{PortalSelect.svelte → core/components/PortalSelect.svelte} +11 -7
  235. package/dist/plot/{ReferenceLine.svelte → core/components/ReferenceLine.svelte} +3 -3
  236. package/dist/plot/{ReferenceLine.svelte.d.ts → core/components/ReferenceLine.svelte.d.ts} +1 -1
  237. package/dist/plot/{ReferenceLine3D.svelte → core/components/ReferenceLine3D.svelte} +5 -5
  238. package/dist/plot/{ReferenceLine3D.svelte.d.ts → core/components/ReferenceLine3D.svelte.d.ts} +5 -5
  239. package/dist/plot/{ReferencePlane.svelte → core/components/ReferencePlane.svelte} +8 -8
  240. package/dist/plot/{ReferencePlane.svelte.d.ts → core/components/ReferencePlane.svelte.d.ts} +5 -5
  241. package/dist/plot/{ZeroLines.svelte → core/components/ZeroLines.svelte} +3 -3
  242. package/dist/plot/{ZeroLines.svelte.d.ts → core/components/ZeroLines.svelte.d.ts} +3 -3
  243. package/dist/plot/{ZoomRect.svelte → core/components/ZoomRect.svelte} +1 -1
  244. package/dist/plot/{ZoomRect.svelte.d.ts → core/components/ZoomRect.svelte.d.ts} +1 -1
  245. package/dist/plot/core/components/index.d.ts +17 -0
  246. package/dist/plot/core/components/index.js +17 -0
  247. package/dist/plot/{data-cleaning.d.ts → core/data-cleaning.d.ts} +71 -1
  248. package/dist/plot/{data-cleaning.js → core/data-cleaning.js} +21 -23
  249. package/dist/plot/{data-transform.d.ts → core/data-transform.d.ts} +2 -2
  250. package/dist/plot/{data-transform.js → core/data-transform.js} +3 -3
  251. package/dist/plot/core/fill-utils.d.ts +34 -0
  252. package/dist/plot/core/fill-utils.js +391 -0
  253. package/dist/plot/core/index.d.ts +10 -0
  254. package/dist/plot/core/index.js +11 -0
  255. package/dist/plot/core/interactions.d.ts +39 -0
  256. package/dist/plot/core/interactions.js +209 -0
  257. package/dist/plot/{layout.d.ts → core/layout.d.ts} +1 -0
  258. package/dist/plot/{layout.js → core/layout.js} +16 -8
  259. package/dist/plot/core/pan-zoom.svelte.d.ts +35 -0
  260. package/dist/plot/core/pan-zoom.svelte.js +221 -0
  261. package/dist/plot/core/placed-tween.svelte.d.ts +21 -0
  262. package/dist/plot/core/placed-tween.svelte.js +68 -0
  263. package/dist/plot/{reference-line.d.ts → core/reference-line.d.ts} +11 -11
  264. package/dist/plot/{reference-line.js → core/reference-line.js} +29 -42
  265. package/dist/plot/core/scales.d.ts +40 -0
  266. package/dist/plot/{scales.js → core/scales.js} +94 -93
  267. package/dist/plot/core/svg.d.ts +3 -0
  268. package/dist/plot/core/svg.js +41 -0
  269. package/dist/plot/{types.d.ts → core/types.d.ts} +36 -85
  270. package/dist/plot/{types.js → core/types.js} +1 -1
  271. package/dist/plot/{utils → core/utils}/label-placement.d.ts +3 -3
  272. package/dist/plot/{utils → core/utils}/label-placement.js +3 -3
  273. package/dist/plot/core/utils/series-visibility.d.ts +26 -0
  274. package/dist/plot/{utils → core/utils}/series-visibility.js +29 -2
  275. package/dist/plot/core/utils.d.ts +12 -0
  276. package/dist/plot/core/utils.js +27 -0
  277. package/dist/plot/{Histogram.svelte → histogram/Histogram.svelte} +174 -551
  278. package/dist/plot/{Histogram.svelte.d.ts → histogram/Histogram.svelte.d.ts} +2 -2
  279. package/dist/plot/{HistogramControls.svelte → histogram/HistogramControls.svelte} +6 -6
  280. package/dist/plot/{HistogramControls.svelte.d.ts → histogram/HistogramControls.svelte.d.ts} +4 -4
  281. package/dist/plot/histogram/index.d.ts +2 -0
  282. package/dist/plot/histogram/index.js +2 -0
  283. package/dist/plot/index.d.ts +8 -41
  284. package/dist/plot/index.js +10 -39
  285. package/dist/plot/sankey/Sankey.svelte +697 -0
  286. package/dist/plot/sankey/Sankey.svelte.d.ts +74 -0
  287. package/dist/plot/sankey/SankeyControls.svelte +98 -0
  288. package/dist/plot/sankey/SankeyControls.svelte.d.ts +19 -0
  289. package/dist/plot/sankey/index.d.ts +4 -0
  290. package/dist/plot/sankey/index.js +3 -0
  291. package/dist/plot/sankey/sankey-types.d.ts +42 -0
  292. package/dist/plot/sankey/sankey-types.js +4 -0
  293. package/dist/plot/sankey/sankey.d.ts +52 -0
  294. package/dist/plot/sankey/sankey.js +189 -0
  295. package/dist/plot/{BinnedScatterPlot.svelte → scatter/BinnedScatterPlot.svelte} +64 -64
  296. package/dist/plot/{BinnedScatterPlot.svelte.d.ts → scatter/BinnedScatterPlot.svelte.d.ts} +6 -6
  297. package/dist/plot/{ElementScatter.svelte → scatter/ElementScatter.svelte} +6 -6
  298. package/dist/plot/{ElementScatter.svelte.d.ts → scatter/ElementScatter.svelte.d.ts} +2 -2
  299. package/dist/plot/{ScatterPlot.svelte → scatter/ScatterPlot.svelte} +297 -1008
  300. package/dist/plot/{ScatterPlot.svelte.d.ts → scatter/ScatterPlot.svelte.d.ts} +10 -18
  301. package/dist/plot/{ScatterPlotControls.svelte → scatter/ScatterPlotControls.svelte} +6 -5
  302. package/dist/plot/{ScatterPlotControls.svelte.d.ts → scatter/ScatterPlotControls.svelte.d.ts} +2 -2
  303. package/dist/plot/{ScatterPoint.svelte → scatter/ScatterPoint.svelte} +7 -7
  304. package/dist/plot/{ScatterPoint.svelte.d.ts → scatter/ScatterPoint.svelte.d.ts} +3 -3
  305. package/dist/plot/{adaptive-density.d.ts → scatter/adaptive-density.d.ts} +14 -4
  306. package/dist/plot/{adaptive-density.js → scatter/adaptive-density.js} +46 -20
  307. package/dist/plot/{binned-scatter-types.d.ts → scatter/binned-scatter-types.d.ts} +5 -12
  308. package/dist/plot/scatter/index.d.ts +7 -0
  309. package/dist/plot/scatter/index.js +5 -0
  310. package/dist/plot/scatter/scatter-data.d.ts +19 -0
  311. package/dist/plot/scatter/scatter-data.js +212 -0
  312. package/dist/plot/{ScatterPlot3D.svelte → scatter-3d/ScatterPlot3D.svelte} +25 -34
  313. package/dist/plot/{ScatterPlot3D.svelte.d.ts → scatter-3d/ScatterPlot3D.svelte.d.ts} +9 -17
  314. package/dist/plot/{ScatterPlot3DControls.svelte → scatter-3d/ScatterPlot3DControls.svelte} +14 -14
  315. package/dist/plot/{ScatterPlot3DControls.svelte.d.ts → scatter-3d/ScatterPlot3DControls.svelte.d.ts} +6 -6
  316. package/dist/plot/{ScatterPlot3DScene.svelte → scatter-3d/ScatterPlot3DScene.svelte} +129 -128
  317. package/dist/plot/{ScatterPlot3DScene.svelte.d.ts → scatter-3d/ScatterPlot3DScene.svelte.d.ts} +6 -15
  318. package/dist/plot/{Surface3D.svelte → scatter-3d/Surface3D.svelte} +7 -6
  319. package/dist/plot/{Surface3D.svelte.d.ts → scatter-3d/Surface3D.svelte.d.ts} +5 -4
  320. package/dist/plot/scatter-3d/index.d.ts +4 -0
  321. package/dist/plot/scatter-3d/index.js +4 -0
  322. package/dist/plot/sunburst/Sunburst.svelte +1041 -0
  323. package/dist/plot/sunburst/Sunburst.svelte.d.ts +97 -0
  324. package/dist/plot/sunburst/SunburstControls.svelte +200 -0
  325. package/dist/plot/sunburst/SunburstControls.svelte.d.ts +26 -0
  326. package/dist/plot/sunburst/index.d.ts +4 -0
  327. package/dist/plot/sunburst/index.js +4 -0
  328. package/dist/plot/sunburst/render.d.ts +34 -0
  329. package/dist/plot/sunburst/render.js +122 -0
  330. package/dist/plot/sunburst/sunburst.d.ts +62 -0
  331. package/dist/plot/sunburst/sunburst.js +269 -0
  332. package/dist/rdf/RdfPlot.svelte +2 -1
  333. package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
  334. package/dist/rdf/calc-rdf.js +11 -24
  335. package/dist/sanitize.js +14 -3
  336. package/dist/scene/SceneCamera.svelte +62 -0
  337. package/dist/scene/SceneCamera.svelte.d.ts +19 -0
  338. package/dist/scene/bind-renderer.svelte.d.ts +2 -0
  339. package/dist/scene/bind-renderer.svelte.js +14 -0
  340. package/dist/scene/index.d.ts +4 -0
  341. package/dist/scene/index.js +5 -0
  342. package/dist/scene/props.js +52 -0
  343. package/dist/scene/types.d.ts +26 -0
  344. package/dist/scene/types.js +1 -0
  345. package/dist/settings.d.ts +79 -3
  346. package/dist/settings.js +321 -1
  347. package/dist/spectral/Bands.svelte +47 -36
  348. package/dist/spectral/Bands.svelte.d.ts +6 -6
  349. package/dist/spectral/BandsAndDos.svelte +23 -25
  350. package/dist/spectral/BrillouinBandsDos.svelte +42 -30
  351. package/dist/spectral/Dos.svelte +15 -23
  352. package/dist/spectral/Dos.svelte.d.ts +4 -3
  353. package/dist/spectral/helpers.d.ts +8 -6
  354. package/dist/spectral/helpers.js +137 -65
  355. package/dist/state.svelte.d.ts +0 -7
  356. package/dist/state.svelte.js +0 -6
  357. package/dist/structure/Arrow.svelte +2 -4
  358. package/dist/structure/AtomLegend.svelte +8 -9
  359. package/dist/structure/AtomLegend.svelte.d.ts +1 -1
  360. package/dist/structure/CanvasTooltip.svelte +1 -0
  361. package/dist/structure/CellSelect.svelte +12 -5
  362. package/dist/structure/CellSelect.svelte.d.ts +2 -1
  363. package/dist/structure/Cylinder.svelte +12 -8
  364. package/dist/structure/Cylinder.svelte.d.ts +4 -1
  365. package/dist/structure/Lattice.svelte +2 -2
  366. package/dist/structure/Structure.svelte +365 -423
  367. package/dist/structure/Structure.svelte.d.ts +5 -15
  368. package/dist/structure/StructureControls.svelte +217 -2
  369. package/dist/structure/StructureControls.svelte.d.ts +5 -3
  370. package/dist/structure/StructureExportPane.svelte +54 -156
  371. package/dist/structure/StructureExportPane.svelte.d.ts +4 -5
  372. package/dist/structure/StructureInfoPane.svelte +10 -9
  373. package/dist/structure/StructureInfoPane.svelte.d.ts +5 -5
  374. package/dist/structure/StructureScene.svelte +376 -208
  375. package/dist/structure/StructureScene.svelte.d.ts +22 -20
  376. package/dist/structure/{label-placement.d.ts → atom-label-placement.d.ts} +3 -3
  377. package/dist/structure/{label-placement.js → atom-label-placement.js} +15 -5
  378. package/dist/structure/atom-properties.d.ts +1 -1
  379. package/dist/structure/atom-properties.js +17 -22
  380. package/dist/structure/bond-order-perception.js +3 -5
  381. package/dist/structure/bonding.d.ts +4 -0
  382. package/dist/structure/bonding.js +134 -63
  383. package/dist/structure/export.d.ts +24 -4
  384. package/dist/structure/export.js +89 -143
  385. package/dist/structure/index.d.ts +4 -4
  386. package/dist/structure/index.js +3 -3
  387. package/dist/structure/measure.d.ts +3 -2
  388. package/dist/structure/measure.js +6 -5
  389. package/dist/structure/parse.d.ts +3 -2
  390. package/dist/structure/parse.js +419 -438
  391. package/dist/structure/partial-occupancy.d.ts +0 -1
  392. package/dist/structure/partial-occupancy.js +1 -1
  393. package/dist/structure/pbc.d.ts +1 -1
  394. package/dist/structure/pbc.js +190 -13
  395. package/dist/structure/polyhedra.d.ts +41 -0
  396. package/dist/structure/polyhedra.js +602 -0
  397. package/dist/structure/site.d.ts +4 -0
  398. package/dist/structure/site.js +1 -0
  399. package/dist/structure/supercell.js +3 -2
  400. package/dist/structure/validation.js +5 -6
  401. package/dist/symmetry/SymmetryElementControls.svelte +69 -0
  402. package/dist/symmetry/SymmetryElementControls.svelte.d.ts +9 -0
  403. package/dist/symmetry/SymmetryElements.svelte +354 -0
  404. package/dist/symmetry/SymmetryElements.svelte.d.ts +24 -0
  405. package/dist/symmetry/SymmetryStats.svelte +113 -8
  406. package/dist/symmetry/WyckoffTable.svelte +68 -7
  407. package/dist/symmetry/WyckoffTable.svelte.d.ts +3 -0
  408. package/dist/symmetry/cell-transform.js +7 -14
  409. package/dist/symmetry/index.d.ts +14 -4
  410. package/dist/symmetry/index.js +291 -72
  411. package/dist/symmetry/spacegroups.d.ts +12 -1
  412. package/dist/symmetry/spacegroups.js +63 -14
  413. package/dist/symmetry/symmetry-elements.d.ts +33 -0
  414. package/dist/symmetry/symmetry-elements.js +521 -0
  415. package/dist/symmetry/wyckoff-db.d.ts +9 -0
  416. package/dist/symmetry/wyckoff-db.js +87 -0
  417. package/dist/table/HeatmapTable.svelte +66 -25
  418. package/dist/table/HeatmapTable.svelte.d.ts +1 -1
  419. package/dist/table/index.d.ts +1 -3
  420. package/dist/table/index.js +1 -1
  421. package/dist/theme/index.js +8 -8
  422. package/dist/tooltip/KCoords.svelte +45 -0
  423. package/dist/tooltip/KCoords.svelte.d.ts +8 -0
  424. package/dist/tooltip/index.d.ts +1 -0
  425. package/dist/tooltip/index.js +1 -0
  426. package/dist/trajectory/Trajectory.svelte +123 -100
  427. package/dist/trajectory/Trajectory.svelte.d.ts +11 -22
  428. package/dist/trajectory/TrajectoryExportPane.svelte +17 -25
  429. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +4 -5
  430. package/dist/trajectory/TrajectoryInfoPane.svelte +5 -3
  431. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +3 -2
  432. package/dist/trajectory/constants.js +6 -2
  433. package/dist/trajectory/extract.js +17 -37
  434. package/dist/trajectory/format-detect.d.ts +1 -1
  435. package/dist/trajectory/format-detect.js +27 -19
  436. package/dist/trajectory/frame-reader.d.ts +0 -1
  437. package/dist/trajectory/frame-reader.js +63 -162
  438. package/dist/trajectory/helpers.d.ts +10 -2
  439. package/dist/trajectory/helpers.js +56 -36
  440. package/dist/trajectory/index.js +1 -1
  441. package/dist/trajectory/parse/ase.d.ts +9 -1
  442. package/dist/trajectory/parse/ase.js +47 -32
  443. package/dist/trajectory/parse/diagnostics.d.ts +3 -0
  444. package/dist/trajectory/parse/diagnostics.js +14 -0
  445. package/dist/trajectory/parse/hdf5.js +1 -1
  446. package/dist/trajectory/parse/index.d.ts +1 -1
  447. package/dist/trajectory/parse/index.js +65 -105
  448. package/dist/trajectory/parse/lammps.d.ts +0 -2
  449. package/dist/trajectory/parse/lammps.js +8 -6
  450. package/dist/trajectory/parse/pymatgen.d.ts +2 -0
  451. package/dist/trajectory/parse/pymatgen.js +74 -0
  452. package/dist/trajectory/parse/vasp.js +38 -18
  453. package/dist/trajectory/parse/xyz.d.ts +13 -1
  454. package/dist/trajectory/parse/xyz.js +102 -94
  455. package/dist/trajectory/plotting.d.ts +1 -2
  456. package/dist/trajectory/plotting.js +16 -113
  457. package/dist/utils.d.ts +2 -0
  458. package/dist/utils.js +7 -5
  459. package/dist/xrd/XrdPlot.svelte +16 -30
  460. package/dist/xrd/broadening.d.ts +2 -1
  461. package/dist/xrd/calc-xrd.js +18 -20
  462. package/dist/xrd/index.d.ts +2 -2
  463. package/dist/xrd/parse.js +2 -2
  464. package/package.json +43 -26
  465. package/dist/element/data.json +0 -11864
  466. package/dist/fermi-surface/marching-cubes.d.ts +0 -2
  467. package/dist/fermi-surface/marching-cubes.js +0 -2
  468. package/dist/plot/PlotControls.svelte.d.ts +0 -4
  469. package/dist/plot/axis-utils.d.ts +0 -19
  470. package/dist/plot/axis-utils.js +0 -78
  471. package/dist/plot/defaults.d.ts +0 -19
  472. package/dist/plot/defaults.js +0 -9
  473. package/dist/plot/fill-utils.d.ts +0 -46
  474. package/dist/plot/fill-utils.js +0 -322
  475. package/dist/plot/hover-lock.svelte.d.ts +0 -14
  476. package/dist/plot/hover-lock.svelte.js +0 -46
  477. package/dist/plot/interactions.d.ts +0 -12
  478. package/dist/plot/interactions.js +0 -101
  479. package/dist/plot/scales.d.ts +0 -48
  480. package/dist/plot/svg.d.ts +0 -1
  481. package/dist/plot/svg.js +0 -11
  482. package/dist/plot/utils/series-visibility.d.ts +0 -15
  483. package/dist/plot/utils.d.ts +0 -1
  484. package/dist/plot/utils.js +0 -14
  485. /package/dist/plot/{PortalSelect.svelte.d.ts → core/components/PortalSelect.svelte.d.ts} +0 -0
  486. /package/dist/plot/{binned-scatter-types.js → scatter/binned-scatter-types.js} +0 -0
@@ -1,13 +1,13 @@
1
1
  <script lang="ts">
2
2
  import type { ColorSchemeName } from '../colors'
3
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'
4
+ import { normalize_show_controls, type ShowControlsProp } from '../controls'
5
+ import { coerce_elem_symbol, type ElementSymbol } from '../element'
7
6
  import { StatusMessage } from '../feedback'
8
7
  import Spinner from '../feedback/Spinner.svelte'
9
8
  import Icon from '../Icon.svelte'
10
- import { create_file_drop_handler, load_from_url } from '../io'
9
+ import { create_file_drop_handler, drag_over_handlers, load_from_url } from '../io'
10
+ import { forward_window_keydown, handle_and_prevent } from '../keyboard'
11
11
  import { parse_volumetric_file } from '../isosurface/parse'
12
12
  import type { IsosurfaceSettings, VolumetricData } from '../isosurface/types'
13
13
  import {
@@ -15,8 +15,8 @@
15
15
  DEFAULT_ISOSURFACE_SETTINGS,
16
16
  tile_volumetric_data,
17
17
  } from '../isosurface/types'
18
- import { ELEM_SYMBOLS } from '../labels'
19
- import { set_fullscreen_bg, toggle_fullscreen } from '../layout'
18
+ import { type FullscreenToggleProp, toggle_fullscreen, ViewerChrome } from '../layout'
19
+ import { sync_fullscreen } from '../layout/fullscreen.svelte'
20
20
  import type { Vec3 } from '../math'
21
21
  import { create_cart_to_frac, create_frac_to_cart } from '../math'
22
22
  import { DEFAULTS } from '../settings'
@@ -56,7 +56,7 @@
56
56
  import { get_property_colors } from './atom-properties'
57
57
  import AtomLegend from './AtomLegend.svelte'
58
58
  import CellSelect from './CellSelect.svelte'
59
- import { BOND_ORDER_OPTIONS, merge_bond_edits } from './bonding'
59
+ import { BOND_ORDER_OPTIONS, merge_bond_edits, remap_bonds_after_deletion } from './bonding'
60
60
  import type { StructureHandlerData } from './index'
61
61
  import { MAX_SELECTED_SITES } from './measure'
62
62
  import { normalize_fractional_coords, parse_any_structure } from './parse'
@@ -64,6 +64,7 @@
64
64
  import StructureExportPane from './StructureExportPane.svelte'
65
65
  import StructureInfoPane from './StructureInfoPane.svelte'
66
66
  import StructureScene from './StructureScene.svelte'
67
+ import { to_error } from '../utils'
67
68
 
68
69
  // Type alias for event handlers to reduce verbosity
69
70
  type EventHandler = (data: StructureHandlerData) => void
@@ -187,15 +188,13 @@
187
188
  structure?: AnyStructure
188
189
  bonds?: StructureBond[]
189
190
  scene_props?: ComponentProps<typeof StructureScene>
190
- /**
191
- * Controls visibility configuration.
192
- * - 'always': controls always visible
193
- * - 'hover': controls visible on component hover (default)
194
- * - 'never': controls never visible
195
- * - object: { mode, hidden, style } for fine-grained control
196
- *
197
- * Control names: 'reset-camera', 'fullscreen', 'measure-mode', 'info-pane', 'export-pane', 'controls'
198
- */
191
+ // Controls visibility configuration.
192
+ // - 'always': controls always visible
193
+ // - 'hover': controls visible on component hover (default)
194
+ // - 'never': controls never visible
195
+ // - object: { mode, hidden, style } for fine-grained control
196
+ //
197
+ // Control names: 'reset-camera', 'fullscreen', 'measure-mode', 'info-pane', 'export-pane', 'controls'
199
198
  show_controls?: ShowControlsProp
200
199
  fullscreen?: boolean
201
200
  // bindable width of the canvas
@@ -216,7 +215,7 @@
216
215
  bond_edit_mode?: BondEditMode
217
216
  bond_edit_order?: BondOrder
218
217
  info_pane_open?: boolean
219
- fullscreen_toggle?: Snippet<[{ fullscreen: boolean }]> | boolean
218
+ fullscreen_toggle?: FullscreenToggleProp
220
219
  bottom_left?: Snippet<[{ structure?: AnyStructure }]>
221
220
  top_right_controls?: Snippet // Additional controls to render at the end of the control buttons row
222
221
  data_url?: string // URL to load structure from (alternative to providing structure directly)
@@ -302,7 +301,7 @@
302
301
  emit_file_load_event(parsed, filename, content)
303
302
  } catch (error) {
304
303
  error_msg = `Failed to parse structure: ${
305
- error instanceof Error ? error.message : String(error)
304
+ to_error(error).message
306
305
  }`
307
306
  on_error?.({ error_msg, filename })
308
307
  }
@@ -333,7 +332,7 @@
333
332
  }
334
333
  } catch (err) {
335
334
  error_msg = `Failed to parse structure from string: ${
336
- err instanceof Error ? err.message : String(err)
335
+ to_error(err).message
337
336
  }`
338
337
  untrack(() => on_error?.({ error_msg, filename: `string` }))
339
338
  } finally {
@@ -467,10 +466,13 @@
467
466
  let bond_order_overrides = $state<StructureBond[]>([])
468
467
  let bond_undo_stack = $state<BondEditHistorySnapshot[]>([])
469
468
  let bond_redo_stack = $state<BondEditHistorySnapshot[]>([])
470
- let bond_history_context = $state<BondEditContext>()
471
- let last_bond_structure_identity = $state(structure)
469
+ // These hold object-identity tokens (structure_identity) compared with ===/!==,
470
+ // so they must stay raw — proxying them via $state would break identity comparisons
471
+ // (state_proxy_equality_mismatch) against the raw `structure` prop.
472
+ let bond_history_context = $state.raw<BondEditContext>()
473
+ let last_bond_structure_identity = $state.raw(structure)
472
474
  let last_emitted_bond_signature = $state<string>()
473
- let bond_edit_snapshot = $state<BondEditSnapshot>()
475
+ let bond_edit_snapshot = $state.raw<BondEditSnapshot>()
474
476
  let has_bond_edits = $derived(
475
477
  added_bonds.length > 0 || removed_bonds.length > 0 ||
476
478
  bond_order_overrides.length > 0,
@@ -638,6 +640,10 @@
638
640
  emit_bonds(edited_bonds)
639
641
  })
640
642
 
643
+ // Elements currently anchoring polyhedra (written by StructureScene, read by
644
+ // StructureControls so per-element toggles reflect actual render state)
645
+ let polyhedra_rendered_elements = $state<string[]>([])
646
+
641
647
  // === Edit-atoms mode state ===
642
648
  let dragging_atoms = $state(false)
643
649
  let undo_stack = $state<AnyStructure[]>([])
@@ -677,9 +683,8 @@
677
683
 
678
684
  // Normalize and validate element symbol (e.g. "fe" → "Fe", "Xx" → null)
679
685
  function normalize_element(input: string): ElementSymbol | null {
680
- const normalized = (input.charAt(0).toUpperCase() +
681
- input.slice(1).toLowerCase()) as ElementSymbol
682
- return ELEM_SYMBOLS.includes(normalized) ? normalized : null
686
+ const normalized = input.charAt(0).toUpperCase() + input.slice(1).toLowerCase()
687
+ return coerce_elem_symbol(normalized) ?? null
683
688
  }
684
689
 
685
690
  function clear_selection() {
@@ -776,7 +781,13 @@
776
781
  })
777
782
 
778
783
  let controls_config = $derived(normalize_show_controls(show_controls))
779
- let viewer_active = $derived(hovered || focused)
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
+ })
780
791
  let scene_gizmo = $derived(viewer_active && (scene_props.gizmo ?? scene_props.show_gizmo))
781
792
  let active_scene_sites = $derived([
782
793
  ...new SvelteSet([...(scene_props.active_sites ?? []), ...highlighted_sites]),
@@ -822,7 +833,7 @@
822
833
  let supercell_structure = $state(structure)
823
834
  let supercell_loading = $state(false)
824
835
  let has_supercell = $derived(
825
- !!supercell_scaling && ![``, `1x1x1`, `1`].includes(supercell_scaling),
836
+ Boolean(supercell_scaling) && ![``, `1x1x1`, `1`].includes(supercell_scaling),
826
837
  )
827
838
  let bond_edits_enabled = $derived(
828
839
  cell_type === `original` && !has_supercell && !supercell_loading,
@@ -865,8 +876,8 @@
865
876
  // For large supercells, show loading state and use async generation
866
877
  const sites_count = base_structure.sites?.length || 0
867
878
  const [nx_str, ny_str, nz_str] = supercell_scaling.split(/[x×]/)
868
- const scaling_mult = (parseInt(nx_str) || 1) * (parseInt(ny_str) || 1) *
869
- (parseInt(nz_str) || 1)
879
+ const scaling_mult = (parseInt(nx_str, 10) || 1) * (parseInt(ny_str, 10) || 1) *
880
+ (parseInt(nz_str, 10) || 1)
870
881
  const estimated_sites = sites_count * scaling_mult
871
882
 
872
883
  // Show spinner for supercells with >1000 estimated sites or scaling >8
@@ -1124,7 +1135,7 @@
1124
1135
  emit_file_load_event(parsed, filename, content)
1125
1136
  } catch (err) {
1126
1137
  error_msg = `Failed to parse structure: ${
1127
- err instanceof Error ? err.message : String(err)
1138
+ to_error(err).message
1128
1139
  }`
1129
1140
  on_error?.({ error_msg, filename })
1130
1141
  }
@@ -1139,7 +1150,9 @@
1139
1150
  },
1140
1151
  })
1141
1152
 
1142
- function handle_keydown(event: KeyboardEvent) {
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 {
1143
1156
  // Don't handle shortcuts if user is typing in an input field
1144
1157
  const target = event.target
1145
1158
  const is_input_focused =
@@ -1151,45 +1164,39 @@
1151
1164
 
1152
1165
  // Allow Escape to cancel add-atom mode even when the element input is focused
1153
1166
  if (event.key === `Escape` && measure_mode === `edit-atoms` && add_atom_mode) {
1154
- event.preventDefault()
1155
1167
  add_atom_mode = false
1156
- return
1168
+ return true
1157
1169
  }
1158
1170
 
1159
- if (is_input_focused) return
1171
+ if (is_input_focused) return false
1160
1172
 
1161
1173
  if (measure_mode === `edit-bonds`) {
1162
1174
  const key = event.key.toLowerCase()
1163
1175
  const plain = !event.ctrlKey && !event.metaKey && !event.altKey
1164
1176
  if (event.ctrlKey || event.metaKey) {
1165
1177
  if (key === `z` && !event.shiftKey) {
1166
- if (bond_undo_stack.length === 0) return
1167
- event.preventDefault()
1178
+ if (bond_undo_stack.length === 0) return false
1168
1179
  undo_bond_edit()
1169
1180
  show_toast(`Undo bond edit (${bond_undo_stack.length} left)`)
1170
- return
1181
+ return true
1171
1182
  } else if (key === `y` || (key === `z` && event.shiftKey)) {
1172
- if (bond_redo_stack.length === 0) return
1173
- event.preventDefault()
1183
+ if (bond_redo_stack.length === 0) return false
1174
1184
  redo_bond_edit()
1175
1185
  show_toast(`Redo bond edit (${bond_redo_stack.length} left)`)
1176
- return
1186
+ return true
1177
1187
  }
1178
1188
  }
1179
1189
  if (key === `a` && plain) {
1180
- event.preventDefault()
1181
1190
  bond_edit_mode = `add`
1182
- return
1191
+ return true
1183
1192
  }
1184
1193
  if (key === `d` && plain) {
1185
- event.preventDefault()
1186
1194
  bond_edit_mode = `delete`
1187
- return
1195
+ return true
1188
1196
  }
1189
1197
  if (event.key === `Escape` && selected_sites.length > 0) {
1190
- event.preventDefault()
1191
1198
  clear_selection()
1192
- return
1199
+ return true
1193
1200
  }
1194
1201
  }
1195
1202
 
@@ -1199,61 +1206,66 @@
1199
1206
  if (event.ctrlKey || event.metaKey) {
1200
1207
  const key = event.key.toLowerCase()
1201
1208
  if (key === `z` && !event.shiftKey) {
1202
- if (undo_stack.length === 0) return
1203
- event.preventDefault()
1209
+ if (undo_stack.length === 0) return false
1204
1210
  undo()
1205
1211
  show_toast(`Undo (${undo_stack.length} left)`)
1206
- return
1212
+ return true
1207
1213
  } else if (key === `y` || (key === `z` && event.shiftKey)) {
1208
- if (redo_stack.length === 0) return
1209
- event.preventDefault()
1214
+ if (redo_stack.length === 0) return false
1210
1215
  redo()
1211
1216
  show_toast(`Redo (${redo_stack.length} left)`)
1212
- return
1217
+ return true
1213
1218
  }
1214
1219
  }
1215
1220
 
1216
1221
  if (event.key === `Delete` || event.key === `Backspace`) {
1217
1222
  // Delete selected atoms
1218
1223
  if (selected_sites.length > 0 && structure?.sites) {
1219
- event.preventDefault()
1220
1224
  is_internal_edit = true
1221
1225
  push_undo()
1222
1226
  const to_delete = scene_to_structure_indices(selected_sites, true)
1223
1227
  const n_deleted = to_delete.size
1224
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
1225
1233
  structure = {
1226
1234
  ...structure,
1227
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
+ }),
1228
1242
  }
1229
1243
  // Clear per-site overrides since indices shifted after deletion
1230
1244
  if (site_radius_overrides?.size > 0) site_radius_overrides.clear()
1231
1245
  clear_bond_edits()
1232
1246
  show_toast(`Deleted ${n_deleted} site${n_deleted > 1 ? `s` : ``}`)
1247
+ return true
1233
1248
  }
1234
- return
1249
+ return false
1235
1250
  }
1236
1251
  const key = event.key.toLowerCase()
1237
1252
  const plain = !event.ctrlKey && !event.metaKey && !event.altKey
1238
1253
 
1239
1254
  if (key === `a` && plain) {
1240
1255
  // Enter add-atom sub-mode (plain 'a' only, not Ctrl+A/Cmd+A/Alt+A)
1241
- event.preventDefault()
1242
1256
  add_atom_mode = !add_atom_mode
1243
- return
1257
+ return true
1244
1258
  }
1245
1259
  // Change element of selected atoms
1246
1260
  if (key === `e` && plain && selected_sites.length > 0) {
1247
- event.preventDefault()
1248
1261
  change_element_mode = !change_element_mode
1249
- return
1262
+ return true
1250
1263
  }
1251
1264
  // Duplicate selected atoms at a small offset
1252
1265
  if (
1253
1266
  key === `d` && (event.ctrlKey || event.metaKey) &&
1254
1267
  selected_sites.length > 0 && structure?.sites
1255
1268
  ) {
1256
- event.preventDefault()
1257
1269
  is_internal_edit = true
1258
1270
  push_undo()
1259
1271
  const orig_indices = scene_to_structure_indices(selected_sites)
@@ -1284,18 +1296,18 @@
1284
1296
  show_toast(
1285
1297
  `Duplicated ${new_sites.length} site${new_sites.length > 1 ? `s` : ``}`,
1286
1298
  )
1287
- return
1299
+ return true
1288
1300
  }
1289
1301
 
1290
1302
  // add_atom_mode Escape is already handled above (before is_input_focused guard)
1291
1303
  if (event.key === `Escape`) {
1292
1304
  if (change_element_mode) {
1293
1305
  change_element_mode = false
1294
- return
1306
+ return true
1295
1307
  }
1296
1308
  if (selected_sites.length > 0) {
1297
1309
  clear_selection()
1298
- return
1310
+ return true
1299
1311
  }
1300
1312
  }
1301
1313
  }
@@ -1303,11 +1315,11 @@
1303
1315
  // Interface shortcuts (require Ctrl/Cmd modifier to avoid accidental triggers)
1304
1316
  const has_modifier = event.ctrlKey || event.metaKey
1305
1317
  if (event.key === `f` && has_modifier && fullscreen_toggle) {
1306
- event.preventDefault()
1307
1318
  toggle_fullscreen(wrapper)
1319
+ return true
1308
1320
  } else if (event.key === `i` && has_modifier && enable_info_pane) {
1309
- event.preventDefault()
1310
1321
  info_pane_open = !info_pane_open
1322
+ return true
1311
1323
  } else if (event.key === `Escape`) {
1312
1324
  // Prioritize closing panes, then exit edit modes, then exit fullscreen
1313
1325
  if (info_pane_open) info_pane_open = false
@@ -1315,10 +1327,19 @@
1315
1327
  else if (export_pane_open) export_pane_open = false
1316
1328
  else if (measure_mode === `edit-bonds` || measure_mode === `edit-atoms`) {
1317
1329
  measure_mode = `distance`
1318
- }
1330
+ } else return false
1331
+ return true
1319
1332
  }
1333
+ return false
1320
1334
  }
1321
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
+
1322
1343
  // === Edit-atoms mode helpers ===
1323
1344
 
1324
1345
  // Map scene indices (into displayed_structure) back to raw structure indices.
@@ -1458,24 +1479,19 @@
1458
1479
  }
1459
1480
  })
1460
1481
 
1461
- $effect(() => { // fullscreen and background
1462
- if (typeof window !== `undefined`) {
1463
- if (fullscreen && !document.fullscreenElement && wrapper) {
1464
- wrapper.requestFullscreen().catch(console.error)
1465
- } else if (!fullscreen && document.fullscreenElement) {
1466
- document.exitFullscreen()
1467
- }
1468
- }
1469
- set_fullscreen_bg(wrapper, fullscreen, `--struct-bg-fullscreen`)
1482
+ sync_fullscreen({
1483
+ get_wrapper: () => wrapper,
1484
+ get_fullscreen: () => fullscreen,
1485
+ set_fullscreen: (val) => (fullscreen = val),
1486
+ bg_css_var: `--struct-bg-fullscreen`,
1487
+ on_change: (val) => on_fullscreen_change?.({ structure, fullscreen: val }),
1470
1488
  })
1471
1489
  </script>
1472
1490
 
1473
- <svelte:document
1474
- onfullscreenchange={() => {
1475
- fullscreen = Boolean(document.fullscreenElement)
1476
- on_fullscreen_change?.({ structure, fullscreen })
1477
- }}
1478
- />
1491
+ <!-- Forward shortcuts to the hovered viewer when focus is on <body> (see
1492
+ forward_window_keydown). Edit modes are excluded so destructive keys
1493
+ (delete/undo) still require focus, not just a hovering mouse. -->
1494
+ <svelte:window onkeydown={forward_window_keydown(() => hovered, handle_hover_keydown)} />
1479
1495
 
1480
1496
  <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
1481
1497
  <div
@@ -1502,30 +1518,15 @@
1502
1518
  if (!(target instanceof HTMLElement)) return
1503
1519
  // Don't reset if double-click was on UI controls/panes/legend
1504
1520
  if (
1505
- target.closest(`.control-buttons`) ||
1506
- target.closest(`.structure-legend`) ||
1507
- target.closest(`.atom-legend`) ||
1508
- target.closest(`.info-pane`) ||
1509
- target.closest(`.export-pane`) ||
1510
- target.closest(`.controls-pane`) ||
1511
- target.tagName === `BUTTON` ||
1512
- target.tagName === `INPUT` ||
1513
- target.tagName === `SELECT`
1521
+ [`.control-buttons`, `.structure-legend`, `.atom-legend`, `.info-pane`, `.export-pane`, `.controls-pane`].some((selector) => target.closest(selector))
1522
+ || target.tagName === `BUTTON` || target.tagName === `INPUT` || target.tagName === `SELECT`
1514
1523
  ) return
1515
1524
  // Reset camera for double-clicks on the 3D scene
1516
1525
  reset_camera()
1517
1526
  }}
1518
1527
  ondrop={handle_file_drop}
1519
- ondragover={(event) => {
1520
- event.preventDefault()
1521
- if (!allow_file_drop) return
1522
- dragover = true
1523
- }}
1524
- ondragleave={(event) => {
1525
- event.preventDefault()
1526
- dragover = false
1527
- }}
1528
- onkeydown={handle_keydown}
1528
+ {...drag_over_handlers({ allow: () => allow_file_drop, set_dragover: (over) => dragover = over })}
1529
+ onkeydown={handle_and_prevent(handle_keydown)}
1529
1530
  {...rest}
1530
1531
  class="structure {rest.class ?? ``}"
1531
1532
  >
@@ -1539,307 +1540,282 @@
1539
1540
  {:else if error_msg}
1540
1541
  <StatusMessage bind:message={error_msg} type="error" dismissible />
1541
1542
  {:else if (structure?.sites?.length ?? 0) > 0}
1542
- <section
1543
- class="control-buttons {controls_config.class}"
1544
- style={controls_config.style}
1543
+ {#snippet reset_camera_btn()}
1544
+ {#if camera_has_moved && controls_config.visible(`reset-camera`)}
1545
+ <button class="reset-camera" onclick={reset_camera} title={reset_text}>
1546
+ <!-- Target/Focus icon for reset camera -->
1547
+ <Icon icon="Reset" />
1548
+ </button>
1549
+ {/if}
1550
+ {/snippet}
1551
+ <ViewerChrome
1552
+ {controls_config}
1553
+ {fullscreen}
1554
+ {fullscreen_toggle}
1555
+ fullscreen_btn_style="padding: 0 3px"
1556
+ {wrapper}
1557
+ before={reset_camera_btn}
1558
+ style="--viewer-buttons-gap: 4pt; --viewer-buttons-btn-padding: 1px 6px; --viewer-buttons-align: stretch"
1545
1559
  >
1546
- {#if controls_config.mode !== `never`}
1547
- {#if camera_has_moved && controls_config.visible(`reset-camera`)}
1548
- <button class="reset-camera" onclick={reset_camera} title={reset_text}>
1549
- <!-- Target/Focus icon for reset camera -->
1550
- <Icon icon="Reset" />
1551
- </button>
1552
- {/if}
1553
- {#if fullscreen_toggle && controls_config.visible(`fullscreen`)}
1560
+ {#if enable_measure_mode && controls_config.visible(`measure-mode`)}
1561
+ <div
1562
+ class="measure-mode-dropdown"
1563
+ {@attach click_outside({ callback: () => measure_menu_open = false })}
1564
+ >
1554
1565
  <button
1555
- type="button"
1556
- onclick={() => fullscreen_toggle && toggle_fullscreen(wrapper)}
1557
- title="{fullscreen ? `Exit` : `Enter`} fullscreen"
1558
- aria-pressed={fullscreen}
1559
- class="fullscreen-toggle"
1560
- style="padding: 0 3px"
1561
- {@attach tooltip()}
1566
+ onclick={() => (measure_menu_open = !measure_menu_open)}
1567
+ title="Measure / Edit"
1568
+ class="view-mode-button"
1569
+ class:active={measure_menu_open}
1570
+ aria-expanded={measure_menu_open}
1571
+ style="transform: scale(1.2)"
1562
1572
  >
1563
- {#if typeof fullscreen_toggle === `function`}
1564
- {@render fullscreen_toggle({ fullscreen })}
1573
+ {#if show_measure_selection_limit}
1574
+ <span class="selection-limit-text">
1575
+ {measured_sites.length}/{MAX_SELECTED_SITES}
1576
+ </span>
1565
1577
  {:else}
1566
- <Icon icon="{fullscreen ? `Exit` : ``}Fullscreen" />
1578
+ <Icon
1579
+ icon={({
1580
+ distance: `Ruler`,
1581
+ angle: `Angle`,
1582
+ 'edit-bonds': `Link`,
1583
+ 'edit-atoms': `Edit`,
1584
+ } as const)[measure_mode]}
1585
+ />
1567
1586
  {/if}
1587
+ <Icon
1588
+ icon="Arrow{measure_menu_open ? `Up` : `Down`}"
1589
+ style="margin-left: -2px"
1590
+ />
1568
1591
  </button>
1569
- {/if}
1570
-
1571
- {#if enable_measure_mode && controls_config.visible(`measure-mode`)}
1572
- <div
1573
- class="measure-mode-dropdown"
1574
- {@attach click_outside({ callback: () => measure_menu_open = false })}
1575
- >
1592
+ {#if show_selection_reset}
1576
1593
  <button
1577
- onclick={() => (measure_menu_open = !measure_menu_open)}
1578
- title="Measure / Edit"
1579
- class="view-mode-button"
1580
- class:active={measure_menu_open}
1581
- aria-expanded={measure_menu_open}
1582
- style="transform: scale(1.2)"
1594
+ type="button"
1595
+ aria-label="Reset selection and bond edits"
1596
+ onclick={() => {
1597
+ clear_selection()
1598
+ clear_bond_edits()
1599
+ }}
1583
1600
  >
1584
- {#if show_measure_selection_limit}
1585
- <span class="selection-limit-text">
1586
- {measured_sites.length}/{MAX_SELECTED_SITES}
1587
- </span>
1588
- {:else}
1589
- <Icon
1590
- icon={({
1591
- distance: `Ruler`,
1592
- angle: `Angle`,
1593
- 'edit-bonds': `Link`,
1594
- 'edit-atoms': `Edit`,
1595
- } as const)[measure_mode]}
1596
- />
1597
- {/if}
1598
- <Icon
1599
- icon="Arrow{measure_menu_open ? `Up` : `Down`}"
1600
- style="margin-left: -2px"
1601
- />
1601
+ <Icon icon="Reset" style="margin-left: -4px" />
1602
1602
  </button>
1603
- {#if show_selection_reset}
1604
- <button
1605
- type="button"
1606
- aria-label="Reset selection and bond edits"
1607
- onclick={() => {
1608
- clear_selection()
1609
- clear_bond_edits()
1610
- }}
1611
- >
1612
- <Icon icon="Reset" style="margin-left: -4px" />
1613
- </button>
1614
- {/if}
1615
- {#if measure_menu_open}
1616
- <div class="view-mode-dropdown">
1617
- {#each [
1618
- { mode: `distance`, icon: `Ruler`, label: `Distance`, scale: 1.1 },
1619
- { mode: `angle`, icon: `Angle`, label: `Angle`, scale: 1.3 },
1620
- {
1621
- mode: `edit-atoms`,
1622
- icon: `Edit`,
1623
- label: `Edit Atoms`,
1624
- scale: 1.0,
1625
- },
1626
- {
1627
- mode: `edit-bonds`,
1628
- icon: `Link`,
1629
- label: `Edit Bonds`,
1630
- scale: 1.0,
1631
- },
1632
- ] as const as
1633
- { mode, icon, label, scale }
1634
- (mode)
1635
- }
1636
- <button
1637
- class="view-mode-option"
1638
- class:selected={measure_mode === mode}
1639
- disabled={mode === `edit-bonds` && !bond_edits_enabled}
1640
- title={mode === `edit-bonds` && !bond_edits_enabled
1641
- ? `Bond editing is only available for the original 1x1x1 cell`
1642
- : label}
1643
- onclick={() => {
1644
- if (mode === `edit-bonds` && !bond_edits_enabled) return
1645
- ;[measure_mode, measure_menu_open] = [mode, false]
1646
- }}
1647
- >
1648
- <Icon {icon} style="transform: scale({scale})" />
1649
- <span>{label}</span>
1650
- </button>
1651
- {/each}
1652
- </div>
1653
- {/if}
1654
- </div>
1655
-
1656
- <!-- Undo/redo buttons (only in edit-atoms mode) -->
1657
- {#if measure_mode === `edit-atoms`}
1658
- <div class="undo-redo-container">
1659
- <button
1660
- type="button"
1661
- aria-label="Undo (Cmd/Ctrl+Z)"
1662
- disabled={undo_stack.length === 0}
1663
- onclick={undo}
1664
- title="Undo (Cmd/Ctrl+Z)"
1665
- class="undo-redo-btn"
1666
- >
1667
- <Icon icon="Undo" />
1668
- {#if undo_stack.length > 0}
1669
- <span class="history-count">{undo_stack.length}</span>
1670
- {/if}
1671
- </button>
1672
- <button
1673
- type="button"
1674
- aria-label="Redo (Cmd/Ctrl+Y or Cmd+Shift+Z)"
1675
- disabled={redo_stack.length === 0}
1676
- onclick={redo}
1677
- title="Redo (Cmd/Ctrl+Y or Cmd+Shift+Z)"
1678
- class="undo-redo-btn"
1679
- >
1680
- <Icon icon="Redo" />
1681
- {#if redo_stack.length > 0}
1682
- <span class="history-count">{redo_stack.length}</span>
1683
- {/if}
1684
- </button>
1685
- </div>
1686
1603
  {/if}
1687
-
1688
- {#if measure_mode === `edit-bonds`}
1689
- <div class="bond-edit-toolbar" aria-label="Bond editing controls">
1690
- {#if bond_edit_mode === `add`}
1691
- <label>
1692
- <span>Bond order</span>
1693
- <select bind:value={bond_edit_order}>
1694
- {#each BOND_ORDER_OPTIONS as { order, label } (label)}
1695
- <option value={order}>{label}</option>
1696
- {/each}
1697
- </select>
1698
- </label>
1699
- {/if}
1700
- <div class="bond-edit-mode-toggle">
1701
- {#each [
1702
- { mode: `add`, label: `Add`, title: `Add: click two atoms` },
1703
- { mode: `delete`, label: `Delete`, title: `Delete: click a bond` },
1704
- ] as const as { mode, label, title } (mode)}
1705
- <button
1706
- type="button"
1707
- class:selected={bond_edit_mode === mode}
1708
- aria-pressed={bond_edit_mode === mode}
1709
- title="{title} ({label[0]})"
1710
- onclick={() => (bond_edit_mode = mode)}
1711
- >
1712
- {label}
1713
- </button>
1714
- {/each}
1715
- </div>
1716
- </div>
1717
- <div class="undo-redo-container">
1718
- <button
1719
- type="button"
1720
- aria-label="Undo bond edit (Cmd/Ctrl+Z)"
1721
- disabled={bond_undo_stack.length === 0}
1722
- onclick={undo_bond_edit}
1723
- title="Undo bond edit (Cmd/Ctrl+Z)"
1724
- class="undo-redo-btn"
1725
- >
1726
- <Icon icon="Undo" />
1727
- {#if bond_undo_stack.length > 0}
1728
- <span class="history-count">{bond_undo_stack.length}</span>
1729
- {/if}
1730
- </button>
1731
- <button
1732
- type="button"
1733
- aria-label="Redo bond edit (Cmd/Ctrl+Y or Cmd+Shift+Z)"
1734
- disabled={bond_redo_stack.length === 0}
1735
- onclick={redo_bond_edit}
1736
- title="Redo bond edit (Cmd/Ctrl+Y or Cmd+Shift+Z)"
1737
- class="undo-redo-btn"
1738
- >
1739
- <Icon icon="Redo" />
1740
- {#if bond_redo_stack.length > 0}
1741
- <span class="history-count">{bond_redo_stack.length}</span>
1742
- {/if}
1743
- </button>
1604
+ {#if measure_menu_open}
1605
+ <div class="view-mode-dropdown">
1606
+ {#each [
1607
+ { mode: `distance`, icon: `Ruler`, label: `Distance`, scale: 1.1 },
1608
+ { mode: `angle`, icon: `Angle`, label: `Angle`, scale: 1.3 },
1609
+ { mode: `edit-atoms`, icon: `Edit`, label: `Edit Atoms`, scale: 1.0 },
1610
+ { mode: `edit-bonds`, icon: `Link`, label: `Edit Bonds`, scale: 1.0 },
1611
+ ] as const as { mode, icon, label, scale } (mode)}
1612
+ <button
1613
+ class="view-mode-option"
1614
+ class:selected={measure_mode === mode}
1615
+ disabled={mode === `edit-bonds` && !bond_edits_enabled}
1616
+ title={mode === `edit-bonds` && !bond_edits_enabled
1617
+ ? `Bond editing is only available for the original 1x1x1 cell`
1618
+ : label}
1619
+ onclick={() => {
1620
+ if (mode === `edit-bonds` && !bond_edits_enabled) return
1621
+ ;[measure_mode, measure_menu_open] = [mode, false]
1622
+ }}
1623
+ >
1624
+ <Icon {icon} style="transform: scale({scale})" />
1625
+ <span>{label}</span>
1626
+ </button>
1627
+ {/each}
1744
1628
  </div>
1745
1629
  {/if}
1630
+ </div>
1746
1631
 
1747
- <!-- Add-atom element input (shown when add_atom_mode is active) -->
1748
- {#if measure_mode === `edit-atoms` && add_atom_mode}
1749
- <div class="add-atom-input">
1750
- <label>
1751
- <span>Element:</span>
1752
- <input
1753
- type="text"
1754
- bind:value={add_element}
1755
- maxlength="2"
1756
- placeholder="C"
1757
- style="width: 3em; text-align: center"
1758
- />
1759
- </label>
1760
- <span style="font-size: 0.75em; opacity: 0.7">Click to place</span>
1761
- </div>
1762
- {/if}
1632
+ <!-- Undo/redo buttons (only in edit-atoms mode) -->
1633
+ {#if measure_mode === `edit-atoms`}
1634
+ <div class="undo-redo-container">
1635
+ <button
1636
+ type="button"
1637
+ aria-label="Undo (Cmd/Ctrl+Z)"
1638
+ disabled={undo_stack.length === 0}
1639
+ onclick={undo}
1640
+ title="Undo (Cmd/Ctrl+Z)"
1641
+ class="undo-redo-btn"
1642
+ >
1643
+ <Icon icon="Undo" />
1644
+ {#if undo_stack.length > 0}
1645
+ <span class="history-count">{undo_stack.length}</span>
1646
+ {/if}
1647
+ </button>
1648
+ <button
1649
+ type="button"
1650
+ aria-label="Redo (Cmd/Ctrl+Y or Cmd+Shift+Z)"
1651
+ disabled={redo_stack.length === 0}
1652
+ onclick={redo}
1653
+ title="Redo (Cmd/Ctrl+Y or Cmd+Shift+Z)"
1654
+ class="undo-redo-btn"
1655
+ >
1656
+ <Icon icon="Redo" />
1657
+ {#if redo_stack.length > 0}
1658
+ <span class="history-count">{redo_stack.length}</span>
1659
+ {/if}
1660
+ </button>
1661
+ </div>
1662
+ {/if}
1763
1663
 
1764
- <!-- Change-element input (shown when 'e' pressed with selection) -->
1765
- {#if measure_mode === `edit-atoms` && change_element_mode &&
1766
- selected_sites.length > 0}
1767
- <div class="add-atom-input">
1664
+ {#if measure_mode === `edit-bonds`}
1665
+ <div class="bond-edit-toolbar" aria-label="Bond editing controls">
1666
+ {#if bond_edit_mode === `add`}
1768
1667
  <label>
1769
- <span>New element:</span>
1770
- <input
1771
- type="text"
1772
- bind:value={change_element_value}
1773
- maxlength="2"
1774
- placeholder="Fe"
1775
- style="width: 3em; text-align: center"
1776
- onkeydown={(event: KeyboardEvent) => {
1777
- if (event.key === `Enter`) {
1778
- handle_change_element(change_element_value)
1779
- } else if (event.key === `Escape`) {
1780
- change_element_mode = false
1781
- }
1782
- event.stopPropagation()
1783
- }}
1784
- {@attach (node: HTMLInputElement) => {
1785
- node.focus()
1786
- }}
1787
- />
1668
+ <span>Bond order</span>
1669
+ <select bind:value={bond_edit_order}>
1670
+ {#each BOND_ORDER_OPTIONS as { order, label } (label)}
1671
+ <option value={order}>{label}</option>
1672
+ {/each}
1673
+ </select>
1788
1674
  </label>
1789
- <span style="font-size: 0.75em; opacity: 0.7">Enter to apply</span>
1675
+ {/if}
1676
+ <div class="bond-edit-mode-toggle">
1677
+ {#each [
1678
+ { mode: `add`, label: `Add`, title: `Add: click two atoms` },
1679
+ { mode: `delete`, label: `Delete`, title: `Delete: click a bond` },
1680
+ ] as const as { mode, label, title } (mode)}
1681
+ <button
1682
+ type="button"
1683
+ class:selected={bond_edit_mode === mode}
1684
+ aria-pressed={bond_edit_mode === mode}
1685
+ title="{title} ({label[0]})"
1686
+ onclick={() => (bond_edit_mode = mode)}
1687
+ >
1688
+ {label}
1689
+ </button>
1690
+ {/each}
1790
1691
  </div>
1791
- {/if}
1692
+ </div>
1693
+ <div class="undo-redo-container">
1694
+ <button
1695
+ type="button"
1696
+ aria-label="Undo bond edit (Cmd/Ctrl+Z)"
1697
+ disabled={bond_undo_stack.length === 0}
1698
+ onclick={undo_bond_edit}
1699
+ title="Undo bond edit (Cmd/Ctrl+Z)"
1700
+ class="undo-redo-btn"
1701
+ >
1702
+ <Icon icon="Undo" />
1703
+ {#if bond_undo_stack.length > 0}
1704
+ <span class="history-count">{bond_undo_stack.length}</span>
1705
+ {/if}
1706
+ </button>
1707
+ <button
1708
+ type="button"
1709
+ aria-label="Redo bond edit (Cmd/Ctrl+Y or Cmd+Shift+Z)"
1710
+ disabled={bond_redo_stack.length === 0}
1711
+ onclick={redo_bond_edit}
1712
+ title="Redo bond edit (Cmd/Ctrl+Y or Cmd+Shift+Z)"
1713
+ class="undo-redo-btn"
1714
+ >
1715
+ <Icon icon="Redo" />
1716
+ {#if bond_redo_stack.length > 0}
1717
+ <span class="history-count">{bond_redo_stack.length}</span>
1718
+ {/if}
1719
+ </button>
1720
+ </div>
1792
1721
  {/if}
1793
1722
 
1794
- {#if enable_info_pane && normalized_structure &&
1795
- controls_config.visible(`info-pane`)}
1796
- <StructureInfoPane
1797
- structure={normalized_structure}
1798
- bind:pane_open={info_pane_open}
1799
- bind:highlighted_sites
1800
- bind:hovered_site_idx
1801
- bind:selected_sites
1802
- {sym_data}
1803
- {@attach tooltip({ content: `Structure info pane` })}
1804
- />
1723
+ <!-- Add-atom element input (shown when add_atom_mode is active) -->
1724
+ {#if measure_mode === `edit-atoms` && add_atom_mode}
1725
+ <div class="add-atom-input">
1726
+ <label>
1727
+ <span>Element:</span>
1728
+ <input
1729
+ type="text"
1730
+ bind:value={add_element}
1731
+ maxlength="2"
1732
+ placeholder="C"
1733
+ style="width: 3em; text-align: center"
1734
+ />
1735
+ </label>
1736
+ <span style="font-size: 0.75em; opacity: 0.7">Click to place</span>
1737
+ </div>
1805
1738
  {/if}
1806
1739
 
1807
- {#if controls_config.visible(`export-pane`)}
1808
- <StructureExportPane
1809
- bind:export_pane_open
1810
- structure={normalized_structure}
1811
- {wrapper}
1812
- {scene}
1813
- {camera}
1814
- bind:png_dpi
1815
- pane_props={{ style: `max-height: calc(${height}px - 50px)` }}
1816
- />
1740
+ <!-- Change-element input (shown when 'e' pressed with selection) -->
1741
+ {#if measure_mode === `edit-atoms` && change_element_mode &&
1742
+ selected_sites.length > 0}
1743
+ <div class="add-atom-input">
1744
+ <label>
1745
+ <span>New element:</span>
1746
+ <input
1747
+ type="text"
1748
+ bind:value={change_element_value}
1749
+ maxlength="2"
1750
+ placeholder="Fe"
1751
+ style="width: 3em; text-align: center"
1752
+ onkeydown={(event: KeyboardEvent) => {
1753
+ if (event.key === `Enter`) {
1754
+ handle_change_element(change_element_value)
1755
+ } else if (event.key === `Escape`) {
1756
+ change_element_mode = false
1757
+ }
1758
+ event.stopPropagation()
1759
+ }}
1760
+ {@attach (node: HTMLInputElement) => {
1761
+ node.focus()
1762
+ }}
1763
+ />
1764
+ </label>
1765
+ <span style="font-size: 0.75em; opacity: 0.7">Enter to apply</span>
1766
+ </div>
1817
1767
  {/if}
1768
+ {/if}
1818
1769
 
1819
- {#if controls_config.visible(`controls`)}
1820
- <StructureControls
1821
- bind:controls_open
1822
- bind:scene_props
1823
- bind:lattice_props
1824
- bind:show_image_atoms
1825
- bind:supercell_scaling
1826
- bind:background_color
1827
- bind:background_opacity
1828
- bind:color_scheme
1829
- bind:atom_color_config
1830
- bind:cell_type
1831
- bind:volumetric_data
1832
- bind:isosurface_settings
1833
- bind:active_volume_idx
1834
- {structure}
1835
- {supercell_loading}
1836
- {sym_data}
1837
- />
1838
- {/if}
1770
+ {#if enable_info_pane && normalized_structure &&
1771
+ controls_config.visible(`info-pane`)}
1772
+ <StructureInfoPane
1773
+ structure={normalized_structure}
1774
+ bind:pane_open={info_pane_open}
1775
+ bind:highlighted_sites
1776
+ bind:hovered_site_idx
1777
+ bind:selected_sites
1778
+ {sym_data}
1779
+ {@attach tooltip({ content: `Structure info pane` })}
1780
+ />
1781
+ {/if}
1839
1782
 
1840
- {@render top_right_controls?.()}
1783
+ {#if controls_config.visible(`export-pane`)}
1784
+ <StructureExportPane
1785
+ bind:export_pane_open
1786
+ structure={normalized_structure}
1787
+ {wrapper}
1788
+ {scene}
1789
+ {camera}
1790
+ bind:png_dpi
1791
+ pane_props={{ style: `max-height: calc(${height}px - 50px)` }}
1792
+ />
1793
+ {/if}
1794
+
1795
+ {#if controls_config.visible(`controls`)}
1796
+ <StructureControls
1797
+ bind:controls_open
1798
+ bind:scene_props
1799
+ bind:lattice_props
1800
+ bind:show_image_atoms
1801
+ bind:supercell_scaling
1802
+ bind:background_color
1803
+ bind:background_opacity
1804
+ bind:color_scheme
1805
+ bind:atom_color_config
1806
+ bind:cell_type
1807
+ bind:volumetric_data
1808
+ bind:isosurface_settings
1809
+ bind:active_volume_idx
1810
+ {structure}
1811
+ {supercell_loading}
1812
+ {sym_data}
1813
+ {polyhedra_rendered_elements}
1814
+ />
1841
1815
  {/if}
1842
- </section>
1816
+
1817
+ {@render top_right_controls?.()}
1818
+ </ViewerChrome>
1843
1819
 
1844
1820
  <AtomLegend
1845
1821
  bind:atom_color_config
@@ -1855,15 +1831,18 @@
1855
1831
  show_mode_toggle={viewer_active}
1856
1832
  {sym_data}
1857
1833
  >
1858
- {#if structure && `lattice` in structure}
1859
- <CellSelect
1860
- bind:supercell_scaling
1861
- bind:cell_type
1862
- {sym_data}
1863
- loading={supercell_loading}
1864
- direction="up"
1865
- />
1866
- {/if}
1834
+ {#snippet children({ mode_menu_open })}
1835
+ {#if structure && `lattice` in structure}
1836
+ <CellSelect
1837
+ bind:supercell_scaling
1838
+ bind:cell_type
1839
+ {sym_data}
1840
+ loading={supercell_loading}
1841
+ direction="up"
1842
+ suppress_hover={mode_menu_open}
1843
+ />
1844
+ {/if}
1845
+ {/snippet}
1867
1846
  </AtomLegend>
1868
1847
 
1869
1848
  <!-- prevent from rendering in vitest runner since WebGLRenderingContext not available -->
@@ -1912,6 +1891,7 @@
1912
1891
  bind:add_element
1913
1892
  bind:cursor={canvas_cursor}
1914
1893
  bind:dragging_atoms
1894
+ bind:polyhedra_rendered_elements
1915
1895
  />
1916
1896
  </Canvas>
1917
1897
  </div>
@@ -1980,7 +1960,7 @@
1980
1960
  }
1981
1961
  /* Avoid accidental text selection while interacting with the viewer */
1982
1962
  .structure :global(canvas),
1983
- .structure section.control-buttons,
1963
+ .structure :global(section.control-buttons),
1984
1964
  .structure .bottom-left {
1985
1965
  user-select: none;
1986
1966
  }
@@ -1991,44 +1971,6 @@
1991
1971
  font-size: var(--struct-bottom-left-font-size, 1.2em);
1992
1972
  padding: var(--struct-bottom-left-padding, 1pt 5pt);
1993
1973
  }
1994
- section.control-buttons {
1995
- position: absolute;
1996
- display: flex;
1997
- top: var(--struct-buttons-top, var(--ctrl-btn-top, 1ex));
1998
- right: var(--struct-buttons-right, var(--ctrl-btn-right, 1ex));
1999
- gap: 4pt;
2000
- /* buttons need higher z-index than AtomLegend to make info/controls panes occlude legend */
2001
- /* we also need crazy high z-index to make info/control pane occlude threlte/extras' <HTML> elements for site labels */
2002
- z-index: var(
2003
- --struct-buttons-z-index,
2004
- var(--z-index-overlay-controls, 100000000)
2005
- );
2006
- opacity: 0;
2007
- pointer-events: none;
2008
- transition: opacity 0.2s ease;
2009
- }
2010
- /* Mode: always - controls always visible */
2011
- section.control-buttons.always-visible {
2012
- opacity: 1;
2013
- pointer-events: auto;
2014
- }
2015
- /* Mode: hover - controls visible on component hover */
2016
- .structure:hover section.control-buttons.hover-visible,
2017
- .structure:focus-within section.control-buttons.hover-visible {
2018
- opacity: 1;
2019
- pointer-events: auto;
2020
- }
2021
- /* Mode: never - stays hidden (default state, no additional CSS needed) */
2022
- section.control-buttons > :global(button) {
2023
- background-color: transparent;
2024
- display: flex;
2025
- padding: 1px 6px;
2026
- border-radius: var(--border-radius, 3pt);
2027
- font-size: clamp(0.85em, 2cqmin, 1.3em);
2028
- }
2029
- section.control-buttons :global(button:hover) {
2030
- background-color: color-mix(in srgb, currentColor 8%, transparent);
2031
- }
2032
1974
  /* Match Trajectory dropdown UI */
2033
1975
  .view-mode-dropdown {
2034
1976
  position: absolute;