json-diff-viewer-component 0.6.3 → 0.6.4

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="https://raw.githubusercontent.com/metaory/json-diff-viewer-component/master/public/logo.svg" alt="logo" height="128" />
2
+ <img src="https://raw.githubusercontent.com/metaory/json-diff-viewer-component/master/public/logo.png" alt="logo" height="128" />
3
3
  <h2>json-diff-viewer</h2>
4
4
  <h5>
5
5
  Compare JSON side-by-side, visually
@@ -36,24 +36,50 @@ npm i json-diff-viewer-component
36
36
 
37
37
  ## Usage
38
38
 
39
- Import the package to register the `<json-diff-viewer>` custom element, then call `setData()` with both JSON values:
39
+ Every integration needs three things:
40
+
41
+ 1. Import the package once to register the custom element.
42
+ 2. Add `<json-diff-viewer>` to the page.
43
+ 3. Provide both values using one of the data APIs below.
44
+
45
+ Import the package:
40
46
 
41
47
  ```js
42
48
  import "json-diff-viewer-component";
49
+ ```
50
+
51
+ ```html
52
+ <json-diff-viewer></json-diff-viewer>
53
+ ```
43
54
 
55
+ Choose one data API. Root values must be non-null.
56
+
57
+ ### `setData(left, right)` (Recommended)
58
+
59
+ Use for dynamic data. Both values update in one render:
60
+
61
+ ```js
44
62
  const viewer = document.querySelector("json-diff-viewer");
45
- viewer.setData(leftObj, rightObj);
63
+ viewer.setData(
64
+ { name: "foo", enabled: true },
65
+ { name: "bar", enabled: true },
66
+ );
46
67
  ```
47
68
 
48
- For testing or extension, the class is also exported:
69
+ ### Properties (Alternative)
70
+
71
+ Use when assigning JavaScript values independently. Rendering starts once both sides are set:
49
72
 
50
73
  ```js
51
- import { JsonDiffViewer } from "json-diff-viewer-component";
74
+ viewer.left = { name: "foo" };
75
+ viewer.right = { name: "bar" };
76
+
77
+ console.log(viewer.left, viewer.right);
52
78
  ```
53
79
 
54
- ### HTML Attributes
80
+ ### HTML Attributes (Alternative)
55
81
 
56
- Static JSON can be passed as attributes. Values must be valid JSON strings:
82
+ Use only for small, static, inline JSON. Each value is parsed with `JSON.parse()`, so invalid JSON throws:
57
83
 
58
84
  ```html
59
85
  <json-diff-viewer
@@ -62,33 +88,32 @@ Static JSON can be passed as attributes. Values must be valid JSON strings:
62
88
  ></json-diff-viewer>
63
89
  ```
64
90
 
65
- - `JSON.parse` parses attribute values. Invalid JSON throws at parse time.
66
- - Prefer `setData()` or property setters for dynamic or object data
91
+ Prefer `setData()` or properties for objects already available in JavaScript.
67
92
 
68
- ### Properties
93
+ ### Class Export
69
94
 
70
- Set `left` and `right` as JavaScript values. Set both before the viewer draws a diff:
95
+ The registered class is exported only when a class reference is needed for testing or extension:
71
96
 
72
97
  ```js
73
- viewer.left = { name: "foo" };
74
- viewer.right = { name: "bar" };
75
-
76
- viewer.left; // read current left value
77
- viewer.right; // read current right value
98
+ import { JsonDiffViewer } from "json-diff-viewer-component";
78
99
  ```
79
100
 
80
101
  ### Built-in Controls
81
102
 
82
- The component toolbar includes:
103
+ No setup is required. The component toolbar includes:
83
104
 
84
105
  - **Show only changed**: filter toggle (default: **on**); hides unchanged nodes
85
106
  - **Collapse all** / **Expand all**: bulk expand/collapse
86
107
  - **Node toggles**: click any object/array line to expand/collapse (synced across both panels)
87
108
 
