UNPKG

@itihon/file-tree-view

Version:

File tree view component based on Web Components API

319 lines (318 loc) 10.7 kB
(function(){"use strict";try{if(typeof document<"u"){var e=document.createElement("style");e.appendChild(document.createTextNode('file-tree-view{display:block;width:220px;height:fit-content;max-height:100%;overflow:auto;background-color:var(--background-color);font-family:Helvetica;font-size:14px;--left-indent: 20px;--line-height: 18px;--default-file-icon: "📄";--default-folder-icon: "📁";--default-expanded-folder-icon: "📂";--selected-color: gainsboro;--focus-color: #cacaca;--background-color: darkgray;--hover-color: silver}ftv-file,ftv-folder{display:block;min-width:100%;width:max-content}ftv-file:focus-visible>.label,ftv-folder:focus-visible>.label,ftv-file:focus>.label,ftv-folder:focus>.label{background-color:var(--focus-color)}ftv-file:focus-visible,ftv-folder:focus-visible,ftv-file:focus,ftv-folder:focus{outline:none}ftv-file[selected]>.label,ftv-folder[selected]>.label{background-color:var(--selected-color)}.label:hover{background-color:var(--hover-color)}.label{display:block;min-width:100%;width:fit-content;height:var(--line-height);cursor:pointer}.label:before{display:inline-block;width:var(--line-height);height:var(--line-height)}.content{display:block;overflow:hidden;height:0;padding-left:var(--left-indent)}ftv-file>.label:before{content:var(--default-file-icon)}ftv-folder>.label:before{content:var(--default-folder-icon)}ftv-folder[expanded]>.label:before{content:var(--default-expanded-folder-icon)}ftv-folder[expanded]>.content{height:fit-content}.noselect{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}')),document.head.appendChild(e)}}catch(o){console.error("vite-plugin-css-injected-by-js",o)}})(); class l extends HTMLElement { getSelfRef() { return this; } getParentRef() { return this.parentNode; } treeViewContainer = null; getNode; getTreeViewContainer() { return this.treeViewContainer; } connectedCallback() { let e = this; for (; e && e.tagName.toLowerCase() !== "file-tree-view"; ) e = e.parentElement; e instanceof l || (this.treeViewContainer = e); } disconnectedCallback() { this.treeViewContainer = null; } constructor(e) { super(), e ? this.getNode = this.getSelfRef : this.getNode = this.getParentRef; } } customElements.define("ftv-auxiliary", l); class u extends l { expandable = !1; label; path = ""; initPath() { let e = this, t = ""; for (; e instanceof u; ) t = "/" + e.getName() + t, e = e.getContainingFolder(); return t; } constructor(e, t = !1) { super(!0); const s = e || this.getAttribute("name") || ""; this.label = new l(!1), this.label.classList.add("label", "noselect"), this.insertAdjacentElement("afterbegin", this.label), this.setName(s), this.setAttribute("name", s), this.tabIndex = -1, this.expandable = t; } connectedCallback() { super.connectedCallback(), this.path = this.initPath(); } getContainingFolder() { const e = this.parentElement; return e instanceof l ? e.getNode() : null; } getName() { return this.label.textContent; } isSelected() { return this.hasAttribute("selected"); } toggleSelected() { this.toggleAttribute("selected"); } select() { this.toggleAttribute("selected", !0); } deselect() { this.toggleAttribute("selected", !1); } isFocused() { return document.activeElement === this; } setName(e) { if (e.trim().length === 0) throw new Error("File or folder name cannot be empty"); this.label.textContent = e; } getRelativePath() { return this.path; } isFolder() { return this.expandable; } } class g extends u { constructor(e) { super(e); } } customElements.define("ftv-file", g); class w extends Map { createStates(e) { return new Map( e.map((t) => [t, /* @__PURE__ */ new Map()]) ); } } const h = new w(), f = { bubbles: !0 }; function N(i, e) { const t = h.get(i); if (t) { const s = t.get("expanded"); if (s) return s.get(e); } } function v(i, e, t) { const s = h.get(i); if (s) { const n = s.get("expanded"); if (n) return n.set(e, t), !0; } return !1; } class c extends u { content; addContent(e = []) { Array.isArray(e) ? this.content.append(...e) : this.content.appendChild(e); } getContent() { return this.content; } clearContent() { for (; this.content.firstChild; ) this.content.firstChild.remove(); } get length() { return this.content.children.length; } constructor(e, t = []) { super(e, !0), this.content = new l(!1), this.content.classList.add("content"), this.content.append( ...t, ...Array.from(this.children).filter( (s) => !(s instanceof l) // filter out the label alreade created in the parend class constructor ) ), this.appendChild(this.content); } connectedCallback() { if (super.connectedCallback(), this.isExpanded()) return; const e = this.getTreeViewContainer(), t = this.getRelativePath(); e && (N(e, t) ? this.expand() : this.collapse()); } disconnectedCallback() { const e = this.getTreeViewContainer(), t = this.getRelativePath(); e && v(e, t, this.isExpanded()), super.disconnectedCallback(); } isExpanded() { return this.hasAttribute("expanded"); } toggleExpanded() { this.toggleAttribute("expanded") ? this.dispatchEvent(new CustomEvent("expand", f)) : this.dispatchEvent(new CustomEvent("collapse", f)); } expand() { this.isExpanded() || (this.toggleAttribute("expanded", !0), this.dispatchEvent(new CustomEvent("expand", f))); } collapse() { this.isExpanded() && (this.toggleAttribute("expanded", !1), this.dispatchEvent(new CustomEvent("collapse", f))); } } customElements.define("ftv-folder", c); const r = { preventScroll: !0 }, F = { block: "nearest" }, m = (i, e = "desc") => e === "desc" ? (t, s) => t[i] < s[i] ? -1 : t[i] > t[i] ? 1 : 0 : (t, s) => t[i] > s[i] ? -1 : t[i] < t[i] ? 1 : 0, y = m("name"), A = m("type", "asc"); class p extends HTMLElement { selectedItem = null; getLastVisibleNode(e) { if (e.isExpanded()) { const t = e.getContent(); if (t.children.length) { const s = t.lastElementChild; return s.isFolder() ? this.getLastVisibleNode(s) : s; } else return e; } return e; } getNextVisibleNode(e) { const t = e.nextElementSibling; if (t) return t; const s = e.getContainingFolder(); if (s) { const n = this.getNextVisibleNode(s); return n === s ? e : n; } return e; } focusPrevious(e) { const t = e.previousElementSibling; if (t) t.isFolder() ? this.getLastVisibleNode(t).focus(r) : t.focus(r); else { const s = e.getContainingFolder(); s && s.focus(r); } } focusNext(e) { if (e.isFolder()) if (e.isExpanded()) { const t = e.getContent(); t.children.length ? t.firstElementChild.focus(r) : this.getNextVisibleNode(e).focus(r); } else this.getNextVisibleNode(e).focus(r); else this.getNextVisibleNode(e).focus(r); } load(e, t = this, s = !0, n = !1) { const { children: o, name: C } = e; let a = t; if (s) { const d = new c(C); a.addContent(d), a = d; } else a = t; o && (n ? o.sort(y).sort(A) : o).forEach( (d) => { const { type: E, name: b } = d; if (E === "file") { const x = new g(b); a.addContent(x); } else this.load(d, a); } ); } getSelectedItem() { return this.selectedItem; } getNodeByPath(e) { let t = null; const s = e.split("/"); for (const n of s) if (n) if (t instanceof c) t = t.getContent().querySelector(`:scope > [name="${n}"]`); else { const o = this.firstElementChild; if (o) if (o.getAttribute("name") === n) t = o; else break; else break; } return t; } addContent(e) { if (this.firstElementChild) throw new Error("This tree-view already has a root element."); this.appendChild(e); } addNode(e, t, s) { const n = e === "/" || e === "" ? this : this.getNodeByPath(e); if (!n) throw new Error(`Path ${e} not found.`); if (n === this && this.firstElementChild) throw new Error("This tree-view already has a root element."); if (!(n instanceof c) && n !== this) throw new Error(`Path ${e} is not a folder.`); if (s === "file") { const o = new g(t); n.addContent(o); } if (s === "folder") { const o = new c(t); n.addContent(o); } } removeNode(e) { const t = this.getNodeByPath(e); if (!t) throw new Error(`Path ${e} not found.`); t.remove(); } connectedCallback() { h.has(this) || h.set(this, h.createStates(["expanded"])); } constructor() { super(), this.addEventListener("click", (e) => { const t = e.target; if (t instanceof p || t.classList.contains("content")) return; const s = t.getNode(); s && s.isFolder() && s.toggleExpanded(), this.selectedItem && this.selectedItem.deselect(), this.selectedItem = s, this.selectedItem.select(); }), this.addEventListener("keydown", (e) => { e.code !== "Tab" && e.preventDefault(); const t = e.target === this ? this.firstElementChild : e.target; if (t) { const s = t.getNode(); if (s) { if (e.code === "ArrowUp" && this.focusPrevious(s), e.code === "ArrowDown" && this.focusNext(s), e.code === "ArrowLeft") if (s.isFolder() && s.isExpanded()) s.collapse(); else { const n = s.getContainingFolder(); n && n.focus(r); } if (e.code === "ArrowRight" && s.isFolder()) if (!s.isExpanded()) s.expand(); else { const n = s.getContent().firstElementChild; n && n.focus(r); } if (e.code === "Home") { const n = this.firstElementChild; n && n.focus(); } if (e.code === "End") { const n = this.lastElementChild; n && (n.isFolder() ? this.getLastVisibleNode(n).focus() : n.focus()); } } } }), this.addEventListener("focusin", (e) => { const s = e.target.firstElementChild, n = this.scrollLeft; s.scrollIntoView(F), this.scrollLeft = n; }), this.tabIndex = 0; } } customElements.define("file-tree-view", p); export { p as default };