json-viewer-element 1.0.0 → 1.0.1

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
@@ -55,6 +55,8 @@ Set value by attribute:
55
55
 
56
56
  Use in Vue framework:
57
57
 
58
+ Vue 2/3 Options API:
59
+
58
60
  ```vue
59
61
  <template>
60
62
  <json-viewer :value="JSON.stringify(json)" boxed copyable sort expand-depth="2" theme="dark"></json-viewer>
@@ -71,6 +73,66 @@ export default {
71
73
  </script>
72
74
  ```
73
75
 
76
+ Vue 3 composition API:
77
+
78
+ ```vue
79
+ <script lang="ts" setup>
80
+ import { ref } from 'vue'
81
+ const json = ref({ hello: "world", arr: [1,2,3] })
82
+ </script>
83
+
84
+ <template>
85
+ <json-viewer :value="JSON.stringify(json)" boxed copyable sort expand-depth="2" theme="dark"></json-viewer>
86
+ </template>
87
+ ```
88
+
89
+ > [!TIP]
90
+ >
91
+ > [Skipping Component Resolution](https://vuejs.org/guide/extras/web-components.html#skipping-component-resolution)
92
+ >
93
+ > 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).
94
+
95
+ ```js
96
+ // vite.config.js
97
+ import vue from '@vitejs/plugin-vue'
98
+ import vueJsx from '@vitejs/plugin-vue-jsx'
99
+
100
+ export default {
101
+ plugins: [
102
+ vue({
103
+ template: {
104
+ compilerOptions: {
105
+ // treat all tags with a dash as custom elements
106
+ isCustomElement: tag => tag.includes('-')
107
+ }
108
+ }
109
+ }),
110
+ vueJsx({
111
+ // treat all tags with a dash as custom elements
112
+ isCustomElement: tag => tag.includes('-')
113
+ }),
114
+ ]
115
+ }
116
+ ```
117
+
118
+ If you're using ESLint with Vue, you may need to configure it to ignore the custom element:
119
+
120
+ ```js
121
+ // eslint.config.js
122
+ export default {
123
+ rules: {
124
+ 'vue/component-name-in-template-casing': [
125
+ 'warn',
126
+ 'PascalCase',
127
+ {
128
+ registeredComponentsOnly: false,
129
+ ignores: ['/^icon-/', 'json-viewer'],
130
+ },
131
+ ],
132
+ },
133
+ }
134
+ ```
135
+
74
136
  ## Props
75
137
 
76
138
  > [!TIP]
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  - 🔑 **排序**:支持键排序
12
12
  - 🔍 **展开深度**:可控初始展开层级
13
13
  - 🧩 **自定义复制按钮**:slot 插槽支持
14
- - 🧬 **类型高亮**:丰富多彩的类型高亮
14
+ - 🧬 **类型高亮**:多种类型高亮
15
15
  - 🛠️ **自定义事件**:支持 copy/toggle 事件监听
16
16
 
17
17
  ## 使用方法
@@ -55,6 +55,8 @@ import 'json-viewer-element'
55
55
 
56
56
  在 Vue 框架中使用:
57
57
 
58
+ Vue 2/3 选项式 API:
59
+
58
60
  ```vue
59
61
  <template>
60
62
  <json-viewer :value="JSON.stringify(json)" boxed copyable sort expand-depth="2" theme="dark"></json-viewer>
@@ -71,10 +73,70 @@ export default {
71
73
  </script>
72
74
  ```
73
75
 