109
+ ### Framework Usage
110
+
111
+ The component remains the same custom element and uses `setData()` for reactive values.
112
+
88
113
  <details>
89
- <summary>Framework Examples</summary>
114
+ <summary>React and Vue examples</summary>
90
115
 
91
- ### React
116
+ #### React
92
117
 
93
118
  ```jsx
94
119
  import { useEffect, useRef } from "react";
@@ -107,7 +132,7 @@ function DiffViewer({ left, right }) {
107
132
  }
108
133
  ```
109
134
 
110
- ### Vue
135
+ #### Vue
111
136
 
112
137
  ```vue
113
138
  <template>
@@ -115,25 +140,15 @@ function DiffViewer({ left, right }) {
115
140
  </template>
116
141
 
117
142
  <script setup>
118
- import { ref, watch } from "vue";
143
+ import { ref, watchEffect } from "vue";
119
144
  import "json-diff-viewer-component";
120
145
 
121
- const props = defineProps({
122
- left: Object,
123
- right: Object,
124
- });
125
-
146
+ const props = defineProps(["left", "right"]);
126
147
  const viewerRef = ref(null);
127
148
 
128
- watch(
129
- () => [props.left, props.right],
130
- () => {
131
- if (viewerRef.value) {
132
- viewerRef.value.setData(props.left, props.right);
133
- }
134
- },
135
- { immediate: true },
136
- );
149
+ watchEffect(() => {
150
+ viewerRef.value?.setData(props.left, props.right);
151
+ });
137
152
  </script>
138
153
  ```
139
154
 
