| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451 | 
							- // @ts-check
 
- /**
 
-  * @typedef {string | number | null | undefined | bigint | boolean | symbol} Primitive
 
-  * @typedef {(...args: any[]) => any} AnyFunction
 
-  */
 
- class PrettyJSONError extends Error {
 
-   /**
 
-    *
 
-    * @param {string} message
 
-    */
 
-   constructor(message) {
 
-     super(message);
 
-     this.name = "PrettyJSONError";
 
-   }
 
- }
 
- class PrettyJSON extends HTMLElement {
 
-   /**
 
-    * @type {any}
 
-    */
 
-   #input;
 
-   /**
 
-    * @type {boolean}
 
-    */
 
-   #isExpanded;
 
-   static get observedAttributes() {
 
-     return ["expand", "key", "truncate-string"];
 
-   }
 
-   static styles = `/* css */
 
-     :host {
 
-       --key-color: #cc0000;
 
-       --arrow-color: #737373;
 
-       --brace-color: #0030f0;
 
-       --bracket-color: #0030f0;
 
-       --string-color: #009900;
 
-       --number-color: #0000ff;
 
-       --null-color: #666666;
 
-       --boolean-color: #d23c91;
 
-       --comma-color: #666666;
 
-       --ellipsis-color: #666666;
 
-       --indent: 2rem;
 
-     }
 
-     @media (prefers-color-scheme: dark) {
 
-       :host {
 
-         --key-color: #f73d3d;
 
-         --arrow-color: #6c6c6c;
 
-         --brace-color: #0690bc;
 
-         --bracket-color: #0690bc;
 
-         --string-color: #21c521;
 
-         --number-color: #0078b3;
 
-         --null-color: #8c8888;
 
-         --boolean-color: #c737b3;
 
-         --comma-color: #848181;
 
-         --ellipsis-color: #c2c2c2;
 
-       }
 
-     }
 
-     button {
 
-       border: none;
 
-       background: transparent;
 
-       cursor: pointer;
 
-       font-family: inherit;
 
-       font-size: 1rem;
 
-       vertical-align: text-bottom;
 
-     }
 
-     .container {
 
-       font-family: monospace;
 
-       font-size: 1rem;
 
-     }
 
-     .key {
 
-       color: var(--key-color);
 
-       margin-right: 0.5rem;
 
-       padding: 0;
 
-     }
 
-     .key .arrow {
 
-       width: 1rem;
 
-       height: 0.75rem;
 
-       margin-left: -1.25rem;
 
-       padding-right: 0.25rem;
 
-       vertical-align: baseline;
 
-     }
 
-     .arrow .triangle {
 
-       fill: var(--arrow-color);
 
-     }
 
-     .comma {
 
-       color: var(--comma-color);
 
-     }
 
-     .brace {
 
-       color: var(--brace-color);
 
-     }
 
-     .string,
 
-     .url {
 
-       color: var(--string-color);
 
-     }
 
-     .number,
 
-     .bigint {
 
-       color: var(--number-color);
 
-     }
 
-     .null {
 
-       color: var(--null-color);
 
-     }
 
-     .boolean {
 
-       color: var(--boolean-color);
 
-     }
 
-     .ellipsis {
 
-       width: 1rem;
 
-       padding: 0;
 
-       color: var(--ellipsis-color);
 
-     }
 
-     .ellipsis::after {
 
-       content: "…";
 
-     }
 
-     .string .ellipsis::after {
 
-       color: var(--string-color);
 
-     }
 
-     .triangle {
 
-       fill: black;
 
-       stroke: black;
 
-       stroke-width: 0;
 
-     }
 
-     .row {
 
-       padding-left: var(--indent);
 
-     }
 
-     .row .row {
 
-       display: block;
 
-     }
 
-     .row > div,
 
-     .row > span {
 
-       display: inline-block;
 
-     }
 
-   `;
 
-   constructor() {
 
-     super();
 
-     this.#isExpanded = true;
 
-     this.attachShadow({ mode: "open" });
 
-   }
 
-   get #expandAttributeValue() {
 
-     const expandAttribute = this.getAttribute("expand");
 
-     if (expandAttribute === null) {
 
-       return 1;
 
-     }
 
-     const expandValue = Number.parseInt(expandAttribute);
 
-     return isNaN(expandValue) || expandValue < 0 ? 0 : expandValue;
 
-   }
 
-   get #truncateStringAttributeValue() {
 
-     const DEFAULT_TRUNCATE_STRING = 500;
 
-     const truncateStringAttribute = this.getAttribute("truncate-string");
 
-     if (truncateStringAttribute === null) {
 
-       return DEFAULT_TRUNCATE_STRING;
 
-     }
 
-     const truncateStringValue = Number.parseInt(truncateStringAttribute);
 
-     return isNaN(truncateStringValue) || truncateStringValue < 0
 
-       ? 0
 
-       : truncateStringValue;
 
-   }
 
-   #toggle() {
 
-     this.#isExpanded = !this.#isExpanded;
 
