350 lines
12 KiB
JavaScript
350 lines
12 KiB
JavaScript
import {
|
|
tab_group_styles_default
|
|
} from "./chunk.EG6EOQHP.js";
|
|
import {
|
|
scrollIntoView
|
|
} from "./chunk.RK73WSZS.js";
|
|
import {
|
|
SlIconButton
|
|
} from "./chunk.Y77CZW56.js";
|
|
import {
|
|
LocalizeController
|
|
} from "./chunk.NH3SRVOC.js";
|
|
import {
|
|
e
|
|
} from "./chunk.UZVKBFXH.js";
|
|
import {
|
|
watch
|
|
} from "./chunk.FA5RT4K4.js";
|
|
import {
|
|
ShoelaceElement,
|
|
e as e2,
|
|
n,
|
|
r
|
|
} from "./chunk.SEXBCYCU.js";
|
|
import {
|
|
x
|
|
} from "./chunk.CXZZ2LVK.js";
|
|
import {
|
|
__decorateClass,
|
|
__spreadValues
|
|
} from "./chunk.KIILAQWQ.js";
|
|
|
|
// src/components/tab-group/tab-group.component.ts
|
|
var SlTabGroup = class extends ShoelaceElement {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.localize = new LocalizeController(this);
|
|
this.tabs = [];
|
|
this.panels = [];
|
|
this.hasScrollControls = false;
|
|
this.placement = "top";
|
|
this.activation = "auto";
|
|
this.noScrollControls = false;
|
|
}
|
|
connectedCallback() {
|
|
const whenAllDefined = Promise.all([
|
|
customElements.whenDefined("sl-tab"),
|
|
customElements.whenDefined("sl-tab-panel")
|
|
]);
|
|
super.connectedCallback();
|
|
this.resizeObserver = new ResizeObserver(() => {
|
|
this.repositionIndicator();
|
|
this.updateScrollControls();
|
|
});
|
|
this.mutationObserver = new MutationObserver((mutations) => {
|
|
if (mutations.some((m) => !["aria-labelledby", "aria-controls"].includes(m.attributeName))) {
|
|
setTimeout(() => this.setAriaLabels());
|
|
}
|
|
if (mutations.some((m) => m.attributeName === "disabled")) {
|
|
this.syncTabsAndPanels();
|
|
}
|
|
});
|
|
this.updateComplete.then(() => {
|
|
this.syncTabsAndPanels();
|
|
this.mutationObserver.observe(this, { attributes: true, childList: true, subtree: true });
|
|
this.resizeObserver.observe(this.nav);
|
|
whenAllDefined.then(() => {
|
|
const intersectionObserver = new IntersectionObserver((entries, observer) => {
|
|
var _a;
|
|
if (entries[0].intersectionRatio > 0) {
|
|
this.setAriaLabels();
|
|
this.setActiveTab((_a = this.getActiveTab()) != null ? _a : this.tabs[0], { emitEvents: false });
|
|
observer.unobserve(entries[0].target);
|
|
}
|
|
});
|
|
intersectionObserver.observe(this.tabGroup);
|
|
});
|
|
});
|
|
}
|
|
disconnectedCallback() {
|
|
super.disconnectedCallback();
|
|
this.mutationObserver.disconnect();
|
|
this.resizeObserver.unobserve(this.nav);
|
|
}
|
|
getAllTabs(options = { includeDisabled: true }) {
|
|
const slot = this.shadowRoot.querySelector('slot[name="nav"]');
|
|
return [...slot.assignedElements()].filter((el) => {
|
|
return options.includeDisabled ? el.tagName.toLowerCase() === "sl-tab" : el.tagName.toLowerCase() === "sl-tab" && !el.disabled;
|
|
});
|
|
}
|
|
getAllPanels() {
|
|
return [...this.body.assignedElements()].filter((el) => el.tagName.toLowerCase() === "sl-tab-panel");
|
|
}
|
|
getActiveTab() {
|
|
return this.tabs.find((el) => el.active);
|
|
}
|
|
handleClick(event) {
|
|
const target = event.target;
|
|
const tab = target.closest("sl-tab");
|
|
const tabGroup = tab == null ? void 0 : tab.closest("sl-tab-group");
|
|
if (tabGroup !== this) {
|
|
return;
|
|
}
|
|
if (tab !== null) {
|
|
this.setActiveTab(tab, { scrollBehavior: "smooth" });
|
|
}
|
|
}
|
|
handleKeyDown(event) {
|
|
const target = event.target;
|
|
const tab = target.closest("sl-tab");
|
|
const tabGroup = tab == null ? void 0 : tab.closest("sl-tab-group");
|
|
if (tabGroup !== this) {
|
|
return;
|
|
}
|
|
if (["Enter", " "].includes(event.key)) {
|
|
if (tab !== null) {
|
|
this.setActiveTab(tab, { scrollBehavior: "smooth" });
|
|
event.preventDefault();
|
|
}
|
|
}
|
|
if (["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) {
|
|
const activeEl = this.tabs.find((t) => t.matches(":focus"));
|
|
const isRtl = this.localize.dir() === "rtl";
|
|
if ((activeEl == null ? void 0 : activeEl.tagName.toLowerCase()) === "sl-tab") {
|
|
let index = this.tabs.indexOf(activeEl);
|
|
if (event.key === "Home") {
|
|
index = 0;
|
|
} else if (event.key === "End") {
|
|
index = this.tabs.length - 1;
|
|
} else if (["top", "bottom"].includes(this.placement) && event.key === (isRtl ? "ArrowRight" : "ArrowLeft") || ["start", "end"].includes(this.placement) && event.key === "ArrowUp") {
|
|
index--;
|
|
} else if (["top", "bottom"].includes(this.placement) && event.key === (isRtl ? "ArrowLeft" : "ArrowRight") || ["start", "end"].includes(this.placement) && event.key === "ArrowDown") {
|
|
index++;
|
|
}
|
|
if (index < 0) {
|
|
index = this.tabs.length - 1;
|
|
}
|
|
if (index > this.tabs.length - 1) {
|
|
index = 0;
|
|
}
|
|
this.tabs[index].focus({ preventScroll: true });
|
|
if (this.activation === "auto") {
|
|
this.setActiveTab(this.tabs[index], { scrollBehavior: "smooth" });
|
|
}
|
|
if (["top", "bottom"].includes(this.placement)) {
|
|
scrollIntoView(this.tabs[index], this.nav, "horizontal");
|
|
}
|
|
event.preventDefault();
|
|
}
|
|
}
|
|
}
|
|
handleScrollToStart() {
|
|
this.nav.scroll({
|
|
left: this.localize.dir() === "rtl" ? this.nav.scrollLeft + this.nav.clientWidth : this.nav.scrollLeft - this.nav.clientWidth,
|
|
behavior: "smooth"
|
|
});
|
|
}
|
|
handleScrollToEnd() {
|
|
this.nav.scroll({
|
|
left: this.localize.dir() === "rtl" ? this.nav.scrollLeft - this.nav.clientWidth : this.nav.scrollLeft + this.nav.clientWidth,
|
|
behavior: "smooth"
|
|
});
|
|
}
|
|
setActiveTab(tab, options) {
|
|
options = __spreadValues({
|
|
emitEvents: true,
|
|
scrollBehavior: "auto"
|
|
}, options);
|
|
if (tab !== this.activeTab && !tab.disabled) {
|
|
const previousTab = this.activeTab;
|
|
this.activeTab = tab;
|
|
this.tabs.forEach((el) => el.active = el === this.activeTab);
|
|
this.panels.forEach((el) => {
|
|
var _a;
|
|
return el.active = el.name === ((_a = this.activeTab) == null ? void 0 : _a.panel);
|
|
});
|
|
this.syncIndicator();
|
|
if (["top", "bottom"].includes(this.placement)) {
|
|
scrollIntoView(this.activeTab, this.nav, "horizontal", options.scrollBehavior);
|
|
}
|
|
if (options.emitEvents) {
|
|
if (previousTab) {
|
|
this.emit("sl-tab-hide", { detail: { name: previousTab.panel } });
|
|
}
|
|
this.emit("sl-tab-show", { detail: { name: this.activeTab.panel } });
|
|
}
|
|
}
|
|
}
|
|
setAriaLabels() {
|
|
this.tabs.forEach((tab) => {
|
|
const panel = this.panels.find((el) => el.name === tab.panel);
|
|
if (panel) {
|
|
tab.setAttribute("aria-controls", panel.getAttribute("id"));
|
|
panel.setAttribute("aria-labelledby", tab.getAttribute("id"));
|
|
}
|
|
});
|
|
}
|
|
repositionIndicator() {
|
|
const currentTab = this.getActiveTab();
|
|
if (!currentTab) {
|
|
return;
|
|
}
|
|
const width = currentTab.clientWidth;
|
|
const height = currentTab.clientHeight;
|
|
const isRtl = this.localize.dir() === "rtl";
|
|
const allTabs = this.getAllTabs();
|
|
const precedingTabs = allTabs.slice(0, allTabs.indexOf(currentTab));
|
|
const offset = precedingTabs.reduce(
|
|
(previous, current) => ({
|
|
left: previous.left + current.clientWidth,
|
|
top: previous.top + current.clientHeight
|
|
}),
|
|
{ left: 0, top: 0 }
|
|
);
|
|
switch (this.placement) {
|
|
case "top":
|
|
case "bottom":
|
|
this.indicator.style.width = `${width}px`;
|
|
this.indicator.style.height = "auto";
|
|
this.indicator.style.translate = isRtl ? `${-1 * offset.left}px` : `${offset.left}px`;
|
|
break;
|
|
case "start":
|
|
case "end":
|
|
this.indicator.style.width = "auto";
|
|
this.indicator.style.height = `${height}px`;
|
|
this.indicator.style.translate = `0 ${offset.top}px`;
|
|
break;
|
|
}
|
|
}
|
|
// This stores tabs and panels so we can refer to a cache instead of calling querySelectorAll() multiple times.
|
|
syncTabsAndPanels() {
|
|
this.tabs = this.getAllTabs({ includeDisabled: false });
|
|
this.panels = this.getAllPanels();
|
|
this.syncIndicator();
|
|
this.updateComplete.then(() => this.updateScrollControls());
|
|
}
|
|
updateScrollControls() {
|
|
if (this.noScrollControls) {
|
|
this.hasScrollControls = false;
|
|
} else {
|
|
this.hasScrollControls = ["top", "bottom"].includes(this.placement) && this.nav.scrollWidth > this.nav.clientWidth;
|
|
}
|
|
}
|
|
syncIndicator() {
|
|
const tab = this.getActiveTab();
|
|
if (tab) {
|
|
this.indicator.style.display = "block";
|
|
this.repositionIndicator();
|
|
} else {
|
|
this.indicator.style.display = "none";
|
|
}
|
|
}
|
|
/** Shows the specified tab panel. */
|
|
show(panel) {
|
|
const tab = this.tabs.find((el) => el.panel === panel);
|
|
if (tab) {
|
|
this.setActiveTab(tab, { scrollBehavior: "smooth" });
|
|
}
|
|
}
|
|
render() {
|
|
const isRtl = this.localize.dir() === "rtl";
|
|
return x`
|
|
<div
|
|
part="base"
|
|
class=${e({
|
|
"tab-group": true,
|
|
"tab-group--top": this.placement === "top",
|
|
"tab-group--bottom": this.placement === "bottom",
|
|
"tab-group--start": this.placement === "start",
|
|
"tab-group--end": this.placement === "end",
|
|
"tab-group--rtl": this.localize.dir() === "rtl",
|
|
"tab-group--has-scroll-controls": this.hasScrollControls
|
|
})}
|
|
@click=${this.handleClick}
|
|
@keydown=${this.handleKeyDown}
|
|
>
|
|
<div class="tab-group__nav-container" part="nav">
|
|
${this.hasScrollControls ? x`
|
|
<sl-icon-button
|
|
part="scroll-button scroll-button--start"
|
|
exportparts="base:scroll-button__base"
|
|
class="tab-group__scroll-button tab-group__scroll-button--start"
|
|
name=${isRtl ? "chevron-right" : "chevron-left"}
|
|
library="system"
|
|
label=${this.localize.term("scrollToStart")}
|
|
@click=${this.handleScrollToStart}
|
|
></sl-icon-button>
|
|
` : ""}
|
|
|
|
<div class="tab-group__nav">
|
|
<div part="tabs" class="tab-group__tabs" role="tablist">
|
|
<div part="active-tab-indicator" class="tab-group__indicator"></div>
|
|
<slot name="nav" @slotchange=${this.syncTabsAndPanels}></slot>
|
|
</div>
|
|
</div>
|
|
|
|
${this.hasScrollControls ? x`
|
|
<sl-icon-button
|
|
part="scroll-button scroll-button--end"
|
|
exportparts="base:scroll-button__base"
|
|
class="tab-group__scroll-button tab-group__scroll-button--end"
|
|
name=${isRtl ? "chevron-left" : "chevron-right"}
|
|
library="system"
|
|
label=${this.localize.term("scrollToEnd")}
|
|
@click=${this.handleScrollToEnd}
|
|
></sl-icon-button>
|
|
` : ""}
|
|
</div>
|
|
|
|
<slot part="body" class="tab-group__body" @slotchange=${this.syncTabsAndPanels}></slot>
|
|
</div>
|
|
`;
|
|
}
|
|
};
|
|
SlTabGroup.styles = tab_group_styles_default;
|
|
SlTabGroup.dependencies = { "sl-icon-button": SlIconButton };
|
|
__decorateClass([
|
|
e2(".tab-group")
|
|
], SlTabGroup.prototype, "tabGroup", 2);
|
|
__decorateClass([
|
|
e2(".tab-group__body")
|
|
], SlTabGroup.prototype, "body", 2);
|
|
__decorateClass([
|
|
e2(".tab-group__nav")
|
|
], SlTabGroup.prototype, "nav", 2);
|
|
__decorateClass([
|
|
e2(".tab-group__indicator")
|
|
], SlTabGroup.prototype, "indicator", 2);
|
|
__decorateClass([
|
|
r()
|
|
], SlTabGroup.prototype, "hasScrollControls", 2);
|
|
__decorateClass([
|
|
n()
|
|
], SlTabGroup.prototype, "placement", 2);
|
|
__decorateClass([
|
|
n()
|
|
], SlTabGroup.prototype, "activation", 2);
|
|
__decorateClass([
|
|
n({ attribute: "no-scroll-controls", type: Boolean })
|
|
], SlTabGroup.prototype, "noScrollControls", 2);
|
|
__decorateClass([
|
|
watch("noScrollControls", { waitUntilFirstUpdate: true })
|
|
], SlTabGroup.prototype, "updateScrollControls", 1);
|
|
__decorateClass([
|
|
watch("placement", { waitUntilFirstUpdate: true })
|
|
], SlTabGroup.prototype, "syncIndicator", 1);
|
|
|
|
export {
|
|
SlTabGroup
|
|
};
|