matterviz 0.3.5 → 0.3.7

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 (229) hide show
  1. package/dist/MillerIndexInput.svelte +5 -5
  2. package/dist/api/optimade.js +3 -3
  3. package/dist/brillouin/BrillouinZone.svelte +5 -2
  4. package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
  5. package/dist/brillouin/BrillouinZoneExportPane.svelte +1 -3
  6. package/dist/brillouin/BrillouinZoneInfoPane.svelte +1 -1
  7. package/dist/brillouin/BrillouinZoneScene.svelte +5 -5
  8. package/dist/brillouin/compute.js +21 -21
  9. package/dist/brillouin/index.d.ts +1 -1
  10. package/dist/brillouin/index.js +0 -1
  11. package/dist/brillouin/types.d.ts +8 -13
  12. package/dist/chempot-diagram/ChemPotDiagram.svelte +3 -3
  13. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +3 -4
  14. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +33 -34
  15. package/dist/chempot-diagram/compute.js +1 -7
  16. package/dist/chempot-diagram/temperature.d.ts +1 -1
  17. package/dist/chempot-diagram/temperature.js +1 -3
  18. package/dist/chempot-diagram/types.d.ts +4 -9
  19. package/dist/colors/index.js +5 -5
  20. package/dist/composition/Composition.svelte +2 -1
  21. package/dist/composition/Formula.svelte +7 -4
  22. package/dist/composition/FormulaFilter.svelte +1 -3
  23. package/dist/composition/format.js +4 -4
  24. package/dist/composition/parse.d.ts +2 -1
  25. package/dist/composition/parse.js +61 -46
  26. package/dist/convex-hull/ConvexHull2D.svelte +62 -51
  27. package/dist/convex-hull/ConvexHull3D.svelte +101 -90
  28. package/dist/convex-hull/ConvexHull4D.svelte +70 -58
  29. package/dist/convex-hull/ConvexHullControls.svelte +24 -35
  30. package/dist/convex-hull/ConvexHullInfoPane.svelte +8 -5
  31. package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +2 -0
  32. package/dist/convex-hull/ConvexHullStats.svelte +9 -2
  33. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +2 -0
  34. package/dist/convex-hull/GasPressureControls.svelte +7 -7
  35. package/dist/convex-hull/StructurePopup.svelte +65 -30
  36. package/dist/convex-hull/StructurePopup.svelte.d.ts +6 -6
  37. package/dist/convex-hull/TemperatureSlider.svelte +8 -5
  38. package/dist/convex-hull/barycentric-coords.d.ts +2 -2
  39. package/dist/convex-hull/barycentric-coords.js +2 -2
  40. package/dist/convex-hull/gas-thermodynamics.js +2 -4
  41. package/dist/convex-hull/helpers.d.ts +13 -2
  42. package/dist/convex-hull/helpers.js +37 -16
  43. package/dist/convex-hull/index.d.ts +1 -0
  44. package/dist/convex-hull/index.js +1 -0
  45. package/dist/convex-hull/thermodynamics.d.ts +2 -1
  46. package/dist/convex-hull/thermodynamics.js +7 -7
  47. package/dist/convex-hull/types.d.ts +15 -15
  48. package/dist/effects.svelte.d.ts +12 -0
  49. package/dist/effects.svelte.js +37 -0
  50. package/dist/element/BohrAtom.svelte +4 -4
  51. package/dist/element/data.json.gz.d.ts +3 -1
  52. package/dist/element/index.d.ts +1 -1
  53. package/dist/element/index.js +0 -1
  54. package/dist/fermi-surface/FermiSurface.svelte +4 -4
  55. package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
  56. package/dist/fermi-surface/FermiSurfaceControls.svelte +15 -19
  57. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  58. package/dist/fermi-surface/FermiSurfaceScene.svelte +8 -6
  59. package/dist/fermi-surface/compute.js +2 -2
  60. package/dist/fermi-surface/export.js +13 -26
  61. package/dist/fermi-surface/parse.js +8 -12
  62. package/dist/fermi-surface/types.d.ts +2 -5
  63. package/dist/heatmap-matrix/HeatmapMatrix.svelte +21 -3
  64. package/dist/heatmap-matrix/index.js +6 -6
  65. package/dist/io/decompress.d.ts +2 -1
  66. package/dist/io/decompress.js +1 -1
  67. package/dist/io/export.js +1 -1
  68. package/dist/io/index.d.ts +1 -1
  69. package/dist/io/index.js +0 -1
  70. package/dist/io/url-drop.js +7 -1
  71. package/dist/isosurface/IsosurfaceControls.svelte +11 -25
  72. package/dist/isosurface/slice.js +1 -1
  73. package/dist/isosurface/types.js +12 -12
  74. package/dist/labels.d.ts +1 -1
  75. package/dist/labels.js +14 -11
  76. package/dist/layout/InfoTag.svelte +6 -4
  77. package/dist/layout/PropertyFilter.svelte +4 -2
  78. package/dist/layout/json-tree/JsonTree.svelte +22 -14
  79. package/dist/layout/json-tree/JsonValue.svelte +2 -2
  80. package/dist/layout/json-tree/types.d.ts +3 -2
  81. package/dist/layout/json-tree/types.js +0 -1
  82. package/dist/layout/json-tree/utils.d.ts +4 -4
  83. package/dist/layout/json-tree/utils.js +12 -20
  84. package/dist/marching-cubes.js +13 -15
  85. package/dist/math.d.ts +11 -1
  86. package/dist/math.js +15 -6
  87. package/dist/overlays/DragControlTab.svelte +98 -0
  88. package/dist/overlays/DragControlTab.svelte.d.ts +8 -0
  89. package/dist/overlays/DraggablePane.svelte +7 -84
  90. package/dist/overlays/index.d.ts +1 -0
  91. package/dist/overlays/index.js +1 -0
  92. package/dist/periodic-table/PeriodicTable.svelte +11 -11
  93. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +4 -2
  94. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
  95. package/dist/phase-diagram/PhaseDiagramControls.svelte +4 -9
  96. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
  97. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +2 -10
  98. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +2 -3
  99. package/dist/phase-diagram/TdbInfoPanel.svelte +3 -3
  100. package/dist/phase-diagram/build-diagram.js +11 -18
  101. package/dist/phase-diagram/diagram-input.d.ts +5 -9
  102. package/dist/phase-diagram/index.d.ts +2 -2
  103. package/dist/phase-diagram/index.js +0 -2
  104. package/dist/phase-diagram/parse.d.ts +2 -2
  105. package/dist/phase-diagram/parse.js +6 -10
  106. package/dist/phase-diagram/svg-to-diagram.js +15 -15
  107. package/dist/phase-diagram/types.d.ts +5 -11
  108. package/dist/phase-diagram/utils.d.ts +2 -2
  109. package/dist/phase-diagram/utils.js +9 -11
  110. package/dist/plot/BarPlot.svelte +162 -314
  111. package/dist/plot/BarPlot.svelte.d.ts +5 -4
  112. package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
  113. package/dist/plot/BinnedScatterPlot.svelte +1114 -0
  114. package/dist/plot/BinnedScatterPlot.svelte.d.ts +66 -0
  115. package/dist/plot/ColorBar.svelte +19 -17
  116. package/dist/plot/ColorBar.svelte.d.ts +1 -1
  117. package/dist/plot/FillArea.svelte +2 -4
  118. package/dist/plot/FillArea.svelte.d.ts +1 -1
  119. package/dist/plot/Histogram.svelte +167 -281
  120. package/dist/plot/Histogram.svelte.d.ts +1 -1
  121. package/dist/plot/HistogramControls.svelte.d.ts +1 -1
  122. package/dist/plot/InteractiveAxisLabel.svelte +5 -3
  123. package/dist/plot/InteractiveAxisLabel.svelte.d.ts +1 -1
  124. package/dist/plot/PlotAxis.svelte +169 -0
  125. package/dist/plot/PlotAxis.svelte.d.ts +24 -0
  126. package/dist/plot/PlotControls.svelte.d.ts +1 -1
  127. package/dist/plot/ReferenceLine3D.svelte +53 -51
  128. package/dist/plot/ReferencePlane.svelte +39 -42
  129. package/dist/plot/ScatterPlot.svelte +300 -367
  130. package/dist/plot/ScatterPlot.svelte.d.ts +8 -5
  131. package/dist/plot/ScatterPlot3D.svelte +33 -6
  132. package/dist/plot/ScatterPlot3D.svelte.d.ts +3 -2
  133. package/dist/plot/ScatterPlot3DControls.svelte +9 -9
  134. package/dist/plot/ScatterPlotControls.svelte +3 -4
  135. package/dist/plot/ScatterPoint.svelte +18 -27
  136. package/dist/plot/ScatterPoint.svelte.d.ts +4 -3
  137. package/dist/plot/Surface3D.svelte +4 -7
  138. package/dist/plot/ZeroLines.svelte +2 -1
  139. package/dist/plot/ZeroLines.svelte.d.ts +2 -1
  140. package/dist/plot/ZoomRect.svelte +2 -2
  141. package/dist/plot/ZoomRect.svelte.d.ts +3 -3
  142. package/dist/plot/adaptive-density.d.ts +69 -0
  143. package/dist/plot/adaptive-density.js +191 -0
  144. package/dist/plot/auto-place.d.ts +43 -0
  145. package/dist/plot/auto-place.js +122 -0
  146. package/dist/plot/axis-utils.js +3 -5
  147. package/dist/plot/binned-scatter-types.d.ts +59 -0
  148. package/dist/plot/binned-scatter-types.js +1 -0
  149. package/dist/plot/data-cleaning.js +1 -1
  150. package/dist/plot/data-transform.js +1 -1
  151. package/dist/plot/fill-utils.d.ts +4 -9
  152. package/dist/plot/fill-utils.js +29 -44
  153. package/dist/plot/index.d.ts +4 -0
  154. package/dist/plot/index.js +2 -0
  155. package/dist/plot/interactions.d.ts +4 -4
  156. package/dist/plot/interactions.js +4 -3
  157. package/dist/plot/layout.d.ts +20 -2
  158. package/dist/plot/layout.js +59 -16
  159. package/dist/plot/reference-line.d.ts +1 -1
  160. package/dist/plot/reference-line.js +9 -11
  161. package/dist/plot/scales.d.ts +1 -1
  162. package/dist/plot/scales.js +20 -23
  163. package/dist/plot/types.d.ts +30 -58
  164. package/dist/plot/types.js +2 -6
  165. package/dist/plot/utils/label-placement.d.ts +24 -3
  166. package/dist/plot/utils/label-placement.js +82 -12
  167. package/dist/plot/utils/series-visibility.d.ts +8 -2
  168. package/dist/plot/utils/series-visibility.js +23 -5
  169. package/dist/rdf/RdfPlot.svelte +5 -5
  170. package/dist/rdf/calc-rdf.js +3 -3
  171. package/dist/sanitize.d.ts +2 -0
  172. package/dist/sanitize.js +2 -0
  173. package/dist/spectral/Bands.svelte +1 -1
  174. package/dist/spectral/BandsAndDos.svelte +22 -16
  175. package/dist/spectral/BrillouinBandsDos.svelte +20 -16
  176. package/dist/spectral/Dos.svelte +1 -1
  177. package/dist/spectral/helpers.d.ts +4 -2
  178. package/dist/spectral/helpers.js +44 -35
  179. package/dist/spectral/index.d.ts +1 -1
  180. package/dist/spectral/index.js +0 -1
  181. package/dist/structure/AtomLegend.svelte +23 -6
  182. package/dist/structure/AtomLegend.svelte.d.ts +1 -0
  183. package/dist/structure/CanvasTooltip.svelte +9 -9
  184. package/dist/structure/CanvasTooltip.svelte.d.ts +1 -1
  185. package/dist/structure/CellSelect.svelte +14 -16
  186. package/dist/structure/Structure.svelte +317 -68
  187. package/dist/structure/Structure.svelte.d.ts +4 -2
  188. package/dist/structure/StructureControls.svelte +20 -45
  189. package/dist/structure/StructureExportPane.svelte +2 -1
  190. package/dist/structure/StructureInfoPane.svelte +10 -8
  191. package/dist/structure/StructureScene.svelte +527 -177
  192. package/dist/structure/StructureScene.svelte.d.ts +5 -2
  193. package/dist/structure/atom-properties.js +4 -4
  194. package/dist/structure/bond-order-perception.js +115 -98
  195. package/dist/structure/bonding.d.ts +27 -1
  196. package/dist/structure/bonding.js +187 -16
  197. package/dist/structure/export.js +1 -1
  198. package/dist/structure/index.d.ts +3 -2
  199. package/dist/structure/index.js +0 -2
  200. package/dist/structure/parse.js +88 -59
  201. package/dist/symmetry/WyckoffTable.svelte +7 -0
  202. package/dist/symmetry/index.js +13 -14
  203. package/dist/table/HeatmapTable.svelte +45 -66
  204. package/dist/table/HeatmapTable.svelte.d.ts +1 -1
  205. package/dist/table/ToggleMenu.svelte +19 -10
  206. package/dist/theme/themes.mjs +12 -0
  207. package/dist/tooltip/index.d.ts +1 -1
  208. package/dist/tooltip/index.js +0 -1
  209. package/dist/trajectory/Trajectory.svelte +43 -15
  210. package/dist/trajectory/TrajectoryInfoPane.svelte +2 -2
  211. package/dist/trajectory/extract.js +1 -1
  212. package/dist/trajectory/frame-reader.js +4 -4
  213. package/dist/trajectory/helpers.d.ts +5 -4
  214. package/dist/trajectory/helpers.js +9 -17
  215. package/dist/trajectory/index.d.ts +2 -2
  216. package/dist/trajectory/index.js +2 -2
  217. package/dist/trajectory/parse/ase.js +4 -4
  218. package/dist/trajectory/parse/hdf5.js +1 -1
  219. package/dist/trajectory/parse/index.js +2 -3
  220. package/dist/trajectory/parse/lammps.js +1 -1
  221. package/dist/trajectory/parse/vasp.js +1 -1
  222. package/dist/trajectory/plotting.d.ts +1 -1
  223. package/dist/trajectory/plotting.js +38 -38
  224. package/dist/trajectory/types.d.ts +1 -1
  225. package/dist/utils.d.ts +1 -0
  226. package/dist/utils.js +9 -0
  227. package/dist/xrd/calc-xrd.js +3 -4
  228. package/dist/xrd/parse.js +1 -1
  229. package/package.json +42 -22