@@ -141,18 +156,20 @@ watch(
141
156
 
142
157
  ## Diff Types
143
158
 
144
- | Type | Color | Description |
145
- | -------- | ------ | ------------------------ |
146
- | Added | Green | Key exists only in right |
147
- | Removed | Red | Key exists only in left |
148
- | Modified | Yellow | Value changed |
159
+ | Type | Default | Description |
160
+ | --- | --- | --- |
161
+ | Added | Green | Key exists only on the right |
162
+ | Removed | Red | Key exists only on the left |
163
+ | Modified | Yellow | Values differ, or a container has changed descendants |
149
164
 
150
165
  ## Styling
151
166
 
152
- Override CSS custom properties (design tokens) on `json-diff-viewer`. Tokens live on `:host`; you set them from outside the shadow DOM.
167
+ The component is fully usable without style configuration. Override CSS custom properties on `json-diff-viewer`. They inherit through the host into its shadow DOM. State foreground tokens also drive their derived backgrounds unless those backgrounds are overridden separately.
153
168
 
154
169
  ### Design Tokens
155
170
 
171
+ Tokens inherit through the host. Override only the values your theme needs.
172
+
156
173
  ```css
157
174
  json-diff-viewer {
158
175
  /* Diff colors */
@@ -171,8 +188,18 @@ json-diff-viewer {
171
188
  --txt: #fafafa; /* Primary text */
172
189
  --dim: #a1a1aa; /* Dimmed/secondary text */
173
190
 
174
- /* Controls */
175
- --slider: var(--bdr); /* Slider toggle active color */
191
+ /* State backgrounds */
192
+ --add-bg: color-mix(in srgb, var(--add) 15%, transparent);
193
+ --rem-bg: color-mix(in srgb, var(--rem) 15%, transparent);
194
+ --mod-bg: color-mix(in srgb, var(--mod) 15%, transparent);
195
+
196
+ /* Controls and interaction */
197
+ --hover: rgb(0 0 0 / 3%); /* Diff row hover */
198
+ --control-bg: var(--bg2); /* Action button background */
199
+ --control-hover: rgb(0 0 0 / 5%); /* Action button hover */
200
+ --control-bdr: var(--bdr); /* Action button border */
201
+ --slider: var(--bdr); /* Slider active track */
202
+ --slider-thumb: var(--br); /* Slider thumb */
176
203
 
177
204
  /* Syntax highlighting */
178
205
  --key: #38bdf8; /* Object keys */
@@ -206,6 +233,43 @@ json-diff-viewer {
206
233
  }
207
234
  ```
208
235
 
236
+ ### Shadow Parts
237
+
238
+ Use `::part()` for structural overrides that are not shared design tokens:
239
+
240
+ | Parts | Elements |
241
+ | --- | --- |
242
+ | `toolbar`, `legend`, `legend-item`, `legend-added`, `legend-removed`, `legend-modified` | Summary and state legend |
243
+ | `actions`, `filter`, `filter-track`, `action-button`, `collapse-button`, `expand-button` | Viewer controls |
244
+ | `content`, `panel`, `panel-left`, `panel-right` | Diff layout and panels |
245
+ | `node`, `node-added`, `node-removed`, `node-modified`, `line` | Diff nodes and rows |
246
+ | `toggle`, `marker`, `marker-added`, `marker-removed`, `marker-modified` | Node controls and state markers |
247
+ | `key`, `separator`, `value`, `value-string`, `value-number`, `value-boolean`, `value-null` | JSON content |
248
+ | `bracket`, `preview`, `empty` | Supporting content and empty state |
249
+
250
+ Elements can expose multiple parts, so generic and specific selectors compose:
251
+
252
+ ```css
253
+ json-diff-viewer {
254
+ --add: lime;
255
+ --add-bg: color-mix(in srgb, lime 20%, transparent);
256
+ }
257
+
258
+ json-diff-viewer::part(toolbar) {
259
+ border-block-end-width: 4px;
260
+ }
261
+
262
+ json-diff-viewer::part(node-added) {
263
+ border-inline-start: 3px solid var(--add);
264
+ }
265
+
266
+ json-diff-viewer::part(collapse-button) {
267
+ border-radius: 999px;
268
+ }
269
+ ```
270
+
271
+ Parts are the supported structural API. Internal classes and other shadow elements remain private. `::part()` cannot select descendants, so target each listed part directly. Changing layout, padding, or typography on `node` and `line` can desynchronize corresponding rows between panels.
272
+
209
273
  ### Sizing
210
274
 
211
275
  Set a height to get scrolling. Without one, the viewer grows to fit all content. Default border-radius is `12px`.
@@ -237,6 +301,7 @@ json-diff-viewer {
237
301
  ```bash
238
302
  npm run dev # start dev server
239
303
  npm run build # build for production
304
+ npm run preview # preview the production build
240
305
  ```
241
306
 
242
307
  ## License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-diff-viewer-component",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
4
4
  "type": "module",
5
5
  "description": "Vanilla JS web component for side-by-side JSON diff visualization",
6
6
  "bugs": "https://github.com/metaory/json-diff-viewer-component/issues",
package/src/lib/styles.js CHANGED
@@ -1,10 +1,15 @@
1
1
  export default `
2
2
  :host {
3
3
  --add: #22c55e; --rem: #ef4444; --mod: #eab308;
4
+ --add-bg: color-mix(in srgb, var(--add) 15%, transparent);
5
+ --rem-bg: color-mix(in srgb, var(--rem) 15%, transparent);
6
+ --mod-bg: color-mix(in srgb, var(--mod) 15%, transparent);
4
7
  --bg: #18181b; --bg2: #27272a; --bdr: #3f3f46;
5
8
  --txt: #fafafa; --dim: #a1a1aa;
6
9
  --key: #38bdf8; --str: #a78bfa; --num: #34d399; --bool: #fb923c; --nul: #f472b6; --br: #71717a;
7
- --slider: var(--bdr);
10
+ --hover: rgb(0 0 0 / 3%);
11
+ --control-bg: var(--bg2); --control-hover: rgb(0 0 0 / 5%); --control-bdr: var(--bdr);
12
+ --slider: var(--bdr); --slider-thumb: var(--br);
8
13
  display: flex; flex-direction: column; font: 13px 'JetBrains Mono', 'Fira Code', monospace;
9
14
  background: var(--bg); color: var(--txt); border-radius: 12px; overflow: hidden;
10
15
  }
@@ -15,7 +20,7 @@ export default `
15
20
  .node { padding-left: 1.25rem; }
16
21
  .node.root { padding-left: 0; }
17
22
  .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(0,0,0,.03); }
23
+ .line:hover { background: var(--hover); }
19
24
  .line.placeholder { cursor: default; pointer-events: none; }
20
25
  .line.placeholder:hover { background: transparent; }
21
26
  .tog { width: 1rem; flex-shrink: 0; color: var(--br); user-select: none; }
@@ -28,9 +33,9 @@ export default `
28
33
  .val-boolean { color: var(--bool); }
29
34
  .val-null { color: var(--nul); font-style: italic; }
30
35
  .br { color: var(--br); }
31
- .node.diff-added { background: rgba(34,197,94,.15); }
32
- .node.diff-removed { background: rgba(239,68,68,.15); }
33
- .node.diff-modified { background: rgba(234,179,8,.15); }
36
+ .node.diff-added { background: var(--add-bg); }
37
+ .node.diff-removed { background: var(--rem-bg); }
38
+ .node.diff-modified { background: var(--mod-bg); }
34
39
  .node.diff-added .key { color: var(--add); }
35
40
  .node.diff-removed .key { color: var(--rem); }
36
41
  .node.diff-modified .key { color: var(--mod); }
@@ -47,13 +52,13 @@ export default `
47
52
  .switch { display: inline-block; cursor: pointer; }
48
53
  .checkbox { display: none; }
49
54
  .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; }
