matterviz 0.3.7 → 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 (324) 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 +8 -3
  6. package/dist/brillouin/BrillouinZone.svelte.d.ts +2 -1
  7. package/dist/brillouin/BrillouinZoneScene.svelte +52 -6
  8. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -0
  9. package/dist/brillouin/BrillouinZoneTooltip.svelte +16 -25
  10. package/dist/brillouin/compute.js +10 -14
  11. package/dist/chempot-diagram/ChemPotDiagram.svelte +14 -13
  12. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +12 -15
  13. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +8 -10
  14. package/dist/chempot-diagram/async-compute.svelte.js +3 -1
  15. package/dist/chempot-diagram/chempot-worker.js +2 -1
  16. package/dist/chempot-diagram/compute.d.ts +1 -1
  17. package/dist/chempot-diagram/compute.js +17 -19
  18. package/dist/colors/index.js +6 -5
  19. package/dist/composition/FormulaFilter.svelte +12 -6
  20. package/dist/composition/PieChart.svelte +6 -5
  21. package/dist/composition/chem-sys.d.ts +8 -0
  22. package/dist/composition/chem-sys.js +85 -0
  23. package/dist/composition/format.js +4 -2
  24. package/dist/composition/index.d.ts +1 -0
  25. package/dist/composition/index.js +1 -0
  26. package/dist/composition/parse.js +25 -13
  27. package/dist/convex-hull/ConvexHull2D.svelte +12 -10
  28. package/dist/convex-hull/ConvexHull3D.svelte +5 -5
  29. package/dist/convex-hull/ConvexHull4D.svelte +5 -9
  30. package/dist/convex-hull/ConvexHullStats.svelte +12 -12
  31. package/dist/convex-hull/GasPressureControls.svelte +4 -4
  32. package/dist/convex-hull/TemperatureSlider.svelte +2 -2
  33. package/dist/convex-hull/demo-temperature.d.ts +1 -1
  34. package/dist/convex-hull/demo-temperature.js +20 -22
  35. package/dist/convex-hull/gas-thermodynamics.d.ts +2 -2
  36. package/dist/convex-hull/gas-thermodynamics.js +22 -30
  37. package/dist/convex-hull/helpers.d.ts +3 -0
  38. package/dist/convex-hull/helpers.js +17 -9
  39. package/dist/convex-hull/index.d.ts +1 -1
  40. package/dist/convex-hull/thermodynamics.js +83 -78
  41. package/dist/convex-hull/types.d.ts +1 -1
  42. package/dist/coordination/CoordinationBarPlot.svelte +23 -23
  43. package/dist/coordination/CoordinationBarPlot.svelte.d.ts +1 -1
  44. package/dist/element/ElementTile.svelte.d.ts +1 -1
  45. package/dist/fermi-surface/FermiSlice.svelte +13 -5
  46. package/dist/fermi-surface/FermiSurface.svelte +11 -5
  47. package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
  48. package/dist/fermi-surface/FermiSurfaceControls.svelte +1 -1
  49. package/dist/fermi-surface/FermiSurfaceScene.svelte +3 -0
  50. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +8 -34
  51. package/dist/fermi-surface/compute.js +59 -59
  52. package/dist/fermi-surface/export.js +3 -2
  53. package/dist/fermi-surface/parse.js +7 -4
  54. package/dist/fermi-surface/types.d.ts +1 -0
  55. package/dist/heatmap-matrix/HeatmapMatrix.svelte +23 -21
  56. package/dist/heatmap-matrix/index.js +1 -1
  57. package/dist/io/decompress.js +4 -2
  58. package/dist/io/export.d.ts +4 -4
  59. package/dist/io/export.js +47 -25
  60. package/dist/io/fetch.js +5 -1
  61. package/dist/io/file-drop.d.ts +1 -1
  62. package/dist/io/file-drop.js +35 -36
  63. package/dist/io/url-drop.js +64 -33
  64. package/dist/isosurface/parse.js +6 -7
  65. package/dist/isosurface/slice.js +5 -4
  66. package/dist/isosurface/types.js +1 -1
  67. package/dist/keyboard.d.ts +3 -0
  68. package/dist/keyboard.js +23 -0
  69. package/dist/labels.d.ts +1 -1
  70. package/dist/labels.js +8 -7
  71. package/dist/layout/PropertyFilter.svelte +3 -2
  72. package/dist/layout/SettingsSection.svelte +1 -1
  73. package/dist/layout/json-tree/JsonNode.svelte +1 -1
  74. package/dist/layout/json-tree/JsonTree.svelte +2 -2
  75. package/dist/layout/json-tree/utils.js +5 -4
  76. package/dist/marching-cubes.js +8 -13
  77. package/dist/math.d.ts +5 -1
  78. package/dist/math.js +24 -9
  79. package/dist/overlays/DraggablePane.svelte +4 -4
  80. package/dist/periodic-table/PeriodicTable.svelte +20 -9
  81. package/dist/periodic-table/PropertySelect.svelte +1 -0
  82. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +9 -3
  83. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
  84. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
  85. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +2 -1
  86. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +1 -1
  87. package/dist/phase-diagram/build-diagram.js +2 -2
  88. package/dist/phase-diagram/parse.js +6 -5
  89. package/dist/phase-diagram/types.d.ts +1 -1
  90. package/dist/phase-diagram/utils.d.ts +3 -3
  91. package/dist/phase-diagram/utils.js +8 -12
  92. package/dist/plot/{BarPlot.svelte → bar/BarPlot.svelte} +229 -587
  93. package/dist/plot/{BarPlot.svelte.d.ts → bar/BarPlot.svelte.d.ts} +5 -5
  94. package/dist/plot/{BarPlotControls.svelte → bar/BarPlotControls.svelte} +6 -5
  95. package/dist/plot/{BarPlotControls.svelte.d.ts → bar/BarPlotControls.svelte.d.ts} +3 -3
  96. package/dist/plot/{SpacegroupBarPlot.svelte → bar/SpacegroupBarPlot.svelte} +6 -6
  97. package/dist/plot/{SpacegroupBarPlot.svelte.d.ts → bar/SpacegroupBarPlot.svelte.d.ts} +1 -1
  98. package/dist/plot/bar/data.d.ts +40 -0
  99. package/dist/plot/bar/data.js +154 -0
  100. package/dist/plot/bar/geometry.d.ts +39 -0
  101. package/dist/plot/bar/geometry.js +60 -0
  102. package/dist/plot/bar/index.d.ts +3 -0
  103. package/dist/plot/bar/index.js +3 -0
  104. package/dist/plot/box/BoxPlot.svelte +1462 -0
  105. package/dist/plot/box/BoxPlot.svelte.d.ts +94 -0
  106. package/dist/plot/box/BoxPlotControls.svelte +109 -0
  107. package/dist/plot/box/BoxPlotControls.svelte.d.ts +19 -0
  108. package/dist/plot/box/Violin.svelte +14 -0
  109. package/dist/plot/box/Violin.svelte.d.ts +70 -0
  110. package/dist/plot/box/box-plot.d.ts +55 -0
  111. package/dist/plot/box/box-plot.js +126 -0
  112. package/dist/plot/box/index.d.ts +5 -0
  113. package/dist/plot/box/index.js +5 -0
  114. package/dist/plot/box/kde.d.ts +16 -0
  115. package/dist/plot/box/kde.js +160 -0
  116. package/dist/plot/box/quantile.d.ts +3 -0
  117. package/dist/plot/box/quantile.js +53 -0
  118. package/dist/plot/{auto-place.js → core/auto-place.js} +2 -2
  119. package/dist/plot/core/axis-utils.d.ts +46 -0
  120. package/dist/plot/core/axis-utils.js +110 -0
  121. package/dist/plot/{AxisLabel.svelte → core/components/AxisLabel.svelte} +2 -2
  122. package/dist/plot/{AxisLabel.svelte.d.ts → core/components/AxisLabel.svelte.d.ts} +1 -1
  123. package/dist/plot/{ColorBar.svelte → core/components/ColorBar.svelte} +36 -33
  124. package/dist/plot/{ColorBar.svelte.d.ts → core/components/ColorBar.svelte.d.ts} +2 -2
  125. package/dist/plot/{ColorScaleSelect.svelte → core/components/ColorScaleSelect.svelte} +4 -3
  126. package/dist/plot/{ColorScaleSelect.svelte.d.ts → core/components/ColorScaleSelect.svelte.d.ts} +2 -2
  127. package/dist/plot/core/components/ControlPane.svelte +46 -0
  128. package/dist/plot/core/components/ControlPane.svelte.d.ts +13 -0
  129. package/dist/plot/{FillArea.svelte → core/components/FillArea.svelte} +17 -6
  130. package/dist/plot/{FillArea.svelte.d.ts → core/components/FillArea.svelte.d.ts} +1 -1
  131. package/dist/plot/{InteractiveAxisLabel.svelte → core/components/InteractiveAxisLabel.svelte} +3 -3
  132. package/dist/plot/{InteractiveAxisLabel.svelte.d.ts → core/components/InteractiveAxisLabel.svelte.d.ts} +2 -2
  133. package/dist/plot/{Line.svelte → core/components/Line.svelte} +30 -13
  134. package/dist/plot/{PlotAxis.svelte → core/components/PlotAxis.svelte} +7 -5
  135. package/dist/plot/{PlotAxis.svelte.d.ts → core/components/PlotAxis.svelte.d.ts} +3 -2
  136. package/dist/plot/{PlotControls.svelte → core/components/PlotControls.svelte} +17 -29
  137. package/dist/plot/core/components/PlotControls.svelte.d.ts +4 -0
  138. package/dist/plot/{PlotLegend.svelte → core/components/PlotLegend.svelte} +21 -10
  139. package/dist/plot/{PlotLegend.svelte.d.ts → core/components/PlotLegend.svelte.d.ts} +3 -2
  140. package/dist/plot/{PlotTooltip.svelte → core/components/PlotTooltip.svelte} +17 -1
  141. package/dist/plot/{PlotTooltip.svelte.d.ts → core/components/PlotTooltip.svelte.d.ts} +8 -0
  142. package/dist/plot/{PortalSelect.svelte → core/components/PortalSelect.svelte} +11 -7
  143. package/dist/plot/{ReferenceLine.svelte → core/components/ReferenceLine.svelte} +3 -3
  144. package/dist/plot/{ReferenceLine.svelte.d.ts → core/components/ReferenceLine.svelte.d.ts} +1 -1
  145. package/dist/plot/{ReferenceLine3D.svelte → core/components/ReferenceLine3D.svelte} +4 -4
  146. package/dist/plot/{ReferenceLine3D.svelte.d.ts → core/components/ReferenceLine3D.svelte.d.ts} +2 -2
  147. package/dist/plot/{ReferencePlane.svelte → core/components/ReferencePlane.svelte} +7 -7
  148. package/dist/plot/{ReferencePlane.svelte.d.ts → core/components/ReferencePlane.svelte.d.ts} +2 -2
  149. package/dist/plot/{ZeroLines.svelte → core/components/ZeroLines.svelte} +3 -3
  150. package/dist/plot/{ZeroLines.svelte.d.ts → core/components/ZeroLines.svelte.d.ts} +3 -3
  151. package/dist/plot/{ZoomRect.svelte → core/components/ZoomRect.svelte} +1 -1
  152. package/dist/plot/{ZoomRect.svelte.d.ts → core/components/ZoomRect.svelte.d.ts} +1 -1
  153. package/dist/plot/core/components/index.d.ts +17 -0
  154. package/dist/plot/core/components/index.js +17 -0
  155. package/dist/plot/{data-cleaning.d.ts → core/data-cleaning.d.ts} +71 -1
  156. package/dist/plot/{data-cleaning.js → core/data-cleaning.js} +3 -5
  157. package/dist/plot/{data-transform.d.ts → core/data-transform.d.ts} +2 -2
  158. package/dist/plot/{data-transform.js → core/data-transform.js} +3 -3
  159. package/dist/plot/core/fill-utils.d.ts +33 -0
  160. package/dist/plot/core/fill-utils.js +388 -0
  161. package/dist/plot/{hover-lock.svelte.js → core/hover-lock.svelte.js} +5 -6
  162. package/dist/plot/core/index.d.ts +10 -0
  163. package/dist/plot/core/index.js +11 -0
  164. package/dist/plot/core/interactions.d.ts +35 -0
  165. package/dist/plot/core/interactions.js +195 -0
  166. package/dist/plot/{layout.d.ts → core/layout.d.ts} +1 -0
  167. package/dist/plot/{layout.js → core/layout.js} +16 -8
  168. package/dist/plot/{reference-line.d.ts → core/reference-line.d.ts} +1 -1
  169. package/dist/plot/{reference-line.js → core/reference-line.js} +23 -36
  170. package/dist/plot/{scales.d.ts → core/scales.d.ts} +2 -2
  171. package/dist/plot/{scales.js → core/scales.js} +84 -85
  172. package/dist/plot/core/svg.d.ts +2 -0
  173. package/dist/plot/core/svg.js +41 -0
  174. package/dist/plot/{types.d.ts → core/types.d.ts} +19 -79
  175. package/dist/plot/{types.js → core/types.js} +1 -1
  176. package/dist/plot/{utils → core/utils}/label-placement.d.ts +2 -2
  177. package/dist/plot/core/utils/series-visibility.d.ts +26 -0
  178. package/dist/plot/{utils → core/utils}/series-visibility.js +29 -2
  179. package/dist/plot/core/utils.d.ts +11 -0
  180. package/dist/plot/core/utils.js +27 -0
  181. package/dist/plot/{Histogram.svelte → histogram/Histogram.svelte} +154 -294
  182. package/dist/plot/{Histogram.svelte.d.ts → histogram/Histogram.svelte.d.ts} +2 -2
  183. package/dist/plot/{HistogramControls.svelte → histogram/HistogramControls.svelte} +6 -6
  184. package/dist/plot/{HistogramControls.svelte.d.ts → histogram/HistogramControls.svelte.d.ts} +4 -4
  185. package/dist/plot/histogram/index.d.ts +2 -0
  186. package/dist/plot/histogram/index.js +2 -0
  187. package/dist/plot/index.d.ts +8 -41
  188. package/dist/plot/index.js +10 -39
  189. package/dist/plot/sankey/Sankey.svelte +700 -0
  190. package/dist/plot/sankey/Sankey.svelte.d.ts +74 -0
  191. package/dist/plot/sankey/SankeyControls.svelte +98 -0
  192. package/dist/plot/sankey/SankeyControls.svelte.d.ts +19 -0
  193. package/dist/plot/sankey/index.d.ts +4 -0
  194. package/dist/plot/sankey/index.js +3 -0
  195. package/dist/plot/sankey/sankey-types.d.ts +42 -0
  196. package/dist/plot/sankey/sankey-types.js +4 -0
  197. package/dist/plot/sankey/sankey.d.ts +52 -0
  198. package/dist/plot/sankey/sankey.js +187 -0
  199. package/dist/plot/{BinnedScatterPlot.svelte → scatter/BinnedScatterPlot.svelte} +61 -59
  200. package/dist/plot/{BinnedScatterPlot.svelte.d.ts → scatter/BinnedScatterPlot.svelte.d.ts} +4 -4
  201. package/dist/plot/{ElementScatter.svelte → scatter/ElementScatter.svelte} +6 -6
  202. package/dist/plot/{ElementScatter.svelte.d.ts → scatter/ElementScatter.svelte.d.ts} +2 -2
  203. package/dist/plot/{ScatterPlot.svelte → scatter/ScatterPlot.svelte} +221 -642
  204. package/dist/plot/{ScatterPlot.svelte.d.ts → scatter/ScatterPlot.svelte.d.ts} +7 -7
  205. package/dist/plot/{ScatterPlotControls.svelte → scatter/ScatterPlotControls.svelte} +6 -5
  206. package/dist/plot/{ScatterPlotControls.svelte.d.ts → scatter/ScatterPlotControls.svelte.d.ts} +1 -1
  207. package/dist/plot/{ScatterPoint.svelte → scatter/ScatterPoint.svelte} +7 -7
  208. package/dist/plot/{ScatterPoint.svelte.d.ts → scatter/ScatterPoint.svelte.d.ts} +3 -3
  209. package/dist/plot/{adaptive-density.d.ts → scatter/adaptive-density.d.ts} +14 -4
  210. package/dist/plot/{adaptive-density.js → scatter/adaptive-density.js} +46 -20
  211. package/dist/plot/{binned-scatter-types.d.ts → scatter/binned-scatter-types.d.ts} +3 -3
  212. package/dist/plot/scatter/index.d.ts +7 -0
  213. package/dist/plot/scatter/index.js +5 -0
  214. package/dist/plot/scatter/scatter-data.d.ts +19 -0
  215. package/dist/plot/scatter/scatter-data.js +212 -0
  216. package/dist/plot/{ScatterPlot3D.svelte → scatter-3d/ScatterPlot3D.svelte} +12 -10
  217. package/dist/plot/{ScatterPlot3D.svelte.d.ts → scatter-3d/ScatterPlot3D.svelte.d.ts} +7 -7
  218. package/dist/plot/{ScatterPlot3DControls.svelte → scatter-3d/ScatterPlot3DControls.svelte} +5 -4
  219. package/dist/plot/{ScatterPlot3DControls.svelte.d.ts → scatter-3d/ScatterPlot3DControls.svelte.d.ts} +2 -2
  220. package/dist/plot/{ScatterPlot3DScene.svelte → scatter-3d/ScatterPlot3DScene.svelte} +11 -11
  221. package/dist/plot/{ScatterPlot3DScene.svelte.d.ts → scatter-3d/ScatterPlot3DScene.svelte.d.ts} +3 -3
  222. package/dist/plot/{Surface3D.svelte → scatter-3d/Surface3D.svelte} +1 -1
  223. package/dist/plot/{Surface3D.svelte.d.ts → scatter-3d/Surface3D.svelte.d.ts} +1 -1
  224. package/dist/plot/scatter-3d/index.d.ts +4 -0
  225. package/dist/plot/scatter-3d/index.js +4 -0
  226. package/dist/plot/sunburst/Sunburst.svelte +1045 -0
  227. package/dist/plot/sunburst/Sunburst.svelte.d.ts +96 -0
  228. package/dist/plot/sunburst/SunburstControls.svelte +200 -0
  229. package/dist/plot/sunburst/SunburstControls.svelte.d.ts +26 -0
  230. package/dist/plot/sunburst/index.d.ts +4 -0
  231. package/dist/plot/sunburst/index.js +4 -0
  232. package/dist/plot/sunburst/render.d.ts +34 -0
  233. package/dist/plot/sunburst/render.js +122 -0
  234. package/dist/plot/sunburst/sunburst.d.ts +62 -0
  235. package/dist/plot/sunburst/sunburst.js +266 -0
  236. package/dist/rdf/RdfPlot.svelte +2 -1
  237. package/dist/rdf/calc-rdf.js +11 -24
  238. package/dist/sanitize.js +1 -1
  239. package/dist/settings.d.ts +65 -1
  240. package/dist/settings.js +262 -0
  241. package/dist/spectral/Bands.svelte +39 -29
  242. package/dist/spectral/Bands.svelte.d.ts +3 -4
  243. package/dist/spectral/BandsAndDos.svelte +1 -1
  244. package/dist/spectral/BrillouinBandsDos.svelte +39 -27
  245. package/dist/spectral/Dos.svelte +10 -19
  246. package/dist/spectral/Dos.svelte.d.ts +2 -2
  247. package/dist/spectral/helpers.d.ts +3 -1
  248. package/dist/spectral/helpers.js +95 -29
  249. package/dist/structure/AtomLegend.svelte +8 -9
  250. package/dist/structure/CellSelect.svelte +1 -2
  251. package/dist/structure/Cylinder.svelte +12 -8
  252. package/dist/structure/Cylinder.svelte.d.ts +4 -1
  253. package/dist/structure/Structure.svelte +78 -72
  254. package/dist/structure/Structure.svelte.d.ts +1 -1
  255. package/dist/structure/StructureInfoPane.svelte +5 -6
  256. package/dist/structure/StructureScene.svelte +11 -10
  257. package/dist/structure/atom-properties.js +6 -6
  258. package/dist/structure/bond-order-perception.js +1 -1
  259. package/dist/structure/bonding.d.ts +1 -0
  260. package/dist/structure/bonding.js +43 -15
  261. package/dist/structure/export.js +27 -23
  262. package/dist/structure/index.d.ts +2 -4
  263. package/dist/structure/index.js +1 -3
  264. package/dist/structure/label-placement.js +4 -4
  265. package/dist/structure/measure.d.ts +3 -2
  266. package/dist/structure/measure.js +6 -5
  267. package/dist/structure/parse.js +121 -103
  268. package/dist/structure/pbc.js +4 -0
  269. package/dist/symmetry/SymmetryStats.svelte +2 -2
  270. package/dist/symmetry/index.d.ts +1 -1
  271. package/dist/symmetry/index.js +22 -24
  272. package/dist/symmetry/spacegroups.d.ts +7 -0
  273. package/dist/symmetry/spacegroups.js +48 -13
  274. package/dist/table/HeatmapTable.svelte +63 -11
  275. package/dist/table/HeatmapTable.svelte.d.ts +1 -1
  276. package/dist/table/index.d.ts +1 -3
  277. package/dist/table/index.js +1 -1
  278. package/dist/theme/index.js +8 -8
  279. package/dist/tooltip/KCoords.svelte +45 -0
  280. package/dist/tooltip/KCoords.svelte.d.ts +8 -0
  281. package/dist/tooltip/index.d.ts +1 -0
  282. package/dist/tooltip/index.js +1 -0
  283. package/dist/trajectory/Trajectory.svelte +66 -40
  284. package/dist/trajectory/Trajectory.svelte.d.ts +2 -1
  285. package/dist/trajectory/TrajectoryExportPane.svelte +2 -1
  286. package/dist/trajectory/TrajectoryInfoPane.svelte +2 -1
  287. package/dist/trajectory/format-detect.d.ts +1 -0
  288. package/dist/trajectory/format-detect.js +25 -11
  289. package/dist/trajectory/frame-reader.js +17 -50
  290. package/dist/trajectory/helpers.js +1 -1
  291. package/dist/trajectory/index.js +1 -1
  292. package/dist/trajectory/parse/hdf5.js +1 -1
  293. package/dist/trajectory/parse/index.js +14 -6
  294. package/dist/trajectory/parse/vasp.js +36 -17
  295. package/dist/trajectory/parse/xyz.d.ts +24 -0
  296. package/dist/trajectory/parse/xyz.js +102 -89
  297. package/dist/trajectory/plotting.d.ts +1 -1
  298. package/dist/trajectory/plotting.js +15 -15
  299. package/dist/utils.d.ts +1 -0
  300. package/dist/utils.js +6 -4
  301. package/dist/xrd/XrdPlot.svelte +2 -1
  302. package/dist/xrd/calc-xrd.js +15 -12
  303. package/dist/xrd/parse.js +2 -2
  304. package/package.json +22 -18
  305. package/dist/plot/PlotControls.svelte.d.ts +0 -4
  306. package/dist/plot/axis-utils.d.ts +0 -19
  307. package/dist/plot/axis-utils.js +0 -78
  308. package/dist/plot/defaults.d.ts +0 -19
  309. package/dist/plot/defaults.js +0 -9
  310. package/dist/plot/fill-utils.d.ts +0 -46
  311. package/dist/plot/fill-utils.js +0 -322
  312. package/dist/plot/interactions.d.ts +0 -12
  313. package/dist/plot/interactions.js +0 -101
  314. package/dist/plot/svg.d.ts +0 -1
  315. package/dist/plot/svg.js +0 -11
  316. package/dist/plot/utils/series-visibility.d.ts +0 -15
  317. package/dist/plot/utils.d.ts +0 -1
  318. package/dist/plot/utils.js +0 -14
  319. /package/dist/plot/{auto-place.d.ts → core/auto-place.d.ts} +0 -0
  320. /package/dist/plot/{Line.svelte.d.ts → core/components/Line.svelte.d.ts} +0 -0
  321. /package/dist/plot/{PortalSelect.svelte.d.ts → core/components/PortalSelect.svelte.d.ts} +0 -0
  322. /package/dist/plot/{hover-lock.svelte.d.ts → core/hover-lock.svelte.d.ts} +0 -0
  323. /package/dist/plot/{utils → core/utils}/label-placement.js +0 -0
  324. /package/dist/plot/{binned-scatter-types.js → scatter/binned-scatter-types.js} +0 -0
