matterviz 0.4.2 → 0.4.3

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 (244) hide show
  1. package/dist/FilePicker.svelte +40 -50
  2. package/dist/api/optimade.js +10 -7
  3. package/dist/app.css +15 -0
  4. package/dist/brillouin/BrillouinZone.svelte +18 -9
  5. package/dist/brillouin/BrillouinZoneScene.svelte +20 -29
  6. package/dist/brillouin/compute.js +10 -16
  7. package/dist/brillouin/geometry.js +2 -2
  8. package/dist/chempot-diagram/ChemPotDiagram.svelte +18 -15
  9. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +17 -113
  10. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +104 -264
  11. package/dist/chempot-diagram/async-compute.svelte.js +12 -13
  12. package/dist/chempot-diagram/color.d.ts +21 -1
  13. package/dist/chempot-diagram/color.js +70 -1
  14. package/dist/chempot-diagram/compute.d.ts +7 -0
  15. package/dist/chempot-diagram/compute.js +85 -26
  16. package/dist/chempot-diagram/export.js +1 -6
  17. package/dist/chempot-diagram/temperature.js +9 -10
  18. package/dist/composition/BarChart.svelte +7 -9
  19. package/dist/composition/Composition.svelte +3 -8
  20. package/dist/composition/Formula.svelte +2 -2
  21. package/dist/composition/FormulaFilter.svelte +27 -93
  22. package/dist/composition/format.js +6 -8
  23. package/dist/composition/parse.js +50 -105
  24. package/dist/constants.d.ts +0 -1
  25. package/dist/constants.js +6 -17
  26. package/dist/convex-hull/ConvexHull2D.svelte +0 -2
  27. package/dist/convex-hull/ConvexHull3D.svelte +19 -47
  28. package/dist/convex-hull/ConvexHull4D.svelte +65 -95
  29. package/dist/convex-hull/StructurePopup.svelte +1 -1
  30. package/dist/convex-hull/gas-thermodynamics.js +21 -53
  31. package/dist/convex-hull/helpers.js +1 -7
  32. package/dist/convex-hull/thermodynamics.js +19 -27
  33. package/dist/coordination/CoordinationBarPlot.svelte +12 -21
  34. package/dist/element/BohrAtom.svelte +0 -1
  35. package/dist/element/ElementTile.svelte +26 -44
  36. package/dist/element/data.d.ts +2 -0
  37. package/dist/element/data.js +5 -1
  38. package/dist/element/index.d.ts +1 -1
  39. package/dist/element/index.js +1 -1
  40. package/dist/fermi-surface/FermiSurface.svelte +22 -23
  41. package/dist/fermi-surface/FermiSurface.svelte.d.ts +2 -6
  42. package/dist/fermi-surface/FermiSurfaceScene.svelte +32 -77
  43. package/dist/fermi-surface/compute.js +22 -55
  44. package/dist/fermi-surface/parse.js +38 -70
  45. package/dist/file-viewer/JsonBrowser.svelte +1043 -0
  46. package/dist/file-viewer/JsonBrowser.svelte.d.ts +9 -0
  47. package/dist/file-viewer/PlotPanel.svelte +350 -0
  48. package/dist/file-viewer/PlotPanel.svelte.d.ts +9 -0
  49. package/dist/file-viewer/TrajectoryWithDos.svelte +30 -0
  50. package/dist/file-viewer/TrajectoryWithDos.svelte.d.ts +10 -0
  51. package/dist/file-viewer/detect.d.ts +11 -0
  52. package/dist/file-viewer/detect.js +381 -0
  53. package/dist/file-viewer/eligibility.d.ts +4 -0
  54. package/dist/file-viewer/eligibility.js +53 -0
  55. package/dist/file-viewer/host-protocol.d.ts +50 -0
  56. package/dist/file-viewer/host-transfer.d.ts +25 -0
  57. package/dist/file-viewer/host-transfer.js +42 -0
  58. package/dist/file-viewer/index.d.ts +5 -0
  59. package/dist/file-viewer/index.js +5 -0
  60. package/dist/file-viewer/main.d.ts +44 -0
  61. package/dist/file-viewer/main.js +535 -0
  62. package/dist/file-viewer/parse.d.ts +9 -0
  63. package/dist/file-viewer/parse.js +170 -0
  64. package/dist/file-viewer/plot-utils.d.ts +24 -0
  65. package/dist/file-viewer/plot-utils.js +191 -0
  66. package/dist/file-viewer/types.d.ts +4 -0
  67. package/dist/file-viewer/types.js +5 -0
  68. package/dist/heatmap-matrix/HeatmapMatrix.svelte +57 -84
  69. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +1 -1
  70. package/dist/index.d.ts +2 -2
  71. package/dist/index.js +1 -1
  72. package/dist/io/ExportPane.svelte +1 -1
  73. package/dist/io/decompress.d.ts +2 -0
  74. package/dist/io/decompress.js +3 -2
  75. package/dist/io/export.d.ts +7 -4
  76. package/dist/io/export.js +70 -18
  77. package/dist/io/file-drop.js +60 -35
  78. package/dist/io/url-drop.d.ts +2 -0
  79. package/dist/io/url-drop.js +28 -16
  80. package/dist/isosurface/Isosurface.svelte +687 -196
  81. package/dist/isosurface/Isosurface.svelte.d.ts +7 -1
  82. package/dist/isosurface/IsosurfaceControls.svelte +564 -85
  83. package/dist/isosurface/IsosurfaceControls.svelte.d.ts +1 -1
  84. package/dist/isosurface/VolumeSlice.svelte +224 -0
  85. package/dist/isosurface/VolumeSlice.svelte.d.ts +28 -0
  86. package/dist/isosurface/coloring.d.ts +20 -0
  87. package/dist/isosurface/coloring.js +118 -0
  88. package/dist/isosurface/geometry-worker-types.d.ts +38 -0
  89. package/dist/isosurface/geometry.worker.d.ts +1 -0
  90. package/dist/isosurface/geometry.worker.js +59 -0
  91. package/dist/isosurface/grid.d.ts +10 -0
  92. package/dist/isosurface/grid.js +31 -0
  93. package/dist/isosurface/index.d.ts +4 -0
  94. package/dist/isosurface/index.js +4 -0
  95. package/dist/isosurface/profile.d.ts +12 -0
  96. package/dist/isosurface/profile.js +19 -0
  97. package/dist/isosurface/sampling.d.ts +34 -0
  98. package/dist/isosurface/sampling.js +408 -0
  99. package/dist/isosurface/slice-rendering.d.ts +13 -0
  100. package/dist/isosurface/slice-rendering.js +79 -0
  101. package/dist/isosurface/slice.d.ts +22 -2
  102. package/dist/isosurface/slice.js +200 -116
  103. package/dist/isosurface/types.d.ts +24 -1
  104. package/dist/isosurface/types.js +120 -8
  105. package/dist/json-path.d.ts +4 -0
  106. package/dist/json-path.js +89 -0
  107. package/dist/labels.js +1 -1
  108. package/dist/layout/InfoCard.svelte +1 -3
  109. package/dist/layout/SettingsSection.svelte +52 -61
  110. package/dist/layout/SettingsSection.svelte.d.ts +6 -7
  111. package/dist/layout/ViewerChrome.svelte +7 -1
  112. package/dist/layout/json-tree/JsonNode.svelte +14 -48
  113. package/dist/layout/json-tree/JsonTree.svelte +48 -95
  114. package/dist/layout/json-tree/JsonValue.svelte +3 -18
  115. package/dist/layout/json-tree/utils.d.ts +0 -3
  116. package/dist/layout/json-tree/utils.js +50 -179
  117. package/dist/marching-cubes.d.ts +8 -1
  118. package/dist/marching-cubes.js +115 -48
  119. package/dist/math.d.ts +3 -2
  120. package/dist/math.js +29 -38
  121. package/dist/overlays/ContextMenu.svelte +15 -32
  122. package/dist/overlays/DraggablePane.svelte +39 -58
  123. package/dist/overlays/DraggablePane.svelte.d.ts +1 -1
  124. package/dist/overlays/GlassChip.svelte +3 -1
  125. package/dist/periodic-table/PeriodicTableControls.svelte +26 -52
  126. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +18 -34
  127. package/dist/phase-diagram/build-diagram.js +1 -6
  128. package/dist/phase-diagram/svg-to-diagram.js +9 -14
  129. package/dist/plot/bar/BarPlot.svelte +38 -42
  130. package/dist/plot/bar/SpacegroupBarPlot.svelte +18 -42
  131. package/dist/plot/box/BoxPlot.svelte +27 -23
  132. package/dist/plot/core/auto-place.d.ts +1 -1
  133. package/dist/plot/core/auto-place.js +1 -1
  134. package/dist/plot/core/data-transform.d.ts +1 -2
  135. package/dist/plot/core/data-transform.js +0 -10
  136. package/dist/plot/core/layout.d.ts +12 -5
  137. package/dist/plot/core/layout.js +37 -27
  138. package/dist/plot/core/pan-zoom.svelte.d.ts +2 -4
  139. package/dist/plot/core/pan-zoom.svelte.js +34 -17
  140. package/dist/plot/core/placed-tween.svelte.d.ts +3 -0
  141. package/dist/plot/core/placed-tween.svelte.js +68 -21
  142. package/dist/plot/core/svg.js +0 -1
  143. package/dist/plot/core/utils/hierarchy-chart.d.ts +1 -0
  144. package/dist/plot/core/utils/hierarchy-chart.js +12 -0
  145. package/dist/plot/core/utils/hierarchy-labels.js +3 -2
  146. package/dist/plot/histogram/Histogram.svelte +21 -26
  147. package/dist/plot/sankey/sankey.js +13 -9
  148. package/dist/plot/scatter/BinnedScatterPlot.svelte +59 -43
  149. package/dist/plot/scatter/ScatterPlot.svelte +67 -52
  150. package/dist/plot/scatter/adaptive-density.d.ts +1 -1
  151. package/dist/plot/scatter/adaptive-density.js +36 -20
  152. package/dist/plot/sunburst/Sunburst.svelte +20 -20
  153. package/dist/plot/treemap/Treemap.svelte +175 -60
  154. package/dist/plot/treemap/Treemap.svelte.d.ts +6 -0
  155. package/dist/plot/treemap/index.d.ts +1 -0
  156. package/dist/plot/treemap/labels.d.ts +38 -0
  157. package/dist/plot/treemap/labels.js +80 -0
  158. package/dist/plot/treemap/treemap.d.ts +4 -2
  159. package/dist/plot/treemap/treemap.js +5 -2
  160. package/dist/rdf/RdfPlot.svelte +17 -35
  161. package/dist/rdf/calc-rdf.js +59 -44
  162. package/dist/rdf/index.d.ts +0 -1
  163. package/dist/sanitize.js +84 -26
  164. package/dist/scene/SceneCamera.svelte +1 -1
  165. package/dist/scene/SceneCamera.svelte.d.ts +1 -1
  166. package/dist/scene/index.d.ts +1 -2
  167. package/dist/scene/index.js +1 -1
  168. package/dist/scene/{props.js → props.svelte.js} +15 -1
  169. package/dist/settings.js +159 -482
  170. package/dist/spectral/Bands.svelte +151 -158
  171. package/dist/spectral/BrillouinBandsDos.svelte +4 -19
  172. package/dist/spectral/Dos.svelte +26 -24
  173. package/dist/spectral/helpers.js +40 -105
  174. package/dist/structure/AtomLegend.svelte +8 -13
  175. package/dist/structure/AtomLegend.svelte.d.ts +2 -3
  176. package/dist/structure/CellSelect.svelte +35 -10
  177. package/dist/structure/Structure.svelte +177 -99
  178. package/dist/structure/Structure.svelte.d.ts +1 -1
  179. package/dist/structure/StructureCarousel.svelte +10 -3
  180. package/dist/structure/StructureControls.svelte +8 -26
  181. package/dist/structure/StructureControls.svelte.d.ts +1 -1
  182. package/dist/structure/StructureInfoPane.svelte +65 -76
  183. package/dist/structure/StructureScene.svelte +75 -109
  184. package/dist/structure/StructureScene.svelte.d.ts +3 -1
  185. package/dist/structure/StructureViewport.svelte +120 -55
  186. package/dist/structure/StructureViewport.svelte.d.ts +3 -1
  187. package/dist/structure/atom-properties.d.ts +1 -1
  188. package/dist/structure/atom-properties.js +4 -3
  189. package/dist/structure/bond-order-perception.js +3 -8
  190. package/dist/structure/bonding.d.ts +0 -1
  191. package/dist/structure/bonding.js +16 -23
  192. package/dist/structure/export.js +50 -74
  193. package/dist/structure/format-detect.js +5 -7
  194. package/dist/structure/index.js +6 -12
  195. package/dist/structure/parse.js +38 -100
  196. package/dist/structure/partial-occupancy.js +7 -4
  197. package/dist/structure/pbc.js +8 -10
  198. package/dist/structure/polyhedra.js +5 -4
  199. package/dist/structure/prop-groups.d.ts +85 -0
  200. package/dist/structure/prop-groups.js +18 -0
  201. package/dist/symmetry/SymmetryStats.svelte +0 -4
  202. package/dist/symmetry/WyckoffTable.svelte +5 -4
  203. package/dist/symmetry/cell-transform.js +5 -14
  204. package/dist/symmetry/index.js +6 -9
  205. package/dist/table/HeatmapTable.svelte +61 -103
  206. package/dist/table/ToggleMenu.svelte +166 -44
  207. package/dist/table/ToggleMenu.svelte.d.ts +1 -1
  208. package/dist/theme/embedded.d.ts +18 -0
  209. package/dist/theme/embedded.js +206 -0
  210. package/dist/theme/index.d.ts +4 -0
  211. package/dist/theme/index.js +1 -2
  212. package/dist/theme/themes.mjs +31 -60
  213. package/dist/tooltip/TooltipContent.svelte +1 -1
  214. package/dist/tooltip/TooltipContent.svelte.d.ts +1 -1
  215. package/dist/tooltip/index.d.ts +8 -1
  216. package/dist/trajectory/Trajectory.svelte +140 -120
  217. package/dist/trajectory/TrajectoryError.svelte +1 -66
  218. package/dist/trajectory/TrajectoryExportPane.svelte +0 -1
  219. package/dist/trajectory/TrajectoryInfoPane.svelte +55 -80
  220. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +2 -1
  221. package/dist/trajectory/extract.js +19 -29
  222. package/dist/trajectory/format-detect.d.ts +2 -0
  223. package/dist/trajectory/format-detect.js +7 -5
  224. package/dist/trajectory/frame-reader.js +3 -4
  225. package/dist/trajectory/helpers.js +16 -49
  226. package/dist/trajectory/index.js +2 -8
  227. package/dist/trajectory/parse/ase.js +1 -1
  228. package/dist/trajectory/parse/index.d.ts +4 -3
  229. package/dist/trajectory/parse/index.js +12 -17
  230. package/dist/trajectory/parse/vaspout-h5.d.ts +0 -1
  231. package/dist/trajectory/parse/vaspout-h5.js +1 -4
  232. package/dist/trajectory/parse/xyz.js +48 -2
  233. package/dist/trajectory/plotting.js +14 -37
  234. package/dist/xrd/XrdPlot.svelte +43 -68
  235. package/dist/xrd/calc-xrd.js +22 -58
  236. package/dist/xrd/parse.js +13 -22
  237. package/package.json +40 -8
  238. package/readme.md +6 -5
  239. package/dist/scene/types.d.ts +0 -26
  240. package/dist/tooltip/types.d.ts +0 -8
  241. package/dist/trajectory/constants.d.ts +0 -6
  242. package/dist/trajectory/constants.js +0 -11
  243. /package/dist/{scene/types.js → file-viewer/host-protocol.js} +0 -0
  244. /package/dist/{tooltip/types.js → isosurface/geometry-worker-types.js} +0 -0
