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

130 lines
3.4 KiB
JavaScript

import Component from "../core/component";
import SaladUI from "..";
/**
* CommandComponent for SaladUI
* Implements filtering, keyboard navigation, and selection for a command palette/list.
*/
class CommandComponent extends Component {
constructor(el, hookContext) {
super(el, { hookContext, ignoreItems: false });
// Set default field state
this.currentItemIdx = 0;
// Core elements
this.input = this.getPart("input");
this.list = this.getPart("list");
this.empty = this.getPart("empty");
this.groups = this.getAllParts("group");
this.items = this.getAllParts("item");
// Bind event handlers
this.input.addEventListener("input", this.handleSearch);
// Initial search/filter
this.handleSearch();
// Prevent default for navigation keys
this.config.preventDefaultKeys = ["Escape", "ArrowDown", "ArrowUp"];
}
getComponentConfig() {
return {
stateMachine: {
idle: { transitions: {} },
},
events: {
idle: {
keyMap: {
Enter: "selectItem",
ArrowDown: "focusNextItem",
ArrowUp: "focusPrevItem",
Escape: "blurInput",
},
},
},
};
}
// Focus item by index, wrap around if needed
focusItem(index) {
if (!this.selectableItems?.length) return;
// Wrap index
if (index < 0) index = this.selectableItems.length - 1;
if (index >= this.selectableItems.length) index = 0;
this.currentItemIdx = index;
// Deselect all
this.items.forEach((item) => {
item.setAttribute("data-selected", "false");
item.setAttribute("aria-selected", "false");
});
// Select current
const selectedItem = this.selectableItems[index];
selectedItem.setAttribute("data-selected", "true");
selectedItem.setAttribute("aria-selected", "true");
}
focusNextItem = () => this.focusItem(this.currentItemIdx + 1);
focusPrevItem = () => this.focusItem(this.currentItemIdx - 1);
blurInput = () => this.input?.blur();
selectItem = () => {
if (this.currentItemIdx === -1) return;
const item = this.selectableItems[this.currentItemIdx];
item.click();
};
// Handle search/filtering
handleSearch = () => {
const query = this.input.value.trim().toLowerCase();
// Filter items
this.items.forEach((item) => {
const text = item.textContent.trim().toLowerCase();
const visible = query === "" || text.includes(query);
item.setAttribute("data-visible", visible ? "true" : "false");
});
this.visibleItems = this.items.filter(
(el) => el.getAttribute("data-visible") === "true",
);
this.selectableItems = this.visibleItems.filter(
(el) => !el.hasAttribute("disabled"),
);
this.groups.forEach((group) => {
const visibleOptions = group.querySelectorAll("[data-visible='true']");
group.setAttribute(
"data-visible",
visibleOptions.length > 0 ? "true" : "false",
);
});
this.focusItem(0);
const noItems = this.visibleItems.length === 0;
if (noItems) {
this.empty.setAttribute("data-visible", "true");
} else {
this.empty.setAttribute("data-visible", "false");
}
};
beforeDestroy() {
this.input.removeEventListener("input", this.handleSearch);
}
}
// Register the component
SaladUI.register("command", CommandComponent);
export default CommandComponent;