177 lines
4.7 KiB
JavaScript
177 lines
4.7 KiB
JavaScript
// saladui/components/tabs.js
|
|
import Component from "../core/component";
|
|
import SaladUI from "../index";
|
|
import Collection from "../core/collection";
|
|
|
|
class TabsComponent extends Component {
|
|
constructor(el, hookContext) {
|
|
super(el, { hookContext });
|
|
|
|
// Initialize core properties
|
|
this.list = this.getPart("list");
|
|
this.triggers = this.getAllParts("trigger");
|
|
this.contents = this.getAllParts("content");
|
|
|
|
// Set keyboard navigation defaults
|
|
this.config.preventDefaultKeys = [
|
|
"ArrowLeft",
|
|
"ArrowRight",
|
|
"Home",
|
|
"End",
|
|
"Enter",
|
|
" ",
|
|
];
|
|
|
|
// Initialize tabs
|
|
this.initialize();
|
|
}
|
|
|
|
initialize() {
|
|
// Initialize collection manager for tabs
|
|
this.collection = new Collection({
|
|
type: "single",
|
|
defaultValue: this.options.defaultValue,
|
|
value: this.options.value,
|
|
getItemValue: (item) => item.getAttribute("data-value"),
|
|
isItemDisabled: (item) => item.getAttribute("data-disabled") === "true",
|
|
});
|
|
|
|
// Register triggers with collection manager
|
|
this.triggers.forEach((trigger) => this.collection.add(trigger));
|
|
|
|
// Setup accessibility attributes
|
|
this.setupAriaAttributes();
|
|
|
|
// Select first tab if none selected
|
|
if (!this.collection.getValue() && this.triggers.length > 0) {
|
|
const firstTrigger = this.collection.getItem("first");
|
|
if (firstTrigger) this.collection.select(firstTrigger);
|
|
}
|
|
|
|
// Initial UI update
|
|
this.updateActiveTab();
|
|
}
|
|
|
|
getComponentConfig() {
|
|
return {
|
|
stateMachine: {
|
|
idle: {
|
|
transitions: { select: "idle" },
|
|
},
|
|
},
|
|
events: {
|
|
idle: {
|
|
keyMap: {
|
|
ArrowLeft: () => this.navigateTab("prev"),
|
|
ArrowRight: () => this.navigateTab("next"),
|
|
Home: () => this.navigateTab("first"),
|
|
End: () => this.navigateTab("last"),
|
|
},
|
|
mouseMap: {
|
|
trigger: { click: (event) => this.handleTriggerClick(event) },
|
|
},
|
|
},
|
|
},
|
|
ariaConfig: {
|
|
list: {
|
|
all: { role: "tablist" },
|
|
},
|
|
trigger: {
|
|
all: {
|
|
role: "tab",
|
|
controls: (el) =>
|
|
`${this.el.id}-content-${el.getAttribute("data-value")}`,
|
|
},
|
|
},
|
|
content: {
|
|
all: {
|
|
role: "tabpanel",
|
|
tabindex: "0",
|
|
},
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
setupAriaAttributes() {
|
|
// Set IDs and ARIA attributes for triggers
|
|
this.triggers.forEach((trigger) => {
|
|
const value = trigger.getAttribute("data-value");
|
|
if (!trigger.id) trigger.id = `${this.el.id}-trigger-${value}`;
|
|
});
|
|
|
|
// Set IDs and ARIA attributes for content panels
|
|
this.contents.forEach((content) => {
|
|
const value = content.getAttribute("data-value");
|
|
if (!content.id) content.id = `${this.el.id}-content-${value}`;
|
|
content.setAttribute("aria-labelledby", `${this.el.id}-trigger-${value}`);
|
|
});
|
|
}
|
|
|
|
handleTriggerClick(event) {
|
|
const trigger = event.currentTarget;
|
|
if (trigger.getAttribute("data-disabled") === "true") return;
|
|
|
|
this.selectTab(trigger.getAttribute("data-value"));
|
|
}
|
|
|
|
selectTab(value) {
|
|
// Find the trigger item
|
|
const triggerItem = this.collection.getItemByValue(value);
|
|
if (!triggerItem || this.collection.isValueSelected(value)) return;
|
|
|
|
// Select the tab
|
|
this.collection.select(triggerItem);
|
|
this.updateActiveTab();
|
|
|
|
// Focus the selected trigger
|
|
triggerItem.instance.focus();
|
|
|
|
// Emit event
|
|
this.pushEvent("tab-changed", { value: value, tab: value });
|
|
}
|
|
|
|
updateActiveTab() {
|
|
const selectedValue = this.collection.getValue();
|
|
|
|
// Update triggers
|
|
this.triggers.forEach((trigger) => {
|
|
const value = trigger.getAttribute("data-value");
|
|
const isActive = value === selectedValue;
|
|
|
|
trigger.setAttribute("data-state", isActive ? "active" : "inactive");
|
|
trigger.setAttribute("aria-selected", isActive.toString());
|
|
trigger.tabIndex = isActive ? 0 : -1;
|
|
});
|
|
|
|
// Update content panels
|
|
this.contents.forEach((content) => {
|
|
const value = content.getAttribute("data-value");
|
|
const isActive = value === selectedValue;
|
|
|
|
content.setAttribute("data-state", isActive ? "active" : "inactive");
|
|
content.hidden = !isActive;
|
|
});
|
|
}
|
|
|
|
navigateTab(direction) {
|
|
const currentItem = this.collection.getItemByValue(
|
|
this.collection.getValue(),
|
|
);
|
|
|
|
const nextItem = this.collection.getItem(direction, currentItem);
|
|
if (nextItem) this.selectTab(nextItem.value);
|
|
}
|
|
|
|
// Cleanup
|
|
destroy() {
|
|
this.collection = null;
|
|
super.destroy();
|
|
}
|
|
}
|
|
|
|
// Register the component
|
|
SaladUI.register("tabs", TabsComponent);
|
|
|
|
export default TabsComponent;
|