package/dist/index.d.ts CHANGED
@@ -16,8 +16,8 @@ export { default as Icon } from './Icon.svelte';
16
16
  export * from './icons';
17
17
  export * from './io';
18
18
  export { default as MillerIndexInput } from './MillerIndexInput.svelte';
19
- export { auto_isosurface_settings, DEFAULT_ISOSURFACE_SETTINGS, generate_layers, grid_data_range, Isosurface as VolumetricIsosurface, IsosurfaceControls, LAYER_COLORS, parse_chgcar, parse_cube, parse_volumetric_file, sample_hkl_slice, tile_volumetric_data, trilinear_interpolate, } from './isosurface';
20
- export type { DataRange, IsosurfaceLayer, IsosurfaceSettings, SliceResult, VolumetricData, VolumetricFileData, } from './isosurface';
19
+ export { auto_color_config, auto_isosurface_settings, auto_volume_layer, compare_volume_grids, compute_scalar_range, create_volume_sampler, DEFAULT_ISO_COLORMAP, DEFAULT_ISOSURFACE_SETTINGS, extract_volume_range, generate_layers, grid_data_range, is_signed_range, ISO_COLORMAPS, Isosurface as VolumetricIsosurface, IsosurfaceControls, label_file_volumes, lattices_match, LAYER_COLORS, materialize_layers, merge_imported_volumes, parse_chgcar, parse_cube, parse_volumetric_file, remove_volume, resolve_contour_thresholds, resolve_slice_color_range, resolve_volume_display_range, sample_hkl_slice, sample_plane_slice, sample_volume_at_positions, sanitize_display_range, scalars_to_vertex_colors, slice_to_rgba, tile_volumetric_data, trilinear_interpolate, VolumeSlice, } from './isosurface';
20
+ export type { CartesianPlane, DataRange, DisplayRange, GridCompatibility, IsoColormap, IsosurfaceLayer, IsosurfaceSettings, OutOfBoundsPolicy, PlaneSliceOptions, SliceResult, VolumeSliceMode, VolumeMergeResult, VolumeDisplayRangeOptions, VolumeSamplerOptions, VolumetricData, VolumetricFileData, } from './isosurface';
21
21
  export * from './labels';
