json-diff-viewer-component 0.2.0 → 0.3.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/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <div align="center">
2
- <img src="public/logo.svg" alt="logo" height="128" />
2
+ <img src="https://raw.githubusercontent.com/metaory/json-diff-viewer-component/refs/heads/master/public/logo.svg" alt="logo" height="128" />
3
3
  <h2>json-diff-viewer</h2>
4
4
  <h5>
5
5
  Compare JSON side-by-side, visually
@@ -11,7 +11,7 @@
11
11
  <br>
12
12
  Perfect for debugging, API comparisons, and configuration diffs
13
13
  </p>
14
- <img src="public/screenshot.png" alt="demo" width="80%" />
14
+ <img src="https://raw.githubusercontent.com/metaory/json-diff-viewer-component/refs/heads/master/public/screenshot.png" alt="demo" width="80%" />
15
15
  <h5>
16
16
  <a href="https://metaory.github.io/json-diff-viewer-component/" target="_blank">metaory.github.io/json-diff-viewer-component</a>
17
17
  </h5>
@@ -23,7 +23,7 @@
23
23
  - Side-by-side synchronized scrolling
24
24
  - Collapsible nodes (synced between panels)
25
25
  - Diff indicators bubble up to parent nodes
26
- - Stats summary (added/removed/modified/type-changed)
26
+ - Stats summary (added/removed/modified)
27
27
  - Syntax highlighting
28
28
  - Zero dependencies
29
29
  - Shadow DOM encapsulation