@@ -1,4 +1,5 @@
1
1
  import { COMPRESSION_EXTENSIONS_REGEX, COMPRESSION_FORMATS } from '../constants';
2
+ import { to_error } from '../utils';
2
3
  export function detect_compression_format(filename) {
3
4
  const lower = filename.toLowerCase();
4
5
  for (const [format, extensions] of Object.entries(COMPRESSION_FORMATS)) {
@@ -45,7 +46,8 @@ export function decompress_file(file) {
45
46
  reader.addEventListener(`load`, () => {
46
47
  try {
47
48
  const result = reader.result;
48
- if (!result)
49
+ // strict null check: empty files legitimately read as '' (falsy)
50
+ if (result === null)
49
51
  throw new Error(`Failed to read file`);
50
52
  if (is_supported && format) {
51
53
  if (!(result instanceof ArrayBuffer))
@@ -62,7 +64,7 @@ export function decompress_file(file) {
62
64
  }
63
65
  }
64
66
  catch (error) {
65
- reject(error);
67
+ reject(to_error(error));
66
68
  }
67
69
  }, { once: true });
68
70
  reader.addEventListener(`error`, () => reject(new Error(`Failed to read file ${file.name}`)));
@@ -2,10 +2,10 @@ import type { AnyStructure } from '../structure';
2
2
  import { type Camera, type Scene } from 'three';
3
3
  export declare function canvas_to_png_blob(canvas: HTMLCanvasElement, png_dpi?: number, scene?: Scene | null, camera?: Camera | null): Promise<Blob>;
4
4
  export declare function export_canvas_as_png(canvas: HTMLCanvasElement | null, structure_or_filename: AnyStructure | string | undefined, png_dpi?: number, scene?: Scene | null, camera?: Camera | null): void;
5
- export declare function svg_to_svg_string(svg_element: SVGElement): string;
6
- export declare function export_svg_as_svg(svg_element: SVGElement | null, filename: string): void;
7
- export declare function svg_to_png_blob(svg_element: SVGElement, png_dpi?: number): Promise<Blob>;
8
- export declare function export_svg_as_png(svg_element: SVGElement | null, filename: string, png_dpi?: number): void;
5
+ export declare function svg_to_svg_string(svg_element: SVGElement, inline_styles?: readonly string[]): string;
6
+ export declare function export_svg_as_svg(svg_element: SVGElement | null, filename: string, inline_styles?: readonly string[]): void;
7
+ export declare function svg_to_png_blob(svg_element: SVGElement, png_dpi?: number, inline_styles?: readonly string[]): Promise<Blob>;
8
+ export declare function export_svg_as_png(svg_element: SVGElement | null, filename: string, png_dpi?: number, inline_styles?: readonly string[]): void;
9
9
  export declare function get_ffmpeg_conversion_command(input_filename: string): string;
10
10
  export declare function export_trajectory_video(canvas: HTMLCanvasElement | null, filename: string, options?: {
11
11
  fps?: number;
package/dist/io/export.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { download } from './fetch';
2
2
  import { create_structure_filename } from '../structure/export';
3
+ import { to_error } from '../utils';
3
4
  import { Vector2 } from 'three';
4
5
  function is_webgl_renderer_like(value) {
5
6
  if (typeof value !== `object` || !value)
@@ -12,7 +13,8 @@ function is_webgl_renderer_like(value) {
12
13
  typeof renderer_obj.setSize === `function`);
13
14
  }
14
15
  function get_canvas_renderer(canvas) {
15
- const renderer_val = canvas[`__renderer`];
16
+ // oxlint-disable-next-line no-underscore-dangle -- three.js stores its renderer here
17
+ const renderer_val = canvas.__renderer;
16
18
  return is_webgl_renderer_like(renderer_val) ? renderer_val : undefined;
17
19
  }
18
20
  // Capture a WebGL canvas as a PNG Blob at the given DPI.
@@ -36,7 +38,7 @@ export function canvas_to_png_blob(canvas, png_dpi = 150, scene = null, camera =
36
38
  }, `image/png`);
37
39
  }
38
40
  catch (error) {
39
- reject(error);
41
+ reject(to_error(error));
40
42
  }
41
43
  });
42
44
  }
@@ -63,7 +65,7 @@ export function canvas_to_png_blob(canvas, png_dpi = 150, scene = null, camera =
63
65
  }
64
66
  catch (error) {
65
67
  restore();
66
- reject(error);
68
+ reject(to_error(error));
67
69
  }
68
70
  });