22
22
  export * from './layout';
23
23
  export * from './math';
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ export * from './icons';
17
17
  export * from './io';
18
18
  export { default as MillerIndexInput } from './MillerIndexInput.svelte';
19
19
  // Explicit exports to avoid name clash with fermi-surface's Isosurface type
20
- export { auto_isosurface_settings, DEFAULT_ISOSURFACE_SETTINGS, generate_layers, grid_data_range, Isosurface as VolumetricIsosurface, IsosurfaceControls, LAYER_COLORS, parse_chgcar, parse_cube, parse_volumetric_file, sample_hkl_slice, tile_volumetric_data, trilinear_interpolate, } from './isosurface';
20
+ export { auto_color_config, auto_isosurface_settings, auto_volume_layer, compare_volume_grids, compute_scalar_range, create_volume_sampler, DEFAULT_ISO_COLORMAP, DEFAULT_ISOSURFACE_SETTINGS, extract_volume_range, generate_layers, grid_data_range, is_signed_range, ISO_COLORMAPS, Isosurface as VolumetricIsosurface, IsosurfaceControls, label_file_volumes, lattices_match, LAYER_COLORS, materialize_layers, merge_imported_volumes, parse_chgcar, parse_cube, parse_volumetric_file, remove_volume, resolve_contour_thresholds, resolve_slice_color_range, resolve_volume_display_range, sample_hkl_slice, sample_plane_slice, sample_volume_at_positions, sanitize_display_range, scalars_to_vertex_colors, slice_to_rgba, tile_volumetric_data, trilinear_interpolate, VolumeSlice, } from './isosurface';
21
21
  export * from './labels';