-     this.setAttribute(
 
-       "expand",
 
-       this.#isExpanded ? String(this.#expandAttributeValue + 1) : "0"
 
-     );
 
-     this.#render();
 
-   }
 
-   /**
 
-    * @param {Record<any, any> | any[] | Primitive | AnyFunction} input
 
-    * @param {number} expand
 
-    * @param {string} [key]
 
-    * @returns {HTMLElement}
 
-    */
 
-   #createChild(input, expand, key) {
 
-     if (this.#isPrimitiveValue(input)) {
 
-       const container = this.#createContainer();
 
-       container.appendChild(this.#createPrimitiveValueElement(input));
 
-       return container;
 
-     }
 
-     return this.#createObjectOrArray(input);
 
-   }
 
-   /**
 
-    * @param {any} input
 
-    * @returns {input is Primitive}
 
-    */
 
-   #isPrimitiveValue(input) {
 
-     return typeof input !== "object" || input === null;
 
-   }
 
-   #isValidStringURL() {
 
-     try {
 
-       new URL(this.#input);
 
-       return true;
 
-     } catch (error) {
 
-       return false;
 
-     }
 
-   }
 
-   /**
 
-    * @param {Primitive} input
 
-    * @returns {HTMLElement}
 
-    */
 
-   #createPrimitiveValueElement(input) {
 
-     const container = document.createElement("div");
 
-     const type = typeof input === "object" ? "null" : typeof input;
 
-     container.className = `primitive value ${type}`;
 
-     if (typeof input === "string") {
 
-       if (this.#isValidStringURL()) {
 
-         const anchor = document.createElement("a");
 
-         anchor.className = "url";
 
-         anchor.href = this.#input;
 
-         anchor.target = "_blank";
 
-         anchor.textContent = input;
 
-         container.append('"', anchor, '"');
 
-       } else if (input.length > this.#truncateStringAttributeValue) {
 
-         container.appendChild(this.#createTruncatedStringElement(input));
 
-       } else {
 
-         container.textContent = JSON.stringify(input);
 
-       }
 
-     } else {
 
-       container.textContent = JSON.stringify(input);
 
-     }
 
-     return container;
 
-   }
 
-   /**
 
-    * @param {string} input
 
-    */
 
-   #createTruncatedStringElement(input) {
 
-     const container = document.createElement("div");
 
-     container.dataset.expandedTimes = "1";
 
-     container.className = "truncated string";
 
-     const ellipsis = document.createElement("button");
 
-     ellipsis.className = "ellipsis";
 
-     ellipsis.addEventListener("click", () => {
 
-       const expandedTimes = Number.parseInt(
 
-         container.dataset.expandedTimes ?? "1"
 
-       );
 
-       container.dataset.expandedTimes = String(expandedTimes + 1);
 
-       const expandedString = input.slice(
 
-         0,
 
-         (expandedTimes + 1) * this.#truncateStringAttributeValue
 
-       );
 
-       const textChild = container.childNodes[1];
 
-       container.replaceChild(
 
-         document.createTextNode(expandedString),
 
-         textChild
 
-       );
 
-     });
 
-     container.append(
 
-       '"',
 
-       input.slice(0, this.#truncateStringAttributeValue),
 
-       ellipsis,
 
-       '"'
 
-     );
 
-     return container;
 
-   }
 
-   /**
 
-    * @returns {HTMLElement}
 
-    */
 
-   #createContainer() {
 
-     const container = document.createElement("div");
 
-     container.className = "container";
 
-     return container;
 
-   }
 
-   /**
 
-    * @param {Record<any, any> | any[]} object
 
-    * @returns {HTMLElement}
 
-    */
 
-   #createObjectOrArray(object) {
 
-     const isArray = Array.isArray(object);
 
-     const objectKeyName = this.getAttribute("key");
 
-     const expand = this.#expandAttributeValue;
 
-     const container = this.#createContainer();
 
-     container.classList.add(isArray ? "array" : "object");
 
-     if (objectKeyName) {
 
-       // if objectKeyName is provided, then it is a row
 
-       container.classList.add("row");
 
-       const keyElement = this.#createKeyElement(objectKeyName, {
 
-         withArrow: true,
 
-         expanded: this.#isExpanded,
 
-       });
 
-       keyElement.addEventListener("click", this.#toggle.bind(this));
 
-       container.appendChild(keyElement);
 
-     }
 
-     const openingBrace = document.createElement("span");
 
-     openingBrace.className = "open brace";
 
-     openingBrace.textContent = isArray ? "[" : "{";
 
-     container.appendChild(openingBrace);
 
-     const closingBrace = document.createElement("span");
 
-     closingBrace.className = "close brace";
 
-     closingBrace.textContent = isArray ? "]" : "}";
 
