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/math.js CHANGED
@@ -147,22 +147,19 @@ export function matrix_inverse_3x3(matrix) {
147
147
  export function mat3x3_vec3_multiply(matrix, vector) {
148
148
  const [a, b, c] = matrix;
149
149
  const [x, y, z] = vector;
150
- const a_new = a[0] * x + a[1] * y + a[2] * z;
151
- const b_new = b[0] * x + b[1] * y + b[2] * z;
152
- const c_new = c[0] * x + c[1] * y + c[2] * z;
153
- return [a_new, b_new, c_new];
150
+ return [
151
+ a[0] * x + a[1] * y + a[2] * z,
152
+ b[0] * x + b[1] * y + b[2] * z,
153
+ c[0] * x + c[1] * y + c[2] * z,
154
+ ];
154
155
  }
156
+ // Add up any number of same-length vectors
155
157
  export function add(...vecs) {
156
- // add up any number of same-length vectors
157
158
  if (vecs.length === 0)
158
159
  throw new Error(`Cannot add zero vectors`);
159
- const first_vec = vecs[0];
160
- const length = first_vec.length;
161
- // Validate all vectors have the same length
162
- for (const vec of vecs) {
163
- if (vec.length !== length) {
164
- throw new Error(`All vectors must have the same length`);
165
- }
160
+ const length = vecs[0].length;
161
+ if (vecs.some((vec) => vec.length !== length)) {
162
+ throw new Error(`All vectors must have the same length`);
166
163
  }
167
164
  const result = Array.from({ length }).fill(0);
168
165
  for (const vec of vecs) {
@@ -180,20 +177,14 @@ export function subtract(vec1, vec2) {
180
177
  }
181
178
  // Validate matrix structure and return column count
182
179
  function validate_matrix(mat, name) {
183
- // Check for empty matrix (no rows)
184
- if (mat.length === 0) {
180
+ if (mat.length === 0)
185
181
  throw new Error(`${name} must have at least one row`);
186
- }
187
182
  if (!mat.every((row) => Array.isArray(row))) {
188
183
  throw new Error(`${name} must contain only array rows (no undefined/non-array elements)`);
189
184
  }
190
- const cols = mat[0]?.length;
191
- if (!Number.isFinite(cols))
192
- throw new Error(`${name} has no columns`);
193
- // Check for zero columns
194
- if (cols === 0) {
185
+ const cols = mat[0].length;
186
+ if (cols === 0)
195
187
  throw new Error(`${name} must have at least one column`);
196
- }
197
188
  if (!mat.every((row) => row.length === cols)) {
198
189
  throw new Error(`${name} must be rectangular`);
199
190
  }
@@ -293,7 +284,10 @@ export function scale_lattice_matrix(orig_matrix, scaling_factors) {
293
284
  [c[0] * nz, c[1] * nz, c[2] * nz],
294
285
  ];
295
286
  }
296
- const create_cart_to_frac_matrix = (lattice) => matrix_inverse_3x3(transpose_3x3_matrix(lattice));
287
+ // Matrix mapping Cartesian coords to fractional (inverse of transposed
288
+ // row-vector lattice). Prefer create_cart_to_frac unless the raw matrix is
289
+ // needed, e.g. for allocation-free scalar arithmetic in hot loops.
290
+ export const create_cart_to_frac_matrix = (lattice) => matrix_inverse_3x3(transpose_3x3_matrix(lattice));
297
291
  // Curried fractional→Cartesian converter (caches transposed matrix)
298
292
  export const create_frac_to_cart = (lattice) => {
299
293
  const transposed = transpose_3x3_matrix(lattice);
@@ -811,11 +805,12 @@ export function compute_bounding_box_2d(vertices) {
811
805
  export function polygon_centroid(vertices) {
812
806
  if (vertices.length === 0)
813
807
  return [0, 0];
814
- if (vertices.length < 3) {
815
- const sum_x = vertices.reduce((acc, [x]) => acc + x, 0);
816
- const sum_y = vertices.reduce((acc, [, y]) => acc + y, 0);
817
- return [sum_x / vertices.length, sum_y / vertices.length];
818
- }
808
+ const vertex_average = () => [
809
+ vertices.reduce((acc, [x]) => acc + x, 0) / vertices.length,
810
+ vertices.reduce((acc, [, y]) => acc + y, 0) / vertices.length,
811
+ ];
812
+ if (vertices.length < 3)
813
+ return vertex_average();
819
814
  let [signed_area, cx, cy] = [0, 0, 0];
820
815
  for (let idx = 0; idx < vertices.length; idx++) {
821
816
  const [x0, y0] = vertices[idx];
@@ -826,12 +821,8 @@ export function polygon_centroid(vertices) {
826
821
  cy += (y0 + y1) * cross;
827
822
  }
828
823
  signed_area *= 0.5;
829
- // Fall back to vertex average for degenerate polygons
830
- if (Math.abs(signed_area) < EPS) {
831
- const sum_x = vertices.reduce((acc, [x]) => acc + x, 0);
832
- const sum_y = vertices.reduce((acc, [, y]) => acc + y, 0);
833
- return [sum_x / vertices.length, sum_y / vertices.length];
834
- }
824
+ if (Math.abs(signed_area) < EPS)
825
+ return vertex_average();
835
826
  const factor = 1 / (6 * signed_area);
836
827
  return [cx * factor, cy * factor];
837
828
  }
@@ -910,11 +901,11 @@ export const cross_2d = (origin, point_a, point_b) => (point_a[0] - origin[0]) *
910
901
  (point_a[1] - origin[1]) * (point_b[0] - origin[0]);
911
902
  // One half of Andrew's monotone chain built from x-then-y *pre-sorted* points
912
903
  // (lower chain; pass reversed input for the upper chain).
913
- export const monotone_chain = (sorted) => {
904
+ export const monotone_chain = (sorted, tolerance = 0) => {
914
905
  const chain = [];
915
906
  for (const pt of sorted) {
916
907
  while (chain.length >= 2 &&
917
- cross_2d(chain[chain.length - 2], chain[chain.length - 1], pt) <= 0) {
908
+ cross_2d(chain[chain.length - 2], chain[chain.length - 1], pt) <= tolerance) {
918
909
  chain.pop();
919
910
  }
920
911
  chain.push(pt);
@@ -923,12 +914,12 @@ export const monotone_chain = (sorted) => {
923
914
  };
924
915
  // Full 2D convex hull via Andrew's monotone chain algorithm.
925
916
  // Returns vertices in counter-clockwise order.
926
- export function convex_hull_2d(points) {
917
+ export function convex_hull_2d(points, tolerance = 0) {
927
918
  if (points.length < 3)
928
919
  return [...points];
929
920
  const sorted = points.toSorted((a, b) => a[0] - b[0] || a[1] - b[1]);
930
- const lower = monotone_chain(sorted);
931
- const upper = monotone_chain(sorted.toReversed());
921
+ const lower = monotone_chain(sorted, tolerance);
922
+ const upper = monotone_chain(sorted.toReversed(), tolerance);
932
923
  // Remove last point of each half (it's the first point of the other)
933
924
  lower.pop();
934
925
  upper.pop();
@@ -38,41 +38,22 @@
38
38
  return { x: Math.max(0, x), y: Math.max(0, y) }
39
39
  }
40
40
 
41
- // Handle click outside to close
42
- function handle_click_outside(event: MouseEvent) {
43
- const target = event.target
44
- if (target instanceof Element && visible) {
45
- const menu = target.closest(`.context-menu`)
46
- if (!menu) on_close?.()
47
- }
48
- }
49
-
50
- // Handle right-click outside to close
51
- function handle_right_click_outside(event: MouseEvent) {
52
- if (!visible) return
53
- const target = event.target
54
- const menu = target instanceof Element ? target.closest(`.context-menu`) : null
55
- if (!menu) {
56
- event.preventDefault()
57
- on_close?.()
58
- }
59
- }
60
-
61
- // Handle keyboard shortcuts
62
- function handle_keydown(event: KeyboardEvent) {
63
- if (event.key === `Escape` && visible) on_close?.()
64
- }
65
-
66
- // Handle option selection
67
- function handle_option_click(section_title: string, option: MenuOption) {
68
- if (!option.disabled) on_select?.(section_title, option)
41
+ // Close on click or right-click outside the menu (right-click also suppresses
42
+ // the native context menu)
43
+ function handle_outside_pointer(event: MouseEvent) {
44
+ if (!visible || !(event.target instanceof Element)) return
45
+ if (event.target.closest(`.context-menu`)) return
46
+ if (event.type === `contextmenu`) event.preventDefault()
47
+ on_close?.()
69
48
  }
70
49
  </script>
71
50
 
72
51
  <svelte:document
73
- onclick={handle_click_outside}
74
- oncontextmenu={handle_right_click_outside}
75
- onkeydown={handle_keydown}
52
+ onclick={handle_outside_pointer}
53
+ oncontextmenu={handle_outside_pointer}
54
+ onkeydown={(event) => {
55
+ if (event.key === `Escape` && visible) on_close?.()
56
+ }}
76
57
  />
77
58
 
78
59
  {#if visible}
@@ -86,7 +67,9 @@
86
67
  <button
87
68
  class:selected={selected_values[title] === option.value}
88
69
  class:disabled={option.disabled}
89
- onclick={() => handle_option_click(title, option)}
70
+ onclick={() => {
71
+ if (!option.disabled) on_select?.(title, option)
72
+ }}
90
73
  >
91
74
  {#if option.icon}
92
75
  <Icon icon={option.icon as IconName} />
@@ -2,7 +2,7 @@
2
2
  import Icon from '../Icon.svelte'
3
3
  import type { IconName } from '../icons'
4
4
  import { DragControlTab } from './'
5
- import type { Snippet } from 'svelte'
5
+ import { onDestroy, type Snippet } from 'svelte'
6
6
  import { draggable, tooltip } from 'svelte-multiselect/attachments'
7
7
  import type { HTMLAttributes } from 'svelte/elements'
8
8
 
@@ -118,25 +118,29 @@
118
118
  onclose()
119
119
  }
120
120
 
121
- function reset_position() {
122
- if (toggle_pane_btn) {
123
- const pos = calculate_position()
124
- initial_position = pos
125
- if (pane_div) {
126
- Object.assign(pane_div.style, {
127
- left: pos.left,
128
- top: pos.top,
129
- right: `auto`,
130
- bottom: `auto`,
131
- width: ``,
132
- maxHeight: ``,
133
- })
134
- // Refresh --pane-viewport-clamp for the new position (matches
135
- // handle_resize and the mount effect; without this a reset after a
136
- // drag + window resize keeps a stale bottom-edge cap)
137
- apply_viewport_clamp()
138
- }
121
+ // Move the pane back under the toggle button, clear manual size overrides and
122
+ // refresh --pane-viewport-clamp (without which a reset after a drag + window
123
+ // resize would keep a stale bottom-edge cap)
124
+ function position_pane() {
125
+ if (pane_div) {
126
+ // Clear manual size first so calculate_position() uses natural pane dimensions
127
+ pane_div.style.width = ``
128
+ pane_div.style.maxHeight = ``
139
129
  }
130
+ const pos = calculate_position()
131
+ initial_position = pos
132
+ if (!pane_div) return
133
+ Object.assign(pane_div.style, {
134
+ left: pos.left,
135
+ top: pos.top,
136
+ right: `auto`,
137
+ bottom: `auto`,
138
+ })
139
+ apply_viewport_clamp()
140
+ }
141
+
142
+ function reset_position() {
143
+ if (toggle_pane_btn) position_pane()
140
144
  // Hide the control buttons after reset
141
145
  show_control_buttons = false
142
146
  has_been_dragged = false
@@ -204,37 +208,26 @@
204
208
  // (Playwright and some browsers synthesize a click after pointer up)
205
209
  if (Date.now() - resize_end_time < 200) return
206
210
 
207
- const target = event.target
208
- const is_toggle_button =
209
- target instanceof Node &&
210
- (target === toggle_pane_btn || (toggle_pane_btn?.contains(target) ?? false))
211
- const is_inside_pane =
212
- target instanceof Node && (target === pane_div || (pane_div?.contains(target) ?? false))
213
-
214
- if (!is_toggle_button && !is_inside_pane && !currently_dragging && !resizing) {
215
- close_pane()
216
- }
211
+ const target = event.target instanceof Node ? event.target : null
212
+ const inside_toggle_or_pane = [toggle_pane_btn, pane_div].some(
213
+ (el) => el && (el === target || el.contains(target)),
214
+ )
215
+ if (!inside_toggle_or_pane && !currently_dragging && !resizing) close_pane()
217
216
  }
218
217
 
219
218
  // Debounced resize handler for better performance
220
- let resize_timeout: ReturnType<typeof setTimeout> | undefined = $state(undefined)
219
+ let resize_timeout: ReturnType<typeof setTimeout> | undefined
220
+ onDestroy(() => resize_timeout && clearTimeout(resize_timeout))
221
221
 
222
222
  function handle_resize() {
223
223
  // Only reposition if pane is visible and hasn't been manually dragged
224
224
  if (!show || has_been_dragged || currently_dragging) return
225
225
 
226
226
  if (resize_timeout) clearTimeout(resize_timeout)
227
- const current_timeout = setTimeout(() => {
228
- if (resize_timeout !== current_timeout) return
229
- if (show && toggle_pane_btn && !has_been_dragged && pane_div) {
230
- const pos = calculate_position()
231
- initial_position = pos
232
- pane_div.style.left = pos.left
233
- pane_div.style.top = pos.top
234
- apply_viewport_clamp()
235
- }
227
+ resize_timeout = setTimeout(() => {
228
+ // Re-check state since it may have changed during the debounce window
229
+ if (show && toggle_pane_btn && !has_been_dragged && pane_div) position_pane()
236
230
  }, 50) // Debounce resize events
237
- resize_timeout = current_timeout
238
231
  }
239
232
 
240
233
  // Fixed panes must never extend past the bottom viewport edge: cap the
@@ -257,21 +250,7 @@
257
250
 
258
251
  // Position pane when shown
259
252
  $effect(() => {
260
- if (show && toggle_pane_btn && !has_been_dragged) {
261
- const pos = calculate_position()
262
- initial_position = pos
263
- if (pane_div) {
264
- Object.assign(pane_div.style, {
265
- left: pos.left,
266
- top: pos.top,
267
- right: `auto`,
268
- bottom: `auto`,
269
- width: ``,
270
- maxHeight: ``,
271
- })
272
- apply_viewport_clamp()
273
- }
274
- }
253
+ if (show && toggle_pane_btn && !has_been_dragged) position_pane()
275
254
  })
276
255
  </script>
277
256
 
@@ -374,7 +353,7 @@
374
353
  background-color: color-mix(in srgb, currentColor 8%, transparent);
375
354
  }
376
355
  div.draggable-pane {
377
- position: absolute; /* Use absolute so pane scrolls with page content */
356
+ /* position set inline via style:position (default absolute: scrolls with page) */
378
357
  background: var(--pane-bg, var(--page-bg, light-dark(white, black)));
379
358
  border: var(
380
359
  --pane-border,
@@ -386,7 +365,9 @@
386
365
  --pane-box-shadow,
387
366
  light-dark(0 4px 20px -4px rgba(0, 0, 0, 0.15), 0 8px 16px -4px rgba(0, 0, 0, 0.3))
388
367
  );
389
- z-index: var(--pane-z-index, 10);
368
+ z-index: var(--pane-z-index, var(--z-index-viewer-pane, 10));
369
+ /* own compositing layer, or WKWebView paints the canvas over this (see app.css) */
370
+ will-change: transform;
390
371
  display: grid;
391
372
  text-align: left;
392
373
  /* Exclude position from being transitioned to prevent sluggish dragging */
@@ -466,7 +447,7 @@
466
447
  .draggable-pane :global(input[type='number']) {
467
448
  box-sizing: border-box;
468
449
  text-align: center;
469
- width: 2.2em;
450
+ width: 3.5em;
470
451
  margin: 0 3pt;
471
452
  flex-shrink: 0;
472
453
  }
@@ -1,5 +1,5 @@
1
1
  import type { IconName } from '../icons';
2
- import type { Snippet } from 'svelte';
2
+ import { type Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
4
  type $$ComponentProps = {
5
5
  show?: boolean;
@@ -18,7 +18,9 @@
18
18
  position: absolute;
19
19
  top: var(--glass-chip-top, 10px);
20
20
  left: var(--glass-chip-left, 10px);
21
- z-index: var(--glass-chip-z, 2);
21
+ z-index: var(--glass-chip-z, var(--z-index-viewer-chip, 2));
22
+ /* own compositing layer, or WKWebView paints the canvas over this (see app.css) */
23
+ will-change: transform;
22
24
  max-width: var(--glass-chip-max-width, calc(100% - 20px));
23
25
  padding: 4px 8px;
24
26
  border-radius: 6px;
@@ -102,40 +102,6 @@
102
102
  }
103
103
  })
104
104
 
105
- // Generic reset function using simple object key access
106
- function reset_property(prop: keyof typeof defaults): void {
107
- const default_value = defaults[prop]
108
-
109
- // Use simple assignment based on property name
110
- if (prop === `tile_gap`) tile_gap = default_value as string
111
- else if (prop === `symbol_font_size`) symbol_font_size = default_value as number
112
- else if (prop === `number_font_size`) number_font_size = default_value as number
113
- else if (prop === `name_font_size`) name_font_size = default_value as number
114
- else if (prop === `value_font_size`) value_font_size = default_value as number
115
- else if (prop === `tooltip_font_size`) tooltip_font_size = default_value as number
116
- else if (prop === `tooltip_bg_color`) tooltip_bg_color = default_value as string
117
- else if (prop === `tile_border_radius`) tile_border_radius = Number(default_value)
118
- else if (prop === `inner_transition_offset`) {
119
- inner_transition_offset = Number(default_value)
120
- } else if (prop === `tile_font_color`) tile_font_color = String(default_value)
121
- else if (prop === `tile_transition_duration`) {
122
- tile_transition_duration = Number(default_value)
123
- } else if (prop === `hover_border_width`) {
124
- hover_border_width = Number(default_value)
125
- } else if (prop === `symbol_font_weight`) {
126
- symbol_font_weight = Number(default_value)
127
- } else if (prop === `number_font_weight`) {
128
- number_font_weight = Number(default_value)
129
- } else if (prop === `tooltip_border_radius`) {
130
- tooltip_border_radius = Number(default_value)
131
- } else if (prop === `tooltip_padding`) tooltip_padding = String(default_value)
132
- else if (prop === `tooltip_line_height`) {
133
- tooltip_line_height = Number(default_value)
134
- } else if (prop === `tooltip_text_align`) {
135
- tooltip_text_align = String(default_value)
136
- }
137
- }
138
-
139
105
  // Check if settings in each section have been modified from defaults
140
106
  let category_colors_modified = $derived(
141
107
  Object.keys(colors.category).some(
@@ -247,21 +213,23 @@
247
213
  <label>
248
214
  <span>Gap between tiles</span>
249
215
  <input type="text" bind:value={tile_gap} placeholder="0.3cqw" />
250
- <button onclick={() => reset_property(`tile_gap`)}>reset</button>
216
+ <button onclick={() => (tile_gap = defaults.tile_gap)}>reset</button>
251
217
  </label>
252
218
 
253
219
  <label>
254
220
  <span>Border radius (pt)</span>
255
221
  <input type="range" min="0" max="10" step="0.5" bind:value={tile_border_radius} />
256
222
  <input type="number" min="0" max="10" step="0.5" bind:value={tile_border_radius} />
257
- <button onclick={() => reset_property(`tile_border_radius`)}>reset</button>
223
+ <button onclick={() => (tile_border_radius = defaults.tile_border_radius)}>reset</button>
258
224
  </label>
259
225
 
260
226
  <label>
261
227
  <span>Inner transition offset</span>
262
228
  <input type="range" min="0.1" max="2" step="0.1" bind:value={inner_transition_offset} />
263
229
  <input type="number" min="0.1" max="2" step="0.1" bind:value={inner_transition_offset} />
264
- <button onclick={() => reset_property(`inner_transition_offset`)}>reset</button>
230
+ <button onclick={() => (inner_transition_offset = defaults.inner_transition_offset)}
231
+ >reset</button
232
+ >
265
233
  </label>
266
234
 
267
235
  <label>
@@ -274,20 +242,22 @@
274
242
  step="0.1"
275
243
  bind:value={tile_transition_duration}
276
244
  />
277
- <button onclick={() => reset_property(`tile_transition_duration`)}>reset</button>
245
+ <button onclick={() => (tile_transition_duration = defaults.tile_transition_duration)}
246
+ >reset</button
247
+ >
278
248
  </label>
279
249
 
280
250
  <label>
281
251
  <span>Hover border width (px)</span>
282
252
  <input type="range" min="0" max="5" step="1" bind:value={hover_border_width} />
283
253
  <input type="number" min="0" max="5" step="1" bind:value={hover_border_width} />
284
- <button onclick={() => reset_property(`hover_border_width`)}>reset</button>
254
+ <button onclick={() => (hover_border_width = defaults.hover_border_width)}>reset</button>
285
255
  </label>
286
256
 
287
257
  <label>
288
258
  <span>Font color</span>
289
259
  <input type="color" bind:value={tile_font_color} />
290
- <button onclick={() => reset_property(`tile_font_color`)}>reset</button>
260
+ <button onclick={() => (tile_font_color = defaults.tile_font_color)}>reset</button>
291
261
  </label>
292
262
  </section>
293
263
 
@@ -303,42 +273,42 @@
303
273
  <span>Symbol size</span>
304
274
  <input type="range" min="20" max="80" step="2" bind:value={symbol_font_size} />
305
275
  <input type="number" min="20" max="80" step="2" bind:value={symbol_font_size} />
306
- <button onclick={() => reset_property(`symbol_font_size`)}>reset</button>
276
+ <button onclick={() => (symbol_font_size = defaults.symbol_font_size)}>reset</button>
307
277
  </label>
308
278
 
309
279
  <label>
310
280
  <span>Number size</span>
311
281
  <input type="range" min="10" max="40" step="1" bind:value={number_font_size} />
312
282
  <input type="number" min="10" max="40" step="1" bind:value={number_font_size} />
313
- <button onclick={() => reset_property(`number_font_size`)}>reset</button>
283
+ <button onclick={() => (number_font_size = defaults.number_font_size)}>reset</button>
314
284
  </label>
315
285
 
316
286
  <label>
317
287
  <span>Name size</span>
318
288
  <input type="range" min="6" max="24" step="1" bind:value={name_font_size} />
319
289
  <input type="number" min="6" max="24" step="1" bind:value={name_font_size} />
320
- <button onclick={() => reset_property(`name_font_size`)}>reset</button>
290
+ <button onclick={() => (name_font_size = defaults.name_font_size)}>reset</button>
321
291
  </label>
322
292
 
323
293
  <label>
324
294
  <span>Value size</span>
325
295
  <input type="range" min="10" max="30" step="1" bind:value={value_font_size} />
326
296
  <input type="number" min="10" max="30" step="1" bind:value={value_font_size} />
327
- <button onclick={() => reset_property(`value_font_size`)}>reset</button>
297
+ <button onclick={() => (value_font_size = defaults.value_font_size)}>reset</button>
328
298
  </label>
329
299
 
330
300
  <label>
331
301
  <span>Symbol weight</span>
332
302
  <input type="range" min="100" max="900" step="100" bind:value={symbol_font_weight} />
333
303
  <input type="number" min="100" max="900" step="100" bind:value={symbol_font_weight} />
334
- <button onclick={() => reset_property(`symbol_font_weight`)}>reset</button>
304
+ <button onclick={() => (symbol_font_weight = defaults.symbol_font_weight)}>reset</button>
335
305
  </label>
336
306
 
337
307
  <label>
338
308
  <span>Number weight</span>
339
309
  <input type="range" min="100" max="900" step="100" bind:value={number_font_weight} />
340
310
  <input type="number" min="100" max="900" step="100" bind:value={number_font_weight} />
341
- <button onclick={() => reset_property(`number_font_weight`)}>reset</button>
311
+ <button onclick={() => (number_font_weight = defaults.number_font_weight)}>reset</button>
342
312
  </label>
343
313
  </section>
344
314
 
@@ -354,33 +324,37 @@
354
324
  <span>Font size (px)</span>
355
325
  <input type="range" min="8" max="24" step="1" bind:value={tooltip_font_size} />
356
326
  <input type="number" min="8" max="24" step="1" bind:value={tooltip_font_size} />
357
- <button onclick={() => reset_property(`tooltip_font_size`)}>reset</button>
327
+ <button onclick={() => (tooltip_font_size = defaults.tooltip_font_size)}>reset</button>
358
328
  </label>
359
329
 
360
330
  <label>
361
331
  <span>Background color</span>
362
332
  <input type="color" bind:value={tooltip_bg_color} />
363
- <button onclick={() => reset_property(`tooltip_bg_color`)}>reset</button>
333
+ <button onclick={() => (tooltip_bg_color = defaults.tooltip_bg_color)}>reset</button>
364
334
  </label>
365
335
 
366
336
  <label>
367
337
  <span>Border radius (px)</span>
368
338
  <input type="range" min="0" max="20" step="1" bind:value={tooltip_border_radius} />
369
339
  <input type="number" min="0" max="20" step="1" bind:value={tooltip_border_radius} />
370
- <button onclick={() => reset_property(`tooltip_border_radius`)}>reset</button>
340
+ <button onclick={() => (tooltip_border_radius = defaults.tooltip_border_radius)}
341
+ >reset</button
342
+ >
371
343
  </label>
372
344
 
373
345
  <label>
374
346
  <span>Padding</span>
375
347
  <input type="text" bind:value={tooltip_padding} placeholder="4px 6px" />
376
- <button onclick={() => reset_property(`tooltip_padding`)}>reset</button>
348
+ <button onclick={() => (tooltip_padding = defaults.tooltip_padding)}>reset</button>
377
349
  </label>
378
350
 
379
351
  <label>
380
352
  <span>Line height</span>
381
353
  <input type="range" min="0.8" max="2" step="0.1" bind:value={tooltip_line_height} />
382
354
  <input type="number" min="0.8" max="2" step="0.1" bind:value={tooltip_line_height} />
383
- <button onclick={() => reset_property(`tooltip_line_height`)}>reset</button>
355
+ <button onclick={() => (tooltip_line_height = defaults.tooltip_line_height)}
356
+ >reset</button
357
+ >
384
358
  </label>
385
359
 
386
360
  <label>
@@ -390,7 +364,7 @@
390
364
  <option value="center">Center</option>
391
365
  <option value="right">Right</option>
392
366
  </select>
393
- <button onclick={() => reset_property(`tooltip_text_align`)}>reset</button>
367
+ <button onclick={() => (tooltip_text_align = defaults.tooltip_text_align)}>reset</button>
394
368
  </label>
395
369
  </section>
396
370
  </div>
@@ -802,13 +802,9 @@
802
802
  fill={merged_config.colors.text}
803
803
  font-size={merged_config.font_size + 2}
804
804
  >
805
- {#if x_axis.label}
806
- {@html sanitize_svg(x_axis.label)}
807
- {:else if effective_data?.x_axis_label}
808
- {@html sanitize_svg(effective_data.x_axis_label)}
809
- {:else}
810
- {@html sanitize_svg(default_x_axis_label)}
811
- {/if}
805
+ {@html sanitize_svg(
806
+ x_axis.label || effective_data?.x_axis_label || default_x_axis_label,
807
+ )}
812
808
  </text>
813
809
  </g>
814
810
 
@@ -845,38 +841,26 @@
845
841
  fill={merged_config.colors.text}
846
842
  font-size={merged_config.font_size + 2}
847
843
  >
848
- {#if y_axis.label}
849
- {@html sanitize_svg(y_axis.label)}
850
- {:else if effective_data?.y_axis_label}
851
- {@html sanitize_svg(effective_data.y_axis_label)}
852
- {:else}
853
- Temperature ({temp_unit})
854
- {/if}
844
+ {@html sanitize_svg(
845
+ y_axis.label || effective_data?.y_axis_label || `Temperature (${temp_unit})`,
846
+ )}
855
847
  </text>
856
848
  </g>
857
849
 
858
850
  <!-- Component labels at corners (supports compound formulas with subscripts) -->
859
851
  {#if show_component_labels}
860
- <text
861
- x={left}
862
- y={bottom + 45}
863
- text-anchor="middle"
864
- fill={merged_config.colors.text}
865
- font-size={merged_config.font_size + 2}
866
- font-weight="bold"
867
- >
868
- {@html sanitize_svg(component_a_svg)}
869
- </text>
870
- <text
871
- x={right}
872
- y={bottom + 45}
873
- text-anchor="middle"
874
- fill={merged_config.colors.text}
875
- font-size={merged_config.font_size + 2}
876
- font-weight="bold"
877
- >
878
- {@html sanitize_svg(component_b_svg)}
879
- </text>
852
+ {#each [component_a_svg, component_b_svg] as svg, idx (idx)}
853
+ <text
854
+ x={idx === 0 ? left : right}
855
+ y={bottom + 45}
856
+ text-anchor="middle"
857
+ fill={merged_config.colors.text}
858
+ font-size={merged_config.font_size + 2}
859
+ font-weight="bold"
860
+ >
861
+ {@html sanitize_svg(svg)}
862
+ </text>
863
+ {/each}
880
864
  {/if}
881
865
  </svg>
882
866
 
@@ -147,12 +147,7 @@ export function build_diagram(input) {
147
147
  title: meta.title,
148
148
  regions: built_regions,
149
149
  boundaries,
150
- special_points: special_points?.map((sp) => ({
151
- id: sp.id,
152
- type: sp.type,
153
- position: sp.position,
154
- label: sp.label,
155
- })),
150
+ special_points,
156
151
  ...(meta.pseudo_binary && { pseudo_binary: meta.pseudo_binary }),
157
152
  ...(meta.x_axis_label && { x_axis_label: meta.x_axis_label }),
158
153
  ...(meta.y_axis_label && { y_axis_label: meta.y_axis_label }),