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.
- package/dist/FilePicker.svelte +37 -20
- package/dist/Icon.svelte +2 -2
- package/dist/MillerIndexInput.svelte +60 -0
- package/dist/MillerIndexInput.svelte.d.ts +7 -0
- package/dist/app.css +38 -2
- package/dist/brillouin/BrillouinZone.svelte +20 -62
- package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneExportPane.svelte +12 -20
- package/dist/brillouin/BrillouinZoneScene.svelte +2 -2
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
- package/dist/chempot-diagram/ChemPotDiagram.svelte +192 -0
- package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +677 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +2688 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotScene3D.svelte +8 -0
- package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +7 -0
- package/dist/chempot-diagram/color.d.ts +10 -0
- package/dist/chempot-diagram/color.js +33 -0
- package/dist/chempot-diagram/compute.d.ts +38 -0
- package/dist/chempot-diagram/compute.js +650 -0
- package/dist/chempot-diagram/index.d.ts +5 -0
- package/dist/chempot-diagram/index.js +5 -0
- package/dist/chempot-diagram/pointer.d.ts +16 -0
- package/dist/chempot-diagram/pointer.js +40 -0
- package/dist/chempot-diagram/temperature.d.ts +15 -0
- package/dist/chempot-diagram/temperature.js +37 -0
- package/dist/chempot-diagram/types.d.ts +83 -0
- package/dist/chempot-diagram/types.js +27 -0
- package/dist/colors/index.d.ts +3 -1
- package/dist/colors/index.js +4 -0
- package/dist/composition/BarChart.svelte +13 -22
- package/dist/composition/BubbleChart.svelte +5 -3
- package/dist/composition/FormulaFilter.svelte +770 -90
- package/dist/composition/FormulaFilter.svelte.d.ts +37 -1
- package/dist/composition/PieChart.svelte +43 -18
- package/dist/composition/PieChart.svelte.d.ts +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +2 -0
- package/dist/convex-hull/ConvexHull.svelte +14 -1
- package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull2D.svelte +14 -45
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +396 -134
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +93 -42
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullControls.svelte +94 -31
- package/dist/convex-hull/ConvexHullControls.svelte.d.ts +4 -2
- package/dist/convex-hull/ConvexHullStats.svelte +697 -128
- package/dist/convex-hull/ConvexHullStats.svelte.d.ts +6 -1
- package/dist/convex-hull/ConvexHullTooltip.svelte +1 -0
- package/dist/convex-hull/GasPressureControls.svelte +72 -38
- package/dist/convex-hull/GasPressureControls.svelte.d.ts +2 -1
- package/dist/convex-hull/TemperatureSlider.svelte +46 -19
- package/dist/convex-hull/TemperatureSlider.svelte.d.ts +2 -1
- package/dist/convex-hull/demo-temperature.d.ts +6 -0
- package/dist/convex-hull/demo-temperature.js +36 -0
- package/dist/convex-hull/gas-thermodynamics.js +16 -5
- package/dist/convex-hull/helpers.d.ts +7 -1
- package/dist/convex-hull/helpers.js +45 -15
- package/dist/convex-hull/index.d.ts +15 -1
- package/dist/convex-hull/index.js +1 -0
- package/dist/convex-hull/thermodynamics.d.ts +8 -21
- package/dist/convex-hull/thermodynamics.js +106 -17
- package/dist/convex-hull/types.d.ts +7 -0
- package/dist/convex-hull/types.js +11 -0
- package/dist/coordination/CoordinationBarPlot.svelte +29 -46
- package/dist/element/BohrAtom.svelte +1 -1
- package/dist/element/data.js +2 -14
- package/dist/element/data.json.gz +0 -0
- package/dist/element/index.d.ts +1 -1
- package/dist/element/index.js +1 -0
- package/dist/element/types.d.ts +1 -0
- package/dist/fermi-surface/FermiSurface.svelte +21 -65
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
- package/dist/fermi-surface/compute.js +1 -21
- package/dist/fermi-surface/marching-cubes.d.ts +2 -13
- package/dist/fermi-surface/marching-cubes.js +2 -519
- package/dist/fermi-surface/parse.js +17 -23
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +1273 -0
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +110 -0
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +171 -0
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +31 -0
- package/dist/heatmap-matrix/index.d.ts +53 -0
- package/dist/heatmap-matrix/index.js +100 -0
- package/dist/heatmap-matrix/shared.d.ts +2 -0
- package/dist/heatmap-matrix/shared.js +4 -0
- package/dist/icons.d.ts +119 -0
- package/dist/icons.js +119 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/io/export.js +15 -3
- package/dist/io/file-drop.d.ts +7 -0
- package/dist/io/file-drop.js +43 -0
- package/dist/io/index.d.ts +2 -2
- package/dist/io/index.js +2 -112
- package/dist/io/types.d.ts +1 -0
- package/dist/io/url-drop.d.ts +2 -0
- package/dist/io/url-drop.js +118 -0
- package/dist/isosurface/Isosurface.svelte +231 -0
- package/dist/isosurface/Isosurface.svelte.d.ts +8 -0
- package/dist/isosurface/IsosurfaceControls.svelte +273 -0
- package/dist/isosurface/IsosurfaceControls.svelte.d.ts +9 -0
- package/dist/isosurface/index.d.ts +5 -0
- package/dist/isosurface/index.js +6 -0
- package/dist/isosurface/parse.d.ts +6 -0
- package/dist/isosurface/parse.js +548 -0
- package/dist/isosurface/slice.d.ts +11 -0
- package/dist/isosurface/slice.js +145 -0
- package/dist/isosurface/types.d.ts +55 -0
- package/dist/isosurface/types.js +178 -0
- package/dist/labels.d.ts +2 -1
- package/dist/labels.js +1 -0
- package/dist/layout/InfoTag.svelte +62 -62
- package/dist/layout/SubpageGrid.svelte +74 -0
- package/dist/layout/SubpageGrid.svelte.d.ts +14 -0
- package/dist/layout/index.d.ts +1 -0
- package/dist/layout/index.js +1 -0
- package/dist/layout/json-tree/JsonNode.svelte +226 -53
- package/dist/layout/json-tree/JsonTree.svelte +425 -51
- package/dist/layout/json-tree/JsonTree.svelte.d.ts +1 -1
- package/dist/layout/json-tree/JsonValue.svelte +218 -97
- package/dist/layout/json-tree/types.d.ts +27 -2
- package/dist/layout/json-tree/utils.d.ts +14 -1
- package/dist/layout/json-tree/utils.js +254 -0
- package/dist/marching-cubes.d.ts +14 -0
- package/dist/marching-cubes.js +519 -0
- package/dist/math.d.ts +8 -0
- package/dist/math.js +374 -7
- package/dist/overlays/ContextMenu.svelte +3 -2
- package/dist/overlays/DraggablePane.svelte +163 -58
- package/dist/overlays/DraggablePane.svelte.d.ts +2 -0
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +232 -77
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -2
- package/dist/phase-diagram/PhaseDiagramControls.svelte +32 -11
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +3 -2
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +103 -0
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +102 -95
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +7 -0
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +100 -26
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +6 -3
- package/dist/phase-diagram/index.d.ts +2 -0
- package/dist/phase-diagram/index.js +2 -0
- package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
- package/dist/phase-diagram/svg-to-diagram.js +865 -0
- package/dist/phase-diagram/types.d.ts +10 -0
- package/dist/phase-diagram/utils.d.ts +7 -4
- package/dist/phase-diagram/utils.js +149 -59
- package/dist/plot/AxisLabel.svelte +26 -0
- package/dist/plot/AxisLabel.svelte.d.ts +16 -0
- package/dist/plot/BarPlot.svelte +473 -228
- package/dist/plot/BarPlot.svelte.d.ts +3 -3
- package/dist/plot/BarPlotControls.svelte +3 -2
- package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ColorBar.svelte +54 -54
- package/dist/plot/ColorBar.svelte.d.ts +1 -1
- package/dist/plot/ElementScatter.svelte +4 -3
- package/dist/plot/FillArea.svelte +4 -1
- package/dist/plot/Histogram.svelte +320 -230
- package/dist/plot/Histogram.svelte.d.ts +2 -2
- package/dist/plot/HistogramControls.svelte +29 -10
- package/dist/plot/HistogramControls.svelte.d.ts +6 -2
- package/dist/plot/InteractiveAxisLabel.svelte.d.ts +2 -2
- package/dist/plot/PlotControls.svelte +109 -27
- package/dist/plot/PlotControls.svelte.d.ts +1 -1
- package/dist/plot/PlotLegend.svelte +1 -1
- package/dist/plot/PortalSelect.svelte +2 -1
- package/dist/plot/ReferenceLine.svelte +2 -1
- package/dist/plot/ReferenceLine.svelte.d.ts +1 -0
- package/dist/plot/ReferencePlane.svelte +1 -3
- package/dist/plot/ScatterPlot.svelte +343 -209
- package/dist/plot/ScatterPlot.svelte.d.ts +3 -3
- package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlot3DControls.svelte +203 -250
- package/dist/plot/ScatterPlot3DScene.svelte +4 -7
- package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlotControls.svelte +95 -55
- package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ZeroLines.svelte +44 -0
- package/dist/plot/ZeroLines.svelte.d.ts +32 -0
- package/dist/plot/ZoomRect.svelte +21 -0
- package/dist/plot/ZoomRect.svelte.d.ts +8 -0
- package/dist/plot/axis-utils.d.ts +1 -1
- package/dist/plot/data-cleaning.js +1 -5
- package/dist/plot/index.d.ts +6 -2
- package/dist/plot/index.js +6 -2
- package/dist/plot/interactions.d.ts +8 -10
- package/dist/plot/interactions.js +10 -19
- package/dist/plot/layout.d.ts +7 -1
- package/dist/plot/layout.js +12 -4
- package/dist/plot/reference-line.d.ts +4 -21
- package/dist/plot/reference-line.js +7 -81
- package/dist/plot/types.d.ts +42 -17
- package/dist/plot/types.js +10 -0
- package/dist/plot/utils/label-placement.js +14 -11
- package/dist/plot/utils.d.ts +1 -0
- package/dist/plot/utils.js +14 -0
- package/dist/rdf/RdfPlot.svelte +55 -66
- package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
- package/dist/rdf/index.d.ts +1 -1
- package/dist/rdf/index.js +1 -1
- package/dist/settings.d.ts +5 -0
- package/dist/settings.js +37 -3
- package/dist/spectral/Bands.svelte +515 -143
- package/dist/spectral/Bands.svelte.d.ts +22 -2
- package/dist/spectral/helpers.d.ts +23 -1
- package/dist/spectral/helpers.js +65 -9
- package/dist/spectral/types.d.ts +2 -0
- package/dist/structure/AtomLegend.svelte +31 -10
- package/dist/structure/AtomLegend.svelte.d.ts +1 -1
- package/dist/structure/CellSelect.svelte +92 -22
- package/dist/structure/Lattice.svelte +2 -0
- package/dist/structure/Structure.svelte +716 -173
- package/dist/structure/Structure.svelte.d.ts +7 -2
- package/dist/structure/StructureControls.svelte +26 -14
- package/dist/structure/StructureControls.svelte.d.ts +5 -1
- package/dist/structure/StructureInfoPane.svelte +7 -1
- package/dist/structure/StructureScene.svelte +386 -95
- package/dist/structure/StructureScene.svelte.d.ts +15 -4
- package/dist/structure/atom-properties.d.ts +6 -2
- package/dist/structure/atom-properties.js +38 -25
- package/dist/structure/export.js +10 -7
- package/dist/structure/ferrox-wasm-types.d.ts +3 -2
- package/dist/structure/ferrox-wasm-types.js +0 -3
- package/dist/structure/ferrox-wasm.d.ts +3 -2
- package/dist/structure/ferrox-wasm.js +1 -2
- package/dist/structure/index.d.ts +7 -0
- package/dist/structure/index.js +22 -0
- package/dist/structure/parse.js +19 -16
- package/dist/structure/partial-occupancy.d.ts +25 -0
- package/dist/structure/partial-occupancy.js +102 -0
- package/dist/structure/validation.js +6 -3
- package/dist/symmetry/SymmetryStats.svelte +18 -4
- package/dist/symmetry/WyckoffTable.svelte +18 -10
- package/dist/symmetry/index.d.ts +7 -4
- package/dist/symmetry/index.js +83 -18
- package/dist/table/HeatmapTable.svelte +468 -69
- package/dist/table/HeatmapTable.svelte.d.ts +13 -1
- package/dist/table/ToggleMenu.svelte +291 -44
- package/dist/table/ToggleMenu.svelte.d.ts +4 -1
- package/dist/table/index.d.ts +3 -0
- package/dist/tooltip/index.d.ts +1 -1
- package/dist/tooltip/index.js +1 -0
- package/dist/trajectory/Trajectory.svelte +147 -145
- package/dist/trajectory/TrajectoryExportPane.svelte +13 -9
- package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +1 -1
- package/dist/trajectory/constants.d.ts +6 -0
- package/dist/trajectory/constants.js +7 -0
- package/dist/trajectory/extract.js +3 -5
- package/dist/trajectory/format-detect.d.ts +9 -0
- package/dist/trajectory/format-detect.js +76 -0
- package/dist/trajectory/frame-reader.d.ts +17 -0
- package/dist/trajectory/frame-reader.js +339 -0
- package/dist/trajectory/helpers.d.ts +15 -0
- package/dist/trajectory/helpers.js +187 -0
- package/dist/trajectory/index.d.ts +1 -0
- package/dist/trajectory/index.js +11 -4
- package/dist/trajectory/parse/ase.d.ts +2 -0
- package/dist/trajectory/parse/ase.js +76 -0
- package/dist/trajectory/parse/hdf5.d.ts +2 -0
- package/dist/trajectory/parse/hdf5.js +121 -0
- package/dist/trajectory/parse/index.d.ts +12 -0
- package/dist/trajectory/parse/index.js +304 -0
- package/dist/trajectory/parse/lammps.d.ts +5 -0
- package/dist/trajectory/parse/lammps.js +169 -0
- package/dist/trajectory/parse/vasp.d.ts +2 -0
- package/dist/trajectory/parse/vasp.js +65 -0
- package/dist/trajectory/parse/xyz.d.ts +2 -0
- package/dist/trajectory/parse/xyz.js +109 -0
- package/dist/trajectory/types.d.ts +11 -0
- package/dist/trajectory/types.js +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +4 -0
- package/dist/xrd/XrdPlot.svelte +6 -4
- package/dist/xrd/calc-xrd.js +0 -1
- package/package.json +33 -23
- package/readme.md +4 -4
- package/dist/trajectory/parse.d.ts +0 -42
- package/dist/trajectory/parse.js +0 -1267
- /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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
+
}
|