@@ -123,12 +123,11 @@ watch(
123
123
 
124
124
  ## Diff Types
125
125
 
126
- | Type | Color | Description |
127
- | ------------ | ------ | ------------------------------------ |
128
- | Added | Green | Key exists only in right |
129
- | Removed | Red | Key exists only in left |
130
- | Modified | Yellow | Value changed |
131
- | Type Changed | Orange | Type mismatch (e.g. number → string) |
126
+ | Type | Color | Description |
127
+ | -------- | ------ | ------------------------ |
128
+ | Added | Green | Key exists only in right |
129
+ | Removed | Red | Key exists only in left |
130
+ | Modified | Yellow | Value changed |
132
131
 
133
132
  ## Styling
134
133
 
@@ -142,7 +141,6 @@ json-diff-viewer {
142
141
  --add: #22c55e; /* Added items */
143
142
  --rem: #ef4444; /* Removed items */
144
143
  --mod: #eab308; /* Modified items */
145
- --typ: #f97316; /* Type changed items */
146
144
 
147
145
  /* Backgrounds */
148
146
  --bg: #18181b; /* Main background */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-diff-viewer-component",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Vanilla JS web component for side-by-side JSON diff visualization",
6
6
  "keywords": [
package/src/lib/diff.js CHANGED
@@ -1,4 +1,4 @@
1
- const TYPE = { UNCHANGED: 'unchanged', ADDED: 'added', REMOVED: 'removed', MODIFIED: 'modified', TYPE_CHANGED: 'type_changed' }
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';
@@ -57,11 +57,10 @@ const diff = (left, right, key = 'root') => {
57
57
  }
58
58
  if (!isObj(left) && !isObj(right)) {
59
59
  if (left === right) return node(key, TYPE.UNCHANGED, left, right);
60
- if (typeOf(left) !== typeOf(right)) return node(key, TYPE.TYPE_CHANGED, left, right);
61
60
  return node(key, TYPE.MODIFIED, left, right);
62
61
  }
63
62
  if (typeOf(left) !== typeOf(right)) {
64
- return node(key, TYPE.TYPE_CHANGED, left, right, { children: [], ...container(left, Array.isArray(left)) });
63
+ return node(key, TYPE.MODIFIED, left, right, { children: [], ...container(left, Array.isArray(left)) });
65
64
  }
66
65
  return diffContainer(left, right, key, Array.isArray(left));
67
66
  };
package/src/lib/styles.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default `
2
2
  :host {
3
- --add: #22c55e; --rem: #ef4444; --mod: #eab308; --typ: #f97316;
3
+ --add: #22c55e; --rem: #ef4444; --mod: #eab308;
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;
@@ -15,7 +15,7 @@ export default `
15
15
  .node { padding-left: 1.25rem; }
16
16
  .node.root { padding-left: 0; }
17
17
  .line { display: flex; align-items: flex-start; gap: 0.5rem; padding: 2px 4px; border-radius: 4px; cursor: pointer; transition: background .15s; }
18
- .line:hover { background: rgba(255,255,255,.05); }
18
+ .line:hover { background: rgba(0,0,0,.03); }
19
19
  .tog { width: 1rem; flex-shrink: 0; color: var(--br); user-select: none; }
20
20
  .tog:hover { color: var(--txt); }
21
21
  .key { color: var(--key); }
@@ -26,27 +26,25 @@ export default `
26
26
  .val-boolean { color: var(--bool); }
27
27
  .val-null { color: var(--nul); font-style: italic; }
28
28
  .br { color: var(--br); }
29
- .diff-added { background: rgba(34,197,94,.15); }
30
- .diff-removed { background: rgba(239,68,68,.15); }
31
- .diff-modified { background: rgba(234,179,8,.15); }
32
- .diff-type_changed { background: rgba(249,115,22,.15); }
33
- .diff-added .key { color: var(--add); }
34
- .diff-removed .key { color: var(--rem); }
35
- .diff-modified .key { color: var(--mod); }
36
- .diff-type_changed .key { color: var(--typ); }
29
+ .node.diff-added { background: rgba(34,197,94,.15); }
30
+ .node.diff-removed { background: rgba(239,68,68,.15); }
31
+ .node.diff-modified { background: rgba(234,179,8,.15); }
32
+ .node.diff-added .key { color: var(--add); }
33
+ .node.diff-removed .key { color: var(--rem); }
34
+ .node.diff-modified .key { color: var(--mod); }
37
35
  .dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; margin-top: 6px; }
38
36
  .dot-added { background: var(--add); }
39
37
  .dot-removed { background: var(--rem); }
40
38
  .dot-modified { background: var(--mod); }
41
- .dot-type_changed { background: var(--typ); }
42
39
  .preview { color: var(--dim); font-style: italic; }
43
40
  .preview::before { content: ' '; }
44
41
  .preview::after { content: ' items'; }
45
- .stats { display: flex; justify-content: space-between; align-items: center; gap: 1rem; padding: .75rem 1rem; background: var(--bg); border-bottom: 2px solid var(--bdr); font-size: 12px; }
42
+ .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
+ .stats-items { display: grid; grid-auto-flow: column; gap: 2rem; justify-content: start; }
46
44
  .stats-buttons { display: flex; gap: 0.5rem; }
47
- .btn-filter, .btn-collapse, .btn-expand { padding: 0.5rem; background: var(--bg2); border: 1px solid var(--bdr); border-radius: 6px; color: var(--txt); cursor: pointer; transition: background .15s, border-color .15s; display: flex; align-items: center; justify-content: center; }
45
+ .btn-filter, .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; }
48
46
  .btn-filter svg, .btn-collapse svg, .btn-expand svg { width: 18px; height: 18px; }
49
- .btn-filter:hover, .btn-collapse:hover, .btn-expand:hover { background: rgba(255,255,255,.05); border-color: var(--dim); }
47
+ .btn-filter:hover, .btn-collapse:hover, .btn-expand:hover { background: rgba(0,0,0,.05); box-shadow: 0 0 4px var(--bdr); }
50
48
  .btn-filter .checkbox-icon { opacity: 0.3; transition: opacity .15s; }
51
49
  .btn-filter .checkbox-icon.checked { opacity: 1; }
52
50
  .stat { display: grid; grid-template-columns: auto 1fr; align-items: baseline; gap: .35rem; }
@@ -54,6 +52,5 @@ export default `
54
52
  .stat-added .dot { background: var(--add); }
55
53
  .stat-removed .dot { background: var(--rem); }
56
54
  .stat-modified .dot { background: var(--mod); }
57
- .stat-type_changed .dot { background: var(--typ); }
58
55
  .empty { padding: 2rem; color: var(--dim); }
59
56
  `
package/src/lib/viewer.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { diff, TYPE } from "./diff.js";
2
2
  import styles from "./styles.js";
3
3
 
4
- const STAT_TYPES = ["added", "removed", "modified", "type_changed"];
4
+ const STAT_TYPES = ["added", "removed", "modified"];
5
5
 
6
6
  const format = (val) => {
7
7
  if (val === null) return ["null", "null"];
@@ -104,15 +104,18 @@ class JsonDiffViewer extends HTMLElement {
104
104
  this.shadowRoot.innerHTML = `
105
105
  <style>${styles}</style>
106
106
  <div class="stats">
107
- ${STAT_TYPES.map((t) => `<div class="stat stat-${t}"><span class="dot"></span>${this.#stats[t]} ${t.replace("_", " ")}</div>`).join("")}
107
+ <div class="stats-items">
108
+ ${STAT_TYPES.map((t) => `<div class="stat stat-${t}"><span class="dot"></span>${this.#stats[t]} ${t.replace("_", " ")}</div>`).join("")}
109
+ </div>
108
110
  <div class="stats-buttons">
109
- <button class="btn-filter" data-action="filter" aria-label="Show only changed">
110
- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" class="checkbox-icon ${this.#showOnlyChanged ? 'checked' : ''}">
111
- <path fill="currentColor" d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
111
+ <button class="btn-filter" data-action="filter" aria-label="Show only changed" title="Show only changed">
112
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" class="checkbox-icon ${this.#showOnlyChanged ? "checked" : ""}">
113
+ <path fill="currentColor" d="M4.25 12a7.75 7.75 0 1 1 15.5 0a7.75 7.75 0 0 1-15.5 0" opacity="0.5" />
114
+ <path fill="currentColor" d="M8.25 12a3.75 3.75 0 1 0 7.5 0a3.75 3.75 0 0 0-7.5 0" />
112
115
  </svg>
113
116
  </button>
114
- <button class="btn-collapse" data-action="collapse"><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>
115
- <button class="btn-expand" data-action="expand"><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>
117
+ <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
+ <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>
116
119
  </div>
117
120
  </div>
118
121
  <div class="container">
@@ -138,10 +141,11 @@ class JsonDiffViewer extends HTMLElement {
138
141
  ? ""
139
142
  : `<span class="key">${node.key}</span><span class="colon">:</span>`;
140
143
  const rootClass = root ? " root" : "";
144
+ const nodeDiffClass = hasDiff && !hasChildDiff ? ` ${diffClass}` : "";
141
145
 
142
146
  if (!node.isArray && !node.isObject) {
143
147
  const [val, type] = format(value);
144
- return `<div class="node${rootClass}"><div class="line ${diffClass}"><span class="tog"></span>${dot}${keyHtml}<span class="val-${type}">${val}</span></div></div>`;
148
+ return `<div class="node${rootClass}${nodeDiffClass}"><div class="line"><span class="tog"></span>${dot}${keyHtml}<span class="val-${type}">${val}</span></div></div>`;
145
149
  }
146
150
 
147
151
  const [open, close] = node.isArray ? ["[", "]"] : ["{", "}"];
@@ -156,10 +160,10 @@ class JsonDiffViewer extends HTMLElement {
156
160
  const preview = `${filteredChildren.length}`;
157
161
 
158
162
  if (!isExpanded) {
159
- return `<div class="node${rootClass}"><div class="line ${diffClass}" 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>`;
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>`;
160
164
  }
161
165
 
162
- return `<div class="node${rootClass}"><div class="line ${diffClass}" data-p="${currentPath}"><span class="tog">▼</span>${dot}${keyHtml}<span class="br">${open}</span></div>${childrenHtml}<div class="line"><span class="tog"></span><span class="br">${close}</span></div></div>`;
166
+ return `<div class="node${rootClass}${nodeDiffClass}"><div class="line" data-p="${currentPath}"><span class="tog">▼</span>${dot}${keyHtml}<span class="br">${open}</span></div>${childrenHtml}<div class="line"><span class="tog"></span><span class="br">${close}</span></div></div>`;
163
167
  }
164
168
 
165
169
  #bind() {