143 lines
4.6 KiB
JavaScript
143 lines
4.6 KiB
JavaScript
// 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.innerHTML = label; // HTML destekler (ikonlu etiketler için)
|
||
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';
|
||
// Material Icons: icon adı string ise text, HTML içeriyorsa innerHTML
|
||
if (item.icon?.includes('<')) {
|
||
icon.innerHTML = item.icon;
|
||
} else {
|
||
icon.textContent = item.icon ?? 'circle';
|
||
}
|
||
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 = 'chevron_right';
|
||
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;
|
||
}
|
||
}
|