matterviz 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. package/dist/FilePicker.svelte +37 -20
  2. package/dist/Icon.svelte +2 -2
  3. package/dist/MillerIndexInput.svelte +60 -0
  4. package/dist/MillerIndexInput.svelte.d.ts +7 -0
  5. package/dist/app.css +38 -2
  6. package/dist/brillouin/BrillouinZone.svelte +20 -62
  7. package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
  8. package/dist/brillouin/BrillouinZoneExportPane.svelte +12 -20
  9. package/dist/brillouin/BrillouinZoneScene.svelte +2 -2
  10. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
  11. package/dist/chempot-diagram/ChemPotDiagram.svelte +192 -0
  12. package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
  13. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +677 -0
  14. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
  15. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +2688 -0
  16. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
  17. package/dist/chempot-diagram/ChemPotScene3D.svelte +8 -0
  18. package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +7 -0
  19. package/dist/chempot-diagram/color.d.ts +10 -0
  20. package/dist/chempot-diagram/color.js +33 -0
  21. package/dist/chempot-diagram/compute.d.ts +38 -0
  22. package/dist/chempot-diagram/compute.js +650 -0
  23. package/dist/chempot-diagram/index.d.ts +5 -0
  24. package/dist/chempot-diagram/index.js +5 -0
  25. package/dist/chempot-diagram/pointer.d.ts +16 -0
  26. package/dist/chempot-diagram/pointer.js +40 -0
  27. package/dist/chempot-diagram/temperature.d.ts +15 -0
  28. package/dist/chempot-diagram/temperature.js +37 -0
  29. package/dist/chempot-diagram/types.d.ts +83 -0
  30. package/dist/chempot-diagram/types.js +27 -0
  31. package/dist/colors/index.d.ts +3 -1
  32. package/dist/colors/index.js +4 -0
  33. package/dist/composition/BarChart.svelte +13 -22
  34. package/dist/composition/BubbleChart.svelte +5 -3
  35. package/dist/composition/FormulaFilter.svelte +770 -90
  36. package/dist/composition/FormulaFilter.svelte.d.ts +37 -1
  37. package/dist/composition/PieChart.svelte +43 -18
  38. package/dist/composition/PieChart.svelte.d.ts +1 -1
  39. package/dist/constants.d.ts +1 -0
  40. package/dist/constants.js +2 -0
  41. package/dist/convex-hull/ConvexHull.svelte +14 -1
  42. package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
  43. package/dist/convex-hull/ConvexHull2D.svelte +14 -45
  44. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
  45. package/dist/convex-hull/ConvexHull3D.svelte +396 -134
  46. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
  47. package/dist/convex-hull/ConvexHull4D.svelte +93 -42
  48. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
  49. package/dist/convex-hull/ConvexHullControls.svelte +94 -31
  50. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +4 -2
  51. package/dist/convex-hull/ConvexHullStats.svelte +697 -128
  52. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +6 -1
  53. package/dist/convex-hull/ConvexHullTooltip.svelte +1 -0
  54. package/dist/convex-hull/GasPressureControls.svelte +72 -38
  55. package/dist/convex-hull/GasPressureControls.svelte.d.ts +2 -1
  56. package/dist/convex-hull/TemperatureSlider.svelte +46 -19
  57. package/dist/convex-hull/TemperatureSlider.svelte.d.ts +2 -1
  58. package/dist/convex-hull/demo-temperature.d.ts +6 -0
  59. package/dist/convex-hull/demo-temperature.js +36 -0
  60. package/dist/convex-hull/gas-thermodynamics.js +16 -5
  61. package/dist/convex-hull/helpers.d.ts +7 -1
  62. package/dist/convex-hull/helpers.js +45 -15
  63. package/dist/convex-hull/index.d.ts +15 -1
  64. package/dist/convex-hull/index.js +1 -0
  65. package/dist/convex-hull/thermodynamics.d.ts +8 -21
  66. package/dist/convex-hull/thermodynamics.js +106 -17
  67. package/dist/convex-hull/types.d.ts +7 -0
  68. package/dist/convex-hull/types.js +11 -0
  69. package/dist/coordination/CoordinationBarPlot.svelte +29 -46
  70. package/dist/element/BohrAtom.svelte +1 -1
  71. package/dist/element/data.js +2 -14
  72. package/dist/element/data.json.gz +0 -0
  73. package/dist/element/index.d.ts +1 -1
  74. package/dist/element/index.js +1 -0
  75. package/dist/element/types.d.ts +1 -0
  76. package/dist/fermi-surface/FermiSurface.svelte +21 -65
  77. package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
  78. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  79. package/dist/fermi-surface/FermiSurfaceScene.svelte +1 -1
  80. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
  81. package/dist/fermi-surface/compute.js +1 -21
  82. package/dist/fermi-surface/marching-cubes.d.ts +2 -13
  83. package/dist/fermi-surface/marching-cubes.js +2 -519
  84. package/dist/fermi-surface/parse.js +17 -23
  85. package/dist/heatmap-matrix/HeatmapMatrix.svelte +1273 -0
  86. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +110 -0
  87. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +171 -0
  88. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +31 -0
  89. package/dist/heatmap-matrix/index.d.ts +53 -0
  90. package/dist/heatmap-matrix/index.js +100 -0
  91. package/dist/heatmap-matrix/shared.d.ts +2 -0
  92. package/dist/heatmap-matrix/shared.js +4 -0
  93. package/dist/icons.d.ts +119 -0
  94. package/dist/icons.js +119 -0
  95. package/dist/index.d.ts +6 -1
  96. package/dist/index.js +6 -1
  97. package/dist/io/export.js +15 -3
  98. package/dist/io/file-drop.d.ts +7 -0
  99. package/dist/io/file-drop.js +43 -0
  100. package/dist/io/index.d.ts +2 -2
  101. package/dist/io/index.js +2 -112
  102. package/dist/io/types.d.ts +1 -0
  103. package/dist/io/url-drop.d.ts +2 -0
  104. package/dist/io/url-drop.js +118 -0
  105. package/dist/isosurface/Isosurface.svelte +231 -0
  106. package/dist/isosurface/Isosurface.svelte.d.ts +8 -0
  107. package/dist/isosurface/IsosurfaceControls.svelte +273 -0
  108. package/dist/isosurface/IsosurfaceControls.svelte.d.ts +9 -0
  109. package/dist/isosurface/index.d.ts +5 -0
  110. package/dist/isosurface/index.js +6 -0
  111. package/dist/isosurface/parse.d.ts +6 -0
  112. package/dist/isosurface/parse.js +548 -0
  113. package/dist/isosurface/slice.d.ts +11 -0
  114. package/dist/isosurface/slice.js +145 -0
  115. package/dist/isosurface/types.d.ts +55 -0
  116. package/dist/isosurface/types.js +178 -0
  117. package/dist/labels.d.ts +2 -1
  118. package/dist/labels.js +1 -0
  119. package/dist/layout/InfoTag.svelte +62 -62
  120. package/dist/layout/SubpageGrid.svelte +74 -0
  121. package/dist/layout/SubpageGrid.svelte.d.ts +14 -0
  122. package/dist/layout/index.d.ts +1 -0
  123. package/dist/layout/index.js +1 -0
  124. package/dist/layout/json-tree/JsonNode.svelte +226 -53
  125. package/dist/layout/json-tree/JsonTree.svelte +425 -51
  126. package/dist/layout/json-tree/JsonTree.svelte.d.ts +1 -1
  127. package/dist/layout/json-tree/JsonValue.svelte +218 -97
  128. package/dist/layout/json-tree/types.d.ts +27 -2
  129. package/dist/layout/json-tree/utils.d.ts +14 -1
  130. package/dist/layout/json-tree/utils.js +254 -0
  131. package/dist/marching-cubes.d.ts +14 -0
  132. package/dist/marching-cubes.js +519 -0
  133. package/dist/math.d.ts +8 -0
  134. package/dist/math.js +374 -7
  135. package/dist/overlays/ContextMenu.svelte +3 -2
  136. package/dist/overlays/DraggablePane.svelte +163 -58
  137. package/dist/overlays/DraggablePane.svelte.d.ts +2 -0
  138. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +232 -77
  139. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -2
  140. package/dist/phase-diagram/PhaseDiagramControls.svelte +32 -11
  141. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +3 -2
  142. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +103 -0
  143. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
  144. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +102 -95
  145. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +7 -0
  146. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +100 -26
  147. package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +6 -3
  148. package/dist/phase-diagram/index.d.ts +2 -0
  149. package/dist/phase-diagram/index.js +2 -0
  150. package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
  151. package/dist/phase-diagram/svg-to-diagram.js +865 -0
  152. package/dist/phase-diagram/types.d.ts +10 -0
  153. package/dist/phase-diagram/utils.d.ts +7 -4
  154. package/dist/phase-diagram/utils.js +149 -59
  155. package/dist/plot/AxisLabel.svelte +26 -0
  156. package/dist/plot/AxisLabel.svelte.d.ts +16 -0
  157. package/dist/plot/BarPlot.svelte +473 -228
  158. package/dist/plot/BarPlot.svelte.d.ts +3 -3
  159. package/dist/plot/BarPlotControls.svelte +3 -2
  160. package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
  161. package/dist/plot/ColorBar.svelte +54 -54
  162. package/dist/plot/ColorBar.svelte.d.ts +1 -1
  163. package/dist/plot/ElementScatter.svelte +4 -3
  164. package/dist/plot/FillArea.svelte +4 -1
  165. package/dist/plot/Histogram.svelte +320 -230
  166. package/dist/plot/Histogram.svelte.d.ts +2 -2
  167. package/dist/plot/HistogramControls.svelte +29 -10
  168. package/dist/plot/HistogramControls.svelte.d.ts +6 -2
  169. package/dist/plot/InteractiveAxisLabel.svelte.d.ts +2 -2
  170. package/dist/plot/PlotControls.svelte +109 -27
  171. package/dist/plot/PlotControls.svelte.d.ts +1 -1
  172. package/dist/plot/PlotLegend.svelte +1 -1
  173. package/dist/plot/PortalSelect.svelte +2 -1
  174. package/dist/plot/ReferenceLine.svelte +2 -1
  175. package/dist/plot/ReferenceLine.svelte.d.ts +1 -0
  176. package/dist/plot/ReferencePlane.svelte +1 -3
  177. package/dist/plot/ScatterPlot.svelte +343 -209
  178. package/dist/plot/ScatterPlot.svelte.d.ts +3 -3
  179. package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
  180. package/dist/plot/ScatterPlot3DControls.svelte +203 -250
  181. package/dist/plot/ScatterPlot3DScene.svelte +4 -7
  182. package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
  183. package/dist/plot/ScatterPlotControls.svelte +95 -55
  184. package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
  185. package/dist/plot/ZeroLines.svelte +44 -0
  186. package/dist/plot/ZeroLines.svelte.d.ts +32 -0
  187. package/dist/plot/ZoomRect.svelte +21 -0
  188. package/dist/plot/ZoomRect.svelte.d.ts +8 -0
  189. package/dist/plot/axis-utils.d.ts +1 -1
  190. package/dist/plot/data-cleaning.js +1 -5
  191. package/dist/plot/index.d.ts +6 -2
  192. package/dist/plot/index.js +6 -2
  193. package/dist/plot/interactions.d.ts +8 -10
  194. package/dist/plot/interactions.js +10 -19
  195. package/dist/plot/layout.d.ts +7 -1
  196. package/dist/plot/layout.js +12 -4
  197. package/dist/plot/reference-line.d.ts +4 -21
  198. package/dist/plot/reference-line.js +7 -81
  199. package/dist/plot/types.d.ts +42 -17
  200. package/dist/plot/types.js +10 -0
  201. package/dist/plot/utils/label-placement.js +14 -11
  202. package/dist/plot/utils.d.ts +1 -0
  203. package/dist/plot/utils.js +14 -0
  204. package/dist/rdf/RdfPlot.svelte +55 -66
  205. package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
  206. package/dist/rdf/index.d.ts +1 -1
  207. package/dist/rdf/index.js +1 -1
  208. package/dist/settings.d.ts +5 -0
  209. package/dist/settings.js +37 -3
  210. package/dist/spectral/Bands.svelte +515 -143
  211. package/dist/spectral/Bands.svelte.d.ts +22 -2
  212. package/dist/spectral/helpers.d.ts +23 -1
  213. package/dist/spectral/helpers.js +65 -9
  214. package/dist/spectral/types.d.ts +2 -0
  215. package/dist/structure/AtomLegend.svelte +31 -10
  216. package/dist/structure/AtomLegend.svelte.d.ts +1 -1
  217. package/dist/structure/CellSelect.svelte +92 -22
  218. package/dist/structure/Lattice.svelte +2 -0
  219. package/dist/structure/Structure.svelte +716 -173
  220. package/dist/structure/Structure.svelte.d.ts +7 -2
  221. package/dist/structure/StructureControls.svelte +26 -14
  222. package/dist/structure/StructureControls.svelte.d.ts +5 -1
  223. package/dist/structure/StructureInfoPane.svelte +7 -1
  224. package/dist/structure/StructureScene.svelte +386 -95
  225. package/dist/structure/StructureScene.svelte.d.ts +15 -4
  226. package/dist/structure/atom-properties.d.ts +6 -2
  227. package/dist/structure/atom-properties.js +38 -25
  228. package/dist/structure/export.js +10 -7
  229. package/dist/structure/ferrox-wasm-types.d.ts +3 -2
  230. package/dist/structure/ferrox-wasm-types.js +0 -3
  231. package/dist/structure/ferrox-wasm.d.ts +3 -2
  232. package/dist/structure/ferrox-wasm.js +1 -2
  233. package/dist/structure/index.d.ts +7 -0
  234. package/dist/structure/index.js +22 -0
  235. package/dist/structure/parse.js +19 -16
  236. package/dist/structure/partial-occupancy.d.ts +25 -0
  237. package/dist/structure/partial-occupancy.js +102 -0
  238. package/dist/structure/validation.js +6 -3
  239. package/dist/symmetry/SymmetryStats.svelte +18 -4
  240. package/dist/symmetry/WyckoffTable.svelte +18 -10
  241. package/dist/symmetry/index.d.ts +7 -4
  242. package/dist/symmetry/index.js +83 -18
  243. package/dist/table/HeatmapTable.svelte +468 -69
  244. package/dist/table/HeatmapTable.svelte.d.ts +13 -1
  245. package/dist/table/ToggleMenu.svelte +291 -44
  246. package/dist/table/ToggleMenu.svelte.d.ts +4 -1
  247. package/dist/table/index.d.ts +3 -0
  248. package/dist/tooltip/index.d.ts +1 -1
  249. package/dist/tooltip/index.js +1 -0
  250. package/dist/trajectory/Trajectory.svelte +147 -145
  251. package/dist/trajectory/TrajectoryExportPane.svelte +13 -9
  252. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +1 -1
  253. package/dist/trajectory/constants.d.ts +6 -0
  254. package/dist/trajectory/constants.js +7 -0
  255. package/dist/trajectory/extract.js +3 -5
  256. package/dist/trajectory/format-detect.d.ts +9 -0
  257. package/dist/trajectory/format-detect.js +76 -0
  258. package/dist/trajectory/frame-reader.d.ts +17 -0
  259. package/dist/trajectory/frame-reader.js +339 -0
  260. package/dist/trajectory/helpers.d.ts +15 -0
  261. package/dist/trajectory/helpers.js +187 -0
  262. package/dist/trajectory/index.d.ts +1 -0
  263. package/dist/trajectory/index.js +11 -4
  264. package/dist/trajectory/parse/ase.d.ts +2 -0
  265. package/dist/trajectory/parse/ase.js +76 -0
  266. package/dist/trajectory/parse/hdf5.d.ts +2 -0
  267. package/dist/trajectory/parse/hdf5.js +121 -0
  268. package/dist/trajectory/parse/index.d.ts +12 -0
  269. package/dist/trajectory/parse/index.js +304 -0
  270. package/dist/trajectory/parse/lammps.d.ts +5 -0
  271. package/dist/trajectory/parse/lammps.js +169 -0
  272. package/dist/trajectory/parse/vasp.d.ts +2 -0
  273. package/dist/trajectory/parse/vasp.js +65 -0
  274. package/dist/trajectory/parse/xyz.d.ts +2 -0
  275. package/dist/trajectory/parse/xyz.js +109 -0
  276. package/dist/trajectory/types.d.ts +11 -0
  277. package/dist/trajectory/types.js +1 -0
  278. package/dist/utils.d.ts +2 -0
  279. package/dist/utils.js +4 -0
  280. package/dist/xrd/XrdPlot.svelte +6 -4
  281. package/dist/xrd/calc-xrd.js +0 -1
  282. package/package.json +33 -23
  283. package/readme.md +4 -4
  284. package/dist/trajectory/parse.d.ts +0 -42
  285. package/dist/trajectory/parse.js +0 -1267
  286. /package/dist/element/{data.json.d.ts → data.json.gz.d.ts} +0 -0