-     if (!this.#isExpanded) {
 
-       const ellipsis = document.createElement("button");
 
-       ellipsis.className = "ellipsis";
 
-       container.appendChild(ellipsis);
 
-       ellipsis.addEventListener("click", this.#toggle.bind(this));
 
-       container.appendChild(closingBrace);
 
-       return container;
 
-     }
 
-     Object.entries(object).forEach(([key, value], index) => {
 
-       // for primitives we make a row here
 
-       if (this.#isPrimitiveValue(value)) {
 
-         const rowContainer = document.createElement("div");
 
-         rowContainer.className = "row";
 
-         if (!isArray) {
 
-           const keyElement = this.#createKeyElement(key);
 
-           rowContainer.appendChild(keyElement);
 
-         }
 
-         rowContainer.appendChild(this.#createPrimitiveValueElement(value));
 
-         container.appendChild(rowContainer);
 
-         const isLast = index === Object.keys(object).length - 1;
 
-         if (!isLast) {
 
-           const comma = document.createElement("span");
 
-           comma.className = "comma";
 
-           comma.textContent = ",";
 
-           rowContainer.appendChild(comma);
 
-         }
 
-         return;
 
-       }
 
-       // for objects and arrays we make a "container row"
 
-       const prettyJsonElement = document.createElement("pretty-json");
 
-       prettyJsonElement.textContent = JSON.stringify(value);
 
-       prettyJsonElement.setAttribute("expand", String(expand - 1));
 
-       prettyJsonElement.setAttribute("key", key);
 
-       container.appendChild(prettyJsonElement);
 
-     });
 
-     container.appendChild(closingBrace);
 
-     return container;
 
-   }
 
-   /**
 
-    * @param {{ expanded?: boolean }} [options]
 
-    * @returns {SVGElement}
 
-    */
 
-   #createArrowElement({ expanded = false } = {}) {
 
-     const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
 
-     svg.setAttribute("width", "100");
 
-     svg.setAttribute("height", "100");
 
-     svg.setAttribute("viewBox", "0 0 100 100");
 
-     svg.setAttribute("class", "arrow");
 
-     const polygon = document.createElementNS(
 
-       "http://www.w3.org/2000/svg",
 
-       "polygon"
 
-     );
 
-     polygon.setAttribute("class", "triangle");
 
-     polygon.setAttribute("points", "0,0 100,50 0,100");
 
-     if (expanded) {
 
-       polygon.setAttribute("transform", "rotate(90 50 50)");
 
-     }
 
-     svg.appendChild(polygon);
 
-     return svg;
 
-   }
 
-   /**
 
-    * @param {string} key
 
-    * @param {{ withArrow?: boolean, expanded?: boolean }} [options]
 
-    * @returns {HTMLElement}
 
-    */
 
-   #createKeyElement(key, { withArrow = false, expanded = false } = {}) {
 
-     const keyElement = document.createElement(withArrow ? "button" : "span");
 
-     keyElement.className = "key";
 
-     if (withArrow) {
 
-       const arrow = this.#createArrowElement({ expanded });
 
-       keyElement.appendChild(arrow);
 
-     }
 
-     const keyName = document.createElement("span");
 
-     keyName.className = "key-name";
 
-     keyName.textContent = JSON.stringify(key);
 
-     keyElement.appendChild(keyName);
 
-     const colon = document.createElement("span");
 
-     colon.className = "colon";
 
-     colon.textContent = ":";
 
-     keyElement.appendChild(colon);
 
-     return keyElement;
 
-   }
 
-   #render() {
 
-     if (!this.shadowRoot) {
 
-       throw new PrettyJSONError("Shadow root not available");
 
-     }
 
-     this.shadowRoot.innerHTML = "";
 
-     this.shadowRoot.appendChild(
 
-       this.#createChild(this.#input, this.#expandAttributeValue)
 
-     );
 
-     if (this.shadowRoot.querySelector("[data-pretty-json]")) {
 
-       return;
 
-     }
 
-     const styles = document.createElement("style");
 
-     styles.setAttribute("data-pretty-json", "");
 
-     styles.textContent = PrettyJSON.styles;
 
-     this.shadowRoot.appendChild(styles);
 
-   }
 
-   /**
 
-    * Handle when attributes change
 
-    * @param {string} name
 
-    * @param {string} _oldValue
 
-    * @param {string | null} newValue
 
-    */
 
-   attributeChangedCallback(name, _oldValue, newValue) {
 
-     if (name === "expand") {
 
-       if (newValue === null) {
 
-         this.#isExpanded = false;
 
-       } else {
 
-         const expandValue = Number.parseInt(newValue);
 
-         this.#isExpanded = !isNaN(expandValue) && expandValue > 0;
 
-       }
 
-       this.#render();
 
-     }
 
-   }
 
-   connectedCallback() {
 
-     try {
 
-       this.#input = JSON.parse(this.textContent ?? "");
 
-     } catch (jsonParseError) {
 
-       const message = `Error parsing JSON: ${jsonParseError instanceof Error ? jsonParseError.message : "Unknown error"}`;
 
-       throw new PrettyJSONError(message);
 
-     }
 
-     this.#render();
 
-   }
 
- }
 
- // Define pretty-json custom element
 
- customElements.define("pretty-json", PrettyJSON);
 
 
  |