@anilkumarthakur/vue3-json-viewer
Version:
A beautiful, customizable JSON viewer component for Vue 3 with TypeScript support, dark/light mode, expand/collapse controls, and syntax highlighting.
570 lines (569 loc) • 13.7 kB
JavaScript
import { createTextVNode as e, createVNode as t, defineComponent as n, inject as r, isVNode as i, provide as a, reactive as o, ref as s, watch as c } from "vue";
//#region src/utils/type-guards.ts
var l = (e) => e === null, u = (e) => e === void 0, d = (e) => typeof e == "string", f = (e) => typeof e == "number", p = (e) => typeof e == "boolean", m = (e) => e instanceof Date, h = (e) => e instanceof RegExp, g = (e) => Array.isArray(e), _ = (e) => typeof e == "object" && !!e && !Array.isArray(e) && !(e instanceof Date) && !(e instanceof RegExp), v = {
null: "#f38ba8",
string: "#a6e3a1",
number: "#fab387",
boolean: "#f9e2af",
date: "#94e2d5",
regexp: "#cba6f7",
arrayEmpty: "#9399b2",
array: "#89b4fa",
objectEmpty: "#9399b2",
object: "#f5c2e7",
default: "#cdd6f4"
}, y = {
null: "#e03131",
string: "#2f9e44",
number: "#e8590c",
boolean: "#f59f00",
date: "#0c8599",
regexp: "#7048e8",
arrayEmpty: "#868e96",
array: "#1971c2",
objectEmpty: "#868e96",
object: "#c2255c",
default: "#343a40"
}, b = [
"#f38ba8",
"#fab387",
"#f9e2af",
"#a6e3a1",
"#89dceb",
"#cba6f7"
], x = [
"#e03131",
"#e8590c",
"#f59f00",
"#2f9e44",
"#1098ad",
"#7048e8"
], S = (e, t) => {
let n = t ? v : y;
return l(e) || u(e) ? n.null : d(e) ? n.string : f(e) ? n.number : p(e) ? n.boolean : m(e) ? n.date : h(e) ? n.regexp : g(e) ? e.length === 0 ? n.arrayEmpty : n.array : _(e) ? Object.keys(e).length === 0 ? n.objectEmpty : n.object : n.default;
}, C = (e, t) => {
let n = t ? b : x;
return n[e % n.length] ?? n[0] ?? "#cdd6f4";
}, w = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
function T(e, t, n) {
return n ? `${e}[${t}]` : w.test(t) ? `${e}.${t}` : `${e}[${JSON.stringify(t)}]`;
}
var E = Symbol("json-viewer-context");
function D(e) {
let { defaultExpanded: t, onToggle: n, onCopy: r } = e, i = o(/* @__PURE__ */ new Map()), a = (e) => {
let n = i.get(e);
return n === void 0 ? t.value : n;
}, s = (e, t, r) => {
i.set(e, r), n?.({
path: e,
key: t,
expanded: r
});
}, c = (e, t) => {
s(e, t, !a(e));
}, l = (e) => {
i.clear(), t.value = e;
};
return {
isExpanded: a,
setExpanded: s,
toggle: c,
expandAll: () => l(!0),
collapseAll: () => l(!1),
reset: l,
notifyCopy: (e, t, n) => {
r?.({
path: e,
key: t,
value: n
});
}
};
}
function O(e) {
a(E, e);
}
function k() {
return r(E, null);
}
//#endregion
//#region src/components/JsonViewer/components/RootControls.tsx
var A = () => t("svg", {
xmlns: "http://www.w3.org/2000/svg",
width: "14",
height: "14",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2"
}, [t("polyline", { points: "6 9 12 15 18 9" }, null)]), j = () => t("svg", {
xmlns: "http://www.w3.org/2000/svg",
width: "14",
height: "14",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2"
}, [t("polyline", { points: "18 15 12 9 6 15" }, null)]), M = /* @__PURE__ */ n({
name: "RootControls",
props: { darkMode: {
type: Boolean,
required: !0
} },
emits: ["expand-all", "collapse-all"],
setup(n, { emit: r }) {
let i = () => r("expand-all"), a = () => r("collapse-all"), o = (e) => ["jv-control-btn", e ? "jv-control-btn-dark" : "jv-control-btn-light"];
return () => t("div", { class: "jv-root-controls" }, [t("button", {
class: o(n.darkMode),
onClick: i,
title: "Expand All"
}, [t(A, null, null), e("Expand All")]), t("button", {
class: o(n.darkMode),
onClick: a,
title: "Collapse All"
}, [t(j, null, null), e("Collapse All")])]);
}
}), N = (e) => l(e) ? "null" : u(e) ? "undefined" : d(e) ? `"${e}"` : p(e) ? e ? "true" : "false" : f(e) ? String(e) : m(e) ? `"${e.toISOString()}"` : h(e) ? e.toString() : String(e), P = (e, t) => {
let n = t ? "-dark" : "-light";
return l(e) || u(e) ? `jv-null jv-null${n}` : d(e) ? `jv-string jv-string${n}` : f(e) ? `jv-number jv-number${n}` : p(e) ? `jv-boolean jv-boolean${n}` : m(e) ? `jv-date jv-date${n}` : h(e) ? `jv-regexp jv-regexp${n}` : t ? "jv-value-dark" : "jv-value-light";
}, F = (e) => e === "object" ? ["{", "}"] : ["[", "]"], I = (e, t, n) => e === "object" ? "obj" : t ? "empty" : String(n), L = (e, t) => {
let n = e === "object" ? "key" : "item";
return `${t} ${t === 1 ? n : `${n}s`}`;
}, R = (e) => {
if (typeof document > "u") return !1;
let t = document.createElement("textarea");
t.value = e, t.setAttribute("readonly", ""), t.style.position = "fixed", t.style.top = "-9999px", t.style.left = "-9999px", document.body.appendChild(t);
let n = document.getSelection(), r = n && n.rangeCount > 0 ? n.getRangeAt(0) : null;
t.select();
let i = !1;
try {
i = document.execCommand("copy") === !0;
} catch {}
return document.body.removeChild(t), n && r && (n.removeAllRanges(), n.addRange(r)), i;
}, z = async (e) => {
let t;
try {
let n = JSON.stringify(e, null, 2);
t = n === void 0 ? String(e) : n;
} catch {
return !1;
}
if (typeof navigator < "u" && navigator.clipboard && typeof navigator.clipboard.writeText == "function") try {
return await navigator.clipboard.writeText(t), !0;
} catch {}
return R(t);
}, B = 1500, V = () => t("svg", {
xmlns: "http://www.w3.org/2000/svg",
width: "14",
height: "14",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2"
}, [t("rect", {
x: "9",
y: "9",
width: "13",
height: "13",
rx: "2",
ry: "2"
}, null), t("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" }, null)]), H = () => t("svg", {
xmlns: "http://www.w3.org/2000/svg",
width: "14",
height: "14",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2"
}, [t("polyline", { points: "20 6 9 17 4 12" }, null)]), U = /* @__PURE__ */ n({
name: "CopyButton",
props: {
darkMode: {
type: Boolean,
required: !0
},
data: {
type: [
Object,
Array,
String,
Number,
Boolean,
Function
],
default: void 0
},
path: {
type: String,
default: "$"
},
parentKey: {
type: [String, Number],
default: ""
}
},
setup(e) {
let n = s(!1), r = k(), i = (t) => {
t.stopPropagation(), z(e.data).then((t) => {
t && (n.value = !0, r?.notifyCopy(e.path ?? "$", String(e.parentKey ?? ""), e.data), setTimeout(() => {
n.value = !1;
}, B));
});
};
return () => t("button", {
class: ["jv-copy-btn", e.darkMode ? "jv-copy-btn-dark" : "jv-copy-btn-light"],
onClick: i,
title: n.value ? "Copied!" : "Copy"
}, [n.value ? t(H, null, null) : t(V, null, null)]);
}
}), W = /* @__PURE__ */ n({
name: "TypeBadge",
props: { type: {
type: String,
required: !0
} },
setup(e, { slots: n }) {
return () => t("span", { class: ["jv-type-badge", e.type === "object" ? "jv-type-object" : "jv-type-array"] }, [n.default?.()]);
}
});
//#endregion
//#region src/components/JsonViewer/components/ContainerRow.tsx
function G(e) {
return typeof e == "function" || Object.prototype.toString.call(e) === "[object Object]" && !i(e);
}
var K = /* @__PURE__ */ n({
name: "ContainerRow",
props: {
parentKey: {
type: [String, Number],
required: !0
},
isArrayItem: {
type: Boolean,
required: !0
},
isEmpty: {
type: Boolean,
required: !0
},
isExpanded: {
type: Boolean,
required: !0
},
size: {
type: Number,
required: !0
},
darkMode: {
type: Boolean,
required: !0
},
keyColor: {
type: String,
required: !0
},
bracketColor: {
type: String,
required: !0
},
data: {
type: [
Object,
Array,
String,
Number,
Boolean
],
required: !0
},
kind: {
type: String,
required: !0
},
path: {
type: String,
required: !0
}
},
emits: ["toggle"],
setup(n, { emit: r }) {
let i = () => {
r("toggle");
};
return () => {
let [r, a] = F(n.kind), o = I(n.kind, n.isEmpty, n.size), s = L(n.kind, n.size), c = !n.isArrayItem && n.parentKey;
return t("div", { class: "jv-row" }, [
c && t("span", {
class: "jv-key jv-clickable",
style: { color: n.keyColor },
onClick: i
}, [
e("\""),
n.parentKey,
e("\"")
]),
c && t("span", {
class: "jv-colon jv-clickable",
onClick: i
}, [e(":")]),
t("span", {
class: "jv-toggle jv-type-indicator",
style: { color: n.bracketColor },
onClick: i
}, [t(W, { type: n.kind }, G(o) ? o : { default: () => [o] }), n.isEmpty ? t("span", null, [r, a]) : t("span", null, [n.isExpanded ? r : `${r}...${a}`])]),
!n.isEmpty && !n.isExpanded && t("span", {
class: ["jv-count", n.darkMode ? "jv-count-dark" : "jv-count-light"],
onClick: i
}, [s]),
t(U, {
darkMode: n.darkMode,
data: n.data,
path: n.path,
parentKey: n.parentKey
}, null)
]);
};
}
}), q = /* @__PURE__ */ n({
name: "PrimitiveValue",
props: {
data: {
type: [
Object,
Array,
String,
Number,
Boolean,
Function
],
default: void 0
},
parentKey: {
type: [String, Number],
required: !0
},
isArrayItem: {
type: Boolean,
required: !0
},
isLast: {
type: Boolean,
required: !0
},
darkMode: {
type: Boolean,
required: !0
},
keyColor: {
type: String,
required: !0
},
path: {
type: String,
required: !0
}
},
setup(n) {
return () => {
let r = !n.isArrayItem && n.parentKey;
return t("div", { class: "jv-item jv-primitive" }, [t("div", { class: "jv-row" }, [
r && t("span", {
class: "jv-key",
style: { color: n.keyColor }
}, [
e("\""),
n.parentKey,
e("\"")
]),
r && t("span", { class: "jv-colon" }, [e(": ")]),
t("span", { class: P(n.data, n.darkMode) }, [N(n.data)]),
!n.isLast && t("span", { class: "jv-comma" }, [e(",")]),
t(U, {
darkMode: n.darkMode,
data: n.data,
path: n.path,
parentKey: n.parentKey
}, null)
])]);
};
}
}), J = "$", Y = /* @__PURE__ */ n({
name: "JsonNode",
props: {
data: {
type: [
Object,
Array,
String,
Number,
Boolean,
Function
],
default: void 0
},
parentKey: {
type: [String, Number],
default: ""
},
level: {
type: Number,
default: 0
},
darkMode: {
type: Boolean,
default: !0
},
expanded: {
type: Boolean,
default: !0
},
isArrayItem: {
type: Boolean,
default: !1
},
isLast: {
type: Boolean,
default: !0
},
path: {
type: String,
default: J
}
},
setup(e) {
let n = k();
if (!n) {
let t = s(e.expanded ?? !0);
c(() => e.expanded, (e) => {
n?.reset(e ?? !0);
}), n = D({ defaultExpanded: t }), O(n);
}
let r = n, i = () => {
r.toggle(e.path ?? J, String(e.parentKey ?? ""));
}, a = (n, a, o) => {
let s = e.level ?? 0, c = e.darkMode ?? !0, l = e.path ?? J, u = C(s, c), d = a.length === 0, f = n === "array", p = r.isExpanded(l);
return t("div", { class: "jv-item" }, [
t(K, {
parentKey: e.parentKey ?? "",
isArrayItem: e.isArrayItem ?? !1,
isEmpty: d,
isExpanded: p,
size: a.length,
darkMode: c,
keyColor: S(e.data, c),
bracketColor: u,
data: e.data,
kind: n,
path: l,
onToggle: i
}, null),
p && !d && t("div", {
class: "jv-children",
style: { borderColor: u + "40" }
}, [a.map(([n, r], i) => {
let o = T(l, n, f);
return t(Y, {
key: n,
data: r,
parentKey: n,
level: s + 1,
darkMode: e.darkMode,
isArrayItem: f,
isLast: i === a.length - 1,
path: o
}, null);
})]),
p && !d && t("div", { class: "jv-row" }, [t("span", {
class: "jv-bracket",
style: { color: u }
}, [o, e.isLast ? "" : ","])])
]);
};
return () => {
let n = e.level ?? 0, i = e.darkMode ?? !0, o = n === 0, s = e.data, c = ["jv-node", {
"jv-root": o,
"jv-dark": i && o,
"jv-light": !i && o
}], l = o && (_(s) || g(s));
if (_(s)) {
let e = Object.entries(s);
return t("div", { class: c }, [l && t(M, {
darkMode: i,
"onExpand-all": r.expandAll,
"onCollapse-all": r.collapseAll
}, null), a("object", e, "}")]);
}
if (g(s)) {
let e = s.map((e, t) => [String(t), e]);
return t("div", { class: c }, [l && t(M, {
darkMode: i,
"onExpand-all": r.expandAll,
"onCollapse-all": r.collapseAll
}, null), a("array", e, "]")]);
}
return t("div", { class: c }, [t(q, {
data: s,
parentKey: e.parentKey ?? "",
isArrayItem: e.isArrayItem ?? !1,
isLast: e.isLast ?? !0,
darkMode: i,
keyColor: S(s, i),
path: e.path ?? J
}, null)]);
};
}
}), X = "$", Z = /* @__PURE__ */ n({
name: "JsonViewer",
props: {
data: {
type: [
Object,
Array,
String,
Number,
Boolean
],
default: () => ({})
},
level: {
type: Number,
default: 0
},
parentKey: {
type: [String, Number],
default: ""
},
darkMode: {
type: Boolean,
default: !0
},
expanded: {
type: Boolean,
default: !0
}
},
emits: {
toggle: (e) => typeof e.path == "string" && typeof e.expanded == "boolean",
copy: (e) => typeof e.path == "string"
},
setup(e, { emit: n }) {
let r = D({
defaultExpanded: s(e.expanded ?? !0),
onToggle: (e) => n("toggle", e),
onCopy: (e) => n("copy", e)
});
return O(r), c(() => e.expanded, (e) => {
r.reset(e ?? !0);
}), () => t(Y, {
darkMode: e.darkMode ?? !0,
data: e.data ?? {},
level: e.level ?? 0,
path: X
}, null);
}
}), Q = { install(e) {
e.component("JsonViewer", Z), e.component("JsonNode", Y);
} };
//#endregion
export { Y as JsonNode, Z as JsonViewer, Q as JsonViewerPlugin, Q as default };
//# sourceMappingURL=index.es.js.map