json-diff-viewer-component 0.3.0 → 0.5.0
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/package.json +1 -1
- package/src/lib/diff.js +45 -26
- package/src/lib/styles.js +14 -6
- package/src/lib/viewer.js +136 -69
package/package.json
CHANGED
package/src/lib/diff.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const TYPE = { UNCHANGED: 'unchanged', ADDED: 'added', REMOVED: 'removed', MODIFIED: 'modified' }
|
|
1
|
+
const TYPE = { UNCHANGED: 'unchanged', ADDED: 'added', REMOVED: 'removed', MODIFIED: 'modified' };
|
|
2
2
|
|
|
3
3
|
const typeOf = (v) => {
|
|
4
4
|
if (v === null) return 'null';
|
|
@@ -8,9 +8,9 @@ const typeOf = (v) => {
|
|
|
8
8
|
|
|
9
9
|
const isObj = (v) => v !== null && typeof v === 'object';
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
const allKeys = (a, b) => [...new Set([...Object.keys(a || {}), ...Object.keys(b || {})])];
|
|
12
12
|
|
|
13
|
-
const
|
|
13
|
+
const createNode = (key, type, left, right, extra = {}) => ({
|
|
14
14
|
key,
|
|
15
15
|
type,
|
|
16
16
|
left,
|
|
@@ -19,50 +19,69 @@ const node = (key, type, left, right, extra = {}) => ({
|
|
|
19
19
|
...extra
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
const
|
|
23
|
-
if (
|
|
22
|
+
const getContainerProps = (val) => {
|
|
23
|
+
if (Array.isArray(val)) return { isArray: true };
|
|
24
24
|
if (isObj(val)) return { isObject: true };
|
|
25
25
|
return {};
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
)
|
|
28
|
+
const mapChildrenForSide = (val, side) => {
|
|
29
|
+
const type = side === 'added' ? TYPE.ADDED : TYPE.REMOVED;
|
|
30
|
+
const createChildNode = (value, key) => {
|
|
31
|
+
const leftValue = side === 'added' ? undefined : value;
|
|
32
|
+
const rightValue = side === 'added' ? value : undefined;
|
|
33
|
+
const childExtra = isObj(value) ? {
|
|
34
|
+
children: mapChildrenForSide(value, side),
|
|
35
|
+
...getContainerProps(value)
|
|
36
|
+
} : {};
|
|
37
|
+
return createNode(key, type, leftValue, rightValue, childExtra);
|
|
38
|
+
};
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
if (Array.isArray(val)) {
|
|
41
|
+
return val.map((item, index) => createChildNode(item, index));
|
|
42
|
+
}
|
|
43
|
+
if (isObj(val)) {
|
|
44
|
+
return Object.entries(val).map(([k, v]) => createChildNode(v, k));
|
|
45
|
+
}
|
|
38
46
|
return [];
|
|
39
47
|
};
|
|
40
48
|
|
|
41
49
|
const diffContainer = (left, right, key, isArr) => {
|
|
42
50
|
const items = isArr
|
|
43
51
|
? Array.from({ length: Math.max(left?.length || 0, right?.length || 0) }, (_, i) => diff(left?.[i], right?.[i], i))
|
|
44
|
-
:
|
|
45
|
-
const hasDiff = items.some(c => c.hasDiff)
|
|
46
|
-
return
|
|
47
|
-
|
|
52
|
+
: allKeys(left, right).map(k => diff(left?.[k], right?.[k], k));
|
|
53
|
+
const hasDiff = items.some(c => c.hasDiff);
|
|
54
|
+
return createNode(key, hasDiff ? TYPE.MODIFIED : TYPE.UNCHANGED, left, right, {
|
|
55
|
+
children: items,
|
|
56
|
+
...getContainerProps(left)
|
|
57
|
+
});
|
|
58
|
+
};
|
|
48
59
|
|
|
49
60
|
const diff = (left, right, key = 'root') => {
|
|
50
61
|
if (left === undefined) {
|
|
51
|
-
const extra = isObj(right) ? {
|
|
52
|
-
|
|
62
|
+
const extra = isObj(right) ? {
|
|
63
|
+
children: mapChildrenForSide(right, 'added'),
|
|
64
|
+
...getContainerProps(right)
|
|
65
|
+
} : {};
|
|
66
|
+
return createNode(key, TYPE.ADDED, left, right, extra);
|
|
53
67
|
}
|
|
54
68
|
if (right === undefined) {
|
|
55
|
-
const extra = isObj(left) ? {
|
|
56
|
-
|
|
69
|
+
const extra = isObj(left) ? {
|
|
70
|
+
children: mapChildrenForSide(left, 'removed'),
|
|
71
|
+
...getContainerProps(left)
|
|
72
|
+
} : {};
|
|
73
|
+
return createNode(key, TYPE.REMOVED, left, right, extra);
|
|
57
74
|
}
|
|
58
75
|
if (!isObj(left) && !isObj(right)) {
|
|
59
|
-
|
|
60
|
-
return node(key, TYPE.MODIFIED, left, right);
|
|
76
|
+
return createNode(key, left === right ? TYPE.UNCHANGED : TYPE.MODIFIED, left, right);
|
|
61
77
|
}
|
|
62
78
|
if (typeOf(left) !== typeOf(right)) {
|
|
63
|
-
return
|
|
79
|
+
return createNode(key, TYPE.MODIFIED, left, right, {
|
|
80
|
+
children: [],
|
|
81
|
+
...getContainerProps(left)
|
|
82
|
+
});
|
|
64
83
|
}
|
|
65
84
|
return diffContainer(left, right, key, Array.isArray(left));
|
|
66
85
|
};
|
|
67
86
|
|
|
68
|
-
export { diff, TYPE }
|
|
87
|
+
export { diff, TYPE };
|
package/src/lib/styles.js
CHANGED
|
@@ -4,6 +4,7 @@ export default `
|
|
|
4
4
|
--bg: #18181b; --bg2: #27272a; --bdr: #3f3f46;
|
|
5
5
|
--txt: #fafafa; --dim: #a1a1aa;
|
|
6
6
|
--key: #38bdf8; --str: #a78bfa; --num: #34d399; --bool: #fb923c; --nul: #f472b6; --br: #71717a;
|
|
7
|
+
--slider: var(--mod);
|
|
7
8
|
display: flex; flex-direction: column; font: 13px 'JetBrains Mono', 'Fira Code', monospace;
|
|
8
9
|
background: var(--bg); color: var(--txt); border-radius: 12px; overflow: hidden;
|
|
9
10
|
}
|
|
@@ -16,6 +17,8 @@ export default `
|
|
|
16
17
|
.node.root { padding-left: 0; }
|
|
17
18
|
.line { display: flex; align-items: flex-start; gap: 0.5rem; padding: 2px 4px; border-radius: 4px; cursor: pointer; transition: background .15s; }
|
|
18
19
|
.line:hover { background: rgba(0,0,0,.03); }
|
|
20
|
+
.line.placeholder { cursor: default; pointer-events: none; }
|
|
21
|
+
.line.placeholder:hover { background: transparent; }
|
|
19
22
|
.tog { width: 1rem; flex-shrink: 0; color: var(--br); user-select: none; }
|
|
20
23
|
.tog:hover { color: var(--txt); }
|
|
21
24
|
.key { color: var(--key); }
|
|
@@ -41,12 +44,17 @@ export default `
|
|
|
41
44
|
.preview::after { content: ' items'; }
|
|
42
45
|
.stats { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 1rem; padding: .75rem 1rem; background: var(--bg); border-bottom: 2px solid var(--bdr); font-size: 12px; }
|
|
43
46
|
.stats-items { display: grid; grid-auto-flow: column; gap: 2rem; justify-content: start; }
|
|
44
|
-
.stats-buttons { display: flex; gap: 0.5rem; }
|
|
45
|
-
.
|
|
46
|
-
.
|
|
47
|
-
.
|
|
48
|
-
.
|
|
49
|
-
.
|
|
47
|
+
.stats-buttons { display: flex; gap: 0.5rem; align-items: center; }
|
|
48
|
+
.switch { display: inline-block; cursor: pointer; }
|
|
49
|
+
.checkbox { display: none; }
|
|
50
|
+
.slider { width: 48px; height: 24px; background-color: var(--bdr); border-radius: 16px; overflow: hidden; display: flex; align-items: center; border: 3px solid transparent; transition: .3s; box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.25) inset; cursor: pointer; }
|
|
51
|
+
.slider::before { content: ''; display: block; width: 100%; height: 100%; background-color: var(--txt); transform: translateX(-24px); border-radius: 16px; transition: .3s; box-shadow: 0 0 10px 3px rgba(0, 0, 0, 0.25); }
|
|
52
|
+
.checkbox:checked ~ .slider::before { transform: translateX(24px); box-shadow: 0 0 10px 3px rgba(0, 0, 0, 0.25); }
|
|
53
|
+
.checkbox:checked ~ .slider { background-color: var(--slider); }
|
|
54
|
+
.checkbox:active ~ .slider::before { transform: translate(0); }
|
|
55
|
+
.btn-collapse, .btn-expand { padding: 0.5rem; background: var(--bg2); border: 1px solid var(--bdr); border-radius: 10px; color: var(--txt); cursor: pointer; transition: background .15s, border-color .15s; display: flex; align-items: center; justify-content: center; }
|
|
56
|
+
.btn-collapse svg, .btn-expand svg { width: 18px; height: 18px; }
|
|
57
|
+
.btn-collapse:hover, .btn-expand:hover { background: rgba(0,0,0,.05); box-shadow: 0 0 4px var(--bdr); }
|
|
50
58
|
.stat { display: grid; grid-template-columns: auto 1fr; align-items: baseline; gap: .35rem; }
|
|
51
59
|
.stat .dot { width: 8px; height: 8px; }
|
|
52
60
|
.stat-added .dot { background: var(--add); }
|
package/src/lib/viewer.js
CHANGED
|
@@ -12,6 +12,44 @@ const format = (val) => {
|
|
|
12
12
|
return [JSON.stringify(val), "string"];
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
const buildPath = (path, key) => path ? `${path}.${key}` : String(key);
|
|
16
|
+
|
|
17
|
+
const filterChildren = (children, showOnlyChanged) =>
|
|
18
|
+
showOnlyChanged ? children.filter((c) => c.hasDiff) : children;
|
|
19
|
+
|
|
20
|
+
const isExpanded = (proxy, path) => proxy[path] !== false;
|
|
21
|
+
|
|
22
|
+
const buildKeyHtml = (key, root, hidden = false) =>
|
|
23
|
+
root ? "" : `<span class="key"${hidden ? ' style="visibility: hidden;"' : ""}>${key}</span><span class="colon"${hidden ? ' style="visibility: hidden;"' : ""}>:</span>`;
|
|
24
|
+
|
|
25
|
+
const buildRootClass = (root) => root ? " root" : "";
|
|
26
|
+
|
|
27
|
+
const getBrackets = (isArray) => isArray ? ["[", "]"] : ["{", "}"];
|
|
28
|
+
|
|
29
|
+
const collectStats = (tree) => {
|
|
30
|
+
const stats = Object.fromEntries(STAT_TYPES.map((t) => [t, 0]));
|
|
31
|
+
const walk = (node, path = "") => {
|
|
32
|
+
const currentPath = buildPath(path, node.key);
|
|
33
|
+
if (node.type !== TYPE.UNCHANGED) stats[node.type]++;
|
|
34
|
+
(node.children || []).forEach((child) => {
|
|
35
|
+
walk(child, currentPath);
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
walk(tree);
|
|
39
|
+
return stats;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const initializeExpanded = (tree, proxy) => {
|
|
43
|
+
const walk = (node, path = "") => {
|
|
44
|
+
const currentPath = buildPath(path, node.key);
|
|
45
|
+
if ((node.isArray || node.isObject) && !node.hasDiff) proxy[currentPath] = false;
|
|
46
|
+
(node.children || []).forEach((child) => {
|
|
47
|
+
walk(child, currentPath);
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
walk(tree);
|
|
51
|
+
};
|
|
52
|
+
|
|
15
53
|
class JsonDiffViewer extends HTMLElement {
|
|
16
54
|
#left = null;
|
|
17
55
|
#right = null;
|
|
@@ -26,7 +64,7 @@ class JsonDiffViewer extends HTMLElement {
|
|
|
26
64
|
},
|
|
27
65
|
});
|
|
28
66
|
#stats = {};
|
|
29
|
-
#showOnlyChanged =
|
|
67
|
+
#showOnlyChanged = true;
|
|
30
68
|
|
|
31
69
|
static observedAttributes = ["left", "right"];
|
|
32
70
|
constructor() {
|
|
@@ -64,43 +102,45 @@ class JsonDiffViewer extends HTMLElement {
|
|
|
64
102
|
#compute() {
|
|
65
103
|
if (!this.#left || !this.#right) return;
|
|
66
104
|
this.#tree = diff(this.#left, this.#right);
|
|
67
|
-
this.#stats =
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
});
|
|
71
|
-
for (const k of Object.keys(this.#exp)) delete this.#exp[k];
|
|
72
|
-
this.#walk(this.#tree, (n, p) => {
|
|
73
|
-
if ((n.isArray || n.isObject) && !n.hasDiff) this.#exp[p] = false;
|
|
105
|
+
this.#stats = collectStats(this.#tree);
|
|
106
|
+
Object.keys(this.#exp).forEach((k) => {
|
|
107
|
+
delete this.#exp[k];
|
|
74
108
|
});
|
|
109
|
+
initializeExpanded(this.#tree, this.#exp);
|
|
75
110
|
this.#render();
|
|
76
111
|
}
|
|
77
112
|
|
|
78
|
-
#walk(node, fn, path = "") {
|
|
79
|
-
const currentPath = path ? `${path}.${node.key}` : String(node.key);
|
|
80
|
-
fn(node, currentPath);
|
|
81
|
-
for (const child of node.children || []) this.#walk(child, fn, currentPath);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
113
|
#collapseAll() {
|
|
85
114
|
if (!this.#tree) return;
|
|
86
115
|
this.#rendering = true;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
116
|
+
const walk = (node, path = "") => {
|
|
117
|
+
const currentPath = buildPath(path, node.key);
|
|
118
|
+
if (node.isArray || node.isObject) this.#proxy[currentPath] = false;
|
|
119
|
+
(node.children || []).forEach((child) => {
|
|
120
|
+
walk(child, currentPath);
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
walk(this.#tree);
|
|
90
124
|
this.#rendering = false;
|
|
91
125
|
this.#render();
|
|
92
126
|
}
|
|
93
127
|
|
|
94
128
|
#expandAll() {
|
|
95
129
|
this.#rendering = true;
|
|
96
|
-
|
|
130
|
+
Object.keys(this.#exp).forEach((k) => {
|
|
131
|
+
delete this.#exp[k];
|
|
132
|
+
});
|
|
97
133
|
this.#rendering = false;
|
|
98
134
|
this.#render();
|
|
99
135
|
}
|
|
100
136
|
|
|
101
137
|
#render() {
|
|
102
|
-
if (!this.#tree)
|
|
103
|
-
|
|
138
|
+
if (!this.#tree) {
|
|
139
|
+
this.shadowRoot.innerHTML = `<style>${styles}</style><div class="empty">Provide left and right JSON</div>`;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const panel = this.shadowRoot.querySelector('.panel');
|
|
143
|
+
const scroll = { top: panel?.scrollTop || 0, left: panel?.scrollLeft || 0 };
|
|
104
144
|
this.shadowRoot.innerHTML = `
|
|
105
145
|
<style>${styles}</style>
|
|
106
146
|
<div class="stats">
|
|
@@ -108,12 +148,10 @@ class JsonDiffViewer extends HTMLElement {
|
|
|
108
148
|
${STAT_TYPES.map((t) => `<div class="stat stat-${t}"><span class="dot"></span>${this.#stats[t]} ${t.replace("_", " ")}</div>`).join("")}
|
|
109
149
|
</div>
|
|
110
150
|
<div class="stats-buttons">
|
|
111
|
-
<
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
</svg>
|
|
116
|
-
</button>
|
|
151
|
+
<label class="switch" aria-label="Show only changed" title="Show only changed">
|
|
152
|
+
<input type="checkbox" class="checkbox" data-action="filter" ${this.#showOnlyChanged ? "checked" : ""}>
|
|
153
|
+
<div class="slider"></div>
|
|
154
|
+
</label>
|
|
117
155
|
<button class="btn-collapse" data-action="collapse" title="Collapse all"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M9 15H6q-.425 0-.712-.288T5 14t.288-.712T6 13h4q.425 0 .713.288T11 14v4q0 .425-.288.713T10 19t-.712-.288T9 18zm6-6h3q.425 0 .713.288T19 10t-.288.713T18 11h-4q-.425 0-.712-.288T13 10V6q0-.425.288-.712T14 5t.713.288T15 6z"/></svg></button>
|
|
118
156
|
<button class="btn-expand" data-action="expand" title="Expand all"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M7 17h3q.425 0 .713.288T11 18t-.288.713T10 19H6q-.425 0-.712-.288T5 18v-4q0-.425.288-.712T6 13t.713.288T7 14zM17 7h-3q-.425 0-.712-.288T13 6t.288-.712T14 5h4q.425 0 .713.288T19 6v4q0 .425-.288.713T18 11t-.712-.288T17 10z"/></svg></button>
|
|
119
157
|
</div>
|
|
@@ -122,84 +160,113 @@ class JsonDiffViewer extends HTMLElement {
|
|
|
122
160
|
${["left", "right"]
|
|
123
161
|
.map((side) => {
|
|
124
162
|
const label = side === "left" ? "Original" : "Modified";
|
|
125
|
-
return `<div class="panel" data-side="${side}"><div class="header">${label}</div>${this.#
|
|
163
|
+
return `<div class="panel" data-side="${side}"><div class="header">${label}</div>${this.#renderNode(this.#tree, side, "")}</div>`;
|
|
126
164
|
})
|
|
127
165
|
.join("")}
|
|
128
166
|
</div>`;
|
|
129
167
|
this.#bind();
|
|
168
|
+
this.shadowRoot.querySelectorAll('.panel').forEach(p => {
|
|
169
|
+
p.scrollTop = scroll.top;
|
|
170
|
+
p.scrollLeft = scroll.left;
|
|
171
|
+
});
|
|
130
172
|
}
|
|
131
173
|
|
|
132
|
-
#
|
|
133
|
-
const currentPath = path
|
|
174
|
+
#renderNode(node, side, path, root = true, placeholderParam = false) {
|
|
175
|
+
const currentPath = buildPath(path, node.key);
|
|
134
176
|
const value = node[side];
|
|
177
|
+
const rootClass = buildRootClass(root);
|
|
178
|
+
const placeholder = value === undefined && node.children?.length ? true : placeholderParam;
|
|
179
|
+
const hidden = placeholder ? ' style="visibility: hidden;"' : "";
|
|
180
|
+
const keyHtml = buildKeyHtml(node.key, root, placeholder);
|
|
181
|
+
|
|
182
|
+
if (value === undefined && !node.children?.length) {
|
|
183
|
+
const otherValue = side === 'left' ? node.right : node.left;
|
|
184
|
+
const [val, type] = format(otherValue);
|
|
185
|
+
const hiddenKey = buildKeyHtml(node.key, root, true);
|
|
186
|
+
return `<div class="node${rootClass}"><div class="line placeholder">${hiddenKey}<span class="val-${type}" style="visibility: hidden;">${val}</span></div></div>`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (value !== undefined && !node.isArray && !node.isObject) {
|
|
190
|
+
const [val, type] = format(value);
|
|
191
|
+
if (placeholder) {
|
|
192
|
+
return `<div class="node${rootClass}"><div class="line placeholder">${keyHtml}<span class="val-${type}"${hidden}>${val}</span></div></div>`;
|
|
193
|
+
}
|
|
194
|
+
const hasDiff = node.hasDiff && node.type !== TYPE.UNCHANGED;
|
|
195
|
+
const diffClass = hasDiff ? `diff-${node.type}` : "";
|
|
196
|
+
const nodeDiffClass = hasDiff ? ` ${diffClass}` : "";
|
|
197
|
+
return `<div class="node${rootClass}${nodeDiffClass}"><div class="line"><span class="tog"></span>${keyHtml}<span class="val-${type}">${val}</span></div></div>`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const [open, close] = getBrackets(node.isArray);
|
|
201
|
+
const expanded = isExpanded(this.#proxy, currentPath);
|
|
202
|
+
const children = node.children || [];
|
|
203
|
+
const filtered = filterChildren(children, this.#showOnlyChanged);
|
|
204
|
+
const childrenHtml = expanded
|
|
205
|
+
? filtered.map((c) => this.#renderNode(c, side, currentPath, false, placeholder)).join("")
|
|
206
|
+
: "";
|
|
207
|
+
const preview = `${filtered.length}`;
|
|
208
|
+
|
|
209
|
+
if (placeholder) {
|
|
210
|
+
if (!expanded) {
|
|
211
|
+
return `<div class="node${rootClass}"><div class="line placeholder">${keyHtml}<span class="br"${hidden}>${open}</span><span class="preview"${hidden}>${preview}</span><span class="br"${hidden}>${close}</span></div></div>`;
|
|
212
|
+
}
|
|
213
|
+
return `<div class="node${rootClass}"><div class="line placeholder">${keyHtml}<span class="br"${hidden}>${open}</span></div>${childrenHtml}<div class="line placeholder"><span class="br"${hidden}>${close}</span></div></div>`;
|
|
214
|
+
}
|
|
215
|
+
|
|
135
216
|
const hasDiff = node.hasDiff && node.type !== TYPE.UNCHANGED;
|
|
136
217
|
const diffClass = hasDiff ? `diff-${node.type}` : "";
|
|
137
|
-
const hasChildDiff = node.hasDiff &&
|
|
218
|
+
const hasChildDiff = node.hasDiff && children.some((c) => c.hasDiff);
|
|
138
219
|
const dotType = node.type === TYPE.UNCHANGED ? "modified" : node.type;
|
|
139
220
|
const dot = hasChildDiff ? `<span class="dot dot-${dotType}"></span>` : "";
|
|
140
|
-
const keyHtml = root
|
|
141
|
-
? ""
|
|
142
|
-
: `<span class="key">${node.key}</span><span class="colon">:</span>`;
|
|
143
|
-
const rootClass = root ? " root" : "";
|
|
144
221
|
const nodeDiffClass = hasDiff && !hasChildDiff ? ` ${diffClass}` : "";
|
|
222
|
+
const toggle = expanded ? "▼" : "▶";
|
|
223
|
+
const dataPath = ` data-p="${currentPath}"`;
|
|
145
224
|
|
|
146
|
-
if (!
|
|
147
|
-
|
|
148
|
-
return `<div class="node${rootClass}${nodeDiffClass}"><div class="line"><span class="tog"></span>${dot}${keyHtml}<span class="val-${type}">${val}</span></div></div>`;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const [open, close] = node.isArray ? ["[", "]"] : ["{", "}"];
|
|
152
|
-
const isExpanded = this.#proxy[currentPath] !== false;
|
|
153
|
-
const filteredChildren = this.#showOnlyChanged
|
|
154
|
-
? node.children?.filter((c) => c.hasDiff) || []
|
|
155
|
-
: node.children || [];
|
|
156
|
-
const childrenHtml =
|
|
157
|
-
filteredChildren
|
|
158
|
-
.map((c) => this.#node(c, side, currentPath, false))
|
|
159
|
-
.join("") || "";
|
|
160
|
-
const preview = `${filteredChildren.length}`;
|
|
161
|
-
|
|
162
|
-
if (!isExpanded) {
|
|
163
|
-
return `<div class="node${rootClass}${nodeDiffClass}"><div class="line" data-p="${currentPath}"><span class="tog">▶</span>${dot}${keyHtml}<span class="br">${open}</span><span class="preview">${preview}</span><span class="br">${close}</span></div></div>`;
|
|
225
|
+
if (!expanded) {
|
|
226
|
+
return `<div class="node${rootClass}${nodeDiffClass}"><div class="line"${dataPath}><span class="tog">${toggle}</span>${dot}${keyHtml}<span class="br">${open}</span><span class="preview">${preview}</span><span class="br">${close}</span></div></div>`;
|
|
164
227
|
}
|
|
165
228
|
|
|
166
|
-
return `<div class="node${rootClass}${nodeDiffClass}"><div class="line"
|
|
229
|
+
return `<div class="node${rootClass}${nodeDiffClass}"><div class="line"${dataPath}><span class="tog">${toggle}</span>${dot}${keyHtml}<span class="br">${open}</span></div>${childrenHtml}<div class="line"><span class="tog"></span><span class="br">${close}</span></div></div>`;
|
|
167
230
|
}
|
|
168
231
|
|
|
169
232
|
#bind() {
|
|
170
233
|
const [leftPanel, rightPanel] = this.shadowRoot.querySelectorAll(".panel");
|
|
171
|
-
|
|
234
|
+
const syncing = { value: false };
|
|
172
235
|
|
|
173
236
|
const syncScroll = (source) => () => {
|
|
174
|
-
if (syncing) return;
|
|
175
|
-
syncing = true;
|
|
237
|
+
if (syncing.value) return;
|
|
238
|
+
syncing.value = true;
|
|
176
239
|
const target = source === leftPanel ? rightPanel : leftPanel;
|
|
177
240
|
target.scrollTop = source.scrollTop;
|
|
178
241
|
target.scrollLeft = source.scrollLeft;
|
|
179
|
-
syncing = false;
|
|
242
|
+
syncing.value = false;
|
|
180
243
|
};
|
|
181
244
|
|
|
182
245
|
leftPanel?.addEventListener("scroll", syncScroll(leftPanel));
|
|
183
246
|
rightPanel?.addEventListener("scroll", syncScroll(rightPanel));
|
|
184
247
|
|
|
185
|
-
|
|
248
|
+
Array.from(this.shadowRoot.querySelectorAll("[data-p]")).forEach((el) => {
|
|
186
249
|
el.onclick = () => {
|
|
187
250
|
this.#proxy[el.dataset.p] = this.#proxy[el.dataset.p] === false;
|
|
188
251
|
};
|
|
189
|
-
}
|
|
252
|
+
});
|
|
190
253
|
|
|
191
|
-
this.shadowRoot
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
this.#showOnlyChanged =
|
|
254
|
+
const filterCheckbox = this.shadowRoot.querySelector(`[data-action="filter"]`);
|
|
255
|
+
if (filterCheckbox) {
|
|
256
|
+
filterCheckbox.addEventListener("change", (e) => {
|
|
257
|
+
this.#showOnlyChanged = e.target.checked;
|
|
195
258
|
this.#render();
|
|
196
259
|
});
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const actions = {
|
|
263
|
+
collapse: () => this.#collapseAll(),
|
|
264
|
+
expand: () => this.#expandAll(),
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
Object.entries(actions).forEach(([action, handler]) => {
|
|
268
|
+
this.shadowRoot.querySelector(`[data-action="${action}"]`)?.addEventListener("click", handler);
|
|
269
|
+
});
|
|
203
270
|
}
|
|
204
271
|
}
|
|
205
272
|
|