69
71
  }
@@ -90,34 +92,56 @@ export function export_canvas_as_png(canvas, structure_or_filename, png_dpi = 15
90
92
  }
91
93
  // Helper to ensure font-family is set on SVG root
92
94
  function set_svg_font_family(svg) {
93
- const style = svg.getAttribute(`style`) || ``;
95
+ const style = svg.getAttribute(`style`) ?? ``;
94
96
  if (!style.includes(`font-family`)) {
95
97
  svg.setAttribute(`style`, `${style}${style ? `;` : ``}font-family:sans-serif;`);
96
98
  }
97
99
  // Also set as attribute for extra robustness
98
100
  svg.setAttribute(`font-family`, `sans-serif`);
99
101
  }
100
- // Serialize an SVG element to a standalone SVG string with proper XML headers.
101
- // Clones the element to avoid mutation, sets font-family/xmlns, and
102
- // prepends XML declaration + SVG DOCTYPE. Returns a complete SVG document string
103
- // suitable for saving to file or further processing.
104
- export function svg_to_svg_string(svg_element) {
105
- const cloned_svg = svg_element.cloneNode(true);
106
- set_svg_font_family(cloned_svg);
107
- if (!cloned_svg.hasAttribute(`xmlns`)) {
108
- cloned_svg.setAttribute(`xmlns`, `http://www.w3.org/2000/svg`);
102
+ // Copy the given computed-style props from each live SVG element to its clone counterpart;
103
+ // identical structure lets querySelectorAll(`*`) walk both in lockstep. Writes clone-only.
104
+ function inline_computed_styles(live, clone, properties) {
105
+ const live_els = [live, ...live.querySelectorAll(`*`)];
106
+ const clone_els = [clone, ...clone.querySelectorAll(`*`)];
107
+ for (const [idx, live_el] of live_els.entries()) {
108
+ const computed = getComputedStyle(live_el);
109
+ for (const prop of properties) {
110
+ const val = computed.getPropertyValue(prop);
111
+ if (val)
112
+ clone_els[idx].setAttribute(prop, val);
113
+ }
114
+ }
115
+ }
116
+ // Clone, inline the given computed-style props, ensure font-family + xmlns, then serialize
117
+ // to a standalone SVG string. Never mutates the live element.
118
+ function serialize_svg_for_export(svg_element, inline_styles = []) {
119
+ const clone = svg_element.cloneNode(true);
120
+ if (inline_styles.length)
121
+ inline_computed_styles(svg_element, clone, inline_styles);
122
+ set_svg_font_family(clone);
123
+ if (!clone.hasAttribute(`xmlns`)) {
124
+ clone.setAttribute(`xmlns`, `http://www.w3.org/2000/svg`);
109
125
  }
110
- const svg_string = new XMLSerializer().serializeToString(cloned_svg);
126
+ return new XMLSerializer().serializeToString(clone);
127
+ }
128
+ // Wrap serialize_svg_for_export's output as a full SVG document string (XML declaration +
129
+ // DOCTYPE + SVG), suitable for saving to file.
130
+ export function svg_to_svg_string(svg_element,
131
+ // CSS props to inline from computed styles as presentation attributes; a standalone SVG
132
+ // drops page stylesheets (e.g. Svelte component styles), so class-based styling is lost.
133
+ inline_styles = []) {
134
+ const svg_string = serialize_svg_for_export(svg_element, inline_styles);
111
135
  return `<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n${svg_string}`;
112
136
  }
113
137
  // Export SVG element as SVG file (triggers browser download)
114
- export function export_svg_as_svg(svg_element, filename) {
138
+ export function export_svg_as_svg(svg_element, filename, inline_styles = []) {
115
139
  if (!svg_element) {
116
140
  console.warn(`SVG element not found for export`);
117
141
  return;
118
142
  }
119
143
  try {
120
- const svg_content = svg_to_svg_string(svg_element);
144
+ const svg_content = svg_to_svg_string(svg_element, inline_styles);
121
145
  download(svg_content, filename, `image/svg+xml;charset=utf-8`);
122
146
  }
123
147
  catch (error) {
@@ -129,7 +153,7 @@ export function export_svg_as_svg(svg_element, filename) {
129
153
  // Image element, and returns the resulting PNG Blob. Rejects if viewBox is
130
154
  // missing or dimensions are invalid (zero width/height).
131
155
  // DPI is converted to a resolution multiplier relative to 72 DPI baseline, capped at 10x.
132
- export function svg_to_png_blob(svg_element, png_dpi = 150) {
156
+ export function svg_to_png_blob(svg_element, png_dpi = 150, inline_styles = []) {
133
157
  const viewBox = svg_element.getAttribute(`viewBox`)?.trim();
134
158
  if (!viewBox)
135
159
  return Promise.reject(new Error(`SVG viewBox not found for PNG export`));
@@ -153,9 +177,7 @@ export function svg_to_png_blob(svg_element, png_dpi = 150) {
153
177
  return Promise.reject(new Error(`Canvas 2D context not available`));
154
178
  canvas.width = pixel_width;
155
179
  canvas.height = pixel_height;
156
- const cloned_svg = svg_element.cloneNode(true);
157
- set_svg_font_family(cloned_svg);
158
- const serialized = new XMLSerializer().serializeToString(cloned_svg);
180
+ const serialized = serialize_svg_for_export(svg_element, inline_styles);
159
181
  const svg_blob = new Blob([serialized], { type: `image/svg+xml;charset=utf-8` });
160
182
  const svg_data_url = URL.createObjectURL(svg_blob);
161
183
  return new Promise((resolve, reject) => {
@@ -172,7 +194,7 @@ export function svg_to_png_blob(svg_element, png_dpi = 150) {
172
194
  }, `image/png`, 1);
173
195
  }
174
196
  catch (error) {
175
- reject(error);
197
+ reject(to_error(error));
176
198
  }
177
199
  finally {
178
200
  URL.revokeObjectURL(svg_data_url);
@@ -186,12 +208,12 @@ export function svg_to_png_blob(svg_element, png_dpi = 150) {
186
208
  });
187
209
  }
188
210
  // Export SVG element as PNG (triggers browser download)
189
- export function export_svg_as_png(svg_element, filename, png_dpi = 150) {
211
+ export function export_svg_as_png(svg_element, filename, png_dpi = 150, inline_styles = []) {
190
212
  if (!svg_element) {
191
213
  console.warn(`SVG element not found for PNG export`);
192
214
  return;
193
215
  }
194
- svg_to_png_blob(svg_element, png_dpi)
216
+ svg_to_png_blob(svg_element, png_dpi, inline_styles)
195
217
  .then((blob) => download(blob, filename, `image/png`))
196
218
  .catch((error) => console.error(`Error exporting PNG:`, error));
197
219
  }
@@ -283,7 +305,7 @@ export async function export_trajectory_video(canvas, filename, options = {}) {
283
305
  resolve();
284
306
  }
285
307
  catch (error) {
286
- reject(error);
308
+ reject(to_error(error));
287
309
  }
288
310
  });
289
311
  recorder.addEventListener(`error`, (event) => {
@@ -309,7 +331,7 @@ export async function export_trajectory_video(canvas, filename, options = {}) {
309
331
  catch (error) {
310
332
  if (!is_resolved) {
311
333
  is_resolved = true;
312
- reject(error);
334
+ reject(to_error(error));
313
335
  }
314
336
  }
315
337
  });
package/dist/io/fetch.js CHANGED
@@ -22,7 +22,11 @@ function default_download(data, filename, type) {
22
22
  link.style.display = `none`;
23
23
  link.href = url;
24
24
  link.download = filename;
25
- document.body.appendChild(link);
25
+ // keep the synthetic download click from bubbling to document-level handlers
26
+ // (e.g. DraggablePane's click-outside) that would dismiss an open pane the export
27
+ // button lives in
28
+ link.addEventListener(`click`, (evt) => evt.stopPropagation());
29
+ document.body.append(link);
26
30
  link.click();
27
31
  link.remove();
28
32
  URL.revokeObjectURL(url);
@@ -4,4 +4,4 @@ export interface FileDropOptions {
4
4
  on_error?: (msg: string) => void;
5
5
  set_loading?: (loading: boolean) => void;
6
6
  }
7
- export declare function create_file_drop_handler(opts: FileDropOptions): (event: DragEvent) => Promise<void>;
7
+ export declare const create_file_drop_handler: (opts: FileDropOptions) => ((event: DragEvent) => Promise<void>);
@@ -1,43 +1,42 @@
1
1
  // Shared file-drop handler composable for drag-and-drop file loading.
2
2
  import { decompress_file } from './decompress';
3
3
  import { handle_url_drop } from './url-drop';
4
+ import { to_error } from '../utils';
4
5
  // Handles URL drops (from FilePicker), direct file drops with decompression,
5
6
  // loading state, and error reporting.
6
- export function create_file_drop_handler(opts) {
7
- return async (event) => {
8
- event.preventDefault();
9
- if (!opts.allow())
7
+ export const create_file_drop_handler = (opts) => async (event) => {
8
+ event.preventDefault();
9
+ if (!opts.allow())
10
+ return;
11
+ opts.set_loading?.(true);
12
+ let drop_filename = ``;
13
+ try {
14
+ let url_error;
15
+ const handled = await handle_url_drop(event, opts.on_drop).catch((exc) => {
16
+ url_error = to_error(exc).message;
17
+ return false;
18
+ });
19
+ if (handled)
20
+ return;
21
+ const file = event.dataTransfer?.files[0];
22
+ if (!file) {
23
+ if (url_error)
24
+ opts.on_error?.(`Failed to load from URL: ${url_error}`);
10
25
  return;
11
- opts.set_loading?.(true);
12
- let drop_filename = ``;
13
- try {
14
- let url_error;
15
- const handled = await handle_url_drop(event, opts.on_drop).catch((exc) => {
16
- url_error = exc instanceof Error ? exc.message : String(exc);
17
- return false;
18
- });
19
- if (handled)
20
- return;
21
- const file = event.dataTransfer?.files[0];
22
- if (!file) {
23
- if (url_error)
24
- opts.on_error?.(`Failed to load from URL: ${url_error}`);
25
- return;
26
- }
27
- drop_filename = file.name;
28
- const { content, filename } = await decompress_file(file);
29
- if (content)
30
- await opts.on_drop(content, filename);
31
- }
32
- catch (exc) {
33
- const detail = exc instanceof Error ? exc.message : String(exc);
34
- const msg = drop_filename
35
- ? `Failed to load file ${drop_filename}: ${detail}`
36
- : `Failed to load file: ${detail}`;
37
- opts.on_error?.(msg);
38
- }
39
- finally {
40
- opts.set_loading?.(false);
41
26
  }
42
- };
43
- }
27
+ drop_filename = file.name;
28
+ const { content, filename } = await decompress_file(file);
29
+ if (content)
30
+ await opts.on_drop(content, filename);
31
+ }
32
+ catch (exc) {
33
+ const detail = to_error(exc).message;
34
+ const msg = drop_filename
35
+ ? `Failed to load file ${drop_filename}: ${detail}`
36
+ : `Failed to load file: ${detail}`;
37
+ opts.on_error?.(msg);
38
+ }
39
+ finally {
40
+ opts.set_loading?.(false);
41
+ }
42
+ };
@@ -1,7 +1,9 @@
1
1
  import { load_binary_traj } from '../trajectory/parse';
2
- const BINARY_EXTENSIONS = new Set(`h5 hdf5 traj npz pkl dat gz gzip zip bz2 xz brml`.split(` `));
2
+ import { decompress_data_binary } from './decompress';
3
+ const BINARY_EXTENSIONS = new Set(`h5 hdf5 traj npz pkl dat gz gzip zip bz2 xz brml raw`.split(` `));
3
4
  const TEXT_EXTENSIONS = new Set(`xyz extxyz json cif poscar yaml yml txt md py js ts css html xml`.split(` `));
4
5
  const VASP_BASENAME_RE = /^(poscar|xdatcar|contcar)$/i;
6
+ const GZ_EXT_RE = /\.(gz|gzip)$/i;
5
7
  // Extract filename from Content-Disposition header, falling back to url_basename.
6
8
  function extract_filename(headers, fallback) {
7
9
  if (!headers)
@@ -9,9 +11,13 @@ function extract_filename(headers, fallback) {
9
11
  const content_disposition_str = headers.get(`content-disposition`);
10
12
  if (!content_disposition_str)
11
13
  return fallback;
12
- const star_match = /filename\*=(?:UTF-8''|)([^;]+)/i.exec(content_disposition_str);
14
+ const star_match = /filename\*=([^;]+)/i.exec(content_disposition_str);
13
15
  if (star_match?.[1]) {
14
- const raw = star_match[1].trim().replace(/^"|"$/g, ``);
16
+ let raw = star_match[1].trim().replaceAll(/^"|"$/g, ``);
17
+ // Strip any RFC 5987 charset'language' prefix; bare values pass through unchanged
18
+ const ext_value_match = /^[\w!#$%&+^`{}~-]+'[\w-]*'(.*)$/.exec(raw);
19
+ if (ext_value_match)
20
+ raw = ext_value_match[1];
15
21
  try {
16
22
  return decodeURIComponent(raw);
17
23
  }
@@ -20,7 +26,24 @@ function extract_filename(headers, fallback) {
20
26
  }
21
27
  }
22
28
  const plain_match = /filename\s*=\s*"?([^";]+)"?/i.exec(content_disposition_str);
23
- return plain_match?.[1]?.trim() || fallback;
29
+ // truthiness check (not ??) so whitespace-only `filename=` values fall back too
30
+ const name = plain_match?.[1]?.trim();
31
+ if (!name)
32
+ return fallback;
33
+ return name;
34
+ }
35
+ const ext_of = (name) => name.split(`.`).pop()?.toLowerCase() ?? ``;
36
+ // Whether the file inside a .gz/.gzip wrapper is a known binary format that a
37
+ // lossy text decode would corrupt (bytes >= 0x80 → U+FFFD)
38
+ const has_binary_inner_ext = (filename) => BINARY_EXTENSIONS.has(ext_of(filename.replace(GZ_EXT_RE, ``)));
39
+ // Gunzip a fetched payload → [content, filename] with .gz/.gzip stripped; content
40
+ // stays an ArrayBuffer for binary inner formats, decoded string otherwise
41
+ async function decompress_gz_payload(buffer, filename) {
42
+ const decompressed = await decompress_data_binary(buffer, `gzip`);
43
+ const content = has_binary_inner_ext(filename)
44
+ ? decompressed
45
+ : new TextDecoder().decode(decompressed);
46
+ return [content, filename.replace(GZ_EXT_RE, ``)];
24
47
  }
25
48
  // Handle URL-based file drop data by fetching content lazily
26
49
  export async function handle_url_drop(drag_event, callback) {
@@ -40,8 +63,10 @@ export async function handle_url_drop(drag_event, callback) {
40
63
  return true;
41
64
  }
42
65
  export async function load_from_url(url, callback) {
43
- const url_basename = url.split(`/`).pop() || url;
44
- const ext = url_basename.split(`.`).pop()?.toLowerCase() || ``;
66
+ // Strip query string/hash before basename/extension detection so pre-signed
67
+ // URLs like traj.h5?X-Amz-Expires=300 still hit the right format path
68
+ const url_basename = url.split(/[?#]/)[0].split(`/`).pop() ?? url;
69
+ const ext = ext_of(url_basename);
45
70
  if (BINARY_EXTENSIONS.has(ext)) {
46
71
  // Force binary mode for known binary files to handle GitHub Pages content-type issues
47
72
  const resp = await fetch(url);
@@ -51,17 +76,11 @@ export async function load_from_url(url, callback) {
51
76
  // Handle gzipped files with proper content-encoding detection
52
77
  if (ext === `gz` || ext === `gzip`) {
53
78
  if (resp.headers.get(`content-encoding`) === `gzip`) {
54
- // Browser automatically decompressed it, so it's text
55
- return callback(await resp.text(), filename);
56
- }
57
- else {
58
- // Need to decompress manually
59
- const { decompress_data } = await import(`./decompress`);
60
- const buffer = await resp.arrayBuffer();
61
- const content = await decompress_data(buffer, `gzip`);
62
- // Remove .gz extension when manually decompressing
63
- return callback(content, filename.replace(/\.gz$/, ``));
79
+ // Browser already decompressed the stored .gz (GitHub Pages-style serving), so
80
+ // the body is the inner file — keep binary inner formats (.h5.gz, ...) binary
81
+ return callback(await (has_binary_inner_ext(filename) ? resp.arrayBuffer() : resp.text()), filename);
64
82
  }
83
+ return callback(...(await decompress_gz_payload(await resp.arrayBuffer(), filename)));
65
84
  }
66
85
  // For H5 files, always load as binary regardless of signature
67
86
  // to handle files that have .h5/.hdf5 extensions but may not have the proper HDF5 signature
@@ -82,40 +101,52 @@ export async function load_from_url(url, callback) {
82
101
  const buffer = await load_binary_traj(resp, `.traj`);
83
102
  return callback(buffer, filename);
84
103
  }
85
- if (resp.headers.get(`content-encoding`) === `gzip`) {
86
- return callback(await resp.text(), filename);
87
- }
104
+ // Content-Encoding is transparent transport compression: fetch already
105
+ // decompressed the body, so binary formats (npz, pkl, brml, ...) must still
106
+ // be read as ArrayBuffer — .text() would corrupt them via lossy UTF-8 decode
88
107
  return callback(await resp.arrayBuffer(), filename);
89
108
  }
90
109
  // Skip Range requests for known text formats to avoid production server issues
91
110
  // Include VASP files that don't have extensions (POSCAR, XDATCAR, CONTCAR)
92
111
  const is_known_text = TEXT_EXTENSIONS.has(ext) || VASP_BASENAME_RE.test(url_basename);
93
- let binary_callback_args;
112
+ let sniffed_callback_args;
94
113
  if (!is_known_text) {
114
+ // Only the Range sniff is guarded (failure → plain text fetch). Once magic bytes
115
+ // commit to a binary format, download/decompress errors must propagate instead of
116
+ // falling through to a text fetch that would parse the binary bytes as garbage.
117
+ let sniffed = null;
95
118
  try {
96
- // Check for magic bytes only for unknown formats
119
+ // Check for magic bytes only for unknown formats (covers extensionless URLs
120
+ // like blob: object URLs whose basenames are UUIDs)
97
121
  const head = await fetch(url, { headers: { Range: `bytes=0-15` } });
98
122
  if (head.ok) {
99
123
  const buf = new Uint8Array(await head.arrayBuffer());
100
124
  const is_gzip = buf[0] === 0x1f && buf[1] === 0x8b;
101
125
  const is_hdf5 = buf[0] === 0x89 && buf[1] === 0x48 && buf[2] === 0x44 && buf[3] === 0x46;
102
- if (is_gzip || is_hdf5) {
103
- const resp = await fetch(url);
104
- if (!resp.ok)
105
- throw new Error(`Fetch failed: ${resp.status}`);
106
- binary_callback_args = [
107
- await resp.arrayBuffer(),
108
- extract_filename(resp.headers, url_basename),
109
- ];
110
- }
126
+ // ASE .traj files start with the Ulm signature "- of Ulm"
127
+ const is_ase_traj = [0x2d, 0x20, 0x6f, 0x66, 0x20, 0x55, 0x6c, 0x6d].every((byte, idx) => buf[idx] === byte);
128
+ if (is_gzip)
129
+ sniffed = `gzip`;
130
+ else if (is_hdf5 || is_ase_traj)
131
+ sniffed = `binary`;
111
132
  }
112
133
  }
113
134
  catch {
114
- // Fall through to text fetch if HEAD request fails
135
+ // Fall through to text fetch if the Range HEAD request fails
136
+ }
137
+ if (sniffed) {
138
+ const resp = await fetch(url);
139
+ if (!resp.ok)
140
+ throw new Error(`Fetch failed: ${resp.status}`);
141
+ const filename = extract_filename(resp.headers, url_basename);
142
+ const buffer = await resp.arrayBuffer();
143
+ // Gunzip sniffed gzip — downstream parsers can't handle raw gzip bytes
144
+ sniffed_callback_args =
145
+ sniffed === `gzip` ? await decompress_gz_payload(buffer, filename) : [buffer, filename];
115
146
  }
116
147
  }
117
- if (binary_callback_args)
118
- return callback(...binary_callback_args);
148
+ if (sniffed_callback_args)
149
+ return callback(...sniffed_callback_args);
119
150
  const resp = await fetch(url);
120
151
  if (!resp.ok)
121
152
  throw new Error(`Fetch failed: ${resp.status}`);
@@ -43,8 +43,8 @@ function parse_float_block(text, pos, max_count, data, data_offset = 0) {
43
43
  const start = pos;
44
44
  while (pos < len && text.charCodeAt(pos) > 32)
45
45
  pos++;
46
- // Parse number using unary + (handles scientific notation)
47
- const num = +text.substring(start, pos);
46
+ // Parse number (handles scientific notation)
47
+ const num = Number(text.slice(start, pos));
48
48
  if (!Number.isNaN(num)) {
49
49
  data[idx++] = num;
50
50
  }
@@ -68,7 +68,7 @@ function read_line(text, pos) {
68
68
  let end = pos;
69
69
  while (end < text.length && text.charCodeAt(end) !== 10 && text.charCodeAt(end) !== 13)
70
70
  end++;
71
- const line = text.substring(pos, end);
71
+ const line = text.slice(pos, end);
72
72
  let next = end;
73
73
  if (next < text.length && text.charCodeAt(next) === 13)
74
74
  next++; // skip \r
@@ -200,7 +200,7 @@ export function parse_chgcar(content) {
200
200
  }
201
201
  // Detect VASP 5+ format (has element symbols before counts)
202
202
  const first_token = cur.line.trim().split(/\s+/)[0];
203
- const has_element_symbols = isNaN(parseInt(first_token));
203
+ const has_element_symbols = isNaN(parseInt(first_token, 10));
204
204
  if (has_element_symbols) {
205
205
  element_symbols = cur.line.trim().split(/\s+/);
206
206
  pos = cur.next;
@@ -210,14 +210,13 @@ export function parse_chgcar(content) {
210
210
  return null;
211
211
  }
212
212
  atom_counts = cur.line.trim().split(/\s+/).map(Number);
213
- pos = cur.next;
214
213
  }
215
214
  else {
216
215
  atom_counts = cur.line.trim().split(/\s+/).map(Number);
217
216
  const fallback_elements = [`H`, `He`, `Li`, `Be`, `B`, `C`, `N`, `O`, `F`, `Ne`];
218
217
  element_symbols = atom_counts.map((_count, idx) => fallback_elements[idx % fallback_elements.length]);
219
- pos = cur.next;
220
218
  }
219
+ pos = cur.next;
221
220
  if (pos >= content.length) {
222
221
  console.error(`CHGCAR: file ends before coordinate mode line`);
223
222
  return null;
@@ -519,7 +518,7 @@ export function parse_volumetric_file(content, filename) {
519
518
  // Content-based detection (only parse first few lines, not the whole file)
520
519
  // Find enough lines for detection without splitting the entire string
521
520
  const detection_end = find_line_offset(content, 10);
522
- const detection_text = content.substring(0, detection_end);
521
+ const detection_text = content.slice(0, detection_end);
523
522
  const lines = detection_text.split(/\r?\n/);
524
523
  // .cube detection: line 3 has 4 numbers (n_atoms + origin), line 4 has 4 numbers (grid dim + voxel)
525
524
  if (lines.length > 4) {
@@ -32,9 +32,10 @@ export function trilinear_interpolate(grid, fx, fy, fz, periodic) {
32
32
  const x1 = periodic ? (x0 + 1) % nx : Math.min(x0 + 1, nx - 1);
33
33
  const y1 = periodic ? (y0 + 1) % ny : Math.min(y0 + 1, ny - 1);
34
34
  const z1 = periodic ? (z0 + 1) % nz : Math.min(z0 + 1, nz - 1);
35
- const xd = gx - Math.floor(gx);
36
- const yd = gy - Math.floor(gy);
37
- const zd = gz - Math.floor(gz);
35
+ // deltas from clamped lower index (non-periodic x0 clamps to nx-2 so floor(gx) may != x0)
36
+ const xd = periodic ? gx - Math.floor(gx) : gx - x0;
37
+ const yd = periodic ? gy - Math.floor(gy) : gy - y0;
38
+ const zd = periodic ? gz - Math.floor(gz) : gz - z0;
38
39
  // 8-point interpolation
39
40
  const c000 = grid[x0][y0][z0];
40
41
  const c001 = grid[x0][y0][z1];
@@ -71,7 +72,7 @@ export function sample_hkl_slice(volume, miller_indices, distance, n_points) {
71
72
  ];
72
73
  if (Math.hypot(...plane_normal) < 1e-12)
73
74
  return null; // degenerate normal
74
- const unit_normal = math.normalize_vec3(plane_normal);
75
+ const unit_normal = math.normalize_vec(plane_normal);
75
76
  // In-plane basis vectors
76
77
  const [u_vec, v_vec] = math.compute_in_plane_basis(unit_normal);
77
78
  const cart_to_frac = math.create_cart_to_frac(lattice);
@@ -13,7 +13,7 @@ export const LAYER_COLORS = [
13
13
  // Compute min/max/abs_max/mean of a 3D grid.
14
14
  // Prefer using the precomputed `data_range` field on VolumetricData when available.
15
15
  export function grid_data_range(grid) {
16
- if (!grid.length || !grid[0]?.length || !grid[0][0]?.length) {
16
+ if (grid.length === 0 || !grid[0]?.length || !grid[0][0]?.length) {
17
17
  return { min: 0, max: 0, abs_max: 0, mean: 0 };
18
18
  }
19
19
  let [min_val, max_val] = [Infinity, -Infinity];
@@ -0,0 +1,3 @@
1
+ export type KeydownHandler = (event: KeyboardEvent) => boolean;
2
+ export declare const handle_and_prevent: (handle: KeydownHandler) => (event: KeyboardEvent) => void;
3
+ export declare const forward_window_keydown: (is_hovered: () => boolean, handle: KeydownHandler) => (event: KeyboardEvent) => void;
@@ -0,0 +1,23 @@
1
+ // Shared helpers for wiring viewer keyboard shortcuts consistently across
2
+ // components (Structure, Trajectory, ...). A handler returns `true` when it
3
+ // handled the event, so the browser default is suppressed in exactly one place
4
+ // instead of scattered `preventDefault()` calls.
5
+ // Wrap a handler for an element-level `onkeydown` binding: run it and suppress
6
+ // the browser default (page scroll, find, ...) when it reports it handled the key.
7
+ export const handle_and_prevent = (handle) => (event) => {
8
+ if (handle(event))
9
+ event.preventDefault();
10
+ };
11
+ // Wrap a handler for a `<svelte:window onkeydown>` binding: forward keydowns to a
12
+ // viewer only while it's hovered and focus is on <body>. This lets shortcuts work
13
+ // without first clicking the viewer, while keeping multiple viewers on one page
14
+ // from all responding and never hijacking keys from a focused input/pane.
15
+ // Suppresses the browser default when the handler reports it handled the key.
16
+ export const forward_window_keydown = (is_hovered, handle) => {
17
+ const run = handle_and_prevent(handle);
18
+ return (event) => {
19
+ const active = document.activeElement;
20
+ if (is_hovered() && (!active || active === document.body))
21
+ run(event);
22
+ };
23
+ };
package/dist/labels.d.ts CHANGED
@@ -11,7 +11,7 @@ export declare const ELEM_PROPERTY_LABELS: Partial<Record<keyof ChemicalElement,
11
11
  export declare const ELEM_HEATMAP_KEYS: (keyof ChemicalElement)[];
12
12
  export declare const ELEM_HEATMAP_LABELS: Partial<Record<string, keyof ChemicalElement>>;
13
13
  export declare const DEFAULT_FMT: [string, string];
14
- export declare const FRACTION_GLYPHS: ReadonlyArray<readonly [number, string]>;
14
+ export declare const FRACTION_GLYPHS: readonly (readonly [number, string])[];
15
15
  export declare const format_num: (num: number, fmt?: string | number) => string;
16
16
  export declare const format_vec3: (vec: Readonly<Vec3>, fmt_spec?: string) => string;
17
17
  export declare const format_bytes: (bytes?: number) => string;
package/dist/labels.js CHANGED
@@ -5,7 +5,7 @@ const is_d3_symbol_name = (name) => Object.hasOwn(d3_symbols, `symbol${name}`);
5
5
  function name_for_symbol(sym) {
6
6
  for (const [key, symbol] of Object.entries(d3_symbols)) {
7
7
  if (symbol === sym && /^symbol[A-Z]/.test(key)) {
8
- const name = key.substring(6);
8
+ const name = key.slice(6);
9
9
  if (is_d3_symbol_name(name))
10
10
  return name;
11
11
  }
@@ -34,7 +34,7 @@ export function format_value(value, formatter) {
34
34
  if (Number.isNaN(value))
35
35
  return `NaN`;
36
36
  // Format and normalize unicode minus
37
- const formatted = format(formatter)(value).replace(/−/g, `-`);
37
+ const formatted = format(formatter)(value).replaceAll('−', `-`);
38
38
  // Handle percentage formatting - remove trailing zeros
39
39
  if (formatter.includes(`%`)) {
40
40
  return formatted.includes(`.`)
@@ -157,9 +157,10 @@ export function parse_si_float(value) {
157
157
  if (typeof value !== `string`)
158
158
  return value;
159
159
  // Remove whitespace and commas
160
- const cleaned = value.trim().replace(/(\d),(\d)/g, `$1$2`);
161
- // Check if the value is a SI-formatted number (e.g. "1.23k", "4.56M", "789µ", "12n")
162
- const match = /^([-+]?\d*\.?\d+)\s*([yzafpnµmkMGTPEZY])?$/i.exec(cleaned);
160
+ const cleaned = value.trim().replaceAll(/(\d),(\d)/g, `$1$2`);
161
+ // SI-formatted number (e.g. "1.23k", "789µ"). Suffixes are case-sensitive (m=milli vs
162
+ // M=mega), so no `i` flag: mismatched-case suffixes return the string as-is.
163
+ const match = /^([-+]?\d*\.?\d+)\s*([yzafpnµmkMGTPEZY])?$/.exec(cleaned);
163
164
  if (match) {
164
165
  const [, num_part, suffix] = match;
165
166
  let multiplier = 1;
@@ -167,7 +168,7 @@ export function parse_si_float(value) {
167
168
  const suffixes = `yzafpnµm kMGTPEZY`;
168
169
  const index = suffixes.indexOf(suffix);
169
170
  if (index !== -1) {
170
- multiplier = Math.pow(1000, index - 8);
171
+ multiplier = 1000 ** (index - 8);
171
172
  }
172
173
  }
173
174
  return parseFloat(num_part) * multiplier;
@@ -232,7 +233,7 @@ export const SUBSCRIPT_MAP = {
232
233
  '9': `₉`,
233
234
  };
234
235
  // replaces all signs and digits with their unicode superscript equivalent
235
- export const superscript_digits = (input) => input.replace(/[\d+-]/g, (match) => is_superscript_key(match) ? SUPERSCRIPT_MAP[match] : match);
236
+ export const superscript_digits = (input) => input.replaceAll(/[\d+-]/g, (match) => is_superscript_key(match) ? SUPERSCRIPT_MAP[match] : match);
236
237
  // Trajectory property configuration: clean labels and units as structured data
237
238
  export const trajectory_property_config = {
238
239
  // Energy properties