json-viewer-element 1.0.0 → 1.0.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/README.en.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # <json-viewer> Element
2
2
 
3
+ [简体中文](./README.md) | English
4
+
3
5
  > 🌈 A lightweight, modern Web Component for JSON visualization and interaction.
4
6
 
5
7
  ## Features
@@ -55,6 +57,8 @@ Set value by attribute:
55
57
 
56
58
  Use in Vue framework:
57
59
 
60
+ Vue 2/3 Options API:
61
+
58
62
  ```vue
59
63
  <template>
60
64
  <json-viewer :value="JSON.stringify(json)" boxed copyable sort expand-depth="2" theme="dark"></json-viewer>
@@ -71,6 +75,66 @@ export default {
71
75
  </script>
72
76
  ```
73
77
 
78
+ Vue 3 composition API:
79
+
80
+ ```vue
81
+ <script lang="ts" setup>
82
+ import { ref } from 'vue'
83
+ const json = ref({ hello: "world", arr: [1,2,3] })
84
+ </script>
85
+
86
+ <template>
87
+ <json-viewer :value="JSON.stringify(json)" boxed copyable sort expand-depth="2" theme="dark"></json-viewer>
88
+ </template>
89
+ ```
90
+
91
+ > [!TIP]
92
+ >
93
+ > [Skipping Component Resolution](https://vuejs.org/guide/extras/web-components.html#skipping-component-resolution)
94
+ >
95
+ > To let Vue know that certain elements should be treated as custom elements and skip component resolution, we can specify the [`compilerOptions.isCustomElement` option](https://vuejs.org/api/application.html#app-config-compileroptions).
96
+
97
+ ```js
98
+ // vite.config.js
99
+ import vue from '@vitejs/plugin-vue'
100
+ import vueJsx from '@vitejs/plugin-vue-jsx'
101
+
102
+ export default {
103
+ plugins: [
104
+ vue({
105
+ template: {
106
+ compilerOptions: {
107
+ // treat all tags with a dash as custom elements
108
+ isCustomElement: tag => tag.includes('-')
109
+ }
110
+ }
111
+ }),
112
+ vueJsx({
113
+ // treat all tags with a dash as custom elements
114
+ isCustomElement: tag => tag.includes('-')
115
+ }),
116
+ ]
117
+ }
118
+ ```
119
+
120
+ If you're using ESLint with Vue, you may need to configure it to ignore the custom element:
121
+
122
+ ```js
123
+ // eslint.config.js
124
+ export default {
125
+ rules: {
126
+ 'vue/component-name-in-template-casing': [
127
+ 'warn',
128
+ 'PascalCase',
129
+ {
130
+ registeredComponentsOnly: false,
131
+ ignores: ['/^icon-/', 'json-viewer'],
132
+ },
133
+ ],
134
+ },
135
+ }
136
+ ```
137
+
74
138
  ## Props
75
139
 
76
140
  > [!TIP]
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # &lt;json-viewer&gt; Element
2
2
 
3
+ 简体中文 | [English](./README.en.md)
4
+
3
5
  > 🌈 一个轻量、现代的 JSON 可视化与交互 Web 组件
4
6
 
5
7
  ## 功能特性
@@ -11,7 +13,7 @@
11
13
  - 🔑 **排序**:支持键排序
12
14
  - 🔍 **展开深度**:可控初始展开层级
13
15
  - 🧩 **自定义复制按钮**:slot 插槽支持
14
- - 🧬 **类型高亮**:丰富多彩的类型高亮
16
+ - 🧬 **类型高亮**:多种类型高亮
15
17
  - 🛠️ **自定义事件**:支持 copy/toggle 事件监听
16
18
 
17
19
  ## 使用方法
@@ -55,6 +57,8 @@ import 'json-viewer-element'
55
57
 
56
58
  在 Vue 框架中使用:
57
59
 
60
+ Vue 2/3 选项式 API:
61
+
58
62
  ```vue
59
63
  <template>
60
64
  <json-viewer :value="JSON.stringify(json)" boxed copyable sort expand-depth="2" theme="dark"></json-viewer>
@@ -71,10 +75,70 @@ export default {
71
75
  </script>
72
76
  ```
73
77
 
78
+ Vue 3 组合式 API:
79
+
80
+ ```vue
81
+ <script lang="ts" setup>
82
+ import { ref } from 'vue'
83
+ const json = ref({ hello: "world", arr: [1,2,3] })
84
+ </script>
85
+
86
+ <template>
87
+ <json-viewer :value="JSON.stringify(json)" boxed copyable sort expand-depth="2" theme="dark"></json-viewer>
88
+ </template>
89
+ ```
90
+
91
+ > [!TIP]
92
+ >
93
+ > [跳过组件解析](https://cn.vuejs.org/guide/extras/web-components.html#skipping-component-resolution)
94
+ >
95
+ > 为了让 Vue 知道某些元素应被视为自定义元素并跳过组件解析,我们可以指定 [`compilerOptions.isCustomElement` 选项](https://cn.vuejs.org/api/application.html#app-config-compileroptions)。
96
+
97
+ ```js
98
+ // vite.config.js
99
+ import vue from '@vitejs/plugin-vue'
100
+ import vueJsx from '@vitejs/plugin-vue-jsx'
101
+
102
+ export default {
103
+ plugins: [
104
+ vue({
105
+ template: {
106
+ compilerOptions: {
107
+ // 将所有带短横线的标签名都视为自定义元素
108
+ isCustomElement: tag => tag.includes('-')
109
+ }
110
+ }
111
+ }),
112
+ vueJsx({
113
+ // 将所有带短横线的标签名都视为自定义元素
114
+ isCustomElement: tag => tag.includes('-')
115
+ }),
116
+ ]
117
+ }
118
+ ```
119
+
120
+ 如果你在 Vue 中使用 ESLint,可能需要配置忽略自定义元素:
121
+
122
+ ```js
123
+ // eslint.config.js
124
+ export default {
125
+ rules: {
126
+ 'vue/component-name-in-template-casing': [
127
+ 'warn',
128
+ 'PascalCase',
129
+ {
130
+ registeredComponentsOnly: false,
131
+ ignores: ['/^icon-/', 'json-viewer'],
132
+ },
133
+ ],
134
+ },
135
+ }
136
+ ```
137
+
74
138
  ## 属性
75
139
 
76
140
  > [!TIP]
77
- > 在 Vue 等框架中使用时,value 和 copyable 对象的值需要转成字符串传入。
141
+ > 在 Vue 等框架中使用时,value 和 copyable 属性的值需要转成字符串传入。
78
142
 
79
143
  | 属性 | 类型 | 默认值 | 说明 |
80
144
  | :----------- | :----------------------------------------- | :------ | :----------------------------------------- |
@@ -40,11 +40,13 @@ export declare class JsonViewerElement extends HTMLElement {
40
40
  attributeChangedCallback(): void;
41
41
  set value(v: any);
42
42
  get value(): any;
43
+ set sort(v: boolean);
44
+ get sort(): boolean;
45
+ set parse(v: boolean);
46
+ get parse(): boolean;
47
+ set copyable(v: CopyableOptions | false);
48
+ get copyable(): CopyableOptions | false;
43
49
  private get expandDepth();
44
- private get sort();
45
- private get theme();
46
- private get parse();
47
- private get copyable();
48
50
  /**
49
51
  * Copy text to clipboard. Uses Clipboard API if available, otherwise fallback.
50
52
  */
@@ -1,416 +1 @@
1
- const togglerSvg = "data:image/svg+xml;charset=utf-8;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMCAwIDggOC04IDh6IiBmaWxsPSIjNjY2Ii8+PC9zdmc+";
2
- const tpl = document.createElement("template");
3
- tpl.innerHTML = `
4
- <style>
5
- /* Light Theme (default) */
6
- :host {
7
- --jv-bg-color: #ffffff;
8
- --jv-border-color: #ddd;
9
- --jv-text-color: #111;
10
- --jv-key-color: #111;
11
- --jv-string-color: #42b983;
12
- --jv-number-color: #fc1e70;
13
- --jv-boolean-color: #fc1e70;
14
- --jv-null-color: #e08331;
15
- --jv-undefined-color: #b0b0b0;
16
- --jv-function-color: #067bca;
17
- --jv-regexp-color: #fc1e70;
18
- --jv-copy-bg: #eee;
19
- --jv-copy-text: #333;
20
- --jv-ellipsis-color: #999999;
21
- --jv-ellipsis-bg: #eeeeee;
22
- --jv-hover-shadow: rgba(0,0,0,0.1);
23
- }
24
-
25
- /* Dark Theme */
26
- :host([theme="dark"]) {
27
- --jv-bg-color: #23272f;
28
- --jv-border-color: #2c313a;
29
- --jv-text-color: #d4d4d4;
30
- --jv-key-color: #79c0ff;
31
- --jv-string-color: #a5d6a7;
32
- --jv-number-color: #e2b86b;
33
- --jv-boolean-color: #ff7b72;
34
- --jv-null-color: #ffab70;
35
- --jv-undefined-color: #d2a8ff;
36
- --jv-function-color: #c678dd;
37
- --jv-regexp-color: #56b6c2;
38
- --jv-copy-bg: #3a3f4b;
39
- --jv-copy-text: #fff;
40
- --jv-ellipsis-color: #6e7681;
41
- --jv-ellipsis-bg: #2c313a;
42
- --jv-hover-shadow: rgba(0,0,0,0.4);
43
- }
44
-
45
- :host {
46
- display: block;
47
- width: 100%;
48
- max-width: 100%;
49
- font-family: Consolas, Menlo, Courier, monospace;
50
- font-size: 14px;
51
- padding: 8px;
52
- overflow-x: auto;
53
- box-sizing: border-box;
54
- position: relative;
55
- background-color: var(--jv-bg-color);
56
- color: var(--jv-text-color);
57
- }
58
-
59
- :host([boxed]) {
60
- border: 1px solid var(--jv-border-color);
61
- border-radius: 4px;
62
- padding: 16px;
63
- transition: box-shadow 0.2s ease;
64
- }
65
- :host([boxed]:hover) {
66
- box-shadow: 0 2px 8px var(--jv-hover-shadow);
67
- }
68
- #root:has(+.align-left) {
69
- margin-top: 16px;
70
- }
71
- .jv-copy {
72
- cursor: pointer;
73
- font-size: 12px;
74
- background: var(--jv-copy-bg);
75
- color: var(--jv-copy-text);
76
- padding: 4px 8px;
77
- border-radius: 3px;
78
- opacity: 0;
79
- transition: opacity 0.2s ease;
80
- }
81
- :host(:hover) .jv-copy {
82
- opacity: 1;
83
- }
84
- slot[name="copy-button"] {
85
- position: absolute;
86
- top: 8px;
87
- right: 8px;
88
- z-index: 10;
89
- opacity: 0;
90
- transition: opacity 0.2s ease;
91
- display: block !important;
92
- }
93
- slot[name="copy-button"][hidden] {
94
- display: none !important;
95
- }
96
- slot[name="copy-button"].align-left {
97
- left: 8px;
98
- right: auto;
99
- }
100
- slot[name="copy-button"].align-right {
101
- right: 8px;
102
- left: auto;
103
- }
104
- :host(:hover) slot[name="copy-button"] {
105
- opacity: 1;
106
- }
107
- .jv-toggle {
108
- background-image: url("${togglerSvg}");
109
- background-repeat: no-repeat;
110
- background-size: contain;
111
- background-position: center center;
112
- cursor: pointer;
113
- width: 10px;
114
- height: 10px;
115
- margin-right: 2px;
116
- display: inline-block;
117
- transition: rotate .1s;
118
- }
119
- .jv-toggle.open {
120
- rotate: 90deg;
121
- }
122
- .jv-key {
123
- color: var(--jv-key-color);
124
- }
125
- .jv-string {
126
- color: var(--jv-string-color);
127
- }
128
- .jv-number {
129
- color: var(--jv-number-color);
130
- }
131
- .jv-boolean {
132
- color: var(--jv-boolean-color);
133
- }
134
- .jv-null {
135
- color: var(--jv-null-color);
136
- }
137
- .jv-undefined {
138
- color: var(--jv-undefined-color);
139
- }
140
- .jv-function {
141
- color: var(--jv-function-color);
142
- }
143
- .jv-regexp {
144
- color: var(--jv-regexp-color);
145
- }
146
- .jv-list {
147
- margin-left: 16px;
148
- }
149
- .jv-item:not(:has(.jv-toggle)) .jv-key {
150
- margin-left: 12px;
151
- }
152
- .jv-item:not(:last-child):after {
153
- content: ',';
154
- }
155
- .jv-node > .jv-ellipsis {
156
- display: none;
157
- }
158
- .jv-node.empty > .jv-list {
159
- display: inline-block;
160
- margin-inline: 4px;
161
- }
162
- .jv-node.collapsed > .jv-list,
163
- .jv-node.collapsed.empty > .jv-ellipsis {
164
- display: none;
165
- }
166
- .jv-node.collapsed > .jv-ellipsis {
167
- color: var(--jv-ellipsis-color);
168
- background-color: var(--jv-ellipsis-bg);
169
- display: inline-block;
170
- line-height: 0.9;
171
- font-size: 0.85em;
172
- vertical-align: 2px;
173
- cursor: pointer;
174
- user-select: none;
175
- padding: 2px 4px;
176
- margin: 0px 4px;
177
- border-radius: 3px;
178
- }
179
- </style>
180
- <div id="root" part="root"></div>
181
- <slot name="copy-button" part="copy-button" hidden>
182
- <span class="jv-copy"></span>
183
- </slot>
184
- `;
185
- class JsonViewerElement extends HTMLElement {
186
- constructor() {
187
- super();
188
- this._value = null;
189
- this.root = this.attachShadow({ mode: "open" });
190
- this.root.appendChild(tpl.content.cloneNode(true));
191
- this.container = this.root.getElementById("root");
192
- }
193
- static get observedAttributes() {
194
- return [
195
- "value",
196
- "expand-depth",
197
- "copyable",
198
- "sort",
199
- "boxed",
200
- "theme",
201
- "parse"
202
- ];
203
- }
204
- connectedCallback() {
205
- this.render();
206
- }
207
- attributeChangedCallback() {
208
- this.render();
209
- }
210
- // ----- Public property: value -----
211
- set value(v) {
212
- if (v === this._value)
213
- return;
214
- this._value = v;
215
- this.render();
216
- }
217
- get value() {
218
- return this._value ?? this.getAttribute("value");
219
- }
220
- // ----- Private getters for props -----
221
- get expandDepth() {
222
- return Number(this.getAttribute("expand-depth") ?? 1);
223
- }
224
- get sort() {
225
- return this.hasAttribute("sort");
226
- }
227
- get theme() {
228
- return this.getAttribute("theme") || "light";
229
- }
230
- get parse() {
231
- return this.getAttribute("parse") !== "false";
232
- }
233
- get copyable() {
234
- if (!this.hasAttribute("copyable"))
235
- return false;
236
- const attr = this.getAttribute("copyable");
237
- const defaultCopyableOptions = { copyText: "Copy", copiedText: "Copied", timeout: 2e3, align: "right" };
238
- if (attr === "" || attr === null)
239
- return defaultCopyableOptions;
240
- try {
241
- return {
242
- ...defaultCopyableOptions,
243
- ...JSON.parse(attr)
244
- };
245
- } catch {
246
- return defaultCopyableOptions;
247
- }
248
- }
249
- /**
250
- * Copy text to clipboard. Uses Clipboard API if available, otherwise fallback.
251
- */
252
- copyText(text) {
253
- if (navigator.clipboard) {
254
- this.copyText = (text2) => navigator.clipboard.writeText(text2);
255
- return this.copyText(text);
256
- }
257
- this.copyText = (text2) => new Promise((resolve, reject) => {
258
- const input = document.createElement("input");
259
- input.value = text2;
260
- document.body.appendChild(input);
261
- input.select();
262
- if (document.execCommand("copy")) {
263
- document.body.removeChild(input);
264
- resolve();
265
- } else {
266
- document.body.removeChild(input);
267
- reject(new Error("Copy failed"));
268
- }
269
- });
270
- return this.copyText(text);
271
- }
272
- /**
273
- * Render the JSON viewer.
274
- */
275
- render() {
276
- if (typeof this.value === "string" && this.parse) {
277
- try {
278
- this._value = JSON.parse(this.value);
279
- } catch {
280
- }
281
- }
282
- this.container.innerHTML = "";
283
- this.container.appendChild(this.build(this._value, 0));
284
- const copyableOptions = this.copyable;
285
- if (copyableOptions) {
286
- const align = copyableOptions.align || "right";
287
- const copySlot = this.root.querySelector('slot[name="copy-button"]');
288
- const customCopyButton = copySlot.assignedElements()[0];
289
- const defaultCopyBtn = this.root.querySelector(".jv-copy");
290
- copySlot.hidden = false;
291
- copySlot.className = `align-${align}`;
292
- if (!customCopyButton) {
293
- let copyTimeout;
294
- defaultCopyBtn.textContent = copyableOptions.copyText;
295
- const newBtn = defaultCopyBtn.cloneNode(true);
296
- defaultCopyBtn.replaceWith(newBtn);
297
- newBtn.textContent = copyableOptions.copyText;
298
- newBtn.addEventListener("click", () => {
299
- const textToCopy = JSON.stringify(this._value, null, 2);
300
- this.copyText(textToCopy).then(() => {
301
- newBtn.textContent = copyableOptions.copiedText;
302
- copyTimeout = window.setTimeout(() => {
303
- newBtn.textContent = copyableOptions.copyText;
304
- clearTimeout(copyTimeout);
305
- }, copyableOptions.timeout);
306
- this.dispatchEvent(new CustomEvent("copy-success", {
307
- detail: { text: textToCopy, options: copyableOptions }
308
- }));
309
- }).catch(() => {
310
- console.warn("Failed to copy text to clipboard");
311
- this.dispatchEvent(new CustomEvent("copy-error", {
312
- detail: { text: textToCopy, options: copyableOptions }
313
- }));
314
- });
315
- });
316
- }
317
- }
318
- }
319
- /**
320
- * Recursively build the JSON tree.
321
- */
322
- build(data, depth) {
323
- if (data === null)
324
- return this.leaf("null", "jv-null");
325
- if (data === void 0)
326
- return this.leaf("undefined", "jv-undefined");
327
- if (typeof data === "boolean")
328
- return this.leaf(String(data), "jv-boolean");
329
- if (typeof data === "number")
330
- return this.leaf(String(data), "jv-number");
331
- if (typeof data === "string")
332
- return this.leaf(`"${data}"`, "jv-string");
333
- if (typeof data === "function")
334
- return this.leaf("<function>", "jv-function");
335
- if (data instanceof RegExp)
336
- return this.leaf("<regexp>", "jv-regexp");
337
- if (data instanceof Date)
338
- return this.leaf(`"${data.toLocaleString()}"`, "jv-string");
339
- const isArr = Array.isArray(data);
340
- const node = document.createElement("span");
341
- node.className = "jv-node";
342
- node.setAttribute("part", "node");
343
- const list = document.createElement("div");
344
- list.className = "jv-list";
345
- list.setAttribute("part", "list");
346
- const keys = isArr ? this.sort ? [...data.keys()].sort((a, b) => a - b) : [...data.keys()] : this.sort ? Object.keys(data).sort() : Object.keys(data);
347
- for (const k of keys) {
348
- const item = document.createElement("div");
349
- const childNode = this.build(data[k], depth + 1);
350
- item.className = "jv-item";
351
- if (childNode instanceof Element && childNode.classList.contains("jv-node")) {
352
- const childToggle = childNode.querySelector(".jv-toggle");
353
- if (childToggle) {
354
- childToggle.remove();
355
- item.append(childToggle);
356
- }
357
- }
358
- if (!isArr) {
359
- const keySpan = document.createElement("span");
360
- keySpan.className = "jv-key";
361
- keySpan.setAttribute("part", "key");
362
- keySpan.textContent = `"${k}": `;
363
- item.append(keySpan);
364
- }
365
- item.append(childNode);
366
- list.append(item);
367
- }
368
- const toggle = document.createElement("span");
369
- toggle.className = "jv-toggle";
370
- toggle.setAttribute("part", "toggle");
371
- if (!node.classList.contains("collapsed")) {
372
- toggle.classList.add("open");
373
- }
374
- toggle.addEventListener("click", () => {
375
- node.classList.toggle("collapsed");
376
- toggle.classList.toggle("open");
377
- this.dispatchEvent(new CustomEvent("toggle", {
378
- detail: {
379
- node,
380
- data,
381
- isCollapsed: node.classList.contains("collapsed")
382
- }
383
- }));
384
- });
385
- const ellipsis = document.createElement("span");
386
- ellipsis.className = "jv-ellipsis";
387
- ellipsis.setAttribute("part", "ellipsis");
388
- ellipsis.textContent = `...${keys.length}`;
389
- ellipsis.addEventListener("click", () => {
390
- node.classList.remove("collapsed");
391
- toggle.classList.add("open");
392
- });
393
- if (depth >= this.expandDepth) {
394
- node.classList.add("collapsed");
395
- toggle.classList.remove("open");
396
- }
397
- if (!keys.length)
398
- node.classList.add("empty");
399
- node.append(toggle, isArr ? "[" : "{", ellipsis, list, isArr ? "]" : "}");
400
- return node;
401
- }
402
- /**
403
- * Create a leaf node for primitive values.
404
- */
405
- leaf(text, cls) {
406
- const s = document.createElement("span");
407
- s.className = `jv-value ${cls}`;
408
- s.setAttribute("part", `value ${cls.replace("jv-", "")}`);
409
- s.textContent = text;
410
- return s;
411
- }
412
- }
413
- customElements.define("json-viewer", JsonViewerElement);
414
- export {
415
- JsonViewerElement
416
- };
1
+ const e=document.createElement("template");e.innerHTML='\n<style>\n/* Light Theme (default) */\n:host {\n --jv-bg-color: #ffffff;\n --jv-border-color: #ddd;\n --jv-text-color: #111;\n --jv-key-color: #111;\n --jv-string-color: #42b983;\n --jv-number-color: #fc1e70;\n --jv-boolean-color: #fc1e70;\n --jv-null-color: #e08331;\n --jv-undefined-color: #b0b0b0;\n --jv-function-color: #067bca;\n --jv-regexp-color: #fc1e70;\n --jv-copy-bg: #eee;\n --jv-copy-text: #333;\n --jv-ellipsis-color: #999999;\n --jv-ellipsis-bg: #eeeeee;\n --jv-hover-shadow: rgba(0,0,0,0.1);\n}\n\n/* Dark Theme */\n:host([theme="dark"]) {\n --jv-bg-color: #23272f;\n --jv-border-color: #2c313a;\n --jv-text-color: #d4d4d4;\n --jv-key-color: #79c0ff;\n --jv-string-color: #a5d6a7;\n --jv-number-color: #e2b86b;\n --jv-boolean-color: #ff7b72;\n --jv-null-color: #ffab70;\n --jv-undefined-color: #d2a8ff;\n --jv-function-color: #c678dd;\n --jv-regexp-color: #56b6c2;\n --jv-copy-bg: #3a3f4b;\n --jv-copy-text: #fff;\n --jv-ellipsis-color: #6e7681;\n --jv-ellipsis-bg: #2c313a;\n --jv-hover-shadow: rgba(0,0,0,0.4);\n}\n\n:host {\n display: block;\n width: 100%;\n max-width: 100%;\n font-family: Consolas, Menlo, Courier, monospace;\n font-size: 14px;\n padding: 8px;\n overflow-x: auto;\n box-sizing: border-box;\n position: relative;\n background-color: var(--jv-bg-color);\n color: var(--jv-text-color);\n}\n\n:host([boxed]) {\n border: 1px solid var(--jv-border-color);\n border-radius: 4px;\n padding: 16px;\n transition: box-shadow 0.2s ease;\n}\n:host([boxed]:hover) {\n box-shadow: 0 2px 8px var(--jv-hover-shadow);\n}\n#root:has(+.align-left) {\n margin-top: 16px;\n}\n.jv-copy {\n cursor: pointer;\n font-size: 12px;\n background: var(--jv-copy-bg);\n color: var(--jv-copy-text);\n padding: 4px 8px;\n border-radius: 3px;\n opacity: 0;\n transition: opacity 0.2s ease;\n}\n:host(:hover) .jv-copy {\n opacity: 1;\n}\nslot[name="copy-button"] {\n position: absolute;\n top: 8px;\n right: 8px;\n z-index: 10;\n opacity: 0;\n transition: opacity 0.2s ease;\n display: block !important;\n}\nslot[name="copy-button"][hidden] {\n display: none !important;\n}\nslot[name="copy-button"].align-left {\n left: 8px;\n right: auto;\n}\nslot[name="copy-button"].align-right {\n right: 8px;\n left: auto;\n}\n:host(:hover) slot[name="copy-button"] {\n opacity: 1;\n}\n.jv-toggle {\n background-image: url("data:image/svg+xml;charset=utf-8;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMCAwIDggOC04IDh6IiBmaWxsPSIjNjY2Ii8+PC9zdmc+");\n background-repeat: no-repeat;\n background-size: contain;\n background-position: center center;\n cursor: pointer;\n width: 10px;\n height: 10px;\n margin-right: 2px;\n display: inline-block;\n transition: rotate .1s;\n}\n.jv-toggle.open {\n rotate: 90deg;\n}\n.jv-key {\n color: var(--jv-key-color);\n}\n.jv-string {\n color: var(--jv-string-color);\n}\n.jv-number {\n color: var(--jv-number-color);\n}\n.jv-boolean {\n color: var(--jv-boolean-color);\n}\n.jv-null {\n color: var(--jv-null-color);\n}\n.jv-undefined {\n color: var(--jv-undefined-color);\n}\n.jv-function {\n color: var(--jv-function-color);\n}\n.jv-regexp {\n color: var(--jv-regexp-color);\n}\n.jv-list {\n margin-left: 16px;\n}\n.jv-item:not(:has(.jv-toggle)) .jv-key {\n margin-left: 12px;\n}\n.jv-item:not(:last-child):after {\n content: \',\';\n}\n.jv-node > .jv-ellipsis {\n display: none;\n}\n.jv-node.empty > .jv-list {\n display: inline-block;\n margin-inline: 4px;\n}\n.jv-node.collapsed > .jv-list,\n.jv-node.collapsed.empty > .jv-ellipsis {\n display: none;\n}\n.jv-node.collapsed > .jv-ellipsis {\n color: var(--jv-ellipsis-color);\n background-color: var(--jv-ellipsis-bg);\n display: inline-block;\n line-height: 0.9;\n font-size: 0.85em;\n vertical-align: 2px;\n cursor: pointer;\n user-select: none;\n padding: 2px 4px;\n margin: 0px 4px;\n border-radius: 3px;\n}\n</style>\n<div id="root" part="root"></div>\n<slot name="copy-button" part="copy-button" hidden>\n <span class="jv-copy"></span>\n</slot>\n';class t extends HTMLElement{constructor(){super(),this._value=null,this.root=this.attachShadow({mode:"open"}),this.root.appendChild(e.content.cloneNode(!0)),this.container=this.root.getElementById("root")}static get observedAttributes(){return["value","expand-depth","copyable","sort","boxed","theme","parse"]}connectedCallback(){this.render()}attributeChangedCallback(){this.render()}set value(e){e!==this._value&&(this._value=e,this.render())}get value(){return this._value??this.getAttribute("value")}set sort(e){this.setAttribute("sort",e?"true":"false")}get sort(){return this.hasAttribute("sort")}set parse(e){this.setAttribute("parse",e?"true":"false")}get parse(){return"false"!==this.getAttribute("parse")}set copyable(e){!1===e?this.removeAttribute("copyable"):this.setAttribute("copyable",JSON.stringify(e))}get copyable(){if(!this.hasAttribute("copyable"))return!1;const e=this.getAttribute("copyable"),t={copyText:"Copy",copiedText:"Copied",timeout:2e3,align:"right"};if(""===e||null===e)return t;try{return{...t,...JSON.parse(e)}}catch{return t}}get expandDepth(){return Number(this.getAttribute("expand-depth")??1)}copyText(e){return navigator.clipboard?(this.copyText=e=>navigator.clipboard.writeText(e),this.copyText(e)):(this.copyText=e=>new Promise((t,n)=>{const o=document.createElement("input");o.value=e,document.body.appendChild(o),o.select(),document.execCommand("copy")?(document.body.removeChild(o),t()):(document.body.removeChild(o),n(new Error("Copy failed")))}),this.copyText(e))}render(){if("string"==typeof this.value&&this.parse)try{this._value=JSON.parse(this.value)}catch{}this.container.innerHTML="",this.container.appendChild(this.build(this._value,0));const e=this.copyable;if(e){const t=e.align||"right",n=this.root.querySelector('slot[name="copy-button"]'),o=n.assignedElements()[0],r=this.root.querySelector(".jv-copy");if(n.hidden=!1,n.className=`align-${t}`,!o){let t;r.textContent=e.copyText;const n=r.cloneNode(!0);r.replaceWith(n),n.textContent=e.copyText,n.addEventListener("click",()=>{const o=JSON.stringify(this._value,null,2);this.copyText(o).then(()=>{n.textContent=e.copiedText,t=window.setTimeout(()=>{n.textContent=e.copyText,clearTimeout(t)},e.timeout),this.dispatchEvent(new CustomEvent("copy-success",{detail:{text:o,options:e}}))}).catch(()=>{console.warn("Failed to copy text to clipboard"),this.dispatchEvent(new CustomEvent("copy-error",{detail:{text:o,options:e}}))})})}}}build(e,t){if(null===e)return this.leaf("null","jv-null");if(void 0===e)return this.leaf("undefined","jv-undefined");if("boolean"==typeof e)return this.leaf(String(e),"jv-boolean");if("number"==typeof e)return this.leaf(String(e),"jv-number");if("string"==typeof e)return this.leaf(`"${e}"`,"jv-string");if("function"==typeof e)return this.leaf("<function>","jv-function");if(e instanceof RegExp)return this.leaf("<regexp>","jv-regexp");if(e instanceof Date)return this.leaf(`"${e.toLocaleString()}"`,"jv-string");const n=Array.isArray(e),o=document.createElement("span");o.className="jv-node",o.setAttribute("part","node");const r=document.createElement("div");r.className="jv-list",r.setAttribute("part","list");const s=n?this.sort?[...e.keys()].sort((e,t)=>e-t):[...e.keys()]:this.sort?Object.keys(e).sort():Object.keys(e);for(const l of s){const o=document.createElement("div"),s=this.build(e[l],t+1);if(o.className="jv-item",s instanceof Element&&s.classList.contains("jv-node")){const e=s.querySelector(".jv-toggle");e&&(e.remove(),o.append(e))}if(!n){const e=document.createElement("span");e.className="jv-key",e.setAttribute("part","key"),e.textContent=`"${l}": `,o.append(e)}o.append(s),r.append(o)}const i=document.createElement("span");i.className="jv-toggle",i.setAttribute("part","toggle"),o.classList.contains("collapsed")||i.classList.add("open"),i.addEventListener("click",()=>{o.classList.toggle("collapsed"),i.classList.toggle("open"),this.dispatchEvent(new CustomEvent("toggle",{detail:{node:o,data:e,isCollapsed:o.classList.contains("collapsed")}}))});const a=document.createElement("span");return a.className="jv-ellipsis",a.setAttribute("part","ellipsis"),a.textContent=`...${s.length}`,a.addEventListener("click",()=>{o.classList.remove("collapsed"),i.classList.add("open")}),t>=this.expandDepth&&(o.classList.add("collapsed"),i.classList.remove("open")),s.length||o.classList.add("empty"),o.append(i,n?"[":"{",a,r,n?"]":"}"),o}leaf(e,t){const n=document.createElement("span");return n.className=`jv-value ${t}`,n.setAttribute("part",`value ${t.replace("jv-","")}`),n.textContent=e,n}}customElements.define("json-viewer",t);export{t as JsonViewerElement};
@@ -1 +1 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).JsonViewerElement={})}(this,function(e){"use strict";const t=document.createElement("template");t.innerHTML='\n<style>\n/* Light Theme (default) */\n:host {\n --jv-bg-color: #ffffff;\n --jv-border-color: #ddd;\n --jv-text-color: #111;\n --jv-key-color: #111;\n --jv-string-color: #42b983;\n --jv-number-color: #fc1e70;\n --jv-boolean-color: #fc1e70;\n --jv-null-color: #e08331;\n --jv-undefined-color: #b0b0b0;\n --jv-function-color: #067bca;\n --jv-regexp-color: #fc1e70;\n --jv-copy-bg: #eee;\n --jv-copy-text: #333;\n --jv-ellipsis-color: #999999;\n --jv-ellipsis-bg: #eeeeee;\n --jv-hover-shadow: rgba(0,0,0,0.1);\n}\n\n/* Dark Theme */\n:host([theme="dark"]) {\n --jv-bg-color: #23272f;\n --jv-border-color: #2c313a;\n --jv-text-color: #d4d4d4;\n --jv-key-color: #79c0ff;\n --jv-string-color: #a5d6a7;\n --jv-number-color: #e2b86b;\n --jv-boolean-color: #ff7b72;\n --jv-null-color: #ffab70;\n --jv-undefined-color: #d2a8ff;\n --jv-function-color: #c678dd;\n --jv-regexp-color: #56b6c2;\n --jv-copy-bg: #3a3f4b;\n --jv-copy-text: #fff;\n --jv-ellipsis-color: #6e7681;\n --jv-ellipsis-bg: #2c313a;\n --jv-hover-shadow: rgba(0,0,0,0.4);\n}\n\n:host {\n display: block;\n width: 100%;\n max-width: 100%;\n font-family: Consolas, Menlo, Courier, monospace;\n font-size: 14px;\n padding: 8px;\n overflow-x: auto;\n box-sizing: border-box;\n position: relative;\n background-color: var(--jv-bg-color);\n color: var(--jv-text-color);\n}\n\n:host([boxed]) {\n border: 1px solid var(--jv-border-color);\n border-radius: 4px;\n padding: 16px;\n transition: box-shadow 0.2s ease;\n}\n:host([boxed]:hover) {\n box-shadow: 0 2px 8px var(--jv-hover-shadow);\n}\n#root:has(+.align-left) {\n margin-top: 16px;\n}\n.jv-copy {\n cursor: pointer;\n font-size: 12px;\n background: var(--jv-copy-bg);\n color: var(--jv-copy-text);\n padding: 4px 8px;\n border-radius: 3px;\n opacity: 0;\n transition: opacity 0.2s ease;\n}\n:host(:hover) .jv-copy {\n opacity: 1;\n}\nslot[name="copy-button"] {\n position: absolute;\n top: 8px;\n right: 8px;\n z-index: 10;\n opacity: 0;\n transition: opacity 0.2s ease;\n display: block !important;\n}\nslot[name="copy-button"][hidden] {\n display: none !important;\n}\nslot[name="copy-button"].align-left {\n left: 8px;\n right: auto;\n}\nslot[name="copy-button"].align-right {\n right: 8px;\n left: auto;\n}\n:host(:hover) slot[name="copy-button"] {\n opacity: 1;\n}\n.jv-toggle {\n background-image: url("data:image/svg+xml;charset=utf-8;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMCAwIDggOC04IDh6IiBmaWxsPSIjNjY2Ii8+PC9zdmc+");\n background-repeat: no-repeat;\n background-size: contain;\n background-position: center center;\n cursor: pointer;\n width: 10px;\n height: 10px;\n margin-right: 2px;\n display: inline-block;\n transition: rotate .1s;\n}\n.jv-toggle.open {\n rotate: 90deg;\n}\n.jv-key {\n color: var(--jv-key-color);\n}\n.jv-string {\n color: var(--jv-string-color);\n}\n.jv-number {\n color: var(--jv-number-color);\n}\n.jv-boolean {\n color: var(--jv-boolean-color);\n}\n.jv-null {\n color: var(--jv-null-color);\n}\n.jv-undefined {\n color: var(--jv-undefined-color);\n}\n.jv-function {\n color: var(--jv-function-color);\n}\n.jv-regexp {\n color: var(--jv-regexp-color);\n}\n.jv-list {\n margin-left: 16px;\n}\n.jv-item:not(:has(.jv-toggle)) .jv-key {\n margin-left: 12px;\n}\n.jv-item:not(:last-child):after {\n content: \',\';\n}\n.jv-node > .jv-ellipsis {\n display: none;\n}\n.jv-node.empty > .jv-list {\n display: inline-block;\n margin-inline: 4px;\n}\n.jv-node.collapsed > .jv-list,\n.jv-node.collapsed.empty > .jv-ellipsis {\n display: none;\n}\n.jv-node.collapsed > .jv-ellipsis {\n color: var(--jv-ellipsis-color);\n background-color: var(--jv-ellipsis-bg);\n display: inline-block;\n line-height: 0.9;\n font-size: 0.85em;\n vertical-align: 2px;\n cursor: pointer;\n user-select: none;\n padding: 2px 4px;\n margin: 0px 4px;\n border-radius: 3px;\n}\n</style>\n<div id="root" part="root"></div>\n<slot name="copy-button" part="copy-button" hidden>\n <span class="jv-copy"></span>\n</slot>\n';class n extends HTMLElement{constructor(){super(),this._value=null,this.root=this.attachShadow({mode:"open"}),this.root.appendChild(t.content.cloneNode(!0)),this.container=this.root.getElementById("root")}static get observedAttributes(){return["value","expand-depth","copyable","sort","boxed","theme","parse"]}connectedCallback(){this.render()}attributeChangedCallback(){this.render()}set value(e){e!==this._value&&(this._value=e,this.render())}get value(){return this._value??this.getAttribute("value")}get expandDepth(){return Number(this.getAttribute("expand-depth")??1)}get sort(){return this.hasAttribute("sort")}get theme(){return this.getAttribute("theme")||"light"}get parse(){return"false"!==this.getAttribute("parse")}get copyable(){if(!this.hasAttribute("copyable"))return!1;const e=this.getAttribute("copyable"),t={copyText:"Copy",copiedText:"Copied",timeout:2e3,align:"right"};if(""===e||null===e)return t;try{return{...t,...JSON.parse(e)}}catch{return t}}copyText(e){return navigator.clipboard?(this.copyText=e=>navigator.clipboard.writeText(e),this.copyText(e)):(this.copyText=e=>new Promise((t,n)=>{const o=document.createElement("input");o.value=e,document.body.appendChild(o),o.select(),document.execCommand("copy")?(document.body.removeChild(o),t()):(document.body.removeChild(o),n(new Error("Copy failed")))}),this.copyText(e))}render(){if("string"==typeof this.value&&this.parse)try{this._value=JSON.parse(this.value)}catch{}this.container.innerHTML="",this.container.appendChild(this.build(this._value,0));const e=this.copyable;if(e){const t=e.align||"right",n=this.root.querySelector('slot[name="copy-button"]'),o=n.assignedElements()[0],r=this.root.querySelector(".jv-copy");if(n.hidden=!1,n.className=`align-${t}`,!o){let t;r.textContent=e.copyText;const n=r.cloneNode(!0);r.replaceWith(n),n.textContent=e.copyText,n.addEventListener("click",()=>{const o=JSON.stringify(this._value,null,2);this.copyText(o).then(()=>{n.textContent=e.copiedText,t=window.setTimeout(()=>{n.textContent=e.copyText,clearTimeout(t)},e.timeout),this.dispatchEvent(new CustomEvent("copy-success",{detail:{text:o,options:e}}))}).catch(()=>{console.warn("Failed to copy text to clipboard"),this.dispatchEvent(new CustomEvent("copy-error",{detail:{text:o,options:e}}))})})}}}build(e,t){if(null===e)return this.leaf("null","jv-null");if(void 0===e)return this.leaf("undefined","jv-undefined");if("boolean"==typeof e)return this.leaf(String(e),"jv-boolean");if("number"==typeof e)return this.leaf(String(e),"jv-number");if("string"==typeof e)return this.leaf(`"${e}"`,"jv-string");if("function"==typeof e)return this.leaf("<function>","jv-function");if(e instanceof RegExp)return this.leaf("<regexp>","jv-regexp");if(e instanceof Date)return this.leaf(`"${e.toLocaleString()}"`,"jv-string");const n=Array.isArray(e),o=document.createElement("span");o.className="jv-node",o.setAttribute("part","node");const r=document.createElement("div");r.className="jv-list",r.setAttribute("part","list");const i=n?this.sort?[...e.keys()].sort((e,t)=>e-t):[...e.keys()]:this.sort?Object.keys(e).sort():Object.keys(e);for(const l of i){const o=document.createElement("div"),i=this.build(e[l],t+1);if(o.className="jv-item",i instanceof Element&&i.classList.contains("jv-node")){const e=i.querySelector(".jv-toggle");e&&(e.remove(),o.append(e))}if(!n){const e=document.createElement("span");e.className="jv-key",e.setAttribute("part","key"),e.textContent=`"${l}": `,o.append(e)}o.append(i),r.append(o)}const s=document.createElement("span");s.className="jv-toggle",s.setAttribute("part","toggle"),o.classList.contains("collapsed")||s.classList.add("open"),s.addEventListener("click",()=>{o.classList.toggle("collapsed"),s.classList.toggle("open"),this.dispatchEvent(new CustomEvent("toggle",{detail:{node:o,data:e,isCollapsed:o.classList.contains("collapsed")}}))});const a=document.createElement("span");return a.className="jv-ellipsis",a.setAttribute("part","ellipsis"),a.textContent=`...${i.length}`,a.addEventListener("click",()=>{o.classList.remove("collapsed"),s.classList.add("open")}),t>=this.expandDepth&&(o.classList.add("collapsed"),s.classList.remove("open")),i.length||o.classList.add("empty"),o.append(s,n?"[":"{",a,r,n?"]":"}"),o}leaf(e,t){const n=document.createElement("span");return n.className=`jv-value ${t}`,n.setAttribute("part",`value ${t.replace("jv-","")}`),n.textContent=e,n}}customElements.define("json-viewer",n),e.JsonViewerElement=n,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).JsonViewerElement={})}(this,function(e){"use strict";const t=document.createElement("template");t.innerHTML='\n<style>\n/* Light Theme (default) */\n:host {\n --jv-bg-color: #ffffff;\n --jv-border-color: #ddd;\n --jv-text-color: #111;\n --jv-key-color: #111;\n --jv-string-color: #42b983;\n --jv-number-color: #fc1e70;\n --jv-boolean-color: #fc1e70;\n --jv-null-color: #e08331;\n --jv-undefined-color: #b0b0b0;\n --jv-function-color: #067bca;\n --jv-regexp-color: #fc1e70;\n --jv-copy-bg: #eee;\n --jv-copy-text: #333;\n --jv-ellipsis-color: #999999;\n --jv-ellipsis-bg: #eeeeee;\n --jv-hover-shadow: rgba(0,0,0,0.1);\n}\n\n/* Dark Theme */\n:host([theme="dark"]) {\n --jv-bg-color: #23272f;\n --jv-border-color: #2c313a;\n --jv-text-color: #d4d4d4;\n --jv-key-color: #79c0ff;\n --jv-string-color: #a5d6a7;\n --jv-number-color: #e2b86b;\n --jv-boolean-color: #ff7b72;\n --jv-null-color: #ffab70;\n --jv-undefined-color: #d2a8ff;\n --jv-function-color: #c678dd;\n --jv-regexp-color: #56b6c2;\n --jv-copy-bg: #3a3f4b;\n --jv-copy-text: #fff;\n --jv-ellipsis-color: #6e7681;\n --jv-ellipsis-bg: #2c313a;\n --jv-hover-shadow: rgba(0,0,0,0.4);\n}\n\n:host {\n display: block;\n width: 100%;\n max-width: 100%;\n font-family: Consolas, Menlo, Courier, monospace;\n font-size: 14px;\n padding: 8px;\n overflow-x: auto;\n box-sizing: border-box;\n position: relative;\n background-color: var(--jv-bg-color);\n color: var(--jv-text-color);\n}\n\n:host([boxed]) {\n border: 1px solid var(--jv-border-color);\n border-radius: 4px;\n padding: 16px;\n transition: box-shadow 0.2s ease;\n}\n:host([boxed]:hover) {\n box-shadow: 0 2px 8px var(--jv-hover-shadow);\n}\n#root:has(+.align-left) {\n margin-top: 16px;\n}\n.jv-copy {\n cursor: pointer;\n font-size: 12px;\n background: var(--jv-copy-bg);\n color: var(--jv-copy-text);\n padding: 4px 8px;\n border-radius: 3px;\n opacity: 0;\n transition: opacity 0.2s ease;\n}\n:host(:hover) .jv-copy {\n opacity: 1;\n}\nslot[name="copy-button"] {\n position: absolute;\n top: 8px;\n right: 8px;\n z-index: 10;\n opacity: 0;\n transition: opacity 0.2s ease;\n display: block !important;\n}\nslot[name="copy-button"][hidden] {\n display: none !important;\n}\nslot[name="copy-button"].align-left {\n left: 8px;\n right: auto;\n}\nslot[name="copy-button"].align-right {\n right: 8px;\n left: auto;\n}\n:host(:hover) slot[name="copy-button"] {\n opacity: 1;\n}\n.jv-toggle {\n background-image: url("data:image/svg+xml;charset=utf-8;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMCAwIDggOC04IDh6IiBmaWxsPSIjNjY2Ii8+PC9zdmc+");\n background-repeat: no-repeat;\n background-size: contain;\n background-position: center center;\n cursor: pointer;\n width: 10px;\n height: 10px;\n margin-right: 2px;\n display: inline-block;\n transition: rotate .1s;\n}\n.jv-toggle.open {\n rotate: 90deg;\n}\n.jv-key {\n color: var(--jv-key-color);\n}\n.jv-string {\n color: var(--jv-string-color);\n}\n.jv-number {\n color: var(--jv-number-color);\n}\n.jv-boolean {\n color: var(--jv-boolean-color);\n}\n.jv-null {\n color: var(--jv-null-color);\n}\n.jv-undefined {\n color: var(--jv-undefined-color);\n}\n.jv-function {\n color: var(--jv-function-color);\n}\n.jv-regexp {\n color: var(--jv-regexp-color);\n}\n.jv-list {\n margin-left: 16px;\n}\n.jv-item:not(:has(.jv-toggle)) .jv-key {\n margin-left: 12px;\n}\n.jv-item:not(:last-child):after {\n content: \',\';\n}\n.jv-node > .jv-ellipsis {\n display: none;\n}\n.jv-node.empty > .jv-list {\n display: inline-block;\n margin-inline: 4px;\n}\n.jv-node.collapsed > .jv-list,\n.jv-node.collapsed.empty > .jv-ellipsis {\n display: none;\n}\n.jv-node.collapsed > .jv-ellipsis {\n color: var(--jv-ellipsis-color);\n background-color: var(--jv-ellipsis-bg);\n display: inline-block;\n line-height: 0.9;\n font-size: 0.85em;\n vertical-align: 2px;\n cursor: pointer;\n user-select: none;\n padding: 2px 4px;\n margin: 0px 4px;\n border-radius: 3px;\n}\n</style>\n<div id="root" part="root"></div>\n<slot name="copy-button" part="copy-button" hidden>\n <span class="jv-copy"></span>\n</slot>\n';class n extends HTMLElement{constructor(){super(),this._value=null,this.root=this.attachShadow({mode:"open"}),this.root.appendChild(t.content.cloneNode(!0)),this.container=this.root.getElementById("root")}static get observedAttributes(){return["value","expand-depth","copyable","sort","boxed","theme","parse"]}connectedCallback(){this.render()}attributeChangedCallback(){this.render()}set value(e){e!==this._value&&(this._value=e,this.render())}get value(){return this._value??this.getAttribute("value")}set sort(e){this.setAttribute("sort",e?"true":"false")}get sort(){return this.hasAttribute("sort")}set parse(e){this.setAttribute("parse",e?"true":"false")}get parse(){return"false"!==this.getAttribute("parse")}set copyable(e){!1===e?this.removeAttribute("copyable"):this.setAttribute("copyable",JSON.stringify(e))}get copyable(){if(!this.hasAttribute("copyable"))return!1;const e=this.getAttribute("copyable"),t={copyText:"Copy",copiedText:"Copied",timeout:2e3,align:"right"};if(""===e||null===e)return t;try{return{...t,...JSON.parse(e)}}catch{return t}}get expandDepth(){return Number(this.getAttribute("expand-depth")??1)}copyText(e){return navigator.clipboard?(this.copyText=e=>navigator.clipboard.writeText(e),this.copyText(e)):(this.copyText=e=>new Promise((t,n)=>{const o=document.createElement("input");o.value=e,document.body.appendChild(o),o.select(),document.execCommand("copy")?(document.body.removeChild(o),t()):(document.body.removeChild(o),n(new Error("Copy failed")))}),this.copyText(e))}render(){if("string"==typeof this.value&&this.parse)try{this._value=JSON.parse(this.value)}catch{}this.container.innerHTML="",this.container.appendChild(this.build(this._value,0));const e=this.copyable;if(e){const t=e.align||"right",n=this.root.querySelector('slot[name="copy-button"]'),o=n.assignedElements()[0],r=this.root.querySelector(".jv-copy");if(n.hidden=!1,n.className=`align-${t}`,!o){let t;r.textContent=e.copyText;const n=r.cloneNode(!0);r.replaceWith(n),n.textContent=e.copyText,n.addEventListener("click",()=>{const o=JSON.stringify(this._value,null,2);this.copyText(o).then(()=>{n.textContent=e.copiedText,t=window.setTimeout(()=>{n.textContent=e.copyText,clearTimeout(t)},e.timeout),this.dispatchEvent(new CustomEvent("copy-success",{detail:{text:o,options:e}}))}).catch(()=>{console.warn("Failed to copy text to clipboard"),this.dispatchEvent(new CustomEvent("copy-error",{detail:{text:o,options:e}}))})})}}}build(e,t){if(null===e)return this.leaf("null","jv-null");if(void 0===e)return this.leaf("undefined","jv-undefined");if("boolean"==typeof e)return this.leaf(String(e),"jv-boolean");if("number"==typeof e)return this.leaf(String(e),"jv-number");if("string"==typeof e)return this.leaf(`"${e}"`,"jv-string");if("function"==typeof e)return this.leaf("<function>","jv-function");if(e instanceof RegExp)return this.leaf("<regexp>","jv-regexp");if(e instanceof Date)return this.leaf(`"${e.toLocaleString()}"`,"jv-string");const n=Array.isArray(e),o=document.createElement("span");o.className="jv-node",o.setAttribute("part","node");const r=document.createElement("div");r.className="jv-list",r.setAttribute("part","list");const s=n?this.sort?[...e.keys()].sort((e,t)=>e-t):[...e.keys()]:this.sort?Object.keys(e).sort():Object.keys(e);for(const l of s){const o=document.createElement("div"),s=this.build(e[l],t+1);if(o.className="jv-item",s instanceof Element&&s.classList.contains("jv-node")){const e=s.querySelector(".jv-toggle");e&&(e.remove(),o.append(e))}if(!n){const e=document.createElement("span");e.className="jv-key",e.setAttribute("part","key"),e.textContent=`"${l}": `,o.append(e)}o.append(s),r.append(o)}const i=document.createElement("span");i.className="jv-toggle",i.setAttribute("part","toggle"),o.classList.contains("collapsed")||i.classList.add("open"),i.addEventListener("click",()=>{o.classList.toggle("collapsed"),i.classList.toggle("open"),this.dispatchEvent(new CustomEvent("toggle",{detail:{node:o,data:e,isCollapsed:o.classList.contains("collapsed")}}))});const a=document.createElement("span");return a.className="jv-ellipsis",a.setAttribute("part","ellipsis"),a.textContent=`...${s.length}`,a.addEventListener("click",()=>{o.classList.remove("collapsed"),i.classList.add("open")}),t>=this.expandDepth&&(o.classList.add("collapsed"),i.classList.remove("open")),s.length||o.classList.add("empty"),o.append(i,n?"[":"{",a,r,n?"]":"}"),o}leaf(e,t){const n=document.createElement("span");return n.className=`jv-value ${t}`,n.setAttribute("part",`value ${t.replace("jv-","")}`),n.textContent=e,n}}customElements.define("json-viewer",n),e.JsonViewerElement=n,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "json-viewer-element",
3
3
  "type": "module",
4
- "version": "1.0.0",
4
+ "version": "1.0.2",
5
5
  "packageManager": "pnpm@10.18.2",
6
6
  "description": "A custom element for viewing and interacting with JSON data.",
7
7
  "author": "Lruihao (https://lruihao.cn)",
@@ -33,7 +33,7 @@
33
33
  "version": "auto-changelog-plus -p && git add CHANGELOG.md"
34
34
  },
35
35
  "devDependencies": {
36
- "@antfu/eslint-config": "^5.4.1",
36
+ "@antfu/eslint-config": "^6.2.0",
37
37
  "auto-changelog-plus": "^1.2.1",
38
38
  "eslint": "^9.36.0",
39
39
  "terser": "^5.44.0",