@@ -5,12 +5,12 @@ import { highlight_matches, tooltip } from 'svelte-multiselect/attachments';
5
5
  import { SvelteSet } from 'svelte/reactivity';
6
6
  import JsonNode from './JsonNode.svelte';
7
7
  import { JSON_TREE_CONTEXT_KEY } from './types';
8
- import { collect_all_paths, find_matching_paths, get_ancestor_paths, parse_path, serialize_for_copy, } from './utils';
8
+ import { build_ghost_map, collect_all_paths, compute_diff, find_matching_paths, format_preview, get_ancestor_paths, parse_path, serialize_for_copy, } from './utils';
9
9
  // Constant set for arrow key detection (avoid allocating on every keydown)
10
10
  const ARROW_KEYS = new Set([`ArrowDown`, `ArrowUp`, `ArrowLeft`, `ArrowRight`]);
11
11
  // Shared empty set for when there's no search query (avoid allocation on every derivation)
12
12
  const EMPTY_MATCHES = new SvelteSet();
13
- let { value, root_label, default_fold_level = 2, auto_fold_arrays = 10, auto_fold_objects = 20, collapsed_paths = $bindable(new SvelteSet()), show_header = true, show_data_types = $bindable(false), show_array_indices = $bindable(true), sort_keys = false, max_string_length = 200, highlight_changes = true, onselect, oncopy, download_filename, ...rest } = $props();
13
+ let { value, root_label, default_fold_level = 2, auto_fold_arrays = 10, auto_fold_objects = 20, collapsed_paths = $bindable(new SvelteSet()), show_header = true, show_data_types = $bindable(false), show_array_indices = $bindable(true), sort_keys = false, max_string_length = 200, highlight_changes = true, onselect, oncopy, download_filename, compare_value, editable = false, onchange, ...rest } = $props();
14
14
  // Internal state
