1 2export class Tabs { 3 private container: HTMLElement; 4 private tabBar: HTMLElement; 5 private nextTabId: number; 6 7 private mkTabBar(container: HTMLElement) { 8 container.classList.add("nav-tabs-container"); 9 this.tabBar = document.createElement("ul"); 10 this.tabBar.id = `tab-bar-${container.id}`; 11 this.tabBar.className = "nav-tabs"; 12 this.tabBar.ondrop = this.tabBarOnDrop.bind(this); 13 this.tabBar.ondragover = this.tabBarOnDragover.bind(this); 14 this.tabBar.onclick = this.tabBarOnClick.bind(this); 15 16 const defaultDiv = document.createElement("div"); 17 defaultDiv.className = "tab-content tab-default"; 18 defaultDiv.id = `tab-content-${container.id}-default`; 19 container.insertBefore(defaultDiv, container.firstChild); 20 container.insertBefore(this.tabBar, container.firstChild); 21 } 22 23 constructor(container: HTMLElement) { 24 this.container = container; 25 this.nextTabId = 0; 26 this.mkTabBar(container); 27 } 28 29 activateTab(tab: HTMLLIElement) { 30 if (typeof tab.dataset.divid !== "string") return; 31 for (const li of this.tabBar.querySelectorAll<HTMLLIElement>("li.active")) { 32 li.classList.remove("active"); 33 this.showTab(li, false); 34 } 35 tab.classList.add("active"); 36 this.showTab(tab, true); 37 } 38 39 clearTabsAndContent() { 40 for (const tab of this.tabBar.querySelectorAll(".nav-tabs > li")) { 41 if (!(tab instanceof HTMLLIElement)) continue; 42 if (tab.classList.contains("persistent-tab")) continue; 43 const tabDiv = document.getElementById(tab.dataset.divid); 44 tabDiv.parentNode.removeChild(tabDiv); 45 tab.parentNode.removeChild(tab); 46 } 47 } 48 49 private showTab(li: HTMLElement, show: boolean = true) { 50 const tabDiv = document.getElementById(li.dataset.divid); 51 tabDiv.style.display = show ? "block" : "none"; 52 } 53 54 public addTab(caption: string): HTMLLIElement { 55 const newTab = document.createElement("li"); 56 newTab.innerHTML = caption; 57 newTab.id = `tab-header-${this.container.id}-${this.nextTabId++}`; 58 const lastTab = this.tabBar.querySelector("li.last-tab"); 59 this.tabBar.insertBefore(newTab, lastTab); 60 return newTab; 61 } 62 63 public addTabAndContent(caption: string): [HTMLLIElement, HTMLDivElement] { 64 const contentDiv = document.createElement("div"); 65 contentDiv.className = "tab-content tab-default"; 66 contentDiv.id = `tab-content-${this.container.id}-${this.nextTabId++}`; 67 contentDiv.style.display = "none"; 68 this.container.appendChild(contentDiv); 69 70 const newTab = this.addTab(caption); 71 newTab.dataset.divid = contentDiv.id; 72 newTab.draggable = true; 73 newTab.ondragstart = this.tabOnDragStart.bind(this); 74 const lastTab = this.tabBar.querySelector("li.last-tab"); 75 this.tabBar.insertBefore(newTab, lastTab); 76 return [newTab, contentDiv]; 77 } 78 79 private moveTabDiv(tab: HTMLLIElement) { 80 const tabDiv = document.getElementById(tab.dataset.divid); 81 tabDiv.style.display = "none"; 82 tab.classList.remove("active"); 83 this.tabBar.parentNode.appendChild(tabDiv); 84 } 85 86 private tabBarOnDrop(e: DragEvent) { 87 if (!(e.target instanceof HTMLElement)) return; 88 e.preventDefault(); 89 const tabId = e.dataTransfer.getData("text"); 90 const tab = document.getElementById(tabId) as HTMLLIElement; 91 if (tab.parentNode != this.tabBar) { 92 this.moveTabDiv(tab); 93 } 94 const dropTab = 95 e.target.parentNode == this.tabBar 96 ? e.target : this.tabBar.querySelector("li.last-tab"); 97 this.tabBar.insertBefore(tab, dropTab); 98 this.activateTab(tab); 99 } 100 101 private tabBarOnDragover(e) { 102 e.preventDefault(); 103 } 104 105 private tabOnDragStart(e: DragEvent) { 106 if (!(e.target instanceof HTMLElement)) return; 107 e.dataTransfer.setData("text", e.target.id); 108 } 109 110 private tabBarOnClick(e: MouseEvent) { 111 const li = e.target as HTMLLIElement; 112 this.activateTab(li); 113 } 114} 115