matterviz 0.3.1 → 0.3.2

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 (257) hide show
  1. package/dist/FilePicker.svelte +37 -20
  2. package/dist/Icon.svelte +2 -2
  3. package/dist/app.css +29 -0
  4. package/dist/brillouin/BrillouinZone.svelte +19 -61
  5. package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
  6. package/dist/brillouin/BrillouinZoneExportPane.svelte +12 -20
  7. package/dist/brillouin/BrillouinZoneScene.svelte +2 -2
  8. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
  9. package/dist/chempot-diagram/ChemPotDiagram.svelte +192 -0
  10. package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
  11. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +677 -0
  12. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
  13. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +2688 -0
  14. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
  15. package/dist/chempot-diagram/ChemPotScene3D.svelte +8 -0
  16. package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +7 -0
  17. package/dist/chempot-diagram/color.d.ts +10 -0
  18. package/dist/chempot-diagram/color.js +33 -0
  19. package/dist/chempot-diagram/compute.d.ts +38 -0
  20. package/dist/chempot-diagram/compute.js +650 -0
  21. package/dist/chempot-diagram/index.d.ts +5 -0
  22. package/dist/chempot-diagram/index.js +5 -0
  23. package/dist/chempot-diagram/pointer.d.ts +16 -0
  24. package/dist/chempot-diagram/pointer.js +40 -0
  25. package/dist/chempot-diagram/temperature.d.ts +15 -0
  26. package/dist/chempot-diagram/temperature.js +37 -0
  27. package/dist/chempot-diagram/types.d.ts +83 -0
  28. package/dist/chempot-diagram/types.js +27 -0
  29. package/dist/colors/index.d.ts +3 -1
  30. package/dist/colors/index.js +4 -0
  31. package/dist/composition/BarChart.svelte +13 -22
  32. package/dist/composition/BubbleChart.svelte +5 -3
  33. package/dist/composition/FormulaFilter.svelte +586 -94
  34. package/dist/composition/FormulaFilter.svelte.d.ts +35 -1
  35. package/dist/composition/PieChart.svelte +43 -18
  36. package/dist/composition/PieChart.svelte.d.ts +1 -1
  37. package/dist/convex-hull/ConvexHull.svelte +4 -2
  38. package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
  39. package/dist/convex-hull/ConvexHull2D.svelte +13 -44
  40. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
  41. package/dist/convex-hull/ConvexHull3D.svelte +16 -7
  42. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
  43. package/dist/convex-hull/ConvexHull4D.svelte +17 -7
  44. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
  45. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +1 -1
  46. package/dist/convex-hull/ConvexHullStats.svelte +701 -226
  47. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +6 -1
  48. package/dist/convex-hull/ConvexHullTooltip.svelte +1 -0
  49. package/dist/convex-hull/demo-temperature.d.ts +6 -0
  50. package/dist/convex-hull/demo-temperature.js +36 -0
  51. package/dist/convex-hull/helpers.d.ts +1 -1
  52. package/dist/convex-hull/helpers.js +2 -4
  53. package/dist/convex-hull/index.d.ts +1 -0
  54. package/dist/convex-hull/index.js +1 -0
  55. package/dist/convex-hull/thermodynamics.d.ts +8 -21
  56. package/dist/convex-hull/thermodynamics.js +106 -17
  57. package/dist/convex-hull/types.d.ts +5 -0
  58. package/dist/convex-hull/types.js +5 -0
  59. package/dist/coordination/CoordinationBarPlot.svelte +29 -46
  60. package/dist/element/BohrAtom.svelte +1 -1
  61. package/dist/element/data.js +2 -14
  62. package/dist/element/data.json.gz +0 -0
  63. package/dist/element/types.d.ts +1 -0
  64. package/dist/fermi-surface/FermiSurface.svelte +20 -64
  65. package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
  66. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  67. package/dist/fermi-surface/FermiSurfaceScene.svelte +1 -1
  68. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
  69. package/dist/fermi-surface/parse.js +16 -22
  70. package/dist/heatmap-matrix/HeatmapMatrix.svelte +1273 -0
  71. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +110 -0
  72. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +171 -0
  73. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +31 -0
  74. package/dist/heatmap-matrix/index.d.ts +53 -0
  75. package/dist/heatmap-matrix/index.js +100 -0
  76. package/dist/heatmap-matrix/shared.d.ts +2 -0
  77. package/dist/heatmap-matrix/shared.js +4 -0
  78. package/dist/icons.d.ts +111 -0
  79. package/dist/icons.js +111 -0
  80. package/dist/index.d.ts +3 -1
  81. package/dist/index.js +3 -1
  82. package/dist/io/export.js +15 -3
  83. package/dist/io/file-drop.d.ts +7 -0
  84. package/dist/io/file-drop.js +43 -0
  85. package/dist/io/index.d.ts +2 -2
  86. package/dist/io/index.js +2 -112
  87. package/dist/io/types.d.ts +1 -0
  88. package/dist/io/url-drop.d.ts +2 -0
  89. package/dist/io/url-drop.js +118 -0
  90. package/dist/isosurface/Isosurface.svelte +101 -45
  91. package/dist/isosurface/IsosurfaceControls.svelte +19 -0
  92. package/dist/isosurface/parse.js +73 -30
  93. package/dist/isosurface/slice.d.ts +2 -1
  94. package/dist/isosurface/slice.js +3 -3
  95. package/dist/isosurface/types.d.ts +13 -1
  96. package/dist/isosurface/types.js +98 -0
  97. package/dist/labels.d.ts +2 -1
  98. package/dist/labels.js +1 -0
  99. package/dist/layout/InfoTag.svelte +62 -62
  100. package/dist/layout/SubpageGrid.svelte +74 -0
  101. package/dist/layout/SubpageGrid.svelte.d.ts +14 -0
  102. package/dist/layout/index.d.ts +1 -0
  103. package/dist/layout/index.js +1 -0
  104. package/dist/layout/json-tree/JsonNode.svelte +83 -85
  105. package/dist/layout/json-tree/JsonTree.svelte +20 -19
  106. package/dist/layout/json-tree/JsonTree.svelte.d.ts +1 -1
  107. package/dist/layout/json-tree/JsonValue.svelte +196 -116
  108. package/dist/layout/json-tree/types.d.ts +10 -2
  109. package/dist/layout/json-tree/utils.d.ts +2 -0
  110. package/dist/layout/json-tree/utils.js +33 -0
  111. package/dist/math.d.ts +7 -0
  112. package/dist/math.js +358 -7
  113. package/dist/overlays/ContextMenu.svelte +3 -2
  114. package/dist/overlays/DraggablePane.svelte +163 -58
  115. package/dist/overlays/DraggablePane.svelte.d.ts +2 -0
  116. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +232 -77
  117. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -2
  118. package/dist/phase-diagram/PhaseDiagramControls.svelte +32 -11
  119. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +3 -2
  120. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +103 -0
  121. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
  122. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +102 -95
  123. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +7 -0
  124. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +100 -26
  125. package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +6 -3
  126. package/dist/phase-diagram/index.d.ts +2 -0
  127. package/dist/phase-diagram/index.js +2 -0
  128. package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
  129. package/dist/phase-diagram/svg-to-diagram.js +865 -0
  130. package/dist/phase-diagram/types.d.ts +10 -0
  131. package/dist/phase-diagram/utils.d.ts +7 -4
  132. package/dist/phase-diagram/utils.js +149 -59
  133. package/dist/plot/AxisLabel.svelte +26 -0
  134. package/dist/plot/AxisLabel.svelte.d.ts +16 -0
  135. package/dist/plot/BarPlot.svelte +473 -228
  136. package/dist/plot/BarPlot.svelte.d.ts +3 -3
  137. package/dist/plot/BarPlotControls.svelte +3 -2
  138. package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
  139. package/dist/plot/ColorBar.svelte +54 -54
  140. package/dist/plot/ColorBar.svelte.d.ts +1 -1
  141. package/dist/plot/ColorScaleSelect.svelte +1 -1
  142. package/dist/plot/ElementScatter.svelte +3 -2
  143. package/dist/plot/FillArea.svelte +4 -1
  144. package/dist/plot/Histogram.svelte +320 -230
  145. package/dist/plot/Histogram.svelte.d.ts +2 -2
  146. package/dist/plot/HistogramControls.svelte +29 -10
  147. package/dist/plot/HistogramControls.svelte.d.ts +6 -2
  148. package/dist/plot/InteractiveAxisLabel.svelte.d.ts +2 -2
  149. package/dist/plot/PlotControls.svelte +109 -27
  150. package/dist/plot/PlotControls.svelte.d.ts +1 -1
  151. package/dist/plot/PlotLegend.svelte +1 -1
  152. package/dist/plot/PortalSelect.svelte +2 -1
  153. package/dist/plot/ReferenceLine.svelte +2 -1
  154. package/dist/plot/ReferenceLine.svelte.d.ts +1 -0
  155. package/dist/plot/ReferencePlane.svelte +1 -3
  156. package/dist/plot/ScatterPlot.svelte +343 -209
  157. package/dist/plot/ScatterPlot.svelte.d.ts +3 -3
  158. package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
  159. package/dist/plot/ScatterPlot3DControls.svelte +203 -250
  160. package/dist/plot/ScatterPlot3DScene.svelte +4 -7
  161. package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
  162. package/dist/plot/ScatterPlotControls.svelte +95 -55
  163. package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
  164. package/dist/plot/ZeroLines.svelte +44 -0
  165. package/dist/plot/ZeroLines.svelte.d.ts +32 -0
  166. package/dist/plot/ZoomRect.svelte +21 -0
  167. package/dist/plot/ZoomRect.svelte.d.ts +8 -0
  168. package/dist/plot/axis-utils.d.ts +1 -1
  169. package/dist/plot/index.d.ts +6 -2
  170. package/dist/plot/index.js +6 -2
  171. package/dist/plot/interactions.d.ts +8 -10
  172. package/dist/plot/interactions.js +2 -3
  173. package/dist/plot/layout.d.ts +7 -1
  174. package/dist/plot/layout.js +12 -4
  175. package/dist/plot/reference-line.d.ts +4 -21
  176. package/dist/plot/reference-line.js +7 -81
  177. package/dist/plot/types.d.ts +42 -17
  178. package/dist/plot/types.js +10 -0
  179. package/dist/plot/utils/label-placement.js +13 -10
  180. package/dist/plot/utils.d.ts +1 -0
  181. package/dist/plot/utils.js +14 -0
  182. package/dist/rdf/RdfPlot.svelte +55 -66
  183. package/dist/settings.d.ts +3 -0
  184. package/dist/settings.js +17 -3
  185. package/dist/spectral/Bands.svelte +515 -143
  186. package/dist/spectral/Bands.svelte.d.ts +22 -2
  187. package/dist/spectral/helpers.d.ts +23 -1
  188. package/dist/spectral/helpers.js +65 -9
  189. package/dist/spectral/types.d.ts +2 -0
  190. package/dist/structure/AtomLegend.svelte +29 -8
  191. package/dist/structure/AtomLegend.svelte.d.ts +1 -1
  192. package/dist/structure/CellSelect.svelte +92 -22
  193. package/dist/structure/Structure.svelte +108 -118
  194. package/dist/structure/Structure.svelte.d.ts +1 -1
  195. package/dist/structure/StructureControls.svelte +25 -22
  196. package/dist/structure/StructureControls.svelte.d.ts +1 -1
  197. package/dist/structure/StructureInfoPane.svelte +7 -1
  198. package/dist/structure/StructureScene.svelte +104 -66
  199. package/dist/structure/StructureScene.svelte.d.ts +2 -1
  200. package/dist/structure/atom-properties.d.ts +6 -2
  201. package/dist/structure/atom-properties.js +38 -25
  202. package/dist/structure/export.js +10 -7
  203. package/dist/structure/ferrox-wasm-types.d.ts +3 -2
  204. package/dist/structure/ferrox-wasm-types.js +0 -3
  205. package/dist/structure/ferrox-wasm.d.ts +3 -2
  206. package/dist/structure/ferrox-wasm.js +1 -2
  207. package/dist/structure/index.d.ts +6 -0
  208. package/dist/structure/index.js +22 -0
  209. package/dist/structure/parse.js +19 -16
  210. package/dist/structure/partial-occupancy.d.ts +25 -0
  211. package/dist/structure/partial-occupancy.js +102 -0
  212. package/dist/structure/validation.js +6 -3
  213. package/dist/symmetry/SymmetryStats.svelte +18 -4
  214. package/dist/symmetry/WyckoffTable.svelte +18 -10
  215. package/dist/symmetry/index.d.ts +7 -4
  216. package/dist/symmetry/index.js +83 -18
  217. package/dist/table/HeatmapTable.svelte +425 -65
  218. package/dist/table/HeatmapTable.svelte.d.ts +12 -1
  219. package/dist/table/ToggleMenu.svelte +2 -0
  220. package/dist/table/index.d.ts +2 -0
  221. package/dist/trajectory/Trajectory.svelte +147 -145
  222. package/dist/trajectory/TrajectoryExportPane.svelte +13 -9
  223. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +1 -1
  224. package/dist/trajectory/constants.d.ts +6 -0
  225. package/dist/trajectory/constants.js +7 -0
  226. package/dist/trajectory/extract.js +3 -5
  227. package/dist/trajectory/format-detect.d.ts +9 -0
  228. package/dist/trajectory/format-detect.js +76 -0
  229. package/dist/trajectory/frame-reader.d.ts +17 -0
  230. package/dist/trajectory/frame-reader.js +339 -0
  231. package/dist/trajectory/helpers.d.ts +15 -0
  232. package/dist/trajectory/helpers.js +187 -0
  233. package/dist/trajectory/index.d.ts +1 -0
  234. package/dist/trajectory/index.js +11 -4
  235. package/dist/trajectory/parse/ase.d.ts +2 -0
  236. package/dist/trajectory/parse/ase.js +76 -0
  237. package/dist/trajectory/parse/hdf5.d.ts +2 -0
  238. package/dist/trajectory/parse/hdf5.js +121 -0
  239. package/dist/trajectory/parse/index.d.ts +12 -0
  240. package/dist/trajectory/parse/index.js +304 -0
  241. package/dist/trajectory/parse/lammps.d.ts +5 -0
  242. package/dist/trajectory/parse/lammps.js +169 -0
  243. package/dist/trajectory/parse/vasp.d.ts +2 -0
  244. package/dist/trajectory/parse/vasp.js +65 -0
  245. package/dist/trajectory/parse/xyz.d.ts +2 -0
  246. package/dist/trajectory/parse/xyz.js +109 -0
  247. package/dist/trajectory/types.d.ts +11 -0
  248. package/dist/trajectory/types.js +1 -0
  249. package/dist/utils.d.ts +2 -0
  250. package/dist/utils.js +4 -0
  251. package/dist/xrd/XrdPlot.svelte +6 -4
  252. package/dist/xrd/calc-xrd.js +0 -1
  253. package/package.json +30 -24
  254. package/readme.md +4 -4
  255. package/dist/trajectory/parse.d.ts +0 -42
  256. package/dist/trajectory/parse.js +0 -1267
  257. /package/dist/element/{data.json.d.ts → data.json.gz.d.ts} +0 -0
