<label>Direction: <button id="direction">Row</button></label>
<button id="add-tab">Add Tab</button>
<wc-tab-panel>
<h1 slot="tab">Tab A</h1>
<div slot="content">Bacon ipsum dolor amet buffalo chicken pastrami pork. Fatback flank picanha spare ribs chuck alcatra. Spare ribs pork
loin sausage t-bone burgdoggen pastrami. Spare ribs kevin beef ribs alcatra ham hock turkey ribeye, prosciutto strip
steak bacon venison shank tri-tip landjaeger beef.
Tail pork loin kielbasa bresaola capicola, alcatra chislic cupim tenderloin pig brisket. Cow pork loin meatball, chislic
salami hamburger beef ribs t-bone strip steak kevin biltong pastrami landjaeger. Cupim doner pastrami, andouille
turducken sirloin landjaeger salami. Flank filet mignon short ribs, boudin sirloin ball tip landjaeger sausage ground
round buffalo. Ham hock rump leberkas, doner capicola short loin bacon chicken pig tongue jerky ball tip turducken
shankle cupim. Short ribs tongue bacon spare ribs doner. Frankfurter jowl turkey filet mignon beef kielbasa shank
meatball beef ribs ball tip turducken tail fatback cow pig.</div>
<h1 slot="tab">Tab B</h1>
<div slot="content">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque ultrices et velit eu rutrum. Etiam bibendum ipsum
vitae vestibulum venenatis. Mauris volutpat libero quam. Vestibulum quis ipsum tempor, egestas libero tristique,
malesuada nibh. Mauris vulputate sapien vitae nibh facilisis, ac pretium purus dictum. Mauris venenatis, turpis ac
dictum sagittis, odio purus tincidunt elit, sit amet fermentum nibh lorem vel tortor. Mauris in libero ac diam volutpat
tempor. In hendrerit sed sem sit amet pellentesque. Vestibulum ac massa vitae odio laoreet dictum at rhoncus elit.
Integer ac est vel enim bibendum tempus. Aenean dignissim a augue laoreet elementum. Vivamus sit amet facilisis ipsum,
id fringilla tortor. Integer vitae est ultricies, tincidunt purus id, eleifend enim. Integer egestas, augue vel
malesuada aliquet, elit turpis condimentum odio, a tristique ipsum nisi scelerisque erat. Vivamus tortor lacus,
tristique ut enim nec, consequat porttitor odio. Phasellus facilisis enim ut nisl molestie, sed convallis ipsum
vulputate. Donec mollis vitae nibh vitae tincidunt. Vestibulum lobortis eu eros vitae luctus. Vestibulum a augue non
lacus dignissim dignissim. Sed eget ipsum lacus. Donec a augue faucibus, vulputate nibh eu, posuere arcu. Curabitur
risus nunc, hendrerit vel mi nec, vehicula gravida nisi. Nam a dolor eu velit dignissim facilisis. Morbi tincidunt
ligula in nibh dignissim, sed ornare diam iaculis.</div>
<h1 slot="tab">Tab C</h1>
<div slot="content">Veggies es bonus vobis, proinde vos postulo essum magis kohlrabi welsh onion daikon amaranth tatsoi tomatillo melon
azuki bean garlic.
Gumbo beet greens corn soko endive gumbo gourd. Parsley shallot courgette tatsoi pea sprouts fava bean collard greens
dandelion okra wakame tomato. Dandelion cucumber earthnut pea peanut soko zucchini.
Turnip greens yarrow ricebean rutabaga endive cauliflower sea lettuce kohlrabi amaranth water spinach avocado daikon
napa cabbage asparagus winter purslane kale. Celery potato scallion desert raisin horseradish spinach carrot soko. Lotus
root water spinach fennel kombu maize bamboo shoot green bean swiss chard seakale pumpkin onion chickpea gram corn pea.
Brussels sprout coriander water chestnut gourd swiss chard wakame kohlrabi beetroot carrot watercress. Corn amaranth
salsify bunya nuts nori azuki bean chickweed potato bell pepper artichoke. </div>
</wc-tab-panel>
label { display: block; margin-bottom: 1rem; }
h1 { margin: 0; }
wc-tab-panel {
--tab-gap: 1rem;
}
class WcTabPanel extends HTMLElement {
static observedAttributes = ["selected-index", "direction"];
#selectedIndex = 0;
#direction = "row";
constructor() {
super();
this.bind(this);
}
bind(element) {
element.render = element.render.bind(element);
element.attachEvents = element.attachEvents.bind(element);
element.cacheDom = element.cacheDom.bind(element);
element.onTabClick = element.onTabClick.bind(element);
element.selectTabByIndex = element.selectTabByIndex.bind(element);
element.onContentSlotChange = element.onContentSlotChange.bind(element);
element.onTabSlotChange = element.onTabSlotChange.bind(element);
}
connectedCallback() {
this.render();
this.cacheDom();
this.attachEvents();
this.dom.tabs[this.#selectedIndex]?.classList.add("selected");
this.dom.contents[this.#selectedIndex]?.classList.add("selected");
}
render() {
this.shadow = this.attachShadow({ mode: "open" });
this.shadow.innerHTML = `
<style>
:host { display: flex; flex-direction: column; }
:host([direction="column"]) { flex-direction: row; }
:host([direction="column"]) .tabs { flex-direction: column; }
.tabs { display: flex; flex-direction: row; flex-wrap: nowrap; gap: var(--tab-gap, 0px); }
.tabs ::slotted(*) { padding: 5px; border: 1px solid #ccc; user-select: none; cursor: pointer; }
.tabs ::slotted(.selected) { background: #efefef; }
.tab-contents ::slotted(*) { display: none; }
.tab-contents ::slotted(.selected) { display: block; padding: 5px; }
</style>
<div class="tabs">
<slot id="tab-slot" name="tab"></slot>
</div>
<div class="tab-contents">
<slot id="content-slot" name="content"></slot>
</div>
`;
}
cacheDom() {
this.dom = {
tabSlot: this.shadow.querySelector("#tab-slot"),
contentSlot: this.shadow.querySelector("#content-slot")
};
this.dom.tabs = this.dom.tabSlot.assignedElements();
this.dom.contents = this.dom.contentSlot.assignedElements();
}
attachEvents() {
this.dom.tabSlot.addEventListener("click", this.onTabClick);
this.dom.tabSlot.addEventListener("slotchange", this.onTabSlotChange);
this.dom.contentSlot.addEventListener("slotchange", this.onContentSlotChange);
}
onTabSlotChange(){
this.dom.tabs = this.dom.tabSlot.assignedElements();
}
onContentSlotChange(){
this.dom.contents = this.dom.contentSlot.assignedElements();
}
onTabClick(e) {
const target = e.target;
if (target.slot === "tab") {
const tabIndex = this.dom.tabs.indexOf(target);
this.selectTabByIndex(tabIndex);
}
}
selectTabByIndex(index) {
const tab = this.dom.tabs[index];
const content = this.dom.contents[index];
if (!tab || !content) return;
this.dom.contents.forEach(p => p.classList.remove("selected"));
this.dom.tabs.forEach(p => p.classList.remove("selected"));
content.classList.add("selected");
tab.classList.add("selected");
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
if(name === "selected-index"){
this.selectedIndex = newValue;
} else {
this[name] = newValue;
}
}
}
set selectedIndex(value) {
this.#selectedIndex = value;
}
get selectedIndex() {
return this.#selectedIndex;
}
set direction(value){
this.#direction = value;
this.setAttribute("direction", value);
}
get direction(){
return this.#direction;
}
}
customElements.define("wc-tab-panel", WcTabPanel);
//Page UI
document.addEventListener("DOMContentLoaded", () => {
const panel = document.querySelector("wc-tab-panel");
const dirButton = document.querySelector("#direction");
const addButton = document.querySelector("#add-tab");
let charCode = 68;
dirButton.addEventListener("click", () => {
if (panel.direction === "row") {
panel.direction = "column";
dirButton.textContent = "column";
} else if (panel.direction === "column") {
panel.direction = "row";
dirButton.textContent = "row";
}
});
addButton.addEventListener("click", () => {
const letter = String.fromCharCode(charCode);
const h1 = document.createElement("h1");
h1.textContent = `Tab ${letter}`;
h1.slot = "tab";
const content = document.createElement("div");
content.textContent = `Hello Tab ${letter}`;
content.slot = "content";
panel.appendChild(h1);
panel.appendChild(content);
charCode++;
});
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.