@@ -23,6 +23,17 @@ export const default_sym_settings = {
23
23
  };
24
24
  let initialized = false;
25
25
  const OCCUPANCY_EPS = 1e-8;
26
+ const to_unit = (value) => value - Math.floor(value);
27
+ const near_zero = (value) => Math.min(value, 1 - value);
28
+ const near_half = (value) => Math.abs(value - 0.5);
29
+ const symmetry_position_key = (pos) => pos.map((coord) => to_unit(coord).toFixed(8)).join(`,`);
30
+ const periodic_distance = (pos1, pos2) => Math.sqrt(pos1.reduce((sum, coord, idx) => {
31
+ // Wrap delta into [-0.5, 0.5) using safe modulo
32
+ const delta = coord - pos2[idx];
33
+ const wrapped = ((((delta + 0.5) % 1) + 1) % 1) - 0.5;
34
+ const distance = Math.abs(wrapped);
35
+ return sum + distance * distance;
36
+ }, 0));
26
37
  export async function ensure_moyo_wasm_ready(wasm_url) {
27
38
  if (initialized)
28
39
  return;
@@ -117,9 +128,6 @@ export async function analyze_structure_symmetry(struct_or_mol, settings) {
117
128
  }
118
129
  // Helper function to score coordinate simplicity for Wyckoff table
119
130
  export function simplicity_score(vec) {
120
- const to_unit = (v) => v - Math.floor(v);
121
- const near_zero = (v) => Math.min(v, 1 - v);
122
- const near_half = (v) => Math.abs(v - 0.5);
123
131
  const [ax, ay, az] = vec?.map(to_unit) ?? [];
124
132
  return (near_zero(ax) +
125
133
  near_zero(ay) +
@@ -180,8 +188,6 @@ export function wyckoff_positions_from_moyo(sym_data) {
180
188
  // Apply symmetry operations to find all equivalent positions for a given fractional coordinate
181
189
  export function apply_symmetry_operations(position, operations, _tolerance = 1e-6) {
182
190
  const seen = new Set();
183
- const wrap = (coord) => coord - Math.floor(coord);
184
- const key = (pos) => pos.map((coord) => wrap(coord).toFixed(8)).join(`,`);
185
191
  return operations
186
192
  .map(({ rotation, translation }) => {
187
193
  // Apply 3x3 rotation matrix and translation: new_pos = R * position + t
@@ -189,10 +195,10 @@ export function apply_symmetry_operations(position, operations, _tolerance = 1e-
189
195
  rotation[dim * 3 + 1] * position[1] +
190
196
  rotation[dim * 3 + 2] * position[2] +
191
197
  translation[dim]);
192
- return new_pos.map(wrap);
198
+ return new_pos.map(to_unit);
193
199
  })
194
200
  .filter((pos) => {
195
- const pos_key = key(pos);
201
+ const pos_key = symmetry_position_key(pos);
196
202
  if (seen.has(pos_key))
197
203
  return false;
198
204
  seen.add(pos_key);
@@ -204,13 +210,6 @@ export function map_wyckoff_to_all_atoms(wyckoff_positions, displayed_structure,
204
210
  if (!sym_data?.operations || !displayed_structure.sites || !orig_structure.sites) {
205
211
  return wyckoff_positions;
206
212
  }
207
- const periodic_distance = (pos1, pos2) => Math.sqrt(pos1.reduce((sum, coord, idx) => {
208
- // Wrap delta into [-0.5, 0.5) using safe modulo
209
- const delta = coord - pos2[idx];
210
- const wrapped = ((((delta + 0.5) % 1) + 1) % 1) - 0.5;
211
- const d = Math.abs(wrapped);
212
- return sum + d * d;
213
- }, 0));
214
213
  return wyckoff_positions.map((wyckoff_pos) => {
215
214
  const indices = (wyckoff_pos.site_indices || [])
216
215
  .filter((idx) => idx < orig_structure.sites.length)
@@ -28,6 +28,35 @@
28
28
  import type { HTMLAttributes } from 'svelte/elements'
29
29
  import { SvelteMap } from 'svelte/reactivity'
30
30
 
31
+ // Helper to check if value is invalid (null, undefined, NaN)
32
+ const is_invalid = (val: unknown) =>
33
+ val == null || (typeof val === `number` && Number.isNaN(val))
34
+
35
+ const NUMERIC_WITH_ERROR_RE =
36
+ /^([-+−]?(?:\d+\.?\d*|\d*\.\d+)(?:[eE][-+−]?\d+)?)\s*(?:±|\+[-−]|\()/
37
+
38
+ const parse_numeric_string = (val: string): number | null => {
39
+ const numeric_str = val.match(NUMERIC_WITH_ERROR_RE)?.[1] ?? val
40
+ if (numeric_str.trim() === ``) return null
41
+ const num = Number(normalize_unicode_minus(numeric_str))
42
+ return isNaN(num) ? null : num
43
+ }
44
+
45
+ // Get sort value from a cell (handles HTML data-sort-value and numbers with errors)
46
+ const get_sort_val = (val: CellVal): string | number => {
47
+ if (typeof val === `string`) {
48
+ // Check for HTML data-sort-value attribute first
49
+ const sort_attr_match = val.match(/data-sort-value="([^"]*)"/)
50
+ if (sort_attr_match) {
51
+ const num = Number(sort_attr_match[1])
52
+ return isNaN(num) ? sort_attr_match[1] : num
53
+ }
54
+ const num = parse_numeric_string(val)
55
+ if (num !== null) return num
56
+ }
57
+ return val as string | number
58
+ }
59
+
31
60
  let {
32
61
  data = $bindable([]),
33
62
  columns = [],
@@ -433,36 +462,6 @@
433
462
 
434
463
  if (!sort_state.column && multi_sort.length === 0) return filtered_data
435
464
 
436
- // Helper to check if value is invalid (null, undefined, NaN)
437
- const is_invalid = (val: unknown) =>
438
- val == null || (typeof val === `number` && Number.isNaN(val))
439
-
440
- // Get sort value from a cell (handles HTML data-sort-value and numbers with errors)
441
- const get_sort_val = (val: CellVal): string | number => {
442
- if (typeof val === `string`) {
443
- // Check for HTML data-sort-value attribute first
444
- const sort_attr_match = val.match(/data-sort-value="([^"]*)"/)
445
- if (sort_attr_match) {
446
- const num = Number(sort_attr_match[1])
447
- return isNaN(num) ? sort_attr_match[1] : num
448
- }
449
- // Handle numbers with error notation: "1.23 ± 0.05" or "1.23 +- 0.05" or "1.23(5)"
450
- // Extract the primary number before the ± or +- or (
451
- // Supports: ± (U+00B1), ASCII +-, Unicode minus − (U+2212), with optional whitespace
452
- const error_match = val.match(
453
- /^([+-−]?\d+\.?\d*(?:[eE][+-−]?\d+)?)\s*(?:[±\u00B1]|[+][−-]|\()/,
454
- )
455
- if (error_match) {
456
- const num = Number(error_match[1])
457
- if (!isNaN(num)) return num
458
- }
459
- // Try parsing as a plain number (handles "1.23" strings)
460
- const plain_num = Number(val)
461
- if (!isNaN(plain_num) && val.trim() !== ``) return plain_num
462
- }
463
- return val as string | number
464
- }
465
-
466
465
  // Build sort criteria: multi_sort takes precedence, fallback to single sort
467
466
  const sort_criteria = multi_sort.length > 0
468
467
  ? multi_sort
@@ -474,7 +473,7 @@
474
473
 
475
474
  return [...filtered_data].sort((row1, row2) => {
476
475
  for (const { column, ascending } of sort_criteria) {
477
- const matched_col = ordered_columns.find((c) => get_col_id(c) === column)
476
+ const matched_col = ordered_columns.find((col) => get_col_id(col) === column)
478
477
  if (!matched_col) continue
479
478
 
480
479
  const col_id = get_col_id(matched_col)
@@ -548,7 +547,7 @@
548
547
  ) {
549
548
  // Find the column using both label and group if provided
550
549
  const col = ordered_columns.find(
551
- (c) => c.label === column && c.group === group,
550
+ (candidate_col) => candidate_col.label === column && candidate_col.group === group,
552
551
  )
553
552
 
554
553
  if (!col) return // Skip if column not found
@@ -558,7 +557,7 @@
558
557
 
559
558
  // Shift+click for multi-column sort
560
559
  if (event.shiftKey) {
561
- const existing_idx = multi_sort.findIndex((s) => s.column === col_id)
560
+ const existing_idx = multi_sort.findIndex((sort_entry) => sort_entry.column === col_id)
562
561
  if (existing_idx >= 0) {
563
562
  // Toggle direction or remove if clicked again
564
563
  const existing = multi_sort[existing_idx]
@@ -567,8 +566,8 @@
567
566
  multi_sort = multi_sort.filter((_, idx) => idx !== existing_idx)
568
567
  } else {
569
568
  // Toggle direction
570
- multi_sort = multi_sort.map((s, idx) =>
571
- idx === existing_idx ? { ...s, ascending: !s.ascending } : s
569
+ multi_sort = multi_sort.map((sort_entry, idx) =>
570
+ idx === existing_idx ? { ...sort_entry, ascending: !sort_entry.ascending } : sort_entry
572
571
  )
573
572
  }
574
573
  } else {
@@ -622,27 +621,7 @@
622
621
  // Extract numeric value from strings with uncertainty notation: "1.23 ± 0.05", "1.23 +- 0.05", "1.23(5)"
623
622
  function parse_numeric_val(val: CellVal): number | null {
624
623
  if (typeof val === `number`) return Number.isNaN(val) ? null : val
625
- if (typeof val !== `string`) return null
626
-
627
- // Handle numbers with error notation: "1.23 ± 0.05" or "1.23 +- 0.05" or "1.23(5)"
628
- // Supports: ± (U+00B1), ASCII +-, Unicode minus − (U+2212), with optional whitespace
629
- // Note: [-+−] has hyphen first to avoid regex range interpretation
630
- // Pattern allows leading decimals like .5 or -.5 via (?:\d+\.?\d*|\d*\.\d+)
631
- const error_match = val.match(
632
- /^([-+−]?(?:\d+\.?\d*|\d*\.\d+)(?:[eE][-+−]?\d+)?)\s*(?:±|\+[-−]|\()/,
633
- )
634
- if (error_match) {
635
- // Normalize unicode minus (U+2212) to ASCII hyphen for Number()
636
- const normalized = normalize_unicode_minus(error_match[1])
637
- const num = Number(normalized)
638
- if (!isNaN(num)) return num
639
- }
640
- // Try parsing as a plain number (handles "1.23" strings)
641
- // Also normalize unicode minus for plain numbers
642
- const normalized_val = normalize_unicode_minus(val)
643
- const plain_num = Number(normalized_val)
644
- if (!isNaN(plain_num) && val.trim() !== ``) return plain_num
645
- return null
624
+ return typeof val === `string` ? parse_numeric_string(val) : null
646
625
  }
647
626
 
648
627
  // Memoize parsed column values to avoid O(N²) re-parsing in calc_color
@@ -704,7 +683,7 @@
704
683
  const col_id = get_col_id(col)
705
684
 
706
685
  // Check multi-sort first
707
- const multi_idx = multi_sort.findIndex((s) => s.column === col_id)
686
+ const multi_idx = multi_sort.findIndex((sort_entry) => sort_entry.column === col_id)
708
687
  if (multi_idx >= 0) {
709
688
  const arrow = multi_sort[multi_idx].ascending ? `↓` : `↑`
710
689
  const badge = multi_sort.length > 1 ? `<sup>${multi_idx + 1}</sup>` : ``
@@ -736,7 +715,7 @@
736
715
  // Row selection using WeakMap-based ID lookup instead of O(n) JSON.stringify comparison
737
716
  function toggle_row_select(row: RowData) {
738
717
  const row_id = get_row_id(row)
739
- const idx = selected_rows.findIndex((r) => get_row_id(r) === row_id)
718
+ const idx = selected_rows.findIndex((selected_row) => get_row_id(selected_row) === row_id)
740
719
  if (idx >= 0) {
741
720
  selected_rows = selected_rows.filter((_, i) => i !== idx)
742
721
  } else {
@@ -746,7 +725,7 @@
746
725
 
747
726
  function is_row_selected(row: RowData): boolean {
748
727
  const row_id = get_row_id(row)
749
- return selected_rows.some((r) => get_row_id(r) === row_id)
728
+ return selected_rows.some((selected_row) => get_row_id(selected_row) === row_id)
750
729
  }
751
730
 
752
731
  // Select-all: checks if every row on the current page is selected
@@ -788,7 +767,7 @@
788
767
  return quote(strip_html(String(val)))
789
768
  })
790
769
  )
791
- return [headers.join(delimiter), ...rows.map((r) => r.join(delimiter))].join(`\n`)
770
+ return [headers.join(delimiter), ...rows.map((row) => row.join(delimiter))].join(`\n`)
792
771
  }
793
772
 
794
773
  function export_csv(filename = `table-export`) {
@@ -890,7 +869,7 @@
890
869
  {/snippet}
891
870
 
892
871
  <div
893
- {@attach tooltip()}
872
+ {@attach tooltip({ allow_html: true })}
894
873
  {...rest_props}
895
874
  bind:this={container_el}
896
875
  class="table-container {rest_props.class ?? ``}"
@@ -1155,13 +1134,13 @@
1155
1134
  {#if !col.group}
1156
1135
  <th class:sticky-col={col.sticky}></th>
1157
1136
  {:else}
1158
- {@const group_cols = visible_columns.filter((c) =>
1159
- c.group === col.group
1137
+ {@const group_cols = visible_columns.filter((column) =>
1138
+ column.group === col.group
1160
1139
  )}
1161
1140
  <!-- Only render the group header once for each group by checking if this is the first column of this group -->
1162
- {#if visible_columns.findIndex((c) => c.group === col.group) ===
1163
- visible_columns.findIndex((c) =>
1164
- c.group === col.group && c.label === col.label
1141
+ {#if visible_columns.findIndex((column) => column.group === col.group) ===
1142
+ visible_columns.findIndex((column) =>
1143
+ column.group === col.group && column.label === col.label
1165
1144
  )}
1166
1145
  <th title={col.description} colspan={group_cols.length}>
1167
1146
  {@html sanitize_html(col.group)}
@@ -44,6 +44,6 @@ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
44
44
  }]>;
45
45
  footer?: Snippet;
46
46
  };
47
- declare const HeatmapTable: import("svelte").Component<$$ComponentProps, {}, "sort" | "data" | "show_controls" | "controls_open" | "show_heatmap" | "column_order" | "selected_rows" | "hidden_columns" | "loading" | "heatmap_opacity">;
47
+ declare const HeatmapTable: import("svelte").Component<$$ComponentProps, {}, "sort" | "data" | "show_controls" | "loading" | "controls_open" | "show_heatmap" | "column_order" | "selected_rows" | "hidden_columns" | "heatmap_opacity">;
48
48
  type HeatmapTable = ReturnType<typeof HeatmapTable>;
49
49
  export default HeatmapTable;
@@ -24,24 +24,31 @@
24
24
  const col_id = (col: Label) => col.key ?? col.label
25
25
 
26
26
  // Snapshot default visibility when column set changes (new dataset).
27
- // Compare by column keys to avoid re-snapshotting on internal columns = [...columns] reactivity.
28
- // Intentionally non-reactive: mutated only inside snapshot_defaults() and compared manually.
29
- let prev_col_keys = ``
30
- let default_visibility: Record<string, boolean> = {}
27
+ // Compare by keys and visibility defaults. Internal updates opt out below.
28
+ // Signature state is non-reactive; default visibility is state so reset UI updates after snapshots.
29
+ let prev_default_signature = ``
30
+ let internal_default_signature: string | undefined
31
+ let default_visibility = $state<Record<string, boolean>>({})
32
+ const default_signature = () =>
33
+ columns.map((col) => `${col_id(col)}:${col.visible !== false}`).join(`\0`)
34
+
31
35
  function snapshot_defaults() {
32
36
  default_visibility = {}
33
37
  for (const col of columns) {
34
38
  default_visibility[col_id(col)] = col.visible !== false
35
39
  }
36
- prev_col_keys = columns.map(col_id).join(`\0`)
40
+ prev_default_signature = default_signature()
37
41
  }
38
42
  snapshot_defaults()
39
43
 
40
44
  $effect(() => {
41
- const current_keys = columns.map(col_id).join(`\0`)
42
- if (current_keys !== prev_col_keys) {
43
- snapshot_defaults()
45
+ const current_signature = default_signature()
46
+ if (current_signature === internal_default_signature) {
47
+ internal_default_signature = undefined
48
+ return
44
49
  }
50
+ if (current_signature === prev_default_signature) return
51
+ snapshot_defaults()
45
52
  })
46
53
 
47
54
  // Check if a column's visibility differs from its default
@@ -55,6 +62,7 @@
55
62
  for (const col of items) {
56
63
  col.visible = default_visibility[col_id(col)] ?? true
57
64
  }
65
+ internal_default_signature = default_signature()
58
66
  columns = [...columns]
59
67
  }
60
68
 
@@ -94,13 +102,14 @@
94
102
 
95
103
  function toggle_section(name: string) {
96
104
  collapsed_sections = collapsed_sections.includes(name)
97
- ? collapsed_sections.filter((s) => s !== name)
105
+ ? collapsed_sections.filter((section) => section !== name)
98
106
  : [...collapsed_sections, name]
99
107
  }
100
108
 
101
109
  function toggle_column_visibility(col: Label, event: Event) {
102
110
  if (!(event.target instanceof HTMLInputElement)) return
103
111
  col.visible = event.target.checked
112
+ internal_default_signature = default_signature()
104
113
  columns = [...columns] // trigger reactivity on parent binding
105
114
  }
106
115
 
@@ -140,7 +149,7 @@
140
149
  <label
141
150
  class="toggle-label"
142
151
  class:disabled={col.disabled}
143
- {@attach tooltip({ content: col.description })}
152
+ {@attach tooltip({ allow_html: true, content: sanitize_html(col.description ?? ``) })}
144
153
  >
145
154
  <input
146
155
  type="checkbox"
@@ -148,6 +148,18 @@ const themes = {
148
148
 
149
149
  // Tooltips
150
150
  'tooltip-bg': tooltip_bg(`243, 244, 246`, `0, 40, 60`),
151
+ 'canvas-tooltip-bg': {
152
+ light: `rgba(226, 232, 240, 0.96)`,
153
+ dark: `rgba(15, 23, 42, 0.96)`,
154
+ white: `rgba(241, 245, 249, 0.98)`,
155
+ black: `rgba(20, 20, 20, 0.98)`,
156
+ },
157
+ 'canvas-tooltip-text-color': {
158
+ light: `#0f172a`,
159
+ dark: `#f8fafc`,
160
+ white: `#0f172a`,
161
+ black: `#f8fafc`,
162
+ },
151
163
  'tooltip-border': {
152
164
  light: `1px solid rgba(0, 0, 0, 0.15)`,
153
165
  dark: `1px solid rgba(255, 255, 255, 0.15)`,
@@ -1,2 +1,2 @@
1
1
  export { default as TooltipContent } from './TooltipContent.svelte';
2
- export * from './types';
2
+ export type * from './types';
@@ -1,2 +1 @@
1
1
  export { default as TooltipContent } from './TooltipContent.svelte';
2
- export * from './types';
@@ -10,6 +10,7 @@
10
10
  import { sanitize_html } from '../sanitize'
11
11
  import { toggle_fullscreen } from '../layout'
12
12
  import type { ControlsConfig, DataSeries, Orientation, Point } from '../plot'
13
+ import type { ScatterHandlerProps } from '../plot/types'
13
14
  import { Histogram, ScatterPlot } from '../plot'
14
15
  import { toggle_series_visibility } from '../plot/utils/series-visibility'
15
16
  import { DEFAULTS } from '../settings'
@@ -229,6 +230,7 @@
229
230
 
230
231
  // Current frame - load on demand for indexed trajectories
231
232
  let current_frame = $state<TrajectoryFrame | null>(null)
233
+ let frame_load_request_id = 0
232
234
 
233
235
  // Auto-play when trajectory changes (handles both props and file loading)
234
236
  $effect(() => {
@@ -254,15 +256,25 @@
254
256
 
255
257
  // Load frame on demand - works for both indexed files and external streaming
256
258
  async function load_frame_on_demand(frame_idx: number) {
257
- if (!trajectory?.frame_loader) return
259
+ const load_trajectory = trajectory
260
+ const frame_loader = load_trajectory?.frame_loader
261
+ if (!load_trajectory || !frame_loader) return
262
+
263
+ const request_id = ++frame_load_request_id
264
+ const request_is_current = () =>
265
+ request_id === frame_load_request_id &&
266
+ trajectory === load_trajectory &&
267
+ current_step_idx === frame_idx
258
268
 
259
269
  try {
260
- const frame = await trajectory.frame_loader.load_frame(
270
+ const frame = await frame_loader.load_frame(
261
271
  orig_data || ``, // Use original_data for indexed files, empty string for external streaming
262
272
  frame_idx,
263
273
  )
274
+ if (!request_is_current()) return
264
275
  current_frame = frame
265
276
  } catch (error) {
277
+ if (!request_is_current()) return
266
278
  console.error(`Failed to load frame ${frame_idx}:`, error)
267
279
  current_frame = null
268
280
  on_error?.({
@@ -292,8 +304,10 @@
292
304
  if (step_labels > 0) {
293
305
  return scaleLinear().domain([0, total_frames - 1]).nice()
294
306
  .ticks(Math.min(step_labels, total_frames))
295
- .map((t) => Math.round(t))
296
- .filter((t, i, arr) => t >= 0 && t < total_frames && arr.indexOf(t) === i)
307
+ .map((tick) => Math.round(tick))
308
+ .filter((tick, idx, ticks) =>
309
+ tick >= 0 && tick < total_frames && ticks.indexOf(tick) === idx
310
+ )
297
311
  }
298
312
  if (step_labels < 0) {
299
313
  const spacing = Math.abs(step_labels)
@@ -760,7 +774,8 @@
760
774
 
761
775
  // Handle click outside to close dropdowns
762
776
  function handle_click_outside(event: MouseEvent) {
763
- const target = event.target as Element
777
+ const target = event.target
778
+ if (!(target instanceof Element)) return
764
779
  if (view_mode_dropdown_open) {
765
780
  const dropdown_wrapper = target.closest(`.view-mode-dropdown-wrapper`)
766
781
  // Don't close if clicking on dropdown wrapper (contains both button and menu)
@@ -773,10 +788,10 @@
773
788
  if (!trajectory) return
774
789
 
775
790
  // Don't handle shortcuts if user is typing in an input field (but allow if it's our step input and not focused)
776
- const target = event.target as HTMLElement
777
- const is_step_input = target.classList.contains(`step-input`)
778
- const is_input_focused = target.tagName === `INPUT` ||
779
- target.tagName === `TEXTAREA`
791
+ const target = event.target instanceof HTMLElement ? event.target : null
792
+ const is_step_input = target?.classList.contains(`step-input`) ?? false
793
+ const is_input_focused =
794
+ target?.tagName === `INPUT` || target?.tagName === `TEXTAREA`
780
795
 
781
796
  // Skip if typing in an input that's not our step input
782
797
  if (is_input_focused && !is_step_input) return
@@ -784,7 +799,7 @@
784
799
  // If typing in step input, only handle certain navigation keys
785
800
  if (is_step_input && is_input_focused) {
786
801
  // Allow normal typing, but handle special navigation keys
787
- if ([`Escape`, `Enter`].includes(event.key)) target.blur() // Remove focus from input
802
+ if ([`Escape`, `Enter`].includes(event.key)) target?.blur() // Remove focus from input
788
803
  return
789
804
  }
790
805
 
@@ -905,7 +920,7 @@
905
920
  <button
906
921
  class="filename"
907
922
  title="Click to copy filename <code>{current_filename}</code>"
908
- {@attach tooltip()}
923
+ {@attach tooltip({ allow_html: true })}
909
924
  onclick={() => {
910
925
  if (current_filename) {
911
926
  navigator.clipboard.writeText(current_filename)
@@ -1170,14 +1185,19 @@
1170
1185
  {...scatter_props}
1171
1186
  legend={{
1172
1187
  ...scatter_props.legend ?? {},
1173
- on_toggle: (series_idx) => {
1188
+ on_toggle: (series_idx: number) => {
1174
1189
  handle_legend_toggle(series_idx)
1175
1190
  scatter_props.legend?.on_toggle?.(series_idx)
1176
1191
  },
1177
1192
  }}
1178
1193
  class="plot {scatter_props.class ?? ``}"
1179
1194
  >
1180
- {#snippet tooltip({ x, y, metadata, label })}
1195
+ {#snippet tooltip({
1196
+ x,
1197
+ y,
1198
+ metadata,
1199
+ label,
1200
+ }: ScatterHandlerProps)}
1181
1201
  {@const formatted_y = typeof y === `number` ? format_num(y) : y}
1182
1202
  Step: {Math.round(x)}<br />
1183
1203
  {@html sanitize_html(metadata?.series_label || label || `Value`)}: {formatted_y}
@@ -1195,7 +1215,7 @@
1195
1215
  mode={histogram_props.mode ?? `overlay`}
1196
1216
  show_legend={histogram_props.show_legend ?? plot_series.length > 1}
1197
1217
  legend={histogram_props.legend}
1198
- on_series_toggle={(series_idx) => {
1218
+ on_series_toggle={(series_idx: number) => {
1199
1219
  handle_legend_toggle(series_idx)
1200
1220
  histogram_props.on_series_toggle?.(series_idx)
1201
1221
  }}
@@ -1203,7 +1223,15 @@
1203
1223
  class="plot {histogram_props.class ?? ``}"
1204
1224
  --ctrl-btn-top="6ex"
1205
1225
  >
1206
- {#snippet tooltip({ value, count, property })}
1226
+ {#snippet tooltip({
1227
+ value,
1228
+ count,
1229
+ property,
1230
+ }: {
1231
+ value: number
1232
+ count: number
1233
+ property?: string
1234
+ })}
1207
1235
  {#if property}<div><strong>{property}</strong></div>{/if}
1208
1236
  <div>Value: {format_num(value)}</div>
1209
1237
  <div>Count: {count}</div>
@@ -5,7 +5,7 @@
5
5
  import DraggablePane from '../overlays/DraggablePane.svelte'
6
6
  import { get_electro_neg_formula } from '../composition'
7
7
  import { SETTINGS_CONFIG } from '../settings'
8
- import { type AnyStructure } from '../structure'
8
+ import type { AnyStructure } from '../structure'
9
9
  import type { ComponentProps } from 'svelte'
10
10
  import type { TrajectoryType } from './index'
11
11
 
@@ -260,7 +260,7 @@
260
260
  { structure },
261
261
  ) => (`lattice` in structure && structure.lattice?.volume))
262
262
  .filter(is_valid_number)
263
- .filter((v) => v > 0)
263
+ .filter((volume) => volume > 0)
264
264
 
265
265
  if (volumes.length > 1) {
266
266
  const vol_change = (Math.max(...volumes) - Math.min(...volumes)) /
@@ -1,5 +1,5 @@
1
1
  // Data extraction functions for trajectory analysis and plotting
2
- import { get_density } from '../structure';
2
+ import { get_density } from '../structure/index';
3
3
  // Common data extractor that extracts energy and structural properties
4
4
  export const energy_data_extractor = (frame) => {
5
5
  const data = {
@@ -104,7 +104,7 @@ export class TrajFrameReader {
104
104
  return this.load_ase_frame(data, frame_number);
105
105
  }
106
106
  async extract_plot_metadata(data, options, on_progress) {
107
- const { sample_rate = 1, properties } = options || {};
107
+ const { sample_rate = 1, properties } = options ?? {};
108
108
  const metadata_list = [];
109
109
  const total_frames = await this.get_total_frames(data);
110
110
  if (this.format === `xyz`) {
@@ -240,11 +240,11 @@ export class TrajFrameReader {
240
240
  const frame_offset = Number(view.getBigInt64(offsets_pos + frame_number * 8, true));
241
241
  const json_length = Number(view.getBigInt64(frame_offset, true));
242
242
  const frame_data = JSON.parse(new TextDecoder().decode(new Uint8Array(data, frame_offset + 8, json_length)));
243
- const positions_ref = frame_data[`positions.`] || frame_data.positions;
243
+ const positions_ref = frame_data[`positions.`] ?? frame_data.positions;
244
244
  const positions = positions_ref?.ndarray
245
245
  ? read_ndarray_from_view(view, positions_ref)
246
246
  : positions_ref;
247
- const numbers_ref = frame_data[`numbers.`] || frame_data.numbers || this.global_numbers;
247
+ const numbers_ref = frame_data[`numbers.`] ?? frame_data.numbers ?? this.global_numbers;
248
248
  const numbers = numbers_ref?.ndarray
249
249
  ? read_ndarray_from_view(view, numbers_ref).flat()
250
250
  : numbers_ref;
@@ -266,7 +266,7 @@ export class TrajFrameReader {
266
266
  console.warn(`Failed to calculate volume for frame ${frame_number}:`, error);
267
267
  }
268
268
  }
269
- return create_trajectory_frame(positions, convert_atomic_numbers(numbers), cell, frame_data.pbc || [true, true, true], frame_number, metadata);
269
+ return create_trajectory_frame(positions, convert_atomic_numbers(numbers), cell, frame_data.pbc ?? [true, true, true], frame_number, metadata);
270
270
  }
271
271
  catch (error) {
272
272
  console.warn(`Failed to load ASE frame ${frame_number}:`, error);
@@ -1,9 +1,10 @@
1
- import type { ElementSymbol } from '../element';
1
+ import type { ElementSymbol } from '../element/types';
2
2
  import * as math from '../math';
3
- import type { AnyStructure, Pbc } from '../structure';
3
+ import type { AnyStructure } from '../structure/index';
4
+ import type { Pbc } from '../structure/pbc';
4
5
  import type { TrajectoryFrame } from './index';
5
- export declare function is_valid_element_symbol(symbol: string): symbol is ElementSymbol;
6
- export declare function coerce_element_symbol(symbol: string): ElementSymbol | undefined;
6
+ export declare const is_valid_element_symbol: (symbol: string) => symbol is ElementSymbol;
7
+ export declare const coerce_element_symbol: (symbol: string) => ElementSymbol | undefined;
7
8
  export declare function validate_3x3_matrix(data: unknown): math.Matrix3x3;
8
9
  export declare const convert_atomic_numbers: (numbers: number[]) => ElementSymbol[];
9
10
  export declare const create_structure: (positions: number[][], elements: ElementSymbol[], lattice_matrix?: math.Matrix3x3, pbc?: Pbc, force_data?: number[][]) => AnyStructure;
@@ -3,25 +3,20 @@ import { ATOMIC_NUMBER_TO_SYMBOL } from '../composition/parse';
3
3
  import { ELEM_SYMBOLS } from '../labels';
4
4
  import * as math from '../math';
5
5
  const element_symbol_set = new Set(ELEM_SYMBOLS);
6
- export function is_valid_element_symbol(symbol) {
7
- return element_symbol_set.has(symbol);
8
- }
9
- export function coerce_element_symbol(symbol) {
10
- return is_valid_element_symbol(symbol) ? symbol : undefined;
11
- }
6
+ const is_valid_row = (row) => {
7
+ if (!(Array.isArray(row) || (ArrayBuffer.isView(row) && `length` in row)))
8
+ return false;
9
+ return math.is_finite_vec3_like(row);
10
+ };
11
+ const is_valid_vec3 = (coords) => Array.isArray(coords) && math.is_finite_vec3_like(coords);
12
+ export const is_valid_element_symbol = (symbol) => element_symbol_set.has(symbol);
13
+ export const coerce_element_symbol = (symbol) => is_valid_element_symbol(symbol) ? symbol : undefined;
12
14
  // Validate that data is a proper 3x3 matrix
13
15
  // Accepts both regular arrays and typed arrays (Float32Array, Float64Array, etc.)
14
16
  export function validate_3x3_matrix(data) {
15
17
  if (!Array.isArray(data) || data.length !== 3) {
16
18
  throw new Error(`Expected 3x3 matrix, got array of length ${Array.isArray(data) ? data.length : `non-array`}`);
17
19
  }
18
- const is_valid_row = (row) => {
19
- if (Array.isArray(row))
20
- return row.length === 3;
21
- if (!ArrayBuffer.isView(row))
22
- return false;
23
- return `length` in row && typeof row.length === `number` && row.length === 3;
24
- };
25
20
  if (!data.every(is_valid_row)) {
26
21
  throw new Error(`Invalid 3x3 matrix structure`);
27
22
  }
@@ -39,9 +34,6 @@ export const create_structure = (positions, elements, lattice_matrix, pbc, force
39
34
  throw new Error(`create_structure requires matching positions and elements lengths, got positions=${positions.length}, elements=${elements.length}`);
40
35
  }
41
36
  const cart_to_frac = lattice_matrix ? math.create_cart_to_frac(lattice_matrix) : null;
42
- const is_valid_vec3 = (coords) => Array.isArray(coords) &&
43
- coords.length === 3 &&
44
- coords.every((value) => typeof value === `number` && Number.isFinite(value));
45
37
  const sites = positions.map((pos, idx) => {
46
38
  if (!is_valid_vec3(pos)) {
47
39
  throw new Error(`Invalid position at index ${idx}: expected 3 finite coordinates`);
@@ -64,7 +56,7 @@ export const create_structure = (positions, elements, lattice_matrix, pbc, force
64
56
  lattice: {
65
57
  matrix: lattice_matrix,
66
58
  ...math.calc_lattice_params(lattice_matrix),
67
- pbc: pbc || [true, true, true],
59
+ pbc: pbc ?? [true, true, true],
68
60
  },
69
61
  }
70
62
  : { sites };
@@ -1,6 +1,6 @@
1
- import type { AnyStructure } from '../structure';
2
- import type Trajectory from './Trajectory.svelte';
3
1
  import type { ComponentProps } from 'svelte';
2
+ import type { AnyStructure } from '../structure/index';
3
+ import type Trajectory from './Trajectory.svelte';
4
4
  export { default as Trajectory } from './Trajectory.svelte';
5
5
  export { default as TrajectoryError } from './TrajectoryError.svelte';
6
6
  export { default as TrajectoryExportPane } from './TrajectoryExportPane.svelte';