22
22
  export * from './layout';
23
23
  export * from './math';
@@ -3,7 +3,7 @@
3
3
  import type { ExportItem, ExportSection } from './types'
4
4
  import DraggablePane from '../overlays/DraggablePane.svelte'
5
5
  import { sanitize_html } from '../sanitize'
6
- import { type ComponentProps, onDestroy, type Snippet } from 'svelte'
6
+ import { onDestroy, type Snippet } from 'svelte'
7
7
  import { tooltip } from 'svelte-multiselect/attachments'
8
8
  import type { HTMLAttributes } from 'svelte/elements'
9
9
 
@@ -3,6 +3,8 @@ import { COMPRESSION_FORMATS } from '../constants';
3
3
  export declare function strip_compression_extensions(filename: string): string;
4
4
  export type CompressionFormat = keyof typeof COMPRESSION_FORMATS;
5
5
  export type CompressionExtension = (typeof COMPRESSION_EXTENSIONS)[number];
6
+ export type BrowserCompressionFormat = Exclude<CompressionFormat, `zip` | `xz` | `bz2`>;
7
+ export declare const is_browser_decompressible_format: (format: CompressionFormat | null) => format is BrowserCompressionFormat;
6
8
  export declare function detect_compression_format(filename: string): CompressionFormat | null;
7
9
  export declare function decompress_data(data: ArrayBuffer | ReadableStream<Uint8Array> | null, format: CompressionFormat): Promise<string>;
8
10
  export declare function decompress_data_binary(data: ArrayBuffer | ReadableStream<Uint8Array> | null, format: CompressionFormat): Promise<ArrayBuffer>;
@@ -8,6 +8,7 @@ export function strip_compression_extensions(filename) {
8
8
  }
9
9
  return base_name;
10
10
  }