@@ -1,8 +1,9 @@
1
1
  <script lang="ts">import { ELEMENT_COLOR_SCHEMES } from '../colors';
2
2
  import { normalize_show_controls } from '../controls';
3
+ import { StatusMessage } from '../feedback';
3
4
  import Spinner from '../feedback/Spinner.svelte';
4
5
  import Icon from '../Icon.svelte';
5
- import { decompress_file, handle_url_drop, load_from_url } from '../io';
6
+ import { create_file_drop_handler, load_from_url } from '../io';
6
7
  import { parse_volumetric_file } from '../isosurface/parse';
7
8
  import { auto_isosurface_settings, DEFAULT_ISOSURFACE_SETTINGS, } from '../isosurface/types';
8
9
  import { ELEM_SYMBOLS } from '../labels';
@@ -10,7 +11,7 @@ import { set_fullscreen_bg, toggle_fullscreen } from '../layout';
10
11
  import { create_cart_to_frac, create_frac_to_cart } from '../math';
11
12
  import { DEFAULTS } from '../settings';
12
13
  import { colors } from '../state.svelte';
13
- import { get_element_counts, get_pbc_image_sites } from './';
14
+ import { get_element_counts, get_pbc_image_sites, get_site_vector, } from './';
14
15
  import { wrap_to_unit_cell } from './pbc';