15
15
  let search_query = $state(``);
16
16
  let search_input_value = $state(``);
@@ -29,11 +29,23 @@ let current_match_index = $state(-1);
29
29
  let content_element = $state();
30
30
  // Reference to the search input for focus management
31
31
  let search_input_element = $state();
32
- // Clear force_expanded when value changes (stale paths from old data)
32
+ // Context menu state (null when closed)
33
+ let context_menu_state = $state(null);
34
+ // Pinned paths for quick reference
35
+ let pinned_paths = $state(new SvelteSet());
36
+ // Selection state for bulk operations
37
+ let selected_paths = $state(new SvelteSet());
38
+ let last_selected_path = $state(null);
39
+ // Copy feedback positioning (null = use default corner position)
40
+ let copy_feedback_pos = $state(null);
41
+ // Clear stale path-based state when value changes
33
42
  let prev_value_ref;
34
43
  $effect.pre(() => {
35
44
  if (prev_value_ref !== undefined && value !== prev_value_ref) {
36
45
  force_expanded.clear();
46
+ pinned_paths = new SvelteSet();
47
+ selected_paths = new SvelteSet();
48
+ last_selected_path = null;
37
49
  }
38
50
  prev_value_ref = value;
39
51
  });
@@ -50,11 +62,13 @@ function handle_search_input(event) {
50
62
  queueMicrotask(() => expand_to_matches());
51
63
  }, 150);
