2024-06-13 00:00:00 -04:00
< html >
< head >
< title > Pattern< / title >
2025-01-11 13:03:27 -05:00
< script >
let u = document.location.href.substring(0, document.location.href.search(/patterns/i));
let p = document.location.pathname.substring(document.location.pathname.search(/patterns/i));
p = p.replace(/\/$|\/index\.html/i, "").substring(9);
window.location = u + "?p=" + p;
< / script >
2024-06-13 00:00:00 -04:00
< / head >
< body data-prismjs-copy-timeout = "1500" >
2024-07-11 21:05:34 -04:00
< h2 > What is it< / h2 >
2024-07-16 03:57:34 -04:00
< p > A tabs component that provides different sections of content that are displayed one at a time when the user selects that information. < / p >
2024-07-28 15:30:51 -04:00
< h2 > When to use it< / h2 >
2024-06-13 00:00:00 -04:00
< p > The tabbed user interface enables users to jump to their target section quickly. Tabs present like logically group information on the same page. Information should < / p >
< ul >
< li > be logically chunked and ordered< / li >
< li > be arallel in nature< / li >
< li > show user's context< / li >
< li > obvious where they begin and end < / li >
< / ul >
< p > Users should not need to see content of multiple tabs simultaneously and the user should be able to easily recognise where they are within the content. < / p >
2024-07-28 15:30:51 -04:00
< h2 > How to use it< / h2 >
2025-12-23 09:12:49 -05:00
< p > The structure of the tab set is defined in html. There are two forms supported. Adding a class of < code class = "inline" > .tab-group< / code > to the container element will work in place of the < code class = "inline" > tabset< / code > tag, and the tab panels can be defined using either < code class = "inline" > tab=""< / code > or < code class = "inline" > data-tab=""< / code > . Passing an optional element to the init function will initialise tabs within that element. Adding a < code class = "inline" > order=""< / code > or < code class = "inline" > data-order=""< / code > element to the tabset you can have the tabs sorted in a consistent order across tabsets.< / p >
< h2 > Example< / h2 >
< pre class = "language-pug" tab = "pug" > tabset(order="tab2, tab1")
div(tab="tab1")
div(tab="tab2")
< / pre >
2024-07-11 21:05:34 -04:00
< tabset id = "tabs" >
< pre class = "language-html" tab = "html" >
2025-12-23 09:12:49 -05:00
< tabset id = "uniqueID" order = "tab title 2,tab title 1" >
< div tab = "[tab title 1]" > < / div >
< div tab = "[tab title 2]" > < / div >
2024-07-23 20:50:36 -04:00
< / tabset > < / pre >
2025-12-23 09:12:49 -05:00
< pre class = "language-pug" tab = "pug" > tabset#uniqueID(order="tab title 2,tab title 1")
div(tab="[tab title 1]")
div(tab="[tab title 2]")
2024-07-16 02:49:53 -04:00
< / pre >
2025-12-23 09:12:49 -05:00
< pre class = "language-css" tab = "css" > tabset, .tab-group {
margin: 2rem 0 1rem 0;
}
2026-06-13 17:57:46 -04:00
tabset [role=tablist] li.selected, .tab-group [role=tablist] li.selected {
background-color: var(--colour-green);
color: var(--colour-white);
}
tabset > ul,
tabset .tab-scroll > ul, .tab-group > ul,
.tab-group .tab-scroll > ul {
2025-12-23 09:12:49 -05:00
display: -webkit-box;
display: -ms-flexbox;
display: flex;
margin: 0;
padding: 0;
2026-06-13 17:57:46 -04:00
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
2025-12-23 09:12:49 -05:00
}
2026-06-13 17:57:46 -04:00
tabset > ul li.separator,
tabset .tab-scroll > ul li.separator, .tab-group > ul li.separator,
.tab-group .tab-scroll > ul li.separator {
2025-12-23 09:12:49 -05:00
border-bottom: 1px solid #7f7f7f;
2026-06-13 17:57:46 -04:00
border-left: 1px solid #7f7f7f;
2025-12-23 09:12:49 -05:00
display: inline-block;
2026-06-13 17:57:46 -04:00
-webkit-box-flex: 1;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
2025-12-23 09:12:49 -05:00
margin: 0.45rem 0 0 0;
2026-06-13 17:57:46 -04:00
width: auto;
}
tabset .tab-hidden, .tab-group .tab-hidden {
display: none;
2025-12-23 09:12:49 -05:00
}
2026-06-13 17:57:46 -04:00
tabset [role=tab], .tab-group [role=tab] {
2025-12-23 09:12:49 -05:00
background-color: #FFF;
border-left: 1px solid #7f7f7f;
border-radius: 0.5rem 0.5rem 0 0;
border-top: 1px solid #7f7f7f;
cursor: pointer;
2026-06-13 17:57:46 -04:00
display: block;
-webkit-box-flex: 0;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
2025-12-23 09:12:49 -05:00
margin: 0;
2026-06-13 17:57:46 -04:00
max-width: 100vw;
overflow: hidden;
2025-12-23 09:12:49 -05:00
padding: 1rem 1.5rem 0.14rem 1.5rem;
2026-06-13 17:57:46 -04:00
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
white-space: normal;
word-break: break-word;
2025-12-23 09:12:49 -05:00
}
2026-06-13 17:57:46 -04:00
tabset [role=tab]:last-of-type, .tab-group [role=tab]:last-of-type {
2025-12-23 09:12:49 -05:00
border-right: 1px solid #7f7f7f;
}
2026-06-13 17:57:46 -04:00
tabset [role=tab]:not(.selected), .tab-group [role=tab]:not(.selected) {
2025-12-23 09:12:49 -05:00
background-color: #f0f0f0;
border-bottom: 1px solid #7f7f7f;
}
2026-06-13 17:57:46 -04:00
tabset [role=tab] span, .tab-group [role=tab] span {
2025-12-23 09:12:49 -05:00
display: block;
margin: 0 0 0.5rem 0;
}
tabset [role=tabpanel], .tab-group [role=tabpanel] {
background-color: #FFF;
border: 1px solid #7f7f7f;
border-top: none;
padding: 1rem;
z-index: 1;
}
tabset [role=tabpanel]:not(.open), .tab-group [role=tabpanel]:not(.open) {
display: none;
2026-06-13 17:57:46 -04:00
}
.tab-scroll {
width: 100%;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none;
-ms-overflow-style: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.tab-scroll::-webkit-scrollbar {
display: none;
}
.tab-scroll > ul {
-ms-flex-wrap: nowrap;
flex-wrap: nowrap;
min-width: 100%;
width: -webkit-max-content;
width: -moz-max-content;
width: max-content;
}
.tab-scroll-wrapper {
position: relative;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.tab-scroll-wrapper::before, .tab-scroll-wrapper::after {
content: "";
position: absolute;
top: 0;
width: 2.5rem;
height: 100%;
pointer-events: none;
z-index: 2;
opacity: 0;
-webkit-transition: opacity 0.2s ease;
transition: opacity 0.2s ease;
}
.tab-scroll-wrapper::before {
background: radial-gradient(circle at left center, rgb(255, 255, 255) 0%, rgba(255, 255, 255, 0.9) 50%, rgba(255, 255, 255, 0.5) 75%, rgba(255, 255, 255, 0.25) 95%, transparent 100%);
border-top-right-radius: 2.5rem;
border-bottom-right-radius: 2.5rem;
left: 0;
}
.tab-scroll-wrapper::after {
right: 0;
background: radial-gradient(circle at right center, rgb(255, 255, 255) 0%, rgba(255, 255, 255, 0.9) 50%, rgba(255, 255, 255, 0.5) 75%, rgba(255, 255, 255, 0.25) 95%, transparent 100%);
border-top-left-radius: 2.5rem;
border-bottom-left-radius: 2.5rem;
}
.tab-scroll-wrapper.is-overflowing::before, .tab-scroll-wrapper.is-overflowing::after {
opacity: 0;
-webkit-transition: opacity 0.15s ease-out;
transition: opacity 0.15s ease-out;
-webkit-transition: opacity 0.12s cubic-bezier(0.4, 0, 0.2, 1);
transition: opacity 0.12s cubic-bezier(0.4, 0, 0.2, 1);
}
.tab-scroll-wrapper.is-overflowing.show-left::before {
opacity: 1;
}
.tab-scroll-wrapper.is-overflowing.show-right::after {
opacity: 1;
}
.tab-scroll-wrapper .tab-scroll-arrow {
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
cursor: pointer;
display: none;
height: 100%;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
position: absolute;
top: 0;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
width: 2.5rem;
z-index: 3;
}
.tab-scroll-wrapper .tab-scroll-arrow path {
fill: #7f7f7f;
}
.tab-scroll-wrapper .tab-scroll-arrow.left {
left: 0;
}
.tab-scroll-wrapper .tab-scroll-arrow.right {
right: 0;
}
.tab-scroll-wrapper .tab-scroll-arrow.hidden {
display: none !important;
pointer-events: none;
visibility: hidden;
}
.tab-scroll-wrapper.is-overflowing .tab-scroll-arrow {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
2025-12-23 09:12:49 -05:00
}< / pre >
2024-07-28 15:30:51 -04:00
< div tab = "scss" >
2025-12-23 09:12:49 -05:00
< h2 > Example< / h2 >
2025-06-10 16:20:24 -04:00
< pre class = "language-sass" > @use "scss/core/tabs/_tabs";
2024-07-28 15:30:51 -04:00
@include tabs{
// optional content block
};
< / pre >
2026-06-13 17:57:46 -04:00
< pre class = "language-sass" > $tab-border: #7f7f7f !default;
2024-07-11 21:05:34 -04:00
2024-07-16 02:49:53 -04:00
$tab-selected: #FFF !default;
2025-08-03 18:31:00 -04:00
$tab-selected-text: #000 !default;
2026-06-13 17:57:46 -04:00
2024-07-16 02:49:53 -04:00
$tab-notselected: #f0f0f0 !default;
2025-08-03 18:31:00 -04:00
$tab-notselected-text: #000 !default;
2024-07-11 21:05:34 -04:00
@mixin tabs {
2026-06-13 17:57:46 -04:00
2024-07-11 21:05:34 -04:00
tabset, .tab-group {
2026-06-13 17:57:46 -04:00
2025-08-03 18:31:00 -04:00
margin: 2rem 0 1rem 0;
2025-08-03 19:21:37 -04:00
2026-06-13 17:57:46 -04:00
[role="tablist"] li.selected {
background-color: var(--colour-green);
color: var(--colour-white);
}
> ul,
.tab-scroll > ul {
2024-06-13 00:00:00 -04:00
display: flex;
margin: 0;
padding: 0;
2026-06-13 17:57:46 -04:00
user-select: none;
li.separator {
border-bottom: 1px solid $tab-border;
border-left: 1px solid $tab-border;
display: inline-block;
flex: 1 1 auto;
margin: .45rem 0 0 0;
width: auto;
}
}
2024-06-13 00:00:00 -04:00
2026-06-13 17:57:46 -04:00
.tab-hidden {
display: none;
}
2025-06-09 19:16:59 -04:00
2026-06-13 17:57:46 -04:00
[role="tab"] {
background-color: $tab-selected;
border-left: 1px solid $tab-border;
border-radius: .5rem .5rem 0 0;
border-top: 1px solid $tab-border;
cursor: pointer;
display: block;
flex: 0 0 auto;
margin: 0;
max-width: 100vw;
overflow: hidden;
padding: 1rem 1.5rem .14rem 1.5rem;
user-select: none;
white-space: normal;
word-break: break-word;
& :last-of-type {
border-right: 1px solid $tab-border;
}
2025-08-03 19:21:37 -04:00
2026-06-13 17:57:46 -04:00
& :not(.selected) {
background-color: $tab-notselected;
border-bottom: 1px solid $tab-border;
}
2025-08-03 19:21:37 -04:00
2026-06-13 17:57:46 -04:00
span {
display: block;
margin: 0 0 .5rem 0;
2024-06-13 00:00:00 -04:00
}
}
[role="tabpanel"] {
2025-06-09 19:16:59 -04:00
background-color: $tab-selected;
2025-08-03 18:31:00 -04:00
border: 1px solid $tab-border;
2025-08-03 19:21:37 -04:00
border-top: none;
2024-06-13 00:00:00 -04:00
padding: 1rem;
z-index: 1;
2025-08-03 18:31:00 -04:00
& :not(.open) {
display: none;
2024-06-13 00:00:00 -04:00
}
2026-06-13 17:57:46 -04:00
@content;
}
}
// scroller
.tab-scroll {
width: 100%;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none;
-ms-overflow-style: none;
user-select: none;
& ::-webkit-scrollbar {
display: none;
}
> ul {
flex-wrap: nowrap;
min-width: 100%;
width: max-content;
}
}
// wrapper (now owns the fade)
.tab-scroll-wrapper {
position: relative;
user-select: none;
$radial: 2.5rem;
// edge fades (this is the fix)
& ::before,
& ::after {
content: "";
position: absolute;
top: 0;
width: 2.5rem;
height: 100%;
pointer-events: none;
z-index: 2;
opacity: 0; // hidden by default
transition: opacity 0.2s ease;
}
// left fade
& ::before {
background: radial-gradient(
circle at left center,
rgba(255,255,255,1) 0%,
rgba(255,255,255,0.9) 50%,
rgba(255,255,255,0.5) 75%,
rgba(255,255,255,0.25) 95%,
transparent 100%
);
border-top-right-radius: $radial;
border-bottom-right-radius: $radial;
left: 0;
}
// right fade
& ::after {
right: 0;
background: radial-gradient(
circle at right center,
rgba(255,255,255,1) 0%,
rgba(255,255,255,0.9) 50%,
rgba(255,255,255,0.5) 75%,
rgba(255,255,255,0.25) 95%,
transparent 100%
);
border-top-left-radius: $radial;
border-bottom-left-radius: $radial;
}
// show fade only when overflowing
& .is-overflowing {
& ::before,
& ::after {
opacity: 0;
transition: opacity 0.15s ease-out;
transition: opacity 0.12s cubic-bezier(0.4, 0, 0.2, 1);
}
& .show-left {
& ::before {
opacity: 1;
}
}
& .show-right {
& ::after {
opacity: 1;
}
}
}
// arrows
.tab-scroll-arrow {
align-items: center;
cursor: pointer;
display: none;
height: 100%;
justify-content: center;
position: absolute;
top: 0;
user-select: none;
width: 2.5rem;
z-index: 3;
path {
fill: $tab-border;
}
& .left {
left: 0;
}
& .right {
right: 0;
}
& .hidden {
display: none !important;
pointer-events: none;
visibility: hidden;
}
}
// only show arrows when overflow
& .is-overflowing {
.tab-scroll-arrow {
display: flex;
}
2025-06-09 19:16:59 -04:00
}
2024-06-13 00:00:00 -04:00
}
}< / pre >
2024-07-28 15:30:51 -04:00
< / div >
< div tab = "js" >
2025-12-23 09:12:49 -05:00
< h2 > Example< / h2 >
2024-07-29 19:12:22 -04:00
< pre class = "language-js" > import * as tabs from "./js/core/tabs/_tabs.js";
2024-07-28 15:30:51 -04:00
tabs.init();< / pre >
2026-06-13 17:57:46 -04:00
< pre class = "language-js" > /* DS2 core (c) 2024-2026 Alexander McIlwraith
Released under Creative Commons Attribution-ShareAlike 4.0 International
*/
2024-07-11 21:05:34 -04:00
2024-09-03 17:55:37 -04:00
// create a pure JS mouse click event
const click = new MouseEvent('click', {
view: window,
bubbles: false,
cancelable: true
});
2026-06-13 17:57:46 -04:00
// wait for an element to appear in the DOM
2025-01-30 22:57:18 -05:00
const waitForElement = (selector) => {
2026-06-13 17:57:46 -04:00
return new Promise(resolve => {
if (document.querySelector(selector)) {
return resolve(document.querySelector(selector));
}
const observer = new MutationObserver(() => {
if (document.querySelector(selector)) {
observer.disconnect();
resolve(document.querySelector(selector));
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
});
};
// Tab logic
const tabNamespace = "#tab:";
2025-01-30 22:57:18 -05:00
2025-06-12 18:53:17 -04:00
const chooseTab = (tab) => {
2026-06-13 17:57:46 -04:00
const siblings = Array.from(
tab.closest('[role="tablist"]').querySelectorAll('[role="tab"]')
);
2025-06-12 18:53:17 -04:00
siblings.forEach(sibling => sibling.classList.remove("selected"));
tab.classList.add("selected");
2026-06-13 17:57:46 -04:00
const tabGroup = tab.closest("tabset, .tab-group");
const tabPanels = Array.from(
tabGroup.querySelectorAll('[role="tabpanel"]')
);
2025-06-12 18:53:17 -04:00
tabPanels.forEach(panel => panel.classList.remove("open"));
const tabPanelId = tab.getAttribute("id").replace("tab", "tab-panel");
document.getElementById(tabPanelId).classList.add("open");
2026-06-13 17:57:46 -04:00
};
2025-06-09 19:16:59 -04:00
2026-06-13 17:57:46 -04:00
let pushStateCount = 0;
2025-06-12 18:53:17 -04:00
let tabsetCount = 0;
2025-06-09 19:16:59 -04:00
2026-06-13 17:57:46 -04:00
export function init(container = document, spacer = true) {
2025-12-23 09:12:49 -05:00
container.querySelectorAll(".tab-group, tabset").forEach(tabGroup => {
2026-06-13 17:57:46 -04:00
if (tabGroup.querySelector('[role="tablist"]') === null) {
2025-12-23 09:12:49 -05:00
if (tabGroup.getAttribute("id") == null) {
2026-06-13 17:57:46 -04:00
tabGroup.setAttribute("id", `tab-group-${tabsetCount++}`);
2025-12-23 09:12:49 -05:00
}
const tabgroup = tabGroup.getAttribute("id");
let tablist = "";
Array.from(tabGroup.children).forEach(child => {
2026-06-13 17:57:46 -04:00
const isDetails = child.nodeName === "DETAILS";
const tabLabel = isDetails ? child.querySelector("summary")?.innerHTML : child.getAttribute("tab") || child.getAttribute("data-tab");
2025-12-23 09:12:49 -05:00
2026-06-13 17:57:46 -04:00
if (tabLabel !== null) {
2025-12-23 09:12:49 -05:00
2026-06-13 17:57:46 -04:00
const tabID = tabLabel.replace(/\W+/g, "-").toLowerCase();
let tabPanel;
2025-12-23 09:12:49 -05:00
2026-06-13 17:57:46 -04:00
if (isDetails) {
2025-12-23 09:12:49 -05:00
tabPanel = child;
tabPanel.setAttribute("open", "");
2025-06-09 19:16:59 -04:00
} else {
2026-06-13 17:57:46 -04:00
tabPanel = document.createElement("div");
2025-12-23 09:12:49 -05:00
tabPanel.appendChild(child.cloneNode(true));
2025-06-09 19:16:59 -04:00
}
2024-09-03 17:55:37 -04:00
2025-12-23 09:12:49 -05:00
tabPanel.id = `tab-panel-${tabgroup}-${tabID}`;
tabPanel.className = tablist === "" ? "open" : "";
tabPanel.setAttribute("role", "tabpanel");
tabPanel.setAttribute("tabindex", "0");
2026-06-13 17:57:46 -04:00
tabPanel.setAttribute(
"aria-labelledby",
`tab-${tabgroup}-${tabID}`
);
2025-12-23 09:12:49 -05:00
child.parentNode.replaceChild(tabPanel, child);
2026-06-13 17:57:46 -04:00
tablist += `< li tabindex = "0" role = "tab" id = "tab-${tabgroup}-${tabID}" >
< span > ${tabLabel}< / span >
< / li > `;
2025-12-23 09:12:49 -05:00
} else {
child.classList.add("tab-hidden");
}
});
2025-06-12 18:53:17 -04:00
2026-06-13 17:57:46 -04:00
const ul = document.createElement("ul");
2025-12-23 09:12:49 -05:00
ul.setAttribute("role", "tablist");
tabGroup.insertBefore(ul, tabGroup.firstChild);
2025-06-12 18:53:17 -04:00
2026-06-13 17:57:46 -04:00
const wrapper = document.createElement("div");
wrapper.className = "tab-scroll-wrapper";
const scroller = document.createElement("div");
scroller.className = "tab-scroll";
const leftArrow = document.createElement("div");
leftArrow.className = "tab-scroll-arrow left";
leftArrow.setAttribute("aria-hidden", "true");
leftArrow.innerHTML = `
< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 -960 960 960" width = "24" height = "24" >
< path d = "M400-80 0-480l400-400 71 71-329 329 329 329-71 71Z" / >
< / svg >
`;
const rightArrow = document.createElement("div");
rightArrow.className = "tab-scroll-arrow right";
rightArrow.setAttribute("aria-hidden", "true");
rightArrow.innerHTML = `
< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 -960 960 960" width = "24" height = "24" >
< path d = "m321-80-71-71 329-329-329-329 71-71 400 400L321-80Z" / >
< / svg >
`;
ul.parentNode.insertBefore(wrapper, ul);
wrapper.appendChild(scroller);
wrapper.appendChild(leftArrow);
wrapper.appendChild(rightArrow);
scroller.appendChild(ul);
const updateScrollState = () => {
const isOverflowing = scroller.scrollWidth > scroller.clientWidth;
wrapper.classList.toggle("is-overflowing", isOverflowing);
const atStart = scroller.scrollLeft < = 1;
const atEnd = scroller.scrollLeft + scroller.clientWidth >= scroller.scrollWidth - 2;
leftArrow.classList.toggle("hidden", !isOverflowing || atStart);
rightArrow.classList.toggle("hidden", !isOverflowing || atEnd);
wrapper.classList.toggle("show-left", isOverflowing & & !atStart);
wrapper.classList.toggle("show-right", isOverflowing & & !atEnd);
};
const scrollAmount = 15; // smaller = smoother per frame
const scrollInterval = 16; // ~60fps
let scrollTimer = null;
const startScrolling = (direction) => {
if (scrollTimer) return;
let speed = 0.1;
scrollTimer = setInterval(() => {
speed = Math.min(speed + 0.01, 5); // ramp up
scroller.scrollBy({
left: direction * scrollAmount * speed,
behavior: "auto"
});
}, scrollInterval);
};
const stopScrolling = () => {
clearInterval(scrollTimer);
scrollTimer = null;
};
leftArrow.addEventListener("mousedown", () => startScrolling(-1));
leftArrow.addEventListener("mouseup", stopScrolling);
leftArrow.addEventListener("mouseleave", stopScrolling);
leftArrow.addEventListener("touchstart", () => startScrolling(-1));
leftArrow.addEventListener("touchend", stopScrolling);
rightArrow.addEventListener("mousedown", () => startScrolling(1));
rightArrow.addEventListener("mouseup", stopScrolling);
rightArrow.addEventListener("mouseleave", stopScrolling);
rightArrow.addEventListener("touchstart", () => startScrolling(1));
rightArrow.addEventListener("touchend", stopScrolling);
const ARROW_ZONE = 40;
wrapper.addEventListener("contextmenu", (e) => {
const isOverflowing = wrapper.classList.contains("is-overflowing");
if (!isOverflowing) return;
const rect = wrapper.getBoundingClientRect();
const isLeftZone = e.clientX < rect.left + ARROW_ZONE ;
const isRightZone = e.clientX > rect.right - ARROW_ZONE;
if (isLeftZone || isRightZone) {
e.preventDefault();
}
});
const scrollClickAmount = 150;
leftArrow.addEventListener("click", () => {
scroller.scrollBy({ left: -scrollClickAmount, behavior: "smooth" });
});
rightArrow.addEventListener("click", () => {
scroller.scrollBy({ left: scrollClickAmount, behavior: "smooth" });
});
scroller.addEventListener("scroll", updateScrollState);
window.addEventListener("resize", updateScrollState);
updateScrollState();
ul.innerHTML = spacer !== true ? tablist : `${tablist}< li role = "separator" class = "separator" > < / li > `;
requestAnimationFrame(updateScrollState);
setTimeout(updateScrollState, 50);
ul.innerHTML = spacer !== true ? tablist : `${tablist}< li role = "separator" class = "separator" > < / li > `;
// Tab ordering
if (tabGroup.hasAttribute("order") || tabGroup.hasAttribute("data-order")) {
const order = (
tabGroup.getAttribute("order") ||
tabGroup.getAttribute("data-order")
).split(",");
2025-12-23 09:12:49 -05:00
const items = Array.from(ul.getElementsByTagName("li"));
2026-06-13 17:57:46 -04:00
2025-12-23 09:12:49 -05:00
items.sort((a, b) => {
const aa = order.indexOf(a.textContent.trim());
const bb = order.indexOf(b.textContent.trim());
2026-06-13 17:57:46 -04:00
if (aa === -1) return 1;
if (bb === -1) return -1;
return aa - bb;
2025-06-12 18:53:17 -04:00
});
2025-12-23 09:12:49 -05:00
2026-06-13 17:57:46 -04:00
ul.innerHTML = "";
2025-12-23 09:12:49 -05:00
items.forEach(item => ul.appendChild(item));
2026-06-13 17:57:46 -04:00
chooseTab(items[0]);
2025-06-12 18:53:17 -04:00
}
2024-09-03 17:55:37 -04:00
2026-06-13 17:57:46 -04:00
// Tab event handlers
2025-12-23 09:12:49 -05:00
tabGroup.querySelectorAll('[role="tab"]').forEach(tab => {
2026-06-13 17:57:46 -04:00
2025-12-23 09:12:49 -05:00
tab.addEventListener("click", (evt) => {
2026-06-13 17:57:46 -04:00
const tabId = tab.getAttribute("id");
const hash = `${tabNamespace}${tabId}`;
2025-12-23 09:12:49 -05:00
chooseTab(evt.currentTarget);
2026-06-13 17:57:46 -04:00
window.history.pushState(
{ tab: tabId },
"",
`${location.pathname}${location.search}${hash}`
);
pushStateCount++;
2025-06-12 18:53:17 -04:00
});
2025-06-09 19:16:59 -04:00
2025-12-23 09:12:49 -05:00
tab.addEventListener("keypress", (e) => {
2026-06-13 17:57:46 -04:00
if (e.which === 32 || e.which === 13) {
e.preventDefault();
2025-12-23 09:12:49 -05:00
e.currentTarget.dispatchEvent(click);
}
2026-06-13 17:57:46 -04:00
});
2025-12-23 09:12:49 -05:00
});
2025-06-09 19:16:59 -04:00
2026-06-13 17:57:46 -04:00
ul.querySelector("li")?.classList.add("selected");
2025-12-23 09:12:49 -05:00
}
});
2026-06-13 17:57:46 -04:00
// Initial hash handling (tabs only)
if (location.hash.startsWith(tabNamespace)) {
const tabId = location.hash.replace(tabNamespace, "");
waitForElement(`#${tabId}`).then(el => {
el.focus();
el.dispatchEvent(click);
});
}
// History navigation (tabs only)
window.addEventListener("popstate", (e) => {
if (!location.hash.startsWith(tabNamespace)) return;
if (e.state?.tab) {
const tab = document.querySelector(`#${e.state.tab}`);
if (tab) chooseTab(tab);
2025-12-23 09:12:49 -05:00
}
});
2026-06-13 17:57:46 -04:00
}< / pre >
2024-07-28 15:30:51 -04:00
< / div >
2024-07-11 21:05:34 -04:00
< / tabset >
2024-06-13 00:00:00 -04:00
< / body >
< / html >