MWSE/public/studio/Column.js

138 lines
4.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// A single column in the Miller-column navigator.
//
// Each column holds a list of Item objects. Selecting an item calls
// item.onSelect() and marks the item as active (highlighted). The column
// also supports optional search filtering.
export default class Column {
constructor({ title, items = [], searchable = true }) {
this.title = title;
this._items = items;
this._active = null; // currently selected item element
this._root = null;
this._list = null;
this._searchable = searchable;
this._filter = '';
this._actionsEl = null;
}
// Build and return the column DOM element.
mount() {
const col = document.createElement('div');
col.className = 'mwse-col';
this._root = col;
const header = document.createElement('div');
header.className = 'mwse-col__header';
header.textContent = this.title;
col.appendChild(header);
if (this._searchable && this._items.length > 6) {
const search = document.createElement('input');
search.type = 'text';
search.className = 'mwse-col__search';
search.placeholder = 'Filter…';
search.addEventListener('input', () => {
this._filter = search.value.toLowerCase();
this._renderItems();
});
col.appendChild(search);
}
const list = document.createElement('div');
list.className = 'mwse-col__list';
this._list = list;
col.appendChild(list);
this._renderItems();
return col;
}
// Replace the item list (re-renders the list area).
setItems(items) {
this._items = items;
this._active = null;
this._filter = '';
this._renderItems();
}
// Add a persistent action button below the list.
addAction(label, className, onClick) {
if (!this._root) return;
if (!this._actionsEl) {
this._actionsEl = document.createElement('div');
this._actionsEl.className = 'mwse-col__actions';
this._root.appendChild(this._actionsEl);
}
const btn = document.createElement('button');
btn.className = `mwse-btn ${className ?? ''}`;
btn.textContent = label;
btn.addEventListener('click', onClick);
this._actionsEl.appendChild(btn);
}
// Force a re-render (e.g. after external state changes meta text).
refresh() {
this._renderItems();
}
// ---- Private --------------------------------------------------------
_renderItems() {
if (!this._list) return;
this._list.innerHTML = '';
const visible = this._filter
? this._items.filter(i => i.label.toLowerCase().includes(this._filter))
: this._items;
for (const item of visible) {
this._list.appendChild(this._buildItem(item));
}
}
_buildItem(item) {
const row = document.createElement('div');
row.className = 'mwse-item';
const icon = document.createElement('span');
icon.className = 'mwse-item__icon';
icon.textContent = item.icon ?? '○';
row.appendChild(icon);
const body = document.createElement('div');
body.className = 'mwse-item__body';
const label = document.createElement('div');
label.className = 'mwse-item__label';
label.textContent = item.label;
body.appendChild(label);
if (item.meta !== undefined) {
const meta = document.createElement('div');
meta.className = 'mwse-item__meta';
meta.textContent = typeof item.meta === 'function' ? item.meta() : item.meta;
body.appendChild(meta);
item._metaEl = meta; // for refresh()
}
row.appendChild(body);
if (item.hasChildren !== false) {
const arrow = document.createElement('span');
arrow.className = 'mwse-item__arrow';
arrow.textContent = '';
row.appendChild(arrow);
}
row.addEventListener('click', () => {
// Deactivate previous selection.
if (this._active) this._active.classList.remove('mwse-item--active');
row.classList.add('mwse-item--active');
this._active = row;
item.onSelect?.(item);
});
return row;
}
}