50
- .slider::before { content: ''; display: block; width: 100%; height: 100%; background-color: var(--br); transform: translateX(-24px); border-radius: 16px; transition: .3s; box-shadow: 0 0 10px 3px rgba(0, 0, 0, 0.25); }
55
+ .slider::before { content: ''; display: block; width: 100%; height: 100%; background-color: var(--slider-thumb); transform: translateX(-24px); border-radius: 16px; transition: .3s; box-shadow: 0 0 10px 3px rgba(0, 0, 0, 0.25); }
51
56
  .checkbox:checked ~ .slider::before { transform: translateX(24px); box-shadow: 0 0 10px 3px rgba(0, 0, 0, 0.25); }
52
57
  .checkbox:checked ~ .slider { background-color: var(--slider); }
53
58
  .checkbox:active ~ .slider::before { transform: translate(0); }
54
- .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; }
59
+ .btn-collapse, .btn-expand { padding: 0.5rem; background: var(--control-bg); border: 1px solid var(--control-bdr); border-radius: 10px; color: var(--txt); cursor: pointer; transition: background .15s, border-color .15s; display: flex; align-items: center; justify-content: center; }
55
60
  .btn-collapse svg, .btn-expand svg { width: 18px; height: 18px; }
56
- .btn-collapse:hover, .btn-expand:hover { background: rgba(0,0,0,.05); box-shadow: 0 0 4px var(--bdr); }
61
+ .btn-collapse:hover, .btn-expand:hover { background: var(--control-hover); box-shadow: 0 0 4px var(--control-bdr); }
57
62
  .stat { display: grid; grid-template-columns: auto 1fr; align-items: baseline; gap: .35rem; }
58
63
  .stat .dot { width: 8px; height: 8px; }
59
64
  .stat-added .dot { background: var(--add); }
package/src/lib/viewer.js CHANGED
@@ -22,7 +22,7 @@ const filterChildren = (children, showOnlyChanged) =>
22
22
  const isExpanded = (proxy, path) => proxy[path] !== false;
23
23
 
24
24
  const buildKeyHtml = (key, root, hidden = false) =>