76
+ Vue 3 组合式 API:
77
+
78
+ ```vue
79
+ <script lang="ts" setup>
80
+ import { ref } from 'vue'
81
+ const json = ref({ hello: "world", arr: [1,2,3] })
82
+ </script>
83
+
84
+ <template>
85
+ <json-viewer :value="JSON.stringify(json)" boxed copyable sort expand-depth="2" theme="dark"></json-viewer>
86
+ </template>
87
+ ```
88
+
89
+ > [!TIP]
90
+ >
91
+ > [跳过组件解析](https://cn.vuejs.org/guide/extras/web-components.html#skipping-component-resolution)
92
+ >
93
+ > 为了让 Vue 知道某些元素应被视为自定义元素并跳过组件解析,我们可以指定 [`compilerOptions.isCustomElement` 选项](https://cn.vuejs.org/api/application.html#app-config-compileroptions)。
94
+
95
+ ```js
96
+ // vite.config.js
97
+ import vue from '@vitejs/plugin-vue'
98
+ import vueJsx from '@vitejs/plugin-vue-jsx'
99
+
100
+ export default {
101
+ plugins: [
102
+ vue({
103
+ template: {
104
+ compilerOptions: {
105
+ // 将所有带短横线的标签名都视为自定义元素
106
+ isCustomElement: tag => tag.includes('-')
107
+ }
108
+ }
109
+ }),
110
+ vueJsx({
111
+ // 将所有带短横线的标签名都视为自定义元素
112
+ isCustomElement: tag => tag.includes('-')
113
+ }),
114
+ ]
115
+ }
116
+ ```
117
+
118
+ 如果你在 Vue 中使用 ESLint,可能需要配置忽略自定义元素:
119
+
120
+ ```js
121
+ // eslint.config.js
122
+ export default {
123
+ rules: {
124
+ 'vue/component-name-in-template-casing': [
125
+ 'warn',
126
+ 'PascalCase',
127
+ {
128
+ registeredComponentsOnly: false,
129
+ ignores: ['/^icon-/', 'json-viewer'],
130
+ },
131
+ ],
132
+ },
133
+ }
134
+ ```
135
+
74
136
  ## 属性
75
137
 
76
138
  > [!TIP]
77
- > 在 Vue 等框架中使用时,value 和 copyable 对象的值需要转成字符串传入。
139
+ > 在 Vue 等框架中使用时,value 和 copyable 属性的值需要转成字符串传入。
78
140
 
79
141
  | 属性 | 类型 | 默认值 | 说明 |
80
142
  | :----------- | :----------------------------------------- | :------ | :----------------------------------------- |
@@ -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
  */
@@ -207,7 +207,6 @@ class JsonViewerElement extends HTMLElement {
207
207
  attributeChangedCallback() {
208
208
  this.render();
209
209
  }
210
- // ----- Public property: value -----
211
210
  set value(v) {
212
211
  if (v === this._value)
213
212
  return;
@@ -217,19 +216,25 @@ class JsonViewerElement extends HTMLElement {
217
216
  get value() {
218
217
  return this._value ?? this.getAttribute("value");
219
218
  }
220
- // ----- Private getters for props -----
221
- get expandDepth() {
222
- return Number(this.getAttribute("expand-depth") ?? 1);
219
+ set sort(v) {
220
+ this.setAttribute("sort", v ? "true" : "false");
223
221
  }
224
222
  get sort() {
225
223
  return this.hasAttribute("sort");
226
224
  }
227
- get theme() {
228
- return this.getAttribute("theme") || "light";
225
+ set parse(v) {
226
+ this.setAttribute("parse", v ? "true" : "false");
229
227
  }
230
228
  get parse() {
231
229
  return this.getAttribute("parse") !== "false";
232
230
  }
231
+ set copyable(v) {
232
+ if (v === false) {
233
+ this.removeAttribute("copyable");
234
+ } else {
235
+ this.setAttribute("copyable", JSON.stringify(v));
236
+ }
237
+ }
233
238
  get copyable() {
234
239
  if (!this.hasAttribute("copyable"))
235
240
  return false;
@@ -246,6 +251,9 @@ class JsonViewerElement extends HTMLElement {
246
251
  return defaultCopyableOptions;
247
252
  }
248
253
  }
254
+ get expandDepth() {
255
+ return Number(this.getAttribute("expand-depth") ?? 1);
256
+ }
249
257
  /**
250
258
  * Copy text to clipboard. Uses Clipboard API if available, otherwise fallback.
251
259
  */
@@ -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.1",
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",