Initial Commit
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
// 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;
|
||||
Reference in New Issue
Block a user