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
@@ -1,6 +1,6 @@
1
- <script lang="ts">import { getContext } from 'svelte';
1
+ <script lang="ts">import { getContext, tick } from 'svelte';
2
2
  import { JSON_TREE_CONTEXT_KEY } from './types';
3
- import { format_preview, values_equal } from './utils';
3
+ import { format_preview, is_css_color, is_url, parse_edited_value, values_equal, } from './utils';
4
4
  let { value, value_type, path, } = $props();
5
5
  const ctx = getContext(JSON_TREE_CONTEXT_KEY);
6
6
  // Track if value just changed for animation
@@ -30,11 +30,31 @@ $effect(() => {
30
30
  clearTimeout(change_timeout);
31
31
  };
32
32
  });
33
- // Handle click to copy
33
+ // Trimmed string for URL/color detection (avoids using raw whitespace in href/style)
34
+ let trimmed_str = $derived(value_type === `string` ? value.trim() : ``);
35
+ // Auto-detect URLs in string values
36
+ let url_detected = $derived(value_type === `string` && is_url(trimmed_str));
37
+ // Auto-detect CSS colors in string values
38
+ let color_detected = $derived(value_type === `string` && is_css_color(trimmed_str) ? trimmed_str : null);
39
+ // Handle click to copy (delayed to avoid firing on double-click-to-edit)
40
+ let click_timer;
41
+ $effect(() => () => {
42
+ if (click_timer)
43
+ clearTimeout(click_timer);
44
+ });
34
45
  async function handle_click(event) {
35
46
  event.stopPropagation();
36
- if (ctx) {
37
- await ctx.copy_value(path, value);
47
+ if (!ctx)
48
+ return;
49
+ // When editable, delay copy so double-click can cancel it
50
+ if (ctx.settings.editable && ctx.onchange) {
51
+ if (click_timer)
52
+ clearTimeout(click_timer);
53
+ const copy_pos = { clientX: event.clientX, clientY: event.clientY };
54
+ click_timer = setTimeout(() => ctx.copy_value(path, value, copy_pos), 250);
55
+ }
56
+ else {
57
+ await ctx.copy_value(path, value, event);
38
58
  }
39
59
  }
40
60
  // Format display value - strings use custom truncation, others use format_preview
@@ -56,102 +76,179 @@ function toggle_expand(event) {
56
76
  event.stopPropagation();
57
77
  is_expanded = !is_expanded;
58
78
  }
79
+ // === Inline Editing ===
80
+ let editing = $state(false);
81
+ let edit_text = $state(``);
82
+ let edit_input = $state(null);
83
+ function start_edit(event) {
84
+ if (!ctx?.settings.editable || !ctx.onchange)
85
+ return;
86
+ event.stopPropagation();
87
+ // Cancel pending click-to-copy
88
+ if (click_timer)
89
+ clearTimeout(click_timer);
90
+ // Pre-fill with raw value (strings without quotes)
91
+ edit_text = value_type === `string` ? value : String(value);
92
+ editing = true;
93
+ tick().then(() => edit_input?.select());
94
+ }
95
+ function commit_edit() {
96
+ if (!editing)
97
+ return;
98
+ editing = false;
99
+ const new_value = parse_edited_value(edit_text);
100
+ if (!values_equal(new_value, value)) {
101
+ ctx?.onchange?.(path, new_value, value);
102
+ }
103
+ }
104
+ function handle_edit_keydown(event) {
105
+ if (event.key === `Enter`) {
106
+ event.preventDefault();
107
+ commit_edit();
108
+ }
109
+ else if (event.key === `Escape`) {
110
+ event.preventDefault();
111
+ editing = false;
112
+ }
113
+ }
59
114
  </script>
60
115
 
61
- <span
62
- class="json-value {value_type}"
63
- class:changed={just_changed}
64
- onclick={handle_click}
65
- onkeydown={(event) => {
66
- if (event.key === `Enter` || event.key === ` `) {
67
- event.preventDefault()
68
- ctx?.copy_value(path, value)
69
- }
70
- }}
71
- role="button"
72
- tabindex="-1"
73
- title="Click to copy"
74
- >
75
- {display_value}
76
- {#if is_truncated}
77
- <button
78
- type="button"
79
- class="expand-btn"
80
- onclick={toggle_expand}
81
- title="Show full string"
82
- >
83
- ...
84
- </button>
85
- {:else if is_long_string && is_expanded}
86
- <button
87
- type="button"
88
- class="expand-btn"
89
- onclick={toggle_expand}
90
- title="Collapse string"
91
- >
92
-
93
- </button>
94
- {/if}
95
- {#if ctx?.settings.show_data_types && value_type !== `null` &&
116
+ {#if editing}
117
+ <!-- svelte-ignore a11y_autofocus -->
118
+ <input
119
+ bind:this={edit_input}
120
+ type="text"
121
+ class="edit-input {value_type}"
122
+ bind:value={edit_text}
123
+ onkeydown={handle_edit_keydown}
124
+ onblur={commit_edit}
125
+ style:width="{Math.max(edit_text.length + 2, 6)}ch"
126
+ autofocus
127
+ />
128
+ {:else}
129
+ <span
130
+ class="json-value {value_type}"
131
+ class:changed={just_changed}
132
+ class:editable={ctx?.settings.editable}
133
+ onclick={handle_click}
134
+ ondblclick={start_edit}
135
+ oncontextmenu={(event) => {
136
+ ctx?.show_context_menu(event, path, value, false, false)
137
+ }}
138
+ onkeydown={(event) => {
139
+ if (event.key === `Enter` || event.key === ` `) {
140
+ event.preventDefault()
141
+ ctx?.copy_value(path, value)
142
+ }
143
+ }}
144
+ role="button"
145
+ tabindex="-1"
146
+ title={ctx?.settings.editable ? `Double-click to edit` : undefined}
147
+ >
148
+ {#if color_detected}
149
+ <span class="color-swatch" style:background={color_detected}></span>
150
+ {/if}
151
+ {#if url_detected}
152
+ <a
153
+ href={encodeURI(trimmed_str)}
154
+ class="url-link"
155
+ target="_blank"
156
+ rel="noopener noreferrer"
157
+ onclick={(event) => event.stopPropagation()}
158
+ title="Open URL in new tab"
159
+ >
160
+ {display_value}
161
+ </a>
162
+ {:else}
163
+ {display_value}
164
+ {/if}
165
+ {#if is_truncated}
166
+ <button
167
+ type="button"
168
+ class="expand-btn"
169
+ onclick={toggle_expand}
170
+ title="Show full string"
171
+ >
172
+ ...
173
+ </button>
174
+ {:else if is_long_string && is_expanded}
175
+ <button
176
+ type="button"
177
+ class="expand-btn"
178
+ onclick={toggle_expand}
179
+ title="Collapse string"
180
+ >
181
+
182
+ </button>
183
+ {/if}
184
+ {#if ctx?.settings.show_data_types && value_type !== `null` &&
96
185
  value_type !== `undefined`}
97
- <span class="type-annotation">{value_type}</span>
98
- {/if}
99
- </span>
186
+ <span class="type-annotation">{value_type}</span>
187
+ {/if}
188
+ </span>
189
+ {/if}
100
190
 
101
191
  <style>
192
+ /* Type-specific colors shared between display and edit input */
193
+ :is(.json-value, .edit-input) {
194
+ &.string {
195
+ color: var(--jt-string, light-dark(#a31515, #ce9178));
196
+ }
197
+ &.number {
198
+ color: var(--jt-number, light-dark(#098658, #b5cea8));
199
+ }
200
+ &.boolean {
201
+ color: var(--jt-boolean, light-dark(#0000ff, #569cd6));
202
+ }
203
+ &.null {
204
+ color: var(--jt-null, light-dark(#808080, #808080));
205
+ }
206
+ }
102
207
  .json-value {
103
208
  cursor: pointer;
104
209
  border-radius: 2px;
105
210
  transition: background-color 0.15s, color 0.15s;
106
- }
107
- .json-value:hover {
108
- background: var(
109
- --jt-hover-bg,
110
- light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.08))
111
- );
112
- }
113
- /* Type-specific colors */
114
- .json-value.string {
115
- color: var(--jt-string, light-dark(#a31515, #ce9178));
116
- word-break: break-word;
117
- }
118
- .json-value.number {
119
- color: var(--jt-number, light-dark(#098658, #b5cea8));
120
- }
121
- .json-value.boolean {
122
- color: var(--jt-boolean, light-dark(#0000ff, #569cd6));
123
- }
124
- .json-value.null,
125
- .json-value.undefined {
126
- color: var(--jt-null, light-dark(#808080, #808080));
127
- font-style: italic;
128
- }
129
- .json-value.date {
130
- color: var(--jt-date, light-dark(#098658, #dcdcaa));
131
- }
132
- .json-value.regexp {
133
- color: var(--jt-regexp, light-dark(#811f3f, #d16969));
134
- }
135
- .json-value.symbol {
136
- color: var(--jt-symbol, light-dark(#267f99, #4ec9b0));
137
- }
138
- .json-value.bigint {
139
- color: var(--jt-bigint, light-dark(#098658, #b5cea8));
140
- }
141
- .json-value.function {
142
- color: var(--jt-function, light-dark(#795e26, #dcdcaa));
143
- font-style: italic;
144
- }
145
- .json-value.error {
146
- color: var(--jt-error, light-dark(#a31515, #f48771));
147
- }
148
- .json-value.circular {
149
- color: var(--jt-circular, light-dark(#808080, #808080));
150
- font-style: italic;
151
- }
152
- /* Change animation */
153
- .json-value.changed {
154
- animation: value-change 1s ease-out;
211
+ &:hover {
212
+ background: var(
213
+ --jt-hover-bg,
214
+ light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.08))
215
+ );
216
+ }
217
+ &.string {
218
+ word-break: break-word;
219
+ }
220
+ &:is(.null, .undefined) {
221
+ font-style: italic;
222
+ }
223
+ &.date {
224
+ color: var(--jt-date, light-dark(#098658, #dcdcaa));
225
+ }
226
+ &.regexp {
227
+ color: var(--jt-regexp, light-dark(#811f3f, #d16969));
228
+ }
229
+ &.symbol {
230
+ color: var(--jt-symbol, light-dark(#267f99, #4ec9b0));
231
+ }
232
+ &.bigint {
233
+ color: var(--jt-bigint, light-dark(#098658, #b5cea8));
234
+ }
235
+ &.function {
236
+ color: var(--jt-function, light-dark(#795e26, #dcdcaa));
237
+ font-style: italic;
238
+ }
239
+ &.error {
240
+ color: var(--jt-error, light-dark(#a31515, #f48771));
241
+ }
242
+ &.circular {
243
+ color: var(--jt-circular, light-dark(#808080, #808080));
244
+ font-style: italic;
245
+ }
246
+ &.changed {
247
+ animation: value-change 1s ease-out;
248
+ }
249
+ &.editable {
250
+ cursor: default;
251
+ }
155
252
  }
156
253
  @keyframes value-change {
157
254
  0% {
@@ -161,7 +258,6 @@ function toggle_expand(event) {
161
258
  background: transparent;
162
259
  }
163
260
  }
164
- /* Expand/collapse button for long strings */
165
261
  .expand-btn {
166
262
  display: inline;
167
263
  background: none;
@@ -171,15 +267,40 @@ function toggle_expand(event) {
171
267
  font-size: 0.85em;
172
268
  padding: 0 2px;
173
269
  margin-left: 2px;
270
+ &:hover {
271
+ text-decoration: underline;
272
+ }
174
273
  }
175
- .expand-btn:hover {
176
- text-decoration: underline;
177
- }
178
- /* Type annotation */
179
274
  .type-annotation {
180
275
  font-size: 0.7em;
181
276
  color: var(--jt-type-annotation, light-dark(#808080, #6a6a6a));
182
277
  margin-left: 4px;
183
278
  opacity: 0.7;
184
279
  }
280
+ .url-link {
281
+ color: var(--jt-url, light-dark(#0066cc, #4fc3f7));
282
+ text-decoration: none;
283
+ &:hover {
284
+ text-decoration: underline;
285
+ }
286
+ }
287
+ .edit-input {
288
+ font: inherit;
289
+ font-family: var(--jt-font-family, 'SF Mono', Monaco, 'Courier New', monospace);
290
+ padding: 0 2px;
291
+ border: 1px solid var(--jt-edit-border, light-dark(#4a90d9, #4a90d9));
292
+ border-radius: 2px;
293
+ background: var(--jt-edit-bg, light-dark(#fff, #1a1a2e));
294
+ outline: none;
295
+ min-width: 4ch;
296
+ }
297
+ .color-swatch {
298
+ display: inline-block;
299
+ width: 10px;
300
+ height: 10px;
301
+ border-radius: 2px;
302
+ border: 1px solid light-dark(rgba(0, 0, 0, 0.2), rgba(255, 255, 255, 0.3));
303
+ vertical-align: middle;
304
+ margin-right: 4px;
305
+ }
185
306
  </style>
@@ -15,6 +15,9 @@ export interface JsonTreeProps {
15
15
  onselect?: (path: string, value: unknown) => void;
16
16
  oncopy?: (path: string, value: string) => void;
17
17
  download_filename?: string;
18
+ compare_value?: unknown;
19
+ editable?: boolean;
20
+ onchange?: (path: string, new_value: unknown, old_value: unknown) => void;
18
21
  }
19
22
  export interface JsonTreeContext {
20
23
  settings: {
@@ -26,6 +29,7 @@ export interface JsonTreeContext {
26
29
  sort_keys: boolean;
27
30
  max_string_length: number;
28
31
  highlight_changes: boolean;
32
+ editable: boolean;
29
33
  };
30
34
  collapsed: Set<string>;
31
35
  force_expanded: Set<string>;
@@ -40,9 +44,30 @@ export interface JsonTreeContext {
40
44
  collapse_all: () => void;
41
45
  collapse_to_level: (level: number) => void;
42
46
  set_focused: (path: string | null) => void;
43
- copy_value: (path: string, value: unknown) => Promise<void>;
44
- copy_path: (path: string) => Promise<void>;
47
+ copy_value: (path: string, value: unknown, event?: CopyEventPosition) => Promise<void>;
48
+ copy_path: (path: string, event?: CopyEventPosition) => Promise<void>;
45
49
  register_path: (path: string) => void;
46
50
  unregister_path: (path: string) => void;
51
+ show_context_menu: (event: MouseEvent, path: string, value: unknown, expandable: boolean, is_collapsed: boolean) => void;
52
+ pinned_paths: Set<string>;
53
+ toggle_pin: (path: string) => void;
54
+ selected_paths: Set<string>;
55
+ toggle_select: (path: string, shift: boolean) => void;
56
+ copy_selected: () => void;
57
+ diff_map: Map<string, DiffEntry> | null;
58
+ ghost_map: Map<string, import('./utils').GhostEntry[]>;
59
+ collapse_children_only: (path: string) => void;
60
+ onchange?: (path: string, new_value: unknown, old_value: unknown) => void;
47
61
  }
62
+ export type CopyEventPosition = {
63
+ clientX: number;
64
+ clientY: number;
65
+ };
48
66
  export declare const JSON_TREE_CONTEXT_KEY: unique symbol;
67
+ export type DiffStatus = `added` | `removed` | `changed`;
68
+ export interface DiffEntry {
69
+ status: DiffStatus;
70
+ path: string;
71
+ old_value?: unknown;
72
+ new_value?: unknown;
73
+ }
@@ -1,4 +1,4 @@
1
- import type { JsonValueType } from './types';
1
+ import type { DiffEntry, JsonValueType } from './types';
2
2
  export declare function get_value_type(value: unknown): JsonValueType;
3
3
  export declare function is_expandable_type(value_type: JsonValueType): boolean;
4
4
  export declare function is_primitive_type(value_type: JsonValueType): boolean;
@@ -14,3 +14,16 @@ export declare function find_matching_paths(value: unknown, query: string, curre
14
14
  export declare function get_ancestor_paths(path: string): string[];
15
15
  export declare function parse_path(path: string): (string | number)[];
16
16
  export declare function values_equal(val_a: unknown, val_b: unknown): boolean;
17
+ export declare function parse_edited_value(text: string): unknown;
18
+ export declare function set_at_path(root: unknown, path_str: string, new_value: unknown, root_label?: string): unknown;
19
+ export declare function is_url(str: string): boolean;
20
+ export declare function is_css_color(str: string): boolean;
21
+ export declare function estimate_byte_size(value: unknown, max_depth?: number, current_depth?: number): number;
22
+ export interface GhostEntry {
23
+ key: string | number;
24
+ value: unknown;
25
+ path: string;
26
+ }
27
+ export declare function build_ghost_map(diff_map: Map<string, DiffEntry>): Map<string, GhostEntry[]>;
28
+ export declare function format_byte_size(bytes: number): string;
29
+ export declare function compute_diff(old_val: unknown, new_val: unknown, current_path?: string, result?: Map<string, DiffEntry>, seen?: WeakSet<object>): Map<string, DiffEntry>;
@@ -390,3 +390,257 @@ export function values_equal(val_a, val_b) {
390
390
  }
391
391
  return false;
392
392
  }
393
+ // Parse a raw edited string into a typed JSON value
394
+ // Numbers, booleans, and null are auto-detected; everything else stays as string
395
+ export function parse_edited_value(text) {
396
+ const trimmed = text.trim();
397
+ if (trimmed === `null`)
398
+ return null;
399
+ if (trimmed === `true`)
400
+ return true;
401
+ if (trimmed === `false`)
402
+ return false;
403
+ const num = Number(trimmed);
404
+ if (trimmed !== `` && Number.isFinite(num))
405
+ return num;
406
+ return text;
407
+ }
408
+ // Set a value at a dot/bracket path in a deep-cloned copy of root
409
+ // root_label is stripped from the path prefix if present
410
+ export function set_at_path(root, path_str, new_value, root_label) {
411
+ const segments = parse_path(path_str);
412
+ const start = root_label && segments[0] === root_label ? 1 : 0;
413
+ if (start >= segments.length)
414
+ return new_value;
415
+ const cloned = JSON.parse(JSON.stringify(root));
416
+ let current = cloned;
417
+ for (let idx = start; idx < segments.length - 1; idx++) {
418
+ const next = current[segments[idx]];
419
+ if (next === undefined || next === null)
420
+ return root; // bail — path no longer valid
421
+ current = next;
422
+ }
423
+ current[segments[segments.length - 1]] = new_value;
424
+ return cloned;
425
+ }
426
+ // URL regex for auto-detection in string values
427
+ const URL_RE = /^https?:\/\/\S+$/;
428
+ // CSS color patterns for swatch rendering
429
+ const HEX_COLOR_RE = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
430
+ const FUNC_COLOR_RE = /^(?:rgba?|hsla?|oklch|oklab|lch|lab|color)\([^)]*\)$/i;
431
+ // Check if a string is a URL
432
+ export function is_url(str) {
433
+ return URL_RE.test(str.trim());
434
+ }
435
+ // Check if a string looks like a CSS color value
436
+ // Rejects strings with semicolons to prevent CSS injection
437
+ export function is_css_color(str) {
438
+ const trimmed = str.trim();
439
+ if (trimmed.includes(`;`))
440
+ return false;
441
+ return HEX_COLOR_RE.test(trimmed) || FUNC_COLOR_RE.test(trimmed);
442
+ }
443
+ // Estimate the serialized byte size of a value (rough approximation)
444
+ // Uses max_depth to avoid expensive deep recursion on large trees
445
+ export function estimate_byte_size(value, max_depth = 4, current_depth = 0) {
446
+ if (current_depth >= max_depth)
447
+ return 10;
448
+ const type = get_value_type(value);
449
+ if (type === `null`)
450
+ return 4;
451
+ if (type === `undefined`)
452
+ return 9;
453
+ if (type === `boolean`)
454
+ return value ? 4 : 5;
455
+ if (type === `number` || type === `bigint`)
456
+ return String(value).length;
457
+ if (type === `string`)
458
+ return value.length + 2;
459
+ if (type === `symbol`)
460
+ return value.toString().length;
461
+ if (type === `function`)
462
+ return 20;
463
+ if (type === `date`)
464
+ return 24;
465
+ if (type === `regexp`)
466
+ return value.toString().length;
467
+ if (type === `error`) {
468
+ return `${value.name}: ${value.message}`.length;
469
+ }
470
+ // Accumulate child sizes for collection types
471
+ const child_depth = current_depth + 1;
472
+ const child_size = (val, overhead = 1) => estimate_byte_size(val, max_depth, child_depth) + overhead;
473
+ if (type === `array`) {
474
+ let size = 2;
475
+ for (const item of value)
476
+ size += child_size(item);
477
+ return size;
478
+ }
479
+ if (type === `object`) {
480
+ let size = 2;
481
+ for (const [key, val] of Object.entries(value)) {
482
+ size += key.length + 4 + child_size(val, 0);
483
+ }
484
+ return size;
485
+ }
486
+ if (type === `map`) {
487
+ let size = 2;
488
+ for (const [, val] of value)
489
+ size += child_size(val, 10);
490
+ return size;
491
+ }
492
+ if (type === `set`) {
493
+ let size = 2;
494
+ for (const val of value)
495
+ size += child_size(val);
496
+ return size;
497
+ }
498
+ return String(value).length;
499
+ }
500
+ // Pre-compute a map of parent_path -> removed children from a diff map
501
+ // This avoids O(diff_size) iteration per expanded node
502
+ export function build_ghost_map(diff_map) {
503
+ const ghost_map = new Map();
504
+ for (const [diff_path, entry] of diff_map) {
505
+ if (entry.status !== `removed`)
506
+ continue;
507
+ const segments = parse_path(diff_path);
508
+ if (segments.length === 0)
509
+ continue;
510
+ const parent_path = segments.length === 1 ? `` : format_path(segments.slice(0, -1));
511
+ const key = segments[segments.length - 1];
512
+ const ghosts = ghost_map.get(parent_path) ?? [];
513
+ ghosts.push({ key, value: entry.old_value, path: diff_path });
514
+ ghost_map.set(parent_path, ghosts);
515
+ }
516
+ return ghost_map;
517
+ }
518
+ // Format byte size as human-readable string (e.g., "1.2 KB")
519
+ export function format_byte_size(bytes) {
520
+ if (bytes < 1024)
521
+ return `${bytes} B`;
522
+ if (bytes < 1024 * 1024)
523
+ return `${(bytes / 1024).toFixed(1)} KB`;
524
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
525
+ }
526
+ // Compute diff between old and new values, returning path -> DiffEntry map
527
+ // Only paths that differ are included (unchanged paths are omitted)
528
+ export function compute_diff(old_val, new_val, current_path = ``, result = new Map(), seen = new WeakSet()) {
529
+ const old_type = get_value_type(old_val);
530
+ const new_type = get_value_type(new_val);
531
+ // Different types = changed
532
+ if (old_type !== new_type) {
533
+ result.set(current_path, {
534
+ status: `changed`,
535
+ path: current_path,
536
+ old_value: old_val,
537
+ new_value: new_val,
538
+ });
539
+ return result;
540
+ }
541
+ // Both primitive: compare values (with NaN === NaN special case)
542
+ if (is_primitive_type(old_type)) {
543
+ const both_nan = typeof old_val === `number` && typeof new_val === `number` &&
544
+ Number.isNaN(old_val) && Number.isNaN(new_val);
545
+ if (!both_nan && old_val !== new_val) {
546
+ result.set(current_path, {
547
+ status: `changed`,
548
+ path: current_path,
549
+ old_value: old_val,
550
+ new_value: new_val,
551
+ });
552
+ }
553
+ return result;
554
+ }
555
+ // Non-expandable special types (date, regexp, etc): compare string forms
556
+ if (!is_expandable_type(old_type)) {
557
+ if (String(old_val) !== String(new_val)) {
558
+ result.set(current_path, {
559
+ status: `changed`,
560
+ path: current_path,
561
+ old_value: old_val,
562
+ new_value: new_val,
563
+ });
564
+ }
565
+ return result;
566
+ }
567
+ // Prevent circular references
568
+ if (typeof old_val === `object` && old_val !== null) {
569
+ if (seen.has(old_val))
570
+ return result;
571
+ seen.add(old_val);
572
+ }
573
+ // Diff two indexed lists element-by-element (shared by array, map, set)
574
+ function diff_indexed(old_items, new_items) {
575
+ const max_len = Math.max(old_items.length, new_items.length);
576
+ for (let idx = 0; idx < max_len; idx++) {
577
+ const child_path = build_path(current_path, idx);
578
+ if (idx >= old_items.length) {
579
+ result.set(child_path, {
580
+ status: `added`,
581
+ path: child_path,
582
+ new_value: new_items[idx],
583
+ });
584
+ }
585
+ else if (idx >= new_items.length) {
586
+ result.set(child_path, {
587
+ status: `removed`,
588
+ path: child_path,
589
+ old_value: old_items[idx],
590
+ });
591
+ }
592
+ else {
593
+ compute_diff(old_items[idx], new_items[idx], child_path, result, seen);
594
+ }
595
+ }
596
+ }
597
+ if (old_type === `array`) {
598
+ diff_indexed(old_val, new_val);
599
+ return result;
600
+ }
601
+ // Objects: compare key sets and recurse
602
+ if (old_type === `object`) {
603
+ const old_obj = old_val;
604
+ const new_obj = new_val;
605
+ const all_keys = new Set([...Object.keys(old_obj), ...Object.keys(new_obj)]);
606
+ for (const key of all_keys) {
607
+ const child_path = build_path(current_path, key);
608
+ const in_old = key in old_obj;
609
+ const in_new = key in new_obj;
610
+ if (!in_old) {
611
+ result.set(child_path, {
612
+ status: `added`,
613
+ path: child_path,
614
+ new_value: new_obj[key],
615
+ });
616
+ }
617
+ else if (!in_new) {
618
+ result.set(child_path, {
619
+ status: `removed`,
620
+ path: child_path,
621
+ old_value: old_obj[key],
622
+ });
623
+ }
624
+ else {
625
+ compute_diff(old_obj[key], new_obj[key], child_path, result, seen);
626
+ }
627
+ }
628
+ return result;
629
+ }
630
+ // Maps: wrap entries as { key, value } to match JsonNode.get_children() rendering
631
+ if (old_type === `map`) {
632
+ diff_indexed(Array.from(old_val.entries()).map(([key, val]) => ({
633
+ key,
634
+ value: val,
635
+ })), Array.from(new_val.entries()).map(([key, val]) => ({
636
+ key,
637
+ value: val,
638
+ })));
639
+ return result;
640
+ }
641
+ if (old_type === `set`) {
642
+ diff_indexed(Array.from(old_val), Array.from(new_val));
643
+ return result;
644
+ }
645
+ return result;
646
+ }