feat(ui): 2.0.0-alpha.13 — finer per-card width scaling (12-col grid + -/+ stepper)

clock/weather etc. default to 1/6 width; sizes store an integer span 1-12
(legacy s/m/l still accepted by /api/dashboard/layout).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
root
2026-06-04 18:23:37 +10:00
parent ae3a45251d
commit f780043f2d
7 changed files with 51 additions and 16 deletions

View File

@@ -29,18 +29,43 @@ async function saveLayout() {
}
// ---- per-card edit controls (drag grip + size + hide), shown only in edit mode via CSS
const STR_SPAN = { s: 2, m: 6, l: 12 }; // legacy size → 12-col span (s = 1/6, m = 1/2, l = full)
function spanOf(def) {
const v = layout.sizes?.[def.id];
if (typeof v === 'number') return Math.max(1, Math.min(12, v));
if (typeof v === 'string') return STR_SPAN[v] || 6;
return STR_SPAN[def.size] || 6;
}
function curSpan(id) {
const node = grid().querySelector(`.sv-card[data-card-id="${id}"]`);
const m = node && (node.style.gridColumn || '').match(/span (\d+)/);
return m ? +m[1] : spanOf(BY_ID.get(id) || {});
}
function setSpan(id, delta) {
const span = Math.max(1, Math.min(12, curSpan(id) + delta));
layout.sizes = { ...layout.sizes, [id]: span };
const node = grid().querySelector(`.sv-card[data-card-id="${id}"]`);
if (node) {
node.style.gridColumn = 'span ' + span;
const lbl = node.querySelector('.sv-span-val');
if (lbl) lbl.textContent = span;
}
saveLayout();
}
function editOverlay(def) {
const grip = el('span', { class: 'sv-grip', draggable: true, title: 'Drag to reorder' }, '⠿');
const sizes = el('span', { class: 'sv-ed-sizes' },
...['s', 'm', 'l'].map(s =>
el('button', { class: 'sv-ed-size', dataset: { s }, onclick: () => setSize(def.id, s) }, s.toUpperCase())));
const stepper = el('span', { class: 'sv-ed-span' },
el('button', { class: 'sv-ed-step', title: 'Narrower', onclick: () => setSpan(def.id, -1) }, ''),
el('span', { class: 'sv-span-val', title: 'Width (of 12)' }, String(spanOf(def))),
el('button', { class: 'sv-ed-step', title: 'Wider', onclick: () => setSpan(def.id, +1) }, '+'));
const hide = el('button', { class: 'sv-ed-hide', title: 'Hide card', onclick: () => hideCard(def.id) }, '✕');
return el('div', { class: 'sv-card-edit' }, grip, sizes, hide);
return el('div', { class: 'sv-card-edit' }, grip, stepper, hide);
}
function mountOne(def) {
const size = layout.sizes?.[def.id] || def.size;
const { root, body } = svCard({ ...def, size });
const span = spanOf(def);
const { root, body } = svCard({ ...def, span });
root.appendChild(editOverlay(def));
grid().appendChild(root);
try { def.mount(body); def.start && def.start(); active.push(def); }