Files
Rivertalk/assets/js/ui/components/accordion.js
T
2026-02-12 17:10:03 -06:00

242 lines
5.9 KiB
JavaScript

// saladui/components/accordion.js
import Component from "../core/component";
import Collection from "../core/collection";
import SaladUI from "../index";
/**
* AccordionItem class to manage individual accordion items
* Handles state transitions and events for a single accordion item
*/
class AccordionItem extends Component {
constructor(itemElement, parentComponent, options) {
const { initialState = "closed" } = options || {};
super(itemElement, { initialState, ignoreItems: false });
this.parent = parentComponent;
this.value = itemElement.dataset.value;
this.disabled = itemElement.dataset.disabled === "true";
this.trigger = itemElement.querySelector("[data-part='item-trigger']");
this.content = itemElement.querySelector("[data-part='item-content']");
this.initialize();
this.setupEvents();
}
getComponentConfig() {
return {
stateMachine: {
closed: {
transitions: {
open: "open",
},
},
open: {
transitions: {
close: "closed",
},
},
},
events: {
closed: {
mouseMap: {
"item-trigger": {
click: "handleTriggerActivation",
},
},
keyMap: {
Enter: "handleTriggerActivation",
" ": "handleTriggerActivation",
},
},
open: {
mouseMap: {
"item-trigger": {
click: "handleTriggerActivation",
},
},
keyMap: {
Enter: "handleTriggerActivation",
" ": "handleTriggerActivation",
},
},
},
hiddenConfig: {
closed: {
"item-content": true,
},
open: {
"item-content": false,
},
},
ariaConfig: {
"item-trigger": {
all: {
controls: () => this.content?.id,
},
open: {
expanded: "true",
},
closed: {
expanded: "false",
},
},
"item-content": {
all: {
labelledby: () => this.trigger?.id,
},
},
},
};
}
initialize() {
if (this.disabled) {
this.trigger.setAttribute("tabindex", "-1");
} else {
this.trigger.setAttribute("tabindex", "0");
}
}
handleEvent(eventType) {
switch (eventType) {
case "select":
return this.transition("open");
case "unselect":
return this.transition("close");
case "focus":
if (this.trigger && !this.disabled) {
this.trigger.focus();
}
return true;
case "blur":
return true;
}
}
handleTriggerActivation(event) {
event.preventDefault();
if (!this.disabled && !this.parent.disabled) {
this.parent.toggleItem(this);
}
}
}
/**
* AccordionComponent class for SaladUI framework
* Manages a collection of accordion items with state transitions
*/
class AccordionComponent extends Component {
constructor(el, hookContext) {
super(el, { hookContext });
// Initialize properties
this.type = this.options.type || "single";
this.disabled = this.options.disabled || false;
// Initialize collection manager
this.collection = new Collection({
type: this.type,
defaultValue: this.options.defaultValue,
value: this.options.value,
getItemValue: (item) => item.value,
isItemDisabled: (item) => item.disabled || this.disabled,
});
// Set keyboard navigation defaults
this.config.preventDefaultKeys = [
"ArrowUp",
"ArrowDown",
"Home",
"End",
"Enter",
" ",
];
// Initialize accordion items
this.initializeItems();
}
getComponentConfig() {
return {
stateMachine: {
idle: {
enter: () => {},
exit: () => {},
transitions: {},
},
},
events: {
idle: {
keyMap: {
ArrowUp: () => this.navigateItem("prev"),
ArrowDown: () => this.navigateItem("next"),
Home: () => this.navigateItem("first"),
End: () => this.navigateItem("last"),
},
},
},
};
}
initializeItems() {
const itemElements = Array.from(
this.el.querySelectorAll("[data-part='item']"),
);
this.items = itemElements.map((element) => {
// Initialize AccordionItem without hook context
const itemValue = element.dataset.value;
element.id = `${this.el.id}-item-${itemValue}`;
// Check if this item is initially open
const isOpen = this.collection.getValue(true).includes(itemValue);
const item = new AccordionItem(element, this, {
initialState: isOpen ? "open" : "closed",
});
this.collection.add(item);
return item;
});
}
toggleItem(item) {
const collectionItem = this.collection.getItemByInstance(item);
if (!collectionItem) return;
// Toggle item selection
this.collection.select(collectionItem);
// Emit event with current value
const value = this.collection.getValue();
this.pushEvent("value-changed", { value });
}
navigateItem(direction) {
const currentFocus = document.activeElement;
const currentItemElement = currentFocus?.closest("[data-part='item']");
let currentItem = null;
if (currentItemElement) {
currentItem = this.items.find((item) => item.el === currentItemElement);
}
let referenceCollectionItem = null;
if (currentItem) {
referenceCollectionItem = this.collection.getItemByInstance(currentItem);
}
// Get the target item using collection manager's navigation methods
const targetItem = this.collection.getItem(
direction,
referenceCollectionItem,
);
if (targetItem) {
this.collection.focus(targetItem);
}
}
}
// Register the component
SaladUI.register("accordion", AccordionComponent);
export default AccordionComponent;