15
16
  import { is_valid_supercell_input, make_supercell } from './supercell';
16
17
  import * as symmetry from '../symmetry';
@@ -141,17 +142,18 @@ $effect(() => {
141
142
  });
142
143
  // Track if force vectors were auto-enabled to prevent repeated triggering
143
144
  let force_vectors_auto_enabled = $state(false);
144
- // Auto-enable force vectors when structure has force data
145
+ // Auto-enable force vectors when structure has vector data (force, magmom, or spin)
145
146
  $effect(() => {
146
147
  if (structure?.sites && !force_vectors_auto_enabled) {
147
- const has_force_data = structure.sites.some((site) => site.properties?.force && Array.isArray(site.properties.force));
148
- // Enable force vectors if structure has force data
149
- if (has_force_data && !scene_props.show_force_vectors) {
148
+ const has_vector_data = structure.sites.some((site) => get_site_vector(site) !== null);
149
+ if (!has_vector_data)
150
+ return;
151
+ if (!scene_props.show_force_vectors) {
150
152
  scene_props.show_force_vectors = true;
151
153
  scene_props.force_scale ??= DEFAULTS.structure.force_scale;
152
154
  scene_props.force_color ??= DEFAULTS.structure.force_color;
153
- force_vectors_auto_enabled = true;
154
155
  }
156
+ force_vectors_auto_enabled = true;
155
157
  }
156
158
  });
157
159
  // Optimize scene props for performance based on structure size and mode
@@ -172,6 +174,7 @@ $effect(() => {
172
174
  let property_colors = $derived(get_property_colors(structure, atom_color_config, scene_props.bonding_strategy, sym_data));
173
175
  let symmetry_run_id = 0;
174
176
  let symmetry_error = $state();
177
+ let last_symmetry_structure_ref = null;
175
178
  // Trigger symmetry analysis when structure is loaded or settings change.
176
179
  // Skip during atom drags — symmetry doesn't change from moving atoms,
177
180
  // and WASM analysis on every drag frame causes severe frame drops.
@@ -183,16 +186,28 @@ $effect(() => {
183
186
  sym_data = null;
184
187
  symmetry_error = undefined;
185
188
  });
189
+ last_symmetry_structure_ref = null;
186
190
  return;
187
191
  }
188
192
  const current_structure = structure;
193
+ const structure_changed = current_structure !== last_symmetry_structure_ref;
194
+ if (structure_changed) {
195
+ untrack(() => {
196
+ sym_data = null;
197
+ symmetry_error = undefined;
198
+ });
199
+ last_symmetry_structure_ref = current_structure;
200
+ }
201
+ else {
202
+ // Keep previous symmetry data while recomputing so bound consumers
203
+ // (e.g. SymmetryStats inputs) do not unmount and lose focus.
204
+ untrack(() => symmetry_error = undefined);
205
+ }
189
206
  const run_id = ++symmetry_run_id;
190
207
  // Destructure symmetry_settings to ensure Svelte tracks changes to symprec and algo
191
208
  // (reading just the object reference isn't sufficient for fine-grained reactivity)
192
209
  const { symprec, algo } = symmetry_settings ?? symmetry.default_sym_settings;
193
210
  const current_settings = { symprec, algo };
194
- // Use untrack to prevent cascading reactivity when resetting state
195
- untrack(() => [sym_data, symmetry_error] = [null, undefined]);
196
211
  symmetry.ensure_moyo_wasm_ready()
197
212
  .then(() => run_id === symmetry_run_id
198
213
  ? symmetry.analyze_structure_symmetry(current_structure, current_settings)
@@ -204,6 +219,7 @@ $effect(() => {
204
219
  })
205
220
  .catch((err) => {
206
221
  if (run_id === symmetry_run_id) {
222
+ untrack(() => sym_data = null);
207
223
  symmetry_error = `Symmetry analysis failed: ${err?.message || err}`;
208
224
  console.error(`Symmetry analysis failed:`, err);
209
225
  }
@@ -458,7 +474,6 @@ let camera = $state(undefined);
458
474
  let orbit_controls = $state(undefined);
459
475
  let rotation_target_ref = $state(undefined);
460
476
  let initial_computed_zoom = $state(undefined);
461
- let camera_move_timeout = $state(null);
462
477
  // Mutual exclusion: opening one pane closes others
463
478
  $effect(() => {
464
479
  if (info_pane_open) {
@@ -480,29 +495,53 @@ $effect(() => {
480
495
  if (structure)
481
496
  camera_has_moved = false;
482
497
  });
483
- // Set camera_has_moved to true when camera starts moving
484
- $effect(() => untrack(() => {
485
- if (camera_is_moving) {
486
- camera_has_moved = true;
487
- // Debounce camera move events to avoid excessive emissions
488
- if (camera_move_timeout)
489
- clearTimeout(camera_move_timeout);
490
- camera_move_timeout = setTimeout(() => {
491
- const { camera_position } = scene_props;
492
- on_camera_move?.({ structure, camera_has_moved, camera_position });
493
- }, 200);
494
- }
495
- }));
498
+ const read_orbit_target = () => {
499
+ if (!orbit_controls?.target)
500
+ return;
501
+ const { x, y, z } = orbit_controls.target;
502
+ return [x, y, z];
503
+ };
504
+ const read_camera_position = () => camera
505
+ ? [camera.position.x, camera.position.y, camera.position.z]
506
+ : scene_props.camera_position;
507
+ // Emit debounced camera updates while controls are active.
508
+ $effect(() => {
509
+ if (!camera_is_moving)
510
+ return;
511
+ camera_has_moved = true;
512
+ const emit_camera_move = () => {
513
+ const camera_position = read_camera_position();
514
+ if (camera_position === undefined)
515
+ return;
516
+ const camera_target = read_orbit_target();
517
+ scene_props.camera_position = camera_position;
518
+ scene_props.camera_target = camera_target;
519
+ on_camera_move?.({
520
+ structure,
521
+ camera_has_moved,
522
+ camera_position,
523
+ camera_target,
524
+ });
525
+ };
526
+ emit_camera_move();
527
+ const emit_interval = setInterval(emit_camera_move, 200);
528
+ return () => clearInterval(emit_interval);
529
+ });
496
530
  function reset_camera() {
497
- // Reset camera position to trigger automatic positioning
531
+ // Reset camera position to trigger automatic positioning.
498
532
  scene_props.camera_position = [0, 0, 0];
533
+ scene_props.camera_target = rotation_target_ref;
499
534
  camera_has_moved = false;
500
- // Manually reset zoom and pan using the exposed initial values
535
+ let camera_position = [0, 0, 0];
536
+ let camera_target = rotation_target_ref;
537
+ // Reset pan/zoom and ensure controls target returns to structure center.
501
538
  if (orbit_controls && camera) {
502
- // Reset the target to the structure center (pan reset)
539
+ if (`reset` in orbit_controls &&
540
+ typeof orbit_controls.reset === `function`)
541
+ orbit_controls.reset();
503
542
  if (orbit_controls.target && rotation_target_ref) {
504
- const [x, y, z] = rotation_target_ref;
505
- orbit_controls.target.set(x, y, z);
543
+ const [target_x, target_y, target_z] = rotation_target_ref;
544
+ orbit_controls.target.set(target_x, target_y, target_z);
506
545
  }
507
546
  // Reset zoom for orthographic camera
508
547
  if (`zoom` in camera && initial_computed_zoom !== undefined) {
@@ -511,11 +550,14 @@ function reset_camera() {
511
550
  ortho_camera.updateProjectionMatrix();
512
551
  }
513
552
  // Call update to apply changes immediately
514
- if (typeof orbit_controls.update === `function`) {
553
+ if (typeof orbit_controls.update === `function`)
515
554
  orbit_controls.update();
516
- }
555
+ camera_position = read_camera_position() ?? camera_position;
556
+ camera_target = read_orbit_target();
517
557
  }
518
- on_camera_reset?.({ structure, camera_has_moved, camera_position: [0, 0, 0] });
558
+ scene_props.camera_position = camera_position;
559
+ scene_props.camera_target = camera_target;
560
+ on_camera_reset?.({ structure, camera_has_moved, camera_position, camera_target });
519
561
  }
520
562
  const emit_file_load_event = (structure, filename, content) => on_file_load?.({
521
563
  structure: structure,
@@ -558,61 +600,33 @@ function parse_file_content(text_content, filename) {
558
600
  structure = parsed;
559
601
  return parsed;
560
602
  }
561
- async function handle_file_drop(event) {
562
- event.preventDefault();
563
- dragover = false;
564
- if (!allow_file_drop)
565
- return;
566
- loading = true;
567
- error_msg = undefined; // Clear previous error when a new file is dropped
568
- try {
569
- // Handle URL-based files (e.g. from FilePicker)
570
- const handled = await handle_url_drop(event, on_file_drop || ((content, filename) => {
571
- try {
572
- const text_content = content instanceof ArrayBuffer
573
- ? new TextDecoder().decode(content)
574
- : content;
575
- const parsed = parse_file_content(text_content, filename);
576
- emit_file_load_event(parsed, filename, content);
577
- }
578
- catch (err) {
579
- error_msg = `Failed to parse structure: ${err}`;
580
- on_error?.({ error_msg, filename });
581
- }
582
- })).catch(() => false);
583
- if (handled)
584
- return;
585
- // Handle file system drops
586
- const file = event.dataTransfer?.files[0];
587
- if (file) {
588
- try {
589
- const { content, filename } = await decompress_file(file);
590
- if (content) {
591
- if (on_file_drop)
592
- on_file_drop(content, filename);
593
- else {
594
- // Parse structure internally when no handler provided
595
- try {
596
- const parsed = parse_file_content(content, filename);
597
- emit_file_load_event(parsed, filename, content);
598
- }
599
- catch (err) {
600
- error_msg = `Failed to parse structure: ${err}`;
601
- on_error?.({ error_msg, filename });
602
- }
603
- }
604
- }
605
- }
606
- catch (error) {
607
- error_msg = `Failed to load file ${file.name}: ${error}`;
608
- on_error?.({ error_msg, filename: file.name });
609
- }
603
+ const handle_file_drop = create_file_drop_handler({
604
+ allow: () => allow_file_drop,
605
+ on_drop: (content, filename) => {
606
+ if (on_file_drop)
607
+ return on_file_drop(content, filename);
608
+ try {
609
+ const text_content = content instanceof ArrayBuffer
610
+ ? new TextDecoder().decode(content)
611
+ : content;
612
+ const parsed = parse_file_content(text_content, filename);
613
+ emit_file_load_event(parsed, filename, content);
610
614
  }
611
- }
612
- finally {
613
- loading = false;
614
- }
615
- }
615
+ catch (err) {
616
+ error_msg = `Failed to parse structure: ${err instanceof Error ? err.message : String(err)}`;
617
+ on_error?.({ error_msg, filename });
618
+ }
619
+ },
620
+ on_error: (msg) => {
621
+ error_msg = msg;
622
+ on_error?.({ error_msg: msg });
623
+ },
624
+ set_loading: (val) => {
625
+ loading = val;
626
+ if (val)
627
+ [error_msg, dragover] = [undefined, false];
628
+ },
629
+ });
616
630
  function handle_keydown(event) {
617
631
  // Don't handle shortcuts if user is typing in an input field
618
632
  const target = event.target;
@@ -726,11 +740,16 @@ function handle_keydown(event) {
726
740
  }
727
741
  }
728
742
  }
729
- // Interface shortcuts
730
- if (event.key === `f` && fullscreen_toggle)
743
+ // Interface shortcuts (require Ctrl/Cmd modifier to avoid accidental triggers)
744
+ const has_modifier = event.ctrlKey || event.metaKey;
745
+ if (event.key === `f` && has_modifier && fullscreen_toggle) {
746
+ event.preventDefault();
731
747
  toggle_fullscreen(wrapper);
732
- else if (event.key === `i` && enable_info_pane)
748
+ }
749
+ else if (event.key === `i` && has_modifier && enable_info_pane) {
750
+ event.preventDefault();
733
751
  info_pane_open = !info_pane_open;
752
+ }
734
753
  else if (event.key === `Escape`) {
735
754
  // Prioritize closing panes, then exit edit modes, then exit fullscreen
736
755
  if (info_pane_open)
@@ -948,10 +967,7 @@ $effect(() => {
948
967
  style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%)"
949
968
  />
950
969
  {:else if error_msg}
951
- <div class="error-state">
952
- <p class="error">{error_msg}</p>
953
- <button onclick={() => (error_msg = undefined)}>Dismiss</button>
954
- </div>
970
+ <StatusMessage bind:message={error_msg} type="error" dismissible />
955
971
  {:else if (structure?.sites?.length ?? 0) > 0}
956
972
  <section
957
973
  class="control-buttons {controls_config.class}"
@@ -1432,32 +1448,6 @@ $effect(() => {
1432
1448
  display: grid;
1433
1449
  place-content: center;
1434
1450
  }
1435
- .error-state {
1436
- display: flex;
1437
- flex-direction: column;
1438
- align-items: center;
1439
- justify-content: center;
1440
- height: var(--struct-height, 500px);
1441
- padding: 2rem;
1442
- text-align: center;
1443
- box-sizing: border-box;
1444
- }
1445
- .error-state p {
1446
- color: var(--error-color, #ff6b6b);
1447
- margin: 0 0 1rem;
1448
- }
1449
- .error-state button {
1450
- padding: 0.5rem 1rem;
1451
- background: var(--error-color, #ff6b6b);
1452
- color: white;
1453
- border: none;
1454
- border-radius: var(--border-radius, 3pt);
1455
- cursor: pointer;
1456
- font-size: 0.9rem;
1457
- }
1458
- .error-state button:hover {
1459
- background: var(--error-color-hover, #ff5252);
1460
- }
1461
1451
  .symmetry-error {
1462
1452
  position: absolute;
1463
1453
  bottom: 1rem;
@@ -78,6 +78,6 @@ type $$ComponentProps = {
78
78
  on_camera_move?: EventHandler;
79
79
  on_camera_reset?: EventHandler;
80
80
  } & Omit<ComponentProps<typeof StructureControls>, `children` | `onclose`> & Omit<HTMLAttributes<HTMLDivElement>, `children`>;
81
- declare const Structure: import("svelte").Component<$$ComponentProps, {}, "height" | "width" | "dragover" | "fullscreen" | "structure" | "hovered" | "controls_open" | "color_scheme" | "background_color" | "background_opacity" | "show_image_atoms" | "info_pane_open" | "loading" | "error_msg" | "atom_color_config" | "hidden_elements" | "hidden_prop_vals" | "element_mapping" | "element_radius_overrides" | "site_radius_overrides" | "selected_sites" | "sym_data" | "supercell_scaling" | "cell_type" | "active_volume_idx" | "measured_sites" | "scene_props" | "lattice_props" | "volumetric_data" | "isosurface_settings" | "wrapper" | "png_dpi" | "measure_mode" | "enable_measure_mode" | "performance_mode" | "displayed_structure" | "symmetry_settings">;
81
+ declare const Structure: import("svelte").Component<$$ComponentProps, {}, "structure" | "height" | "width" | "dragover" | "controls_open" | "loading" | "fullscreen" | "hovered" | "color_scheme" | "background_color" | "background_opacity" | "show_image_atoms" | "info_pane_open" | "wrapper" | "sym_data" | "error_msg" | "atom_color_config" | "hidden_elements" | "hidden_prop_vals" | "element_mapping" | "element_radius_overrides" | "site_radius_overrides" | "selected_sites" | "supercell_scaling" | "cell_type" | "active_volume_idx" | "measured_sites" | "scene_props" | "lattice_props" | "volumetric_data" | "isosurface_settings" | "png_dpi" | "measure_mode" | "enable_measure_mode" | "performance_mode" | "displayed_structure" | "symmetry_settings">;
82
82
  type Structure = ReturnType<typeof Structure>;
83
83
  export default Structure;
@@ -1,11 +1,11 @@
1
1
  <script lang="ts">import { AXIS_COLORS, ELEMENT_COLOR_SCHEMES } from '../colors';
2
+ import IsosurfaceControls from '../isosurface/IsosurfaceControls.svelte';
2
3
  import { format_num } from '../labels';
3
4
  import { SettingsSection } from '../layout';
4
5
  import { to_degrees, to_radians } from '../math';
5
6
  import DraggablePane from '../overlays/DraggablePane.svelte';
6
7
  import { ColorScaleSelect } from '../plot';
7
8
  import { DEFAULTS, SETTINGS_CONFIG } from '../settings';
8
- import IsosurfaceControls from '../isosurface/IsosurfaceControls.svelte';
9
9
  import { Lattice, StructureScene } from './';
10
10
  import { is_valid_supercell_input } from './supercell';
11
11
  import Select from 'svelte-multiselect';
@@ -103,7 +103,12 @@ function get_representative_colors(scheme_name) {
103
103
 
104
104
  <DraggablePane
105
105
  bind:show={controls_open}
106
- pane_props={{ ...pane_props, class: `controls-pane ${pane_props?.class ?? ``}` }}
106
+ resizable="both"
107
+ pane_props={{
108
+ ...pane_props,
109
+ class: `controls-pane ${pane_props?.class ?? ``}`,
110
+ style: `--pane-max-height: 70vh; ${pane_props?.style ?? ``}`,
111
+ }}
107
112
  toggle_props={{
108
113
  title: controls_open ? `` : `Structure controls`,
109
114
  ...toggle_props,
@@ -111,6 +116,14 @@ function get_representative_colors(scheme_name) {
111
116
  }}
112
117
  {...rest}
113
118
  >
119
+ {#if volumetric_data?.length && isosurface_settings}
120
+ <IsosurfaceControls
121
+ bind:settings={isosurface_settings}
122
+ volumes={volumetric_data}
123
+ bind:active_volume_idx
124
+ />
125
+ {/if}
126
+
114
127
  <SettingsSection
115
128
  title="Visibility"
116
129
  current_values={{
@@ -424,9 +437,7 @@ function get_representative_colors(scheme_name) {
424
437
  Same size atoms
425
438
  <input type="checkbox" bind:checked={scene_props.same_size_atoms} />
426
439
  </label>
427
- <span
428
- class="label"
429
- style="align-items: start"
440
+ <label
430
441
  {@attach tooltip({ content: SETTINGS_CONFIG.color_scheme.description })}
431
442
  >
432
443
  Color scheme
@@ -436,7 +447,10 @@ function get_representative_colors(scheme_name) {
436
447
  minSelect={1}
437
448
  bind:selected={color_scheme_selected}
438
449
  liOptionStyle="padding: 3pt 6pt;"
439
- style="width: 10em; border: none"
450
+ liSelectedStyle="background-color: transparent;"
451
+ ulSelectedStyle="display: contents;"
452
+ inputStyle="flex: none; min-width: 0; width: 0; opacity: 0;"
453
+ style="flex: 1; min-width: 0; border: none; margin-left: 4pt"
440
454
  aria-label="Color scheme"
441
455
  >
442
456
  {#snippet children({ option })}
@@ -454,9 +468,8 @@ function get_representative_colors(scheme_name) {
454
468
  </div>
455
469
  {/snippet}
456
470
  </Select>
457
- </span>
471
+ </label>
458
472
  <label
459
- style="align-items: start"
460
473
  {@attach tooltip({ content: SETTINGS_CONFIG.structure.atom_color_mode.description })}
461
474
  >
462
475
  Atom coloring
@@ -470,9 +483,7 @@ function get_representative_colors(scheme_name) {
470
483
  </select>
471
484
  </label>
472
485
  {#if atom_color_config.mode !== `element`}
473
- <span
474
- class="label"
475
- style="align-items: start; white-space: nowrap"
486
+ <label
476
487
  {@attach tooltip({ content: SETTINGS_CONFIG.structure.atom_color_scale.description })}
477
488
  >
478
489
  Color scale
@@ -484,10 +495,10 @@ function get_representative_colors(scheme_name) {
484
495
  wrapper_style: `width: 100%;`,
485
496
  title_style: `font-size: 0.95em;`,
486
497
  }}
487
- style="width: 100%; border: none"
498
+ style="flex: 1; min-width: 0; border: none"
488
499
  aria-label="Color scale"
489
500
  />
490
- </span>
501
+ </label>
491
502
  {/if}
492
503
  </SettingsSection>
493
504
 
@@ -884,14 +895,6 @@ function get_representative_colors(scheme_name) {
884
895
  </label>
885
896
  </SettingsSection>
886
897
  {/if}
887
-
888
- {#if volumetric_data?.length && isosurface_settings}
889
- <IsosurfaceControls
890
- bind:settings={isosurface_settings}
891
- volumes={volumetric_data}
892
- bind:active_volume_idx
893
- />
894
- {/if}
895
898
  </DraggablePane>
896
899
 
897
900
  <style>
@@ -925,7 +928,7 @@ function get_representative_colors(scheme_name) {
925
928
  justify-content: space-between;
926
929
  width: 100%;
927
930
  }
928
- label, .label {
931
+ label {
929
932
  display: flex;
930
933
  align-items: center;
931
934
  gap: 10pt;
@@ -1,5 +1,5 @@
1
- import DraggablePane from '../overlays/DraggablePane.svelte';
2
1
  import type { IsosurfaceSettings, VolumetricData } from '../isosurface/types';
2
+ import DraggablePane from '../overlays/DraggablePane.svelte';
3
3
  import type { AnyStructure } from './';
4
4
  import { Lattice, StructureScene } from './';
5
5
  import type { AtomColorConfig } from './atom-properties';
@@ -96,12 +96,18 @@ let pane_data = $derived.by(() => {
96
96
  else
97
97
  roto_translations++;
98
98
  }
99
+ const international_symbol = sym_data.international_short;
100
+ const space_group_symbol = (sym_data.hm_symbol ?? international_symbol)
101
+ ?.replace(/\s+/g, ``);
102
+ const space_group_value = space_group_symbol
103
+ ? `${sym_data.number} (${space_group_symbol})`
104
+ : String(sym_data.number);
99
105
  sections.push({
100
106
  title: `Symmetry`,
101
107
  items: [
102
108
  {
103
109
  label: `Space Group`,
104
- value: String(sym_data.number),
110
+ value: space_group_value,
105
111
  key: `symmetry-space-group`,
106
112
  },
107
113
  {