25
- root ? "" : `<span class="key"${hidden ? ' style="visibility: hidden;"' : ""}>${escapeHtml(key)}</span><span class="colon"${hidden ? ' style="visibility: hidden;"' : ""}>:</span>`;
25
+ root ? "" : `<span class="key" part="key"${hidden ? ' style="visibility: hidden;"' : ""}>${escapeHtml(key)}</span><span class="colon" part="separator"${hidden ? ' style="visibility: hidden;"' : ""}>:</span>`;
26
26
 
27
27
  const buildRootClass = (root) => root ? " root" : "";
28
28
 
@@ -138,30 +138,30 @@ class JsonDiffViewer extends HTMLElement {
138
138
 
139
139
  #render() {
140
140
  if (!this.#tree) {
141
- this.shadowRoot.innerHTML = `<style>${styles}</style><div class="empty">Provide left and right JSON</div>`;
141
+ this.shadowRoot.innerHTML = `<style>${styles}</style><div class="empty" part="empty">Provide left and right JSON</div>`;
142
142
  return;
143
143
  }
144
144
  const panel = this.shadowRoot.querySelector('.panel');
145
145
  const scroll = { top: panel?.scrollTop || 0, left: panel?.scrollLeft || 0 };
146
146
  this.shadowRoot.innerHTML = `
147
147
  <style>${styles}</style>
148
- <div class="stats">
149
- <div class="stats-items">
150
- ${STAT_TYPES.map((t) => `<div class="stat stat-${t}"><span class="dot"></span>${this.#stats[t]} ${t.replace("_", " ")}</div>`).join("")}
148
+ <div class="stats" part="toolbar">
149
+ <div class="stats-items" part="legend">
150
+ ${STAT_TYPES.map((t) => `<div class="stat stat-${t}" part="legend-item legend-${t}"><span class="dot" part="marker marker-${t}"></span>${this.#stats[t]} ${t.replace("_", " ")}</div>`).join("")}
151
151
  </div>
152
- <div class="stats-buttons">
153
- <label class="switch" aria-label="Show only changed" title="Show only changed">
152
+ <div class="stats-buttons" part="actions">
153
+ <label class="switch" part="filter" aria-label="Show only changed" title="Show only changed">
154
154
  <input type="checkbox" class="checkbox" data-action="filter" ${this.#showOnlyChanged ? "checked" : ""}>
155
- <div class="slider"></div>
155
+ <div class="slider" part="filter-track"></div>
156
156
  </label>
157
- <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>
158
- <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>
157
+ <button class="btn-collapse" part="action-button collapse-button" 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>
158
+ <button class="btn-expand" part="action-button expand-button" 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>
159
159
  </div>
160
160
  </div>
161
- <div class="container">
161
+ <div class="container" part="content">
162
162
  ${["left", "right"]
163
163
  .map((side) => {
164
- return `<div class="panel" data-side="${side}">${this.#renderNode(this.#tree, side, "")}</div>`;
164
+ return `<div class="panel" part="panel panel-${side}" data-side="${side}">${this.#renderNode(this.#tree, side, "")}</div>`;
165
165
  })
166
166
  .join("")}
167
167
  </div>`;
@@ -184,18 +184,18 @@ class JsonDiffViewer extends HTMLElement {
184
184
  const otherValue = side === 'left' ? node.right : node.left;
185
185
  const [val, type] = format(otherValue);
186
186
  const hiddenKey = buildKeyHtml(node.key, root, true);
187
- return `<div class="node${rootClass}"><div class="line placeholder">${hiddenKey}<span class="val-${type}" style="visibility: hidden;">${escapeHtml(val)}</span></div></div>`;
187
+ return `<div class="node${rootClass}" part="node"><div class="line placeholder" part="line">${hiddenKey}<span class="val-${type}" part="value value-${type}" style="visibility: hidden;">${escapeHtml(val)}</span></div></div>`;
188
188
  }
189
189
 
190
190
  if (value !== undefined && !node.isArray && !node.isObject) {
191
191
  const [val, type] = format(value);
192
192
  if (placeholder) {
193
- return `<div class="node${rootClass}"><div class="line placeholder">${keyHtml}<span class="val-${type}"${hidden}>${escapeHtml(val)}</span></div></div>`;
193
+ return `<div class="node${rootClass}" part="node"><div class="line placeholder" part="line">${keyHtml}<span class="val-${type}" part="value value-${type}"${hidden}>${escapeHtml(val)}</span></div></div>`;
194
194
  }
195
195
  const hasDiff = node.hasDiff && node.type !== TYPE.UNCHANGED;
196
196
  const diffClass = hasDiff ? `diff-${node.type}` : "";
197
197
  const nodeDiffClass = hasDiff ? ` ${diffClass}` : "";
198
- return `<div class="node${rootClass}${nodeDiffClass}"><div class="line"><span class="tog"></span>${keyHtml}<span class="val-${type}">${escapeHtml(val)}</span></div></div>`;
198
+ return `<div class="node${rootClass}${nodeDiffClass}" part="node${hasDiff ? ` node-${node.type}` : ""}"><div class="line" part="line"><span class="tog" part="toggle"></span>${keyHtml}<span class="val-${type}" part="value value-${type}">${escapeHtml(val)}</span></div></div>`;
199
199
  }
200
200
 
201
201
  const [open, close] = getBrackets(node.isArray);
@@ -209,25 +209,25 @@ class JsonDiffViewer extends HTMLElement {
209
209
 
210
210
  if (placeholder) {
211
211
  if (!expanded) {
212
- 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
+ return `<div class="node${rootClass}" part="node"><div class="line placeholder" part="line">${keyHtml}<span class="br" part="bracket"${hidden}>${open}</span><span class="preview" part="preview"${hidden}>${preview}</span><span class="br" part="bracket"${hidden}>${close}</span></div></div>`;
213
213
  }
214
- 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
+ return `<div class="node${rootClass}" part="node"><div class="line placeholder" part="line">${keyHtml}<span class="br" part="bracket"${hidden}>${open}</span></div>${childrenHtml}<div class="line placeholder" part="line"><span class="br" part="bracket"${hidden}>${close}</span></div></div>`;
215
215
  }
216
216
 
217
217
  const hasDiff = node.hasDiff && node.type !== TYPE.UNCHANGED;
218
218
  const diffClass = hasDiff ? `diff-${node.type}` : "";
219
219
  const hasChildDiff = node.hasDiff && children.some((c) => c.hasDiff);
220
220
  const dotType = node.type === TYPE.UNCHANGED ? "modified" : node.type;
221
- const dot = hasChildDiff ? `<span class="dot dot-${dotType}"></span>` : "";
221
+ const dot = hasChildDiff ? `<span class="dot dot-${dotType}" part="marker marker-${dotType}"></span>` : "";
222
222
  const nodeDiffClass = hasDiff && !hasChildDiff ? ` ${diffClass}` : "";
223
223
  const toggle = expanded ? "▼" : "▶";
224
224
  const dataPath = ` data-p="${escapeHtml(currentPath)}"`;
225
225
 
226
226
  if (!expanded) {
227
- 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>`;
227
+ return `<div class="node${rootClass}${nodeDiffClass}" part="node${hasDiff ? ` node-${node.type}` : ""}"><div class="line" part="line"${dataPath}><span class="tog" part="toggle">${toggle}</span>${dot}${keyHtml}<span class="br" part="bracket">${open}</span><span class="preview" part="preview">${preview}</span><span class="br" part="bracket">${close}</span></div></div>`;
228
228
  }
229
229
 
230
- 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>`;
230
+ return `<div class="node${rootClass}${nodeDiffClass}" part="node${hasDiff ? ` node-${node.type}` : ""}"><div class="line" part="line"${dataPath}><span class="tog" part="toggle">${toggle}</span>${dot}${keyHtml}<span class="br" part="bracket">${open}</span></div>${childrenHtml}<div class="line" part="line"><span class="tog" part="toggle"></span><span class="br" part="bracket">${close}</span></div></div>`;
231
231
  }
232
232
 
233
233
  #bind() {