110 lines
3.7 KiB
JavaScript
110 lines
3.7 KiB
JavaScript
// Generated, do not edit. sha256:09856719543cb019750495d6ca12c5afc9eb3cc35cdf6a20a0f887e365cc177f
|
|
import { esc } from './escape.js';
|
|
|
|
function plainError(panel, err, id) {
|
|
console.error(`[tabs] ${id || ''}`, err);
|
|
panel.innerHTML = `<div class="alert alert-error">${esc(err && err.message ? err.message : String(err))}</div>`;
|
|
}
|
|
let onError = plainError;
|
|
|
|
export function configureTabs({ onError: handler = null } = {}) {
|
|
onError = handler || plainError;
|
|
}
|
|
|
|
export function rowLayout(count, content, room) {
|
|
const rows = count > 1 && content > room ? 2 : 1;
|
|
return { rows, cols: Math.ceil(count / rows) };
|
|
}
|
|
|
|
export function create(host, opts = {}) {
|
|
if (!host) return null;
|
|
const { items = [], render, onChange, size = '', syncOn = [], barHost = null } = opts;
|
|
const list = items.filter(it => it && (!it.when || it.when()));
|
|
|
|
let current = opts.active;
|
|
if (!list.some(it => it.id === current)) current = list[0] && list[0].id;
|
|
|
|
const bar = document.createElement('div');
|
|
bar.className = 'tabset' + (size === 'lg' ? ' tabset--lg' : '');
|
|
bar.setAttribute('role', 'tablist');
|
|
const panel = document.createElement('div');
|
|
panel.setAttribute('role', 'tabpanel');
|
|
|
|
if (host._tabsCleanup) host._tabsCleanup();
|
|
host.innerHTML = '';
|
|
if (barHost) { barHost.innerHTML = ''; barHost.append(bar); host.append(panel); }
|
|
else host.append(bar, panel);
|
|
|
|
function drawBar() {
|
|
bar.innerHTML = list.map(it => {
|
|
const on = it.id === current;
|
|
const dot = typeof it.dot === 'function' ? (it.dot() || '') : (it.dot || '');
|
|
return `<button class="tabset-tab${on ? ' is-on' : ''}" role="tab" aria-selected="${on}" data-tab="${it.id}">${it.label}${dot}</button>`;
|
|
}).join('');
|
|
bar.querySelectorAll('[data-tab]').forEach(b => { b.onclick = () => select(b.dataset.tab); });
|
|
if (size === 'lg') fitRows();
|
|
}
|
|
|
|
function fitRows() {
|
|
if (!bar.isConnected) return;
|
|
bar.classList.remove('tabset--rows');
|
|
const box = bar.getBoundingClientRect();
|
|
const last = bar.children[bar.children.length - 1];
|
|
const reach = last ? last.getBoundingClientRect().right - box.left : 0;
|
|
const { rows, cols } = rowLayout(list.length, Math.round(reach), Math.round(box.width));
|
|
if (rows < 2) return;
|
|
bar.classList.add('tabset--rows');
|
|
bar.style.setProperty('--tabset-cols', String(cols));
|
|
}
|
|
let seenWidth = -1;
|
|
const resize = size === 'lg' && typeof ResizeObserver === 'function'
|
|
? new ResizeObserver(entries => {
|
|
if (!bar.isConnected) { cleanup(); return; }
|
|
const width = entries[entries.length - 1].contentRect.width;
|
|
if (width === seenWidth) return;
|
|
seenWidth = width;
|
|
fitRows();
|
|
})
|
|
: null;
|
|
|
|
function drawPanel() {
|
|
panel.innerHTML = '';
|
|
if (!render) return;
|
|
try {
|
|
const r = render(current, panel);
|
|
if (r && typeof r.then === 'function') r.catch(err => onError(panel, err, current));
|
|
} catch (err) { onError(panel, err, current); }
|
|
}
|
|
|
|
function select(id, force) {
|
|
if (!force && id === current) return;
|
|
if (!list.some(it => it.id === id)) return;
|
|
current = id;
|
|
drawBar();
|
|
drawPanel();
|
|
if (onChange) onChange(id);
|
|
}
|
|
|
|
const onSync = () => { if (!bar.isConnected) { cleanup(); return; } drawBar(); };
|
|
function cleanup() {
|
|
syncOn.forEach(ev => document.removeEventListener(ev, onSync));
|
|
if (resize) resize.disconnect();
|
|
if (host._tabsCleanup === cleanup) host._tabsCleanup = null;
|
|
}
|
|
syncOn.forEach(ev => document.addEventListener(ev, onSync));
|
|
if (resize) resize.observe(bar);
|
|
host._tabsCleanup = cleanup;
|
|
|
|
drawBar();
|
|
drawPanel();
|
|
|
|
return {
|
|
el: host, bar, panel,
|
|
get active() { return current; },
|
|
select: id => select(id),
|
|
refresh: () => drawBar(),
|
|
redraw: () => drawPanel(),
|
|
destroy: cleanup,
|
|
};
|
|
}
|