11
+ export const is_browser_decompressible_format = (format) => format !== null && format !== `zip` && format !== `xz` && format !== `bz2`;
11
12
  export function detect_compression_format(filename) {
12
13
  const lower = filename.toLowerCase();
13
14
  for (const [format, extensions] of Object.entries(COMPRESSION_FORMATS)) {
@@ -24,7 +25,7 @@ export async function decompress_data(data, format) {
24
25
  // Decompress data and return as ArrayBuffer (for binary files like .brml.gz)
25
26
  export async function decompress_data_binary(data, format) {
26
27
  try {
27
- if (format === `zip` || format === `xz` || format === `bz2`) {
28
+ if (!is_browser_decompressible_format(format)) {
28
29
  throw new Error(`${format.toUpperCase()} decompression is not supported in the browser. ` +
29
30
  `Please extract the ${format.toUpperCase()} file first.`);
30
31
  }
@@ -51,7 +52,7 @@ const to_content = (filename, buffer) => is_binary_payload(filename, buffer) ? b
51
52
  export async function decompress_file(file) {
52
53
  const format = detect_compression_format(file.name);
53
54
  // zip/xz/bz2 are handled by their own code paths; null = not compressed
54
- const decompressible = format !== null && format !== `zip` && format !== `xz` && format !== `bz2` ? format : null;
55
+ const decompressible = is_browser_decompressible_format(format) ? format : null;
55
56
  const buffer = await file.arrayBuffer();
56
57
  if (decompressible) {
57
58
  const filename = file.name.replace(COMPRESSION_EXTENSIONS_REGEX, ``);
@@ -8,10 +8,13 @@ export declare const scene_registry: WeakMap<HTMLCanvasElement, {
8
8
  export declare const dpi_to_scale: (png_dpi: number) => number;
9
9
  export declare function canvas_to_png_blob(canvas: HTMLCanvasElement, png_dpi?: number, scene?: Scene | null, camera?: Camera | null): Promise<Blob>;
10
10
  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;
11
- export declare function svg_to_svg_string(svg_element: SVGElement, inline_styles?: readonly string[]): string;
12
- export declare function export_svg_as_svg(svg_element: SVGElement | null, filename: string, inline_styles?: readonly string[]): void;
13
- export declare function svg_to_png_blob(svg_element: SVGElement, png_dpi?: number, inline_styles?: readonly string[]): Promise<Blob>;
14
- export declare function export_svg_as_png(svg_element: SVGElement | null, filename: string, png_dpi?: number, inline_styles?: readonly string[]): void;
11
+ export interface SvgExportOptions {
12
+ viewbox_padding?: number | `stroke`;
13
+ }
14
+ export declare function svg_to_svg_string(svg_element: SVGElement, inline_styles?: readonly string[], options?: SvgExportOptions): string;
15
+ export declare function export_svg_as_svg(svg_element: SVGElement | null, filename: string, inline_styles?: readonly string[], options?: SvgExportOptions): void;
16
+ export declare function svg_to_png_blob(svg_element: SVGElement, png_dpi?: number, inline_styles?: readonly string[], options?: SvgExportOptions): Promise<Blob>;
17
+ export declare function export_svg_as_png(svg_element: SVGElement | null, filename: string, png_dpi?: number, inline_styles?: readonly string[], options?: SvgExportOptions): void;
15
18
  export declare function observe_canvas_presence(wrapper: HTMLElement | undefined, set: (has_canvas: boolean) => void): (() => void) | undefined;
16
19
  export declare const estimate_video_bitrate: (pixel_count: number, fps: number) => number;
17
20
  export declare function get_ffmpeg_conversion_command(input_filename: string): string;
package/dist/io/export.js CHANGED
@@ -94,6 +94,54 @@ function set_svg_font_family(svg) {
94
94
  // Also set as attribute for extra robustness
95
95
  svg.setAttribute(`font-family`, `sans-serif`);
96
96
  }
97
+ function svg_viewbox(svg, padding = 0) {
98
+ const parts = svg
99
+ .getAttribute(`viewBox`)
100
+ ?.trim()
101
+ .split(/[\s,]+/)
102
+ .map(Number);
103
+ if (parts?.length !== 4 || !parts.every(Number.isFinite))
104
+ return null;
105
+ const [x, y, width, height] = parts;
106
+ if (width <= 0 || height <= 0)
107
+ return null;
108
+ const padded = [
109
+ x - padding,
110
+ y - padding,
111
+ width + 2 * padding,
112
+ height + 2 * padding,
113
+ ];
114
+ return padded.every(Number.isFinite) && padded[2] > 0 && padded[3] > 0 ? padded : null;
115
+ }
116
+ function resolve_viewbox_padding(svg, options) {
117
+ if (options.viewbox_padding !== `stroke`) {
118
+ return Number.isFinite(options.viewbox_padding)
119
+ ? Math.max(0, options.viewbox_padding ?? 0)
120
+ : 0;
121
+ }
122
+ let max_stroke_width = 0;
123
+ for (const element of [svg, ...svg.querySelectorAll(`*`)]) {
124
+ const computed = getComputedStyle(element);
125
+ const inline_style = element.style;
126
+ const stroke = [
127
+ inline_style.stroke,
128
+ computed.getPropertyValue(`stroke`),
129
+ element.getAttribute(`stroke`),
130
+ ].find(Boolean) ?? ``;
131
+ if (!stroke || stroke === `none` || stroke === `transparent`)
132
+ continue;
133
+ // oxlint-disable-next-line unicorn/prefer-number-coercion -- CSS lengths include units
134
+ const stroke_width = Number.parseFloat([
135
+ inline_style.strokeWidth,
136
+ computed.getPropertyValue(`stroke-width`),
137
+ element.getAttribute(`stroke-width`),
138
+ ].find(Boolean) ?? ``);
139
+ if (Number.isFinite(stroke_width)) {
140
+ max_stroke_width = Math.max(max_stroke_width, stroke_width);
141
+ }
142
+ }
143
+ return max_stroke_width / 2;
144
+ }
97
145
  // Copy the given computed-style props from each live SVG element to its clone counterpart;
98
146
  // identical structure lets querySelectorAll(`*`) walk both in lockstep. Writes clone-only.
99
147
  function inline_computed_styles(live, clone, properties) {
@@ -110,10 +158,17 @@ function inline_computed_styles(live, clone, properties) {
110
158
  }
111
159
  // Clone, inline the given computed-style props, ensure font-family + xmlns, then serialize
112
160
  // to a standalone SVG string. Never mutates the live element.
113
- function serialize_svg_for_export(svg_element, inline_styles = []) {
161
+ function serialize_svg_for_export(svg_element, inline_styles = [], viewbox_padding = 0, strip_dimensions = false) {
114
162
  const clone = svg_element.cloneNode(true);
115
163
  if (inline_styles.length)
116
164
  inline_computed_styles(svg_element, clone, inline_styles);
165
+ const padded_viewbox = viewbox_padding > 0 ? svg_viewbox(clone, viewbox_padding) : null;
166
+ if (padded_viewbox)
167
+ clone.setAttribute(`viewBox`, padded_viewbox.join(` `));
168
+ if (strip_dimensions) {
169
+ clone.removeAttribute(`width`);
170
+ clone.removeAttribute(`height`);
171
+ }
117
172
  set_svg_font_family(clone);
118
173
  if (!clone.hasAttribute(`xmlns`)) {
119
174
  clone.setAttribute(`xmlns`, `http://www.w3.org/2000/svg`);
@@ -125,18 +180,19 @@ function serialize_svg_for_export(svg_element, inline_styles = []) {
125
180
  export function svg_to_svg_string(svg_element,
126
181
  // CSS props to inline from computed styles as presentation attributes; a standalone SVG
127
182
  // drops page stylesheets (e.g. Svelte component styles), so class-based styling is lost.
128
- inline_styles = []) {
129
- const svg_string = serialize_svg_for_export(svg_element, inline_styles);
183
+ inline_styles = [], options = {}) {
184
+ const viewbox_padding = resolve_viewbox_padding(svg_element, options);
185
+ const svg_string = serialize_svg_for_export(svg_element, inline_styles, viewbox_padding);
130
186
  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}`;
131
187
  }
132
188
  // Export SVG element as SVG file (triggers browser download)
133
- export function export_svg_as_svg(svg_element, filename, inline_styles = []) {
189
+ export function export_svg_as_svg(svg_element, filename, inline_styles = [], options = {}) {
134
190
  if (!svg_element) {
135
191
  console.warn(`SVG element not found for export`);
136
192
  return;
137
193
  }
138
194
  try {
139
- const svg_content = svg_to_svg_string(svg_element, inline_styles);
195
+ const svg_content = svg_to_svg_string(svg_element, inline_styles, options);
140
196
  download(svg_content, filename, `image/svg+xml;charset=utf-8`);
141
197
  }
142
198
  catch (error) {
@@ -148,18 +204,14 @@ export function export_svg_as_svg(svg_element, filename, inline_styles = []) {
148
204
  // Image element, and returns the resulting PNG Blob. Rejects if viewBox is
149
205
  // missing or dimensions are invalid (zero width/height).
150
206
  // DPI is converted to a resolution multiplier relative to 72 DPI baseline, capped at 10x.
151
- export function svg_to_png_blob(svg_element, png_dpi = 150, inline_styles = []) {
152
- const viewBox = svg_element.getAttribute(`viewBox`)?.trim();
153
- if (!viewBox)
207
+ export function svg_to_png_blob(svg_element, png_dpi = 150, inline_styles = [], options = {}) {
208
+ if (!svg_element.getAttribute(`viewBox`)?.trim())
154
209
  return Promise.reject(new Error(`SVG viewBox not found for PNG export`));
155
- const parts = viewBox.split(/[\s,]+/).map(Number);
156
- if (parts.length < 4 || !parts.every(Number.isFinite)) {
157
- return Promise.reject(new Error(`Invalid SVG dimensions for PNG export`));
158
- }
159
- const [, , width, height] = parts;
160
- if (!(width > 0) || !(height > 0)) {
210
+ const padding = resolve_viewbox_padding(svg_element, options);
211
+ const padded_viewbox = svg_viewbox(svg_element, padding);
212
+ if (!padded_viewbox)
161
213
  return Promise.reject(new Error(`Invalid SVG dimensions for PNG export`));
162
- }
214
+ const [, , width, height] = padded_viewbox;
163
215
  if (!Number.isFinite(png_dpi) || png_dpi <= 0) {
164
216
  return Promise.reject(new Error(`Invalid PNG DPI for export`));
165
217
  }
@@ -173,7 +225,7 @@ export function svg_to_png_blob(svg_element, png_dpi = 150, inline_styles = [])
173
225
  return Promise.reject(new Error(`Canvas 2D context not available`));
174
226
  canvas.width = pixel_width;
175
227
  canvas.height = pixel_height;
176
- const serialized = serialize_svg_for_export(svg_element, inline_styles);
228
+ const serialized = serialize_svg_for_export(svg_element, inline_styles, padding, padding > 0);
177
229
  const svg_blob = new Blob([serialized], { type: `image/svg+xml;charset=utf-8` });
178
230
  const svg_data_url = URL.createObjectURL(svg_blob);
179
231
  return new Promise((resolve, reject) => {
@@ -204,12 +256,12 @@ export function svg_to_png_blob(svg_element, png_dpi = 150, inline_styles = [])
204
256
  });
205
257
  }
206
258
  // Export SVG element as PNG (triggers browser download)
207
- export function export_svg_as_png(svg_element, filename, png_dpi = 150, inline_styles = []) {
259
+ export function export_svg_as_png(svg_element, filename, png_dpi = 150, inline_styles = [], options = {}) {
208
260
  if (!svg_element) {
209
261
  console.warn(`SVG element not found for PNG export`);
210
262
  return;
211
263
  }
212
- svg_to_png_blob(svg_element, png_dpi, inline_styles)
264
+ svg_to_png_blob(svg_element, png_dpi, inline_styles, options)
213
265
  .then((blob) => download(blob, filename, `image/png`))
214
266
  .catch((error) => console.error(`Error exporting PNG:`, error));
215
267
  }
@@ -1,6 +1,6 @@
1
1
  // Shared file-drop handler composable for drag-and-drop file loading.
2
2
  import { decompress_file } from './decompress';
3
- import { handle_url_drop } from './url-drop';
3
+ import { dropped_file_url, load_from_url } from './url-drop';
4
4
  import { to_error } from '../utils';
5
5
  // Drag-over visual-state handlers for file-drop zones; spread onto the drop target
6
6
  // alongside `ondrop` from create_file_drop_handler
@@ -16,40 +16,65 @@ export const drag_over_handlers = (opts) => ({
16
16
  ondragleave: () => opts.set_dragover(false),
17
17
  });
18
18
  // Handles URL drops (from FilePicker), direct file drops with decompression,
19
- // loading state, and error reporting.
20
- export const create_file_drop_handler = (opts) => async (event) => {
21
- event.preventDefault();
22
- if (!opts.allow())
23
- return;
24
- opts.set_loading?.(true);
25
- let drop_filename = ``;
26
- try {
27
- let url_error;
28
- const handled = await handle_url_drop(event, opts.on_drop).catch((exc) => {
29
- url_error = to_error(exc).message;
30
- return false;
31
- });
32
- if (handled)
33
- return;
34
- const file = event.dataTransfer?.files[0];
35
- if (!file) {
36
- if (url_error)
37
- opts.on_error?.(`Failed to load from URL: ${url_error}`);
38
- return;
19
+ // loading state, and error reporting. Multiple dropped files are processed
20
+ // sequentially in drop order so e.g. several cube files can be imported at once.
21
+ // Overlapping drops are queued: a batch starting while a previous one is still
22
+ // processing would interleave on_drop state mutations (e.g. torn volume lists).
23
+ export const create_file_drop_handler = (opts) => {
24
+ async function process_batch(url, files) {
25
+ opts.set_loading?.(true);
26
+ try {
27
+ // One failing item must not abort the rest of the batch
28
+ const failures = [];
29
+ if (url) {
30
+ try {
31
+ await load_from_url(url, opts.on_drop);
32
+ }
33
+ catch (exc) {
34
+ // URL failed; if plain files were also dropped, still process them
35
+ // and fold the URL failure into the aggregate report
36
+ if (files.length === 0) {
37
+ opts.on_error?.(`Failed to load from URL: ${to_error(exc).message}`);
38
+ return;
39
+ }
40
+ failures.push(`URL ${url}: ${to_error(exc).message}`);
41
+ }
42
+ }
43
+ if (files.length === 0)
44
+ return;
45
+ for (const file of files) {
46
+ try {
47
+ const { content, filename } = await decompress_file(file);
48
+ if (content)
49
+ await opts.on_drop(content, filename);
50
+ else
51
+ failures.push(`${file.name}: file is empty`);
52
+ }
53
+ catch (exc) {
54
+ failures.push(`${file.name}: ${to_error(exc).message}`);
55
+ }
56
+ }
57
+ if (failures.length > 0) {
58
+ opts.on_error?.(`Failed to load ${failures.length} file${failures.length > 1 ? `s` : ``} — ${failures.join(`; `)}`);
59
+ }
60
+ }
61
+ catch (exc) {
62
+ opts.on_error?.(`Failed to load file: ${to_error(exc).message}`);
63
+ }
64
+ finally {
65
+ opts.set_loading?.(false);
39
66
  }
40
- drop_filename = file.name;
41
- const { content, filename } = await decompress_file(file);
42
- if (content)
43
- await opts.on_drop(content, filename);
44
- }
45
- catch (exc) {
46
- const detail = to_error(exc).message;
47
- const msg = drop_filename
48
- ? `Failed to load file ${drop_filename}: ${detail}`
49
- : `Failed to load file: ${detail}`;
50
- opts.on_error?.(msg);
51
- }
52
- finally {
53
- opts.set_loading?.(false);
54
67
  }
68
+ let queue = Promise.resolve();
69
+ return (event) => {
70
+ event.preventDefault();
71
+ if (!opts.allow())
72
+ return Promise.resolve();
73
+ // DataTransfer contents are only readable during drop-event dispatch, so
74
+ // capture them before deferring to the queue
75
+ const url = dropped_file_url(event);
76
+ const files = Array.from(event.dataTransfer?.files ?? []);
77
+ queue = queue.then(() => process_batch(url, files)).catch(() => undefined);
78
+ return queue;
79
+ };
55
80
  };
@@ -1,2 +1,4 @@
1
+ export declare const basename_from_url: (url: string) => string;
2
+ export declare function dropped_file_url(drag_event: DragEvent): string | undefined;
1
3
  export declare function handle_url_drop(drag_event: DragEvent, callback: (content: string | ArrayBuffer, filename: string) => Promise<void> | void): Promise<boolean>;
2
4
  export declare function load_from_url(url: string, callback: (content: string | ArrayBuffer, filename: string) => Promise<void> | void): Promise<void>;
@@ -1,6 +1,14 @@
1
1
  import { load_binary_traj } from '../trajectory/parse';
2
2
  import { decompress_data_binary } from './decompress';
3
3
  import { BINARY_EXTENSIONS, ext_of, has_binary_inner_ext, has_binary_magic, has_gzip_magic, has_hdf5_magic, is_known_text_file, strip_gz_ext, } from './is-binary';
4
+ // Strip query/hash; last path segment (same basename load_from_url uses).
5
+ // Trailing-slash URLs yield an empty segment — fall back to the original URL.
6
+ export const basename_from_url = (url) => {
7
+ const basename = url.split(/[?#]/)[0].split(`/`).pop();
8
+ if (!basename)
9
+ return url;
10
+ return basename;
11
+ };
4
12
  // Extract filename from Content-Disposition header, falling back to url_basename.
5
13
  function extract_filename(headers, fallback) {
6
14
  if (!headers)
@@ -38,27 +46,35 @@ async function decompress_gz_payload(buffer, filename) {
38
46
  : new TextDecoder().decode(decompressed);
39
47
  return [content, strip_gz_ext(filename)];
40
48
  }
41
- // Handle URL-based file drop data by fetching content lazily
42
- export async function handle_url_drop(drag_event, callback) {
49
+ // Extract the URL of a FilePicker-style drop payload. Synchronous because
50
+ // DataTransfer.getData is only readable during drop-event dispatch — callers
51
+ // that defer processing (e.g. drop queues) must capture the URL up front.
52
+ export function dropped_file_url(drag_event) {
43
53
  const json_data = drag_event.dataTransfer?.getData(`application/json`);
44
54
  if (!json_data)
45
- return false;
46
- let file_info;
55
+ return undefined;
47
56
  try {
48
- file_info = JSON.parse(json_data);
57
+ // Runtime-check instead of trusting the FileInfo cast: drop payloads are
58
+ // external input and a truthy non-string url must not reach fetch()
59
+ const { url } = JSON.parse(json_data);
60
+ return typeof url === `string` && url ? url : undefined;
49
61
  }
50
62
  catch {
51
- return false;
63
+ return undefined;
52
64
  }
53
- if (!file_info.url)
65
+ }
66
+ // Handle URL-based file drop data by fetching content lazily
67
+ export async function handle_url_drop(drag_event, callback) {
68
+ const url = dropped_file_url(drag_event);
69
+ if (!url)
54
70
  return false;
55
- await load_from_url(file_info.url, callback);
71
+ await load_from_url(url, callback);
56
72
  return true;
57
73
  }
58
74
  export async function load_from_url(url, callback) {
59
75
  // Strip query string/hash before basename/extension detection so pre-signed
60
76
  // URLs like traj.h5?X-Amz-Expires=300 still hit the right format path
61
- const url_basename = url.split(/[?#]/)[0].split(`/`).pop() ?? url;
77
+ const url_basename = basename_from_url(url);
62
78
  const ext = ext_of(url_basename);
63
79
  if (BINARY_EXTENSIONS.has(ext)) {
64
80
  // Force binary mode for known binary files to handle GitHub Pages content-type issues
@@ -99,9 +115,7 @@ export async function load_from_url(url, callback) {
99
115
  }
100
116
  // Skip Range requests for known text formats to avoid production server issues
101
117
  // Include VASP files that don't have extensions (POSCAR, XDATCAR, CONTCAR)
102
- const is_known_text = is_known_text_file(url_basename);
103
- let sniffed_callback_args;
104
- if (!is_known_text) {
118
+ if (!is_known_text_file(url_basename)) {
105
119
  // Only the Range sniff is guarded (failure → plain text fetch). Once magic bytes
106
120
  // commit to a binary format, download/decompress errors must propagate instead of
107
121
  // falling through to a text fetch that would parse the binary bytes as garbage.
@@ -129,12 +143,10 @@ export async function load_from_url(url, callback) {
129
143
  const filename = extract_filename(resp.headers, url_basename);
130
144
  const buffer = await resp.arrayBuffer();
131
145
  // Gunzip sniffed gzip — downstream parsers can't handle raw gzip bytes
132
- sniffed_callback_args =
133
- sniffed === `gzip` ? await decompress_gz_payload(buffer, filename) : [buffer, filename];
146
+ const args = sniffed === `gzip` ? await decompress_gz_payload(buffer, filename) : [buffer, filename];
147
+ return callback(...args);
134
148
  }
135
149
  }
136
- if (sniffed_callback_args)
137
- return callback(...sniffed_callback_args);
138
150
  const resp = await fetch(url);
139
151
  if (!resp.ok)
140
152
  throw new Error(`Fetch failed: ${resp.status}`);