json-format-tree
Version:
json格式化组件,配合json-source-map实现高亮效果
613 lines (612 loc) • 17 kB
JavaScript
import { defineComponent as F, createVNode as d, computed as k, reactive as H, createTextVNode as $, ref as R, watchEffect as Y, watch as I } from "vue";
function E(e) {
return Object.prototype.toString.call(e).slice(8, -1).toLowerCase();
}
function V(e, n = "", o = 0, u) {
const {
key: C,
index: v,
type: m = "content",
showComma: s = !1,
length: w = 1
} = u || {}, f = E(e);
if (f === "array") {
const b = A(
e.map(
(N, g, T) => V(N, `${n}/${g}`, o + 1, {
index: g,
showComma: g !== T.length - 1,
length: w,
type: m
})
)
);
return [
V("[", n, o, {
showComma: !1,
key: C,
length: e.length,
type: "arrayStart"
})[0]
].concat(
b,
V("]", n, o, {
showComma: s,
length: e.length,
type: "arrayEnd"
})[0]
);
} else if (f === "object") {
const b = Object.keys(e), N = A(
b.map(
(g, T, P) => V(
e[g],
/^[a-zA-Z_]\w*$/.test(g) ? `${n}/${g}` : `${n}/${g}`,
o + 1,
{
key: g,
showComma: T !== P.length - 1,
length: w,
type: m
}
)
)
);
return [
V("{", n, o, {
showComma: !1,
key: C,
index: v,
length: b.length,
type: "objectStart"
})[0]
].concat(
N,
V("}", n, o, { showComma: s, length: b.length, type: "objectEnd" })[0]
);
}
return [
{
content: e,
level: o,
key: C,
index: v,
path: n,
showComma: s,
length: w,
type: m
}
];
}
function A(e) {
if (typeof Array.prototype.flat == "function")
return e.flat();
const n = [...e], o = [];
for (; n.length; ) {
const u = n.shift();
Array.isArray(u) ? n.unshift(...u) : o.push(u);
}
return o;
}
function O(e, n = /* @__PURE__ */ new WeakMap()) {
if (e == null) return e;
if (e instanceof Date) return new Date(e);
if (e instanceof RegExp) return new RegExp(e);
if (typeof e != "object") return e;
if (n.get(e))
return n.get(e);
if (Array.isArray(e)) {
const u = e.map((C) => O(C, n));
return n.set(e, u), u;
}
const o = {};
for (const u in e)
o[u] = O(e[u], n);
return n.set(e, o), o;
}
function q(e) {
let n;
return e === "null" ? n = null : e === "undefined" ? n = void 0 : e === "true" ? n = !0 : e === "false" ? n = !1 : e[0] + e[e.length - 1] === '""' || e[0] + e[e.length - 1] === "''" ? n = e.slice(1, -1) : typeof Number(e) == "number" && !isNaN(Number(e)) || e === "NaN" ? n = Number(e) : n = e, n;
}
const z = /* @__PURE__ */ F({
props: {
data: {
required: !0,
type: String
},
onClick: Function
},
render() {
const {
data: e
} = this, {
onClick: n
} = this;
return d("span", {
class: "vjs-tree-brackets",
onClick: n
}, [e]);
}
}), J = /* @__PURE__ */ F({
props: {
nodeType: {
required: !0,
type: String
},
onClick: Function
},
render() {
const {
nodeType: e
} = this, {
onClick: n
} = this, o = e === "objectStart" || e === "arrayStart";
return !o && !(e === "objectCollapsed" || e === "arrayCollapsed") ? null : d("span", {
class: `vjs-carets vjs-carets-${o ? "open" : "close"}`,
onClick: n
}, [d("svg", {
viewBox: "0 0 1024 1024",
focusable: "false",
"data-icon": "caret-down",
width: "1em",
height: "1em",
fill: "currentColor",
"aria-hidden": "true"
}, [d("path", {
d: "M840.4 300H183.6c-19.7 0-30.7 20.8-18.5 35l328.4 380.8c9.4 10.9 27.5 10.9 37 0L858.9 335c12.2-14.2 1.2-35-18.5-35z"
}, null)])]);
}
}), K = {
// Whether to display the length of (array|object).
showLength: {
type: Boolean,
default: !1
},
// Whether the key name uses double quotes.
showDoubleQuotes: {
type: Boolean,
default: !0
},
// Custom render for key.
renderNodeKey: Function,
// Custom render for value.
renderNodeValue: Function,
// Define the selection method supported by the data level, which is not available by default.
selectableType: String,
// Whether to display the selection control.
showSelectController: {
type: Boolean,
default: !1
},
// Whether to display the data level connection.
showLine: {
type: Boolean,
default: !1
},
showLineNumber: {
type: Boolean,
default: !1
},
// Whether to trigger selection when clicking on the node.
selectOnClickNode: {
type: Boolean,
default: !0
},
// When using the selectableType, define whether current path/content is enabled.
nodeSelectable: {
type: Function,
default: () => !0
},
// Highlight current node when selected.
highlightSelectedNode: {
type: Boolean,
default: !1
},
showIcon: {
type: Boolean,
default: !0
},
theme: {
type: String,
default: "light"
},
showKeyValueSpace: {
type: Boolean,
default: !0
},
editable: {
type: Boolean,
default: !1
},
editableTrigger: {
type: String,
default: "click"
},
onNodeClick: {
type: Function
},
onBracketsClick: {
type: Function
},
onIconClick: {
type: Function
},
onValueChange: {
type: Function
}
}, U = /* @__PURE__ */ F({
name: "TreeNode",
props: {
...K,
// Current node data.
node: {
type: Object,
required: !0
},
// Whether the current node is collapsed.
collapsed: Boolean,
// Whether the current node is checked(When using the selection function).
checked: Boolean,
style: Object,
onSelectedChange: {
type: Function
},
pointer: {
type: Object,
default: () => {
}
}
},
emits: ["nodeClick", "bracketsClick", "iconClick", "selectedChange", "valueChange"],
setup(e, {
emit: n
}) {
const o = k(() => E(e.node.content)), u = k(() => `vjs-value vjs-value-${o.value}`), C = k(() => e.showDoubleQuotes ? `"${e.node.key}"` : e.node.key), v = () => {
const l = e.renderNodeKey;
return l ? l({
node: e.node,
defaultKey: C.value || ""
}) : C.value;
}, m = k(() => e.selectableType === "multiple"), s = k(() => e.selectableType === "single"), w = k(() => e.nodeSelectable(e.node) && (m.value || s.value)), f = H({
editing: !1
}), b = (l) => {
var t;
const y = (t = l.target) == null ? void 0 : t.value, B = q(y);
n("valueChange", B, e.node.path);
}, N = k(() => {
var y;
let l = (y = e.node) == null ? void 0 : y.content;
return l === null ? l = "null" : l === void 0 && (l = "undefined"), o.value === "string" ? `"${l}"` : l + "";
}), g = () => {
const l = e.renderNodeValue;
return l ? l({
node: e.node,
defaultValue: N.value
}) : N.value;
}, T = () => {
n("bracketsClick", !e.collapsed, e.node);
}, P = () => {
n("iconClick", !e.collapsed, e.node);
}, x = () => {
n("nodeClick", e.node), w.value && e.selectOnClickNode && n("selectedChange", e.node);
}, D = (l) => {
if (e.editable && !f.editing) {
f.editing = !0;
const y = (B) => {
var t;
B.target !== l.target && ((t = B.target) == null ? void 0 : t.parentElement) !== l.target && (f.editing = !1, document.removeEventListener("click", y));
};
document.removeEventListener("click", y), document.addEventListener("click", y);
}
};
return () => {
const {
node: l,
pointer: y
} = e;
return d("div", {
class: {
"vjs-tree-node": !0,
"has-selector": e.showSelectController,
"has-carets": e.showIcon,
"is-highlight": e.highlightSelectedNode && e.checked,
dark: e.theme === "dark"
},
onClick: x,
style: e.style,
"data-pointer": JSON.stringify(y)
}, [e.showLineNumber && d("span", {
class: "vjs-node-index"
}, [l.id + 1]), d("div", {
class: "vjs-indent"
}, [Array.from(Array(l.level)).map((B, t) => d("div", {
key: t,
class: {
"vjs-indent-unit": !0,
"has-line": e.showLine
}
}, null)), e.showIcon && d(J, {
nodeType: l.type,
onClick: P
}, null)]), l.key && d("span", {
class: "vjs-key"
}, [v(), d("span", {
class: "vjs-colon"
}, [`:${e.showKeyValueSpace ? " " : ""}`])]), d("span", null, [l.type !== "content" && l.content ? d(z, {
data: l.content.toString(),
onClick: T
}, null) : d("span", {
class: u.value,
onClick: e.editable && (!e.editableTrigger || e.editableTrigger === "click") ? D : void 0,
onDblclick: e.editable && e.editableTrigger === "dblclick" ? D : void 0
}, [e.editable && f.editing ? d("input", {
value: N.value,
onChange: b,
style: {
padding: "3px 8px",
border: "1px solid #eee",
boxShadow: "none",
boxSizing: "border-box",
borderRadius: 5,
fontFamily: "inherit"
}
}, null) : g()]), l.showComma && d("span", null, [","]), e.showLength && e.collapsed && d("span", {
class: "vjs-comment"
}, [$(" // "), l.length, $(" items ")])])]);
};
}
}), W = /* @__PURE__ */ F({
name: "Tree",
props: {
...K,
// JSONLike data.
data: {
type: [String, Number, Boolean, Array, Object],
default: null
},
collapsedNodeLength: {
type: Number,
default: 1 / 0
},
// Define the depth of the tree, nodes greater than this depth will not be expanded.
deep: {
type: Number,
default: 8
},
pathCollapsible: {
type: Function,
default: () => !1
},
// Data root path.
rootPath: {
type: String,
default: ""
},
// Whether to use virtual scroll, usually applied to big data.
virtual: {
type: Boolean,
default: !1
},
// When using virtual scroll, set the height of tree.
height: {
type: Number,
default: 400
},
// When using virtual scroll, define the height of each row.
itemHeight: {
type: Number,
default: 20
},
// When there is a selection function, define the selected path.
// For multiple selections, it is an array ['root.a','root.b'], for single selection, it is a string of 'root.a'.
selectedValue: {
type: [String, Array],
default: () => ""
},
// Collapsed control.
collapsedOnClickBrackets: {
type: Boolean,
default: !0
},
style: Object,
onSelectedChange: {
type: Function
},
theme: {
type: String,
default: "light"
},
//json字符串位置映射
pointers: {
type: Object,
default: () => {
}
},
renderEffect: {
type: Function
}
},
// slots: ['renderNodeKey', 'renderNodeValue'],
emits: ["nodeClick", "bracketsClick", "iconClick", "selectedChange", "update:selectedValue", "update:data"],
setup(e, {
emit: n,
slots: o,
expose: u
}) {
const C = R(), v = k(() => V(e.data, e.rootPath)), m = (t, i) => v.value.reduce((c, a) => {
var S;
const r = a.level >= t || a.length >= i, h = (S = e.pathCollapsible) == null ? void 0 : S.call(e, a);
return (a.type === "objectStart" || a.type === "arrayStart") && (r || h) ? {
...c,
[a.path]: 1
} : c;
}, {}), s = H({
translateY: 0,
visibleData: null,
hiddenPaths: m(e.deep, e.collapsedNodeLength)
}), w = k(() => {
let t = null;
const i = [], c = v.value.length;
for (let a = 0; a < c; a++) {
const h = {
...v.value[a],
id: a
}, S = s.hiddenPaths[h.path];
if (t && t.path === h.path) {
const L = t.type === "objectStart", j = {
...h,
...t,
showComma: h.showComma,
content: L ? "{...}" : "[...]",
type: L ? "objectCollapsed" : "arrayCollapsed"
};
t = null, i.push(j);
} else if (S && !t) {
t = h;
continue;
} else {
if (t) continue;
i.push(h);
}
}
return i;
}), f = k(() => {
const t = e.selectedValue;
return t && e.selectableType === "multiple" && Array.isArray(t) ? t : [t];
}), b = () => {
var i;
const t = w.value;
if (e.virtual) {
const c = e.height / e.itemHeight, a = ((i = C.value) == null ? void 0 : i.scrollTop) || 0, r = Math.floor(a / e.itemHeight);
let h = r < 0 ? 0 : r + c > t.length ? t.length - c : r;
h < 0 && (h = 0);
const S = h + c;
s.translateY = h * e.itemHeight, s.visibleData = t.filter((L, j) => j >= h && j < S);
} else
s.visibleData = t;
}, N = () => {
b();
}, g = ({
path: t
}) => {
const i = e.selectableType;
if (i === "multiple") {
const c = f.value.findIndex((r) => r === t), a = [...f.value];
c !== -1 ? a.splice(c, 1) : a.push(t), n("update:selectedValue", a), n("selectedChange", a, [...f.value]);
} else if (i === "single" && f.value[0] !== t) {
const [c] = f.value, a = t;
n("update:selectedValue", a), n("selectedChange", a, c);
}
}, T = (t) => {
n("nodeClick", t);
}, P = (t, i) => {
if (t)
s.hiddenPaths = {
...s.hiddenPaths,
[i]: 1
};
else {
const c = {
...s.hiddenPaths
};
delete c[i], s.hiddenPaths = c;
}
}, x = (t, i) => {
e.collapsedOnClickBrackets && P(t, i.path), n("bracketsClick", t, i);
}, D = (t, i) => {
P(t, i.path), n("iconClick", t, i);
}, l = (t, i) => {
const c = O(e.data), a = e.rootPath;
new Function("data", "val", `data${i.slice(a.length)}=val`)(c, t), n("update:data", c);
};
Y(() => {
w.value && b();
}), I(() => e.deep, (t) => {
t && (s.hiddenPaths = m(t, e.collapsedNodeLength));
}), I(() => e.collapsedNodeLength, (t) => {
t && (s.hiddenPaths = m(e.deep, t));
});
const y = {};
return u({
getContentPoint: () => y
}), () => {
const t = e.renderNodeKey ?? o.renderNodeKey, i = e.renderNodeValue ?? o.renderNodeValue, {
pointers: c
} = e, a = s.visibleData && s.visibleData.map((r, h) => {
function S(M) {
return M.replace(/\\u([0-9A-Fa-f]{4})/g, function(Z, Q) {
return String.fromCodePoint(parseInt(Q, 16));
});
}
let L = S(r.path);
const j = c ? c[L] : "";
return e.renderEffect && e.renderEffect(j, r, h), j && (y[JSON.stringify(j)] = r.content), d(U, {
key: r.id,
node: r,
collapsed: !!s.hiddenPaths[r.path],
theme: e.theme,
showDoubleQuotes: e.showDoubleQuotes,
showLength: e.showLength,
checked: f.value.includes(r.path),
selectableType: e.selectableType,
showLine: e.showLine,
showLineNumber: e.showLineNumber,
showSelectController: e.showSelectController,
selectOnClickNode: e.selectOnClickNode,
nodeSelectable: e.nodeSelectable,
highlightSelectedNode: e.highlightSelectedNode,
editable: e.editable,
editableTrigger: e.editableTrigger,
showIcon: e.showIcon,
showKeyValueSpace: e.showKeyValueSpace,
renderNodeKey: t,
renderNodeValue: i,
onNodeClick: T,
onBracketsClick: x,
onIconClick: D,
onSelectedChange: g,
onValueChange: l,
style: e.itemHeight && e.itemHeight !== 20 ? {
lineHeight: `${e.itemHeight}px`
} : {},
pointer: c[L]
}, null);
});
return d("div", {
ref: C,
class: {
"vjs-tree": !0,
"is-virtual": e.virtual,
dark: e.theme === "dark"
},
onScroll: e.virtual ? N : void 0,
style: e.showLineNumber ? {
paddingLeft: `${Number(v.value.length.toString().length) * 12}px`,
...e.style
} : e.style
}, [e.virtual ? d("div", {
class: "vjs-tree-list",
style: {
height: `${e.height}px`
}
}, [d("div", {
class: "vjs-tree-list-holder",
style: {
height: `${w.value.length * e.itemHeight}px`
}
}, [d("div", {
class: "vjs-tree-list-holder-inner",
style: {
transform: `translateY(${s.translateY}px)`
}
}, [a])])]) : a]);
};
}
});
function G(e) {
e.component("jsonTree", W);
}
export {
W as JsonTree,
G as default
};