52
64
  }
65
+ // Root path used everywhere - avoids repeating `root_label ?? ''`
66
+ let root_path = $derived(root_label ?? ``);
53
67
  // Compute search matches
54
68
  let search_matches = $derived.by(() => {
55
69
  if (!search_query)
56
70
  return EMPTY_MATCHES;
57
- return new SvelteSet(find_matching_paths(value, search_query, root_label ?? ``));
71
+ return new SvelteSet(find_matching_paths(value, search_query, root_path));
58
72
  });
59
73
  // Sorted matches list for navigation (maintains DOM order via registered_paths_list)
60
74
  let sorted_matches = $derived.by(() => {
@@ -154,21 +168,12 @@ function toggle_collapse(path, is_currently_collapsed) {
154
168
  }
155
169
  // Toggle collapse recursively for all descendants
156
170
  function toggle_collapse_recursive(path, collapse) {
157
- const all_paths = collect_all_paths(value, root_label ?? ``);
158
- // Find all paths that start with this path (descendants)
159
- // Empty path means root without label - all paths are descendants
160
- let descendants = path === `` ? all_paths : all_paths.filter((p) => p === path || p.startsWith(path + `.`) || p.startsWith(path + `[`));
161
- // Always include the current path itself (collect_all_paths excludes empty root path)
162
- if (!descendants.includes(path))
163
- descendants = [path, ...descendants];
164
- if (collapse) {
165
- for (const desc of descendants) {
171
+ for (const desc of get_descendants(path)) {
172
+ if (collapse) {
166
173
  force_expanded.delete(desc);
167
174
  collapsed_paths.add(desc);
168
175
  }
169
- }
170
- else {
171
- for (const desc of descendants) {
176
+ else {
172
177
  collapsed_paths.delete(desc);
173
178
  force_expanded.add(desc);
174
179
  }
@@ -176,16 +181,25 @@ function toggle_collapse_recursive(path, collapse) {
176
181
  collapsed_paths = new SvelteSet(collapsed_paths);
177
182
  force_expanded = new SvelteSet(force_expanded);
178
183
  }
184
+ // Get all descendant paths of a given path (including the path itself)
185
+ function get_descendants(target_path) {
186
+ const all_paths = collect_all_paths(value, root_path);
187
+ const descendants = target_path === `` ? all_paths : all_paths.filter((p) => p === target_path || p.startsWith(target_path + `.`) ||
188
+ p.startsWith(target_path + `[`));
189
+ return descendants.includes(target_path)
190
+ ? descendants
191
+ : [target_path, ...descendants];
192
+ }
179
193
  function expand_all() {
180
- force_expanded = new SvelteSet(collect_all_paths(value, root_label ?? ``));
194
+ force_expanded = new SvelteSet(collect_all_paths(value, root_path));
181
195
  collapsed_paths = new SvelteSet();
182
196
  }
183
197
  function collapse_all() {
184
198
  force_expanded = new SvelteSet();
185
- collapsed_paths = new SvelteSet(collect_all_paths(value, root_label ?? ``));
199
+ collapsed_paths = new SvelteSet(collect_all_paths(value, root_path));
186
200
  }
187
201
  function collapse_to_level(level) {
188
- const all_paths = collect_all_paths(value, root_label ?? ``);
202
+ const all_paths = collect_all_paths(value, root_path);
189
203
  const new_collapsed = new SvelteSet();
190
204
  const new_expanded = new SvelteSet();
191
205
  for (const path of all_paths) {
@@ -203,8 +217,9 @@ function set_focused(path) {
203
217
  if (path)
204
218
  onselect?.(path, get_value_at_path(path));
205
219
  }
206
- // Shared clipboard copy with feedback
207
- async function copy_to_clipboard(path, text) {
220
+ // Shared clipboard copy with feedback (event used for inline positioning)
221
+ async function copy_to_clipboard(path, text, event) {
222
+ copy_feedback_pos = event ? { x: event.clientX, y: event.clientY } : null;
208
223
  try {
209
224
  await navigator.clipboard.writeText(text);
210
225
  copy_feedback_error = false;
@@ -259,6 +274,91 @@ function get_value_at_path(path) {
259
274
  }
260
275
  return current;
261
276
  }
277
+ // Compute diff map when compare_value is provided
278
+ let diff_map = $derived.by(() => {
279
+ if (compare_value === undefined)
280
+ return null;
281
+ return compute_diff(compare_value, value, root_path);
282
+ });
283
+ // Pre-compute ghost children map for O(1) lookup per node
284
+ let ghost_map = $derived(diff_map ? build_ghost_map(diff_map) : new Map());
285
+ // Collapse all descendants but keep the given node expanded (single batch)
286
+ function collapse_children_only(target_path) {
287
+ for (const desc of get_descendants(target_path)) {
288
+ if (desc === target_path) {
289
+ collapsed_paths.delete(desc);
290
+ force_expanded.add(desc);
291
+ }
292
+ else {
293
+ force_expanded.delete(desc);
294
+ collapsed_paths.add(desc);
295
+ }
296
+ }
297
+ collapsed_paths = new SvelteSet(collapsed_paths);
298
+ force_expanded = new SvelteSet(force_expanded);
299
+ }
300
+ // Context menu handlers
301
+ function show_context_menu(event, ctx_path, ctx_value, expandable, is_collapsed) {
302
+ event.preventDefault();
303
+ context_menu_state = {
304
+ x: event.clientX,
305
+ y: event.clientY,
306
+ path: ctx_path,
307
+ value: ctx_value,
308
+ expandable,
309
+ is_collapsed,
310
+ };
311
+ }
312
+ function close_context_menu() {
313
+ context_menu_state = null;
314
+ }
315
+ // Run action with current context menu state, then close
316
+ function ctx_menu_action(action) {
317
+ if (context_menu_state)
318
+ action(context_menu_state);
319
+ close_context_menu();
320
+ }
321
+ // Pin/unpin a path for quick reference
322
+ function toggle_pin(pin_path) {
323
+ if (pinned_paths.has(pin_path))
324
+ pinned_paths.delete(pin_path);
325
+ else
326
+ pinned_paths.add(pin_path);
327
+ pinned_paths = new SvelteSet(pinned_paths);
328
+ }
329
+ // Toggle selection of a path (with shift for range select)
330
+ function toggle_select(select_path, shift) {
331
+ if (shift && last_selected_path) {
332
+ const start_idx = registered_paths_list.indexOf(last_selected_path);
333
+ const end_idx = registered_paths_list.indexOf(select_path);
334
+ if (start_idx !== -1 && end_idx !== -1) {
335
+ const [from, to] = start_idx < end_idx
336
+ ? [start_idx, end_idx]
337
+ : [end_idx, start_idx];
338
+ for (let idx = from; idx <= to; idx++) {
339
+ selected_paths.add(registered_paths_list[idx]);
340
+ }
341
+ selected_paths = new SvelteSet(selected_paths);
342
+ }
343
+ }
344
+ else {
345
+ if (selected_paths.has(select_path))
346
+ selected_paths.delete(select_path);
347
+ else
348
+ selected_paths.add(select_path);
349
+ selected_paths = new SvelteSet(selected_paths);
350
+ }
351
+ last_selected_path = select_path;
352
+ }
353
+ // Copy all selected node values to clipboard
354
+ function copy_selected() {
355
+ if (selected_paths.size === 0)
356
+ return;
357
+ const text = [...selected_paths]
358
+ .map((sel_path) => serialize_for_copy(get_value_at_path(sel_path)))
359
+ .join(`\n`);
360
+ copy_to_clipboard(`[selection]`, text);
361
+ }
262
362
  // Create context
263
363
  const context = {
264
364
  get settings() {
@@ -271,6 +371,7 @@ const context = {
271
371
  sort_keys,
272
372
  max_string_length,
273
373
  highlight_changes,
374
+ editable,
274
375
  };
275
376
  },
276
377
  get collapsed() {
@@ -298,14 +399,53 @@ const context = {
298
399
  collapse_all,
299
400
  collapse_to_level,
300
401
  set_focused,
301
- copy_value: (path, val) => copy_to_clipboard(path, serialize_for_copy(val)),
302
- copy_path: (path) => copy_to_clipboard(path, path),
402
+ copy_value: (val_path, val, event) => copy_to_clipboard(val_path, serialize_for_copy(val), event),
403
+ copy_path: (cp_path, event) => copy_to_clipboard(cp_path, cp_path, event),
303
404
  register_path,
304
405
  unregister_path,
406
+ show_context_menu,
407
+ get pinned_paths() {
408
+ return pinned_paths;
409
+ },
410
+ toggle_pin,
411
+ get selected_paths() {
412
+ return selected_paths;
413
+ },
414
+ toggle_select,
415
+ copy_selected,
416
+ get diff_map() {
417
+ return diff_map;
418
+ },
419
+ get ghost_map() {
420
+ return ghost_map;
421
+ },
422
+ collapse_children_only,
423
+ get onchange() {
424
+ return onchange;
425
+ },
305
426
  };
306
427
  setContext(JSON_TREE_CONTEXT_KEY, context);
307
428
  // Keyboard navigation at tree level
308
429
  function handle_tree_keydown(event) {
430
+ // Escape closes context menu first, then clears selection
431
+ if (event.key === `Escape`) {
432
+ if (context_menu_state) {
433
+ close_context_menu();
434
+ return;
435
+ }
436
+ if (selected_paths.size > 0) {
437
+ selected_paths = new SvelteSet();
438
+ return;
439
+ }
440
+ }
441
+ // Ctrl/Cmd+C with selection copies all selected
442
+ if ((event.key === `c` || event.key === `C`) &&
443
+ (event.ctrlKey || event.metaKey) &&
444
+ selected_paths.size > 0) {
445
+ event.preventDefault();
446
+ copy_selected();
447
+ return;
448
+ }
309
449
  if (!focused_path) {
310
450
  // Focus first node on any arrow key
311
451
  if (ARROW_KEYS.has(event.key)) {
@@ -526,6 +666,51 @@ function handle_search_keydown(event) {
526
666
  </div>
527
667
  {/if}
528
668
 
669
+ {#if pinned_paths.size > 0}
670
+ <div class="pinned-panel">
671
+ <div class="pinned-header">
672
+ <span>Pinned ({pinned_paths.size})</span>
673
+ <button
674
+ type="button"
675
+ class="pinned-clear-btn"
676
+ onclick={() => {
677
+ pinned_paths = new SvelteSet()
678
+ }}
679
+ >
680
+ Clear
681
+ </button>
682
+ </div>
683
+ {#each [...pinned_paths] as pinned_path (pinned_path)}
684
+ <div class="pinned-item">
685
+ <button
686
+ type="button"
687
+ class="pinned-path"
688
+ onclick={() =>
689
+ copy_to_clipboard(
690
+ pinned_path,
691
+ serialize_for_copy(get_value_at_path(pinned_path)),
692
+ )}
693
+ title="Click to copy value"
694
+ {@attach tooltip()}
695
+ >
696
+ {pinned_path}
697
+ </button>
698
+ <span class="pinned-value">{
699
+ format_preview(get_value_at_path(pinned_path))
700
+ }</span>
701
+ <button
702
+ type="button"
703
+ class="unpin-btn"
704
+ onclick={() => toggle_pin(pinned_path)}
705
+ title="Unpin"
706
+ >
707
+
708
+ </button>
709
+ </div>
710
+ {/each}
711
+ </div>
712
+ {/if}
713
+
529
714
  <div
530
715
  bind:this={content_element}
531
716
  class="json-tree-content"
@@ -534,13 +719,85 @@ function handle_search_keydown(event) {
534
719
  css_class: `json-tree-search-match`,
535
720
  })}
536
721
  >
537
- {#if copy_feedback_path !== null}
538
- <div class="copy-feedback" class:error={copy_feedback_error}>
539
- {copy_feedback_error ? `Copy failed` : `Copied!`}
540
- </div>
541
- {/if}
542
- <JsonNode node_key={root_label ?? null} {value} path={root_label ?? ``} depth={0} />
722
+ <JsonNode node_key={root_label ?? null} {value} path={root_path} depth={0} />
543
723
  </div>
724
+
725
+ {#if copy_feedback_path !== null}
726
+ <div
727
+ class="copy-feedback"
728
+ class:error={copy_feedback_error}
729
+ style={copy_feedback_pos
730
+ ? `left: ${copy_feedback_pos.x}px; top: ${copy_feedback_pos.y - 24}px`
731
+ : `right: 8px; top: 8px`}
732
+ >
733
+ {copy_feedback_error ? `Copy failed` : `Copied!`}
734
+ </div>
735
+ {/if}
736
+
737
+ {#if context_menu_state}
738
+ <button
739
+ type="button"
740
+ class="context-menu-backdrop"
741
+ onclick={close_context_menu}
742
+ oncontextmenu={(ev) => {
743
+ ev.preventDefault()
744
+ close_context_menu()
745
+ }}
746
+ aria-label="Close context menu"
747
+ tabindex="-1"
748
+ >
749
+ </button>
750
+ <menu
751
+ class="context-menu"
752
+ style:left="{Math.min(context_menu_state.x, window.innerWidth - 180)}px"
753
+ style:top="{Math.min(context_menu_state.y, window.innerHeight - 200)}px"
754
+ >
755
+ <li>
756
+ <button
757
+ type="button"
758
+ onclick={() =>
759
+ ctx_menu_action((st) =>
760
+ copy_to_clipboard(st.path, serialize_for_copy(st.value))
761
+ )}
762
+ >
763
+ <Icon icon="Copy" style="width: 12px; height: 12px" /> Copy value
764
+ </button>
765
+ </li>
766
+ <li>
767
+ <button
768
+ type="button"
769
+ onclick={() => ctx_menu_action((st) => copy_to_clipboard(st.path, st.path))}
770
+ >
771
+ Copy path
772
+ </button>
773
+ </li>
774
+ <li class="separator"></li>
775
+ {#if context_menu_state.expandable}
776
+ <li>
777
+ <button
778
+ type="button"
779
+ onclick={() =>
780
+ ctx_menu_action((st) =>
781
+ st.is_collapsed
782
+ ? toggle_collapse_recursive(st.path, false)
783
+ : collapse_children_only(st.path)
784
+ )}
785
+ >
786
+ {context_menu_state.is_collapsed ? `Expand` : `Collapse`} all children
787
+ </button>
788
+ </li>
789
+ <li class="separator"></li>
790
+ {/if}
791
+ <li>
792
+ <button
793
+ type="button"
794
+ onclick={() => ctx_menu_action((st) => toggle_pin(st.path))}
795
+ >
796
+ {pinned_paths.has(context_menu_state.path) ? `Unpin` : `Pin`} this path
797
+ </button>
798
+ </li>
799
+ </menu>
800
+ {/if}
544
801
  </div>
545
802
 
546
803
  <style>
@@ -579,12 +836,14 @@ function handle_search_keydown(event) {
579
836
  border-radius: var(--jt-border-radius, 4px);
580
837
  overflow: hidden;
581
838
  }
839
+ /* --jt-header-bg, --jt-header-border, --jt-btn-bg, --jt-btn-hover-bg, --jt-btn-active-bg
840
+ intentionally removed in favor of transparent/opacity-based button styling.
841
+ Use --jt-hover-bg to customize the button hover background. */
582
842
  .json-tree-header {
583
843
  display: flex;
584
844
  align-items: center;
585
845
  gap: 8px;
586
846
  padding: 6px 8px;
587
- background: var(--jt-header-bg);
588
847
  flex-wrap: wrap;
589
848
  }
590
849
  .search-wrapper {
@@ -645,26 +904,22 @@ function handle_search_keydown(event) {
645
904
  min-width: 24px;
646
905
  height: 24px;
647
906
  padding: 2px 6px;
648
- border: 1px solid var(--jt-header-border);
649
- background: var(--jt-btn-bg, light-dark(white, rgba(255, 255, 255, 0.1)));
907
+ border: none;
908
+ background: transparent;
650
909
  border-radius: 3px;
651
910
  cursor: pointer;
652
911
  font-size: 11px;
653
912
  font-weight: 500;
654
913
  color: inherit;
655
- transition: background 0.15s;
914
+ opacity: 0.6;
915
+ transition: opacity 0.15s, background 0.15s;
656
916
  }
657
917
  .controls button:hover {
658
- background: var(
659
- --jt-btn-hover-bg,
660
- light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.15))
661
- );
918
+ opacity: 1;
919
+ background: var(--jt-hover-bg);
662
920
  }
663
921
  .controls button.active {
664
- background: var(
665
- --jt-btn-active-bg,
666
- light-dark(rgba(0, 0, 0, 0.12), rgba(255, 255, 255, 0.2))
667
- );
922
+ opacity: 1;
668
923
  }
669
924
  .match-nav {
670
925
  display: flex;
@@ -678,17 +933,16 @@ function handle_search_keydown(event) {
678
933
  width: 20px;
679
934
  height: 20px;
680
935
  padding: 0;
681
- border: 1px solid var(--jt-header-border);
682
- background: var(--jt-btn-bg, light-dark(white, rgba(255, 255, 255, 0.1)));
936
+ border: none;
937
+ background: transparent;
683
938
  border-radius: 3px;
684
939
  cursor: pointer;
685
940
  color: inherit;
941
+ opacity: 0.6;
686
942
  }
687
943
  .nav-btn:hover {
688
- background: var(
689
- --jt-btn-hover-bg,
690
- light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.15))
691
- );
944
+ opacity: 1;
945
+ background: var(--jt-hover-bg);
692
946
  }
693
947
  .match-count {
694
948
  font-size: 11px;
@@ -728,9 +982,7 @@ function handle_search_keydown(event) {
728
982
  max-height: var(--jt-max-height, none);
729
983
  }
730
984
  .copy-feedback {
731
- position: absolute;
732
- top: 8px;
733
- right: 8px;
985
+ position: fixed;
734
986
  background: var(--success-color, #10b981);
735
987
  color: white;
736
988
  padding: 4px 8px;
@@ -738,7 +990,8 @@ function handle_search_keydown(event) {
738
990
  font-size: 11px;
739
991
  animation: fade-in-out 1s ease-out forwards;
740
992
  pointer-events: none;
741
- z-index: 10;
993
+ z-index: 1002;
994
+ white-space: nowrap;
742
995
  }
743
996
  .copy-feedback.error {
744
997
  background: var(--error-color, #ef4444);
@@ -759,4 +1012,125 @@ function handle_search_keydown(event) {
759
1012
  opacity: 0;
760
1013
  }
761
1014
  }
1015
+ .context-menu-backdrop {
1016
+ position: fixed;
1017
+ inset: 0;
1018
+ z-index: 1000;
1019
+ background: none;
1020
+ border: none;
1021
+ padding: 0;
1022
+ margin: 0;
1023
+ cursor: default;
1024
+ }
1025
+ .context-menu {
1026
+ position: fixed;
1027
+ z-index: 1001;
1028
+ background: var(--jt-ctx-bg, light-dark(white, #2d2d2d));
1029
+ border: 1px solid
1030
+ var(--jt-ctx-border, light-dark(rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.15)));
1031
+ border-radius: 6px;
1032
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
1033
+ padding: 4px 0;
1034
+ min-width: 160px;
1035
+ font-size: 12px;
1036
+ list-style: none;
1037
+ margin: 0;
1038
+ }
1039
+ .context-menu button {
1040
+ display: flex;
1041
+ align-items: center;
1042
+ gap: 8px;
1043
+ width: 100%;
1044
+ padding: 6px 12px;
1045
+ border: none;
1046
+ background: none;
1047
+ color: inherit;
1048
+ cursor: pointer;
1049
+ text-align: left;
1050
+ font: inherit;
1051
+ }
1052
+ .context-menu button:hover {
1053
+ background: var(
1054
+ --jt-ctx-hover,
1055
+ light-dark(rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.1))
1056
+ );
1057
+ }
1058
+ .context-menu .separator {
1059
+ height: 1px;
1060
+ margin: 4px 8px;
1061
+ background: var(
1062
+ --jt-ctx-border,
1063
+ light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1))
1064
+ );
1065
+ }
1066
+ .pinned-panel :where(button) {
1067
+ background: none;
1068
+ border: none;
1069
+ cursor: pointer;
1070
+ padding: 0;
1071
+ font: inherit;
1072
+ color: inherit;
1073
+ }
1074
+ .pinned-panel {
1075
+ border-bottom: 1px solid var(--jt-header-border);
1076
+ padding: 4px 8px;
1077
+ background: var(--jt-header-bg);
1078
+ font-size: 11px;
1079
+ max-height: 120px;
1080
+ overflow-y: auto;
1081
+ }
1082
+ .pinned-header {
1083
+ display: flex;
1084
+ align-items: center;
1085
+ justify-content: space-between;
1086
+ padding: 2px 0;
1087
+ font-weight: 500;
1088
+ opacity: 0.7;
1089
+ }
1090
+ .pinned-clear-btn {
1091
+ font-size: 10px;
1092
+ opacity: 0.6;
1093
+ padding: 1px 4px;
1094
+ }
1095
+ .pinned-clear-btn:hover {
1096
+ opacity: 1;
1097
+ text-decoration: underline;
1098
+ }
1099
+ .pinned-item {
1100
+ display: flex;
1101
+ align-items: center;
1102
+ gap: 4px;
1103
+ padding: 2px 4px;
1104
+ border-radius: 2px;
1105
+ }
1106
+ .pinned-item:hover {
1107
+ background: var(--jt-hover-bg);
1108
+ }
1109
+ .pinned-path {
1110
+ color: var(--jt-key, light-dark(#001080, #9cdcfe));
1111
+ font-family: var(--jt-font-family);
1112
+ white-space: nowrap;
1113
+ overflow: hidden;
1114
+ text-overflow: ellipsis;
1115
+ max-width: 200px;
1116
+ }
1117
+ .pinned-path:hover {
1118
+ text-decoration: underline;
1119
+ }
1120
+ .pinned-value {
1121
+ color: var(--jt-preview, light-dark(#808080, #808080));
1122
+ font-style: italic;
1123
+ white-space: nowrap;
1124
+ overflow: hidden;
1125
+ text-overflow: ellipsis;
1126
+ flex: 1;
1127
+ }
1128
+ .unpin-btn {
1129
+ padding: 0 2px;
1130
+ opacity: 0.5;
1131
+ font-size: 10px;
1132
+ }
1133
+ .unpin-btn:hover {
1134
+ opacity: 1;
1135
+ }
762
1136
  </style>
@@ -1,6 +1,6 @@
1
1
  import type { HTMLAttributes } from 'svelte/elements';
2
2
  import type { JsonTreeProps } from './types';
3
- type $$ComponentProps = JsonTreeProps & Omit<HTMLAttributes<HTMLDivElement>, `onselect`>;
3
+ type $$ComponentProps = JsonTreeProps & Omit<HTMLAttributes<HTMLDivElement>, `onselect` | `onchange`>;
4
4
  declare const JsonTree: import("svelte").Component<$$ComponentProps, {}, "collapsed_paths" | "show_data_types" | "show_array_indices">;
5
5
  type JsonTree = ReturnType<typeof JsonTree>;
6
6
  export default JsonTree;