@muten/shadcn 0.2.9 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/kit.css +8 -1
- package/package.json +1 -1
- package/registry/field.muten +3 -3
- package/registry/kit/table.js +18 -7
- package/registry/kit-widget.js +5 -2
package/kit.css
CHANGED
|
@@ -771,12 +771,16 @@ input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webki
|
|
|
771
771
|
.cx-dt td.cx-dt-check { grid-area: c; align-self: center; width: auto; }
|
|
772
772
|
.cx-dt td.cx-dt-who { grid-area: who; }
|
|
773
773
|
.cx-dt td.cx-dt-svc { grid-area: meta; color: var(--muted-foreground); font-size: 13px; }
|
|
774
|
+
/* the narrow row holds one text column in its place; any other text column gets a line of its own under it */
|
|
775
|
+
.cx-dt td.cx-dt-svc ~ td.cx-dt-svc { grid-area: auto; grid-column: 2 / -1; }
|
|
776
|
+
.cx-dt tbody tr { grid-auto-rows: min-content; }
|
|
774
777
|
.cx-dt td.cx-dt-when { display: none; }
|
|
775
778
|
.cx-dt td.cx-dt-st { grid-area: st; justify-self: end; }
|
|
776
779
|
.cx-dt td.cx-dt-num { grid-area: amt; }
|
|
777
780
|
.cx-dt td.cx-dt-act { display: none; }
|
|
778
781
|
.cx-dt-who small { display: none; }
|
|
779
|
-
.cx-dt td.cx-dt-svc::after { content: " · " attr(data-when); }
|
|
782
|
+
.cx-dt td.cx-dt-svc[data-when]:not([data-when=""])::after { content: " · " attr(data-when); }
|
|
783
|
+
.cx-dt td.cx-dt-svc ~ td.cx-dt-svc::after { content: none; }
|
|
780
784
|
}
|
|
781
785
|
|
|
782
786
|
/* ── Board ── */
|
|
@@ -5541,3 +5545,6 @@ a.cx-bc-item:hover { color: var(--foreground); }
|
|
|
5541
5545
|
/* LangScope: a plain block; its host draws nothing */
|
|
5542
5546
|
.cx-lang-scope { display: contents; }
|
|
5543
5547
|
.cx-lang-host { display: none; }
|
|
5548
|
+
|
|
5549
|
+
/* ── Field: a field that must be filled says so at its label ── */
|
|
5550
|
+
.cn-label.cx-required::after { content: " *"; color: var(--destructive); }
|
package/package.json
CHANGED
package/registry/field.muten
CHANGED
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
# Field(label: "Description", description: "Shows in your shop.") { TextArea(value: desc) }
|
|
4
4
|
# Two short fields side by side: FieldRow { Field(…) { … } Field(…) { … } } (one column on a phone).
|
|
5
5
|
# error: a message that replaces the description in the same place (nothing jumps) and marks the field invalid.
|
|
6
|
-
# orientation: vertical | horizontal.
|
|
6
|
+
# orientation: vertical | horizontal. required: true marks the label (a red asterisk) for a field that must be filled.
|
|
7
7
|
# FieldSet - sets the size and the look of every field inside it, the kit's own too (PasswordField, PhoneField…):
|
|
8
8
|
# FieldSet(size: "2xl", variant: "filled") { Field(label: "Email") { … } PasswordField(label: "Password") }
|
|
9
9
|
# FieldSeparator - a line with a word in the middle, between two ways in: FieldSeparator(label: "or with your email")
|
|
10
10
|
# FieldHead - a label with an action beside it: FieldHead(label: "Password") { LinkBtn(to: "/recover", variant: "link", size: "sm") { Span "Forgot it?" } }
|
|
11
11
|
# shadcn-for-muten · part · no deps · Nova (.cn-field) + kit.css.
|
|
12
|
-
part Field(label: text = "", description: text = "", error: text = "", orientation: text = "vertical") {
|
|
12
|
+
part Field(label: text = "", description: text = "", error: text = "", orientation: text = "vertical", required: bool = false) {
|
|
13
13
|
Stack class("cn-field cn-field-orientation-{$orientation} group/field") data(slot: "field", orientation: $orientation, invalid: $error != "") {
|
|
14
|
-
Span "{$label}" class("cn-label") data(slot: "label")
|
|
14
|
+
Span "{$label}" class("cn-label", "cx-required" when $required) data(slot: "label")
|
|
15
15
|
slot
|
|
16
16
|
when $error == "" { Text "{$description}" class("cn-field-description") data(slot: "field-description") }
|
|
17
17
|
when $error != "" { Text "{$error}" class("cn-field-error") data(slot: "field-error") aria(role: "alert") }
|
package/registry/kit/table.js
CHANGED
|
@@ -39,8 +39,12 @@ export function dataTable(root, cfg, core, on) {
|
|
|
39
39
|
const cbHtml = (label, on) => `<button data-slot="checkbox" role="checkbox" data-state="${on ? 'checked' : 'unchecked'}" aria-checked="${on}" class="cn-checkbox" aria-label="${label}"><span data-slot="checkbox-indicator" class="cn-checkbox-indicator"><svg class="cb-mark" viewBox="0 0 24 24">${CB_CHECK}</svg></span></button>`;
|
|
40
40
|
const firstSort = dateCol ? { key: dateCol.key, dir: 1 } : { key: COLS[0]?.key, dir: 1 };
|
|
41
41
|
const bulk = list(cfg.bulk);
|
|
42
|
+
// rows are selectable only when there is something to do with them: without bulk actions, no checkbox column
|
|
43
|
+
const picks = bulk.length > 0;
|
|
44
|
+
// the phone's «Filters» sheet only when there is a filter besides the search
|
|
45
|
+
const sheetFilters = () => FILTERS.some((f) => f.type !== 'search');
|
|
42
46
|
const CELL_CLASS = { person: 'cx-dt-who', text: 'cx-dt-svc', date: 'cx-dt-when', status: 'cx-dt-st', money: 'cx-dt-num' };
|
|
43
|
-
root.innerHTML = `<div class="cx-dt-host"><div class="cx-dt-toolbar"><div class="cx-hstack cx-dt-tools" role="toolbar" aria-label="${T('Filters')}"></div><span class="cx-text" data-variant="caption" data-dt-shown aria-live="polite"></span></div><div class="cx-hstack cx-dt-chips" hidden></div><div class="cx-dt-bulk" aria-live="polite"><div><span data-dt-count></span><div class="cx-hstack" style="gap:6px">${bulk.map((b) => ui.button({ label: b.label, variant: b.variant || 'outline', size: 'xs', attrs: `data-dt-bulk="${b.id}"` })).join('')}${ui.button({ label: T('Clear selection'), variant: 'ghost', size: 'xs', attrs: 'data-dt-unselect' })}</div></div></div><div class="cx-dt-scroll cx-scroll"><table class="cx-dt"><thead><tr
|
|
47
|
+
root.innerHTML = `<div class="cx-dt-host"><div class="cx-dt-toolbar"><div class="cx-hstack cx-dt-tools" role="toolbar" aria-label="${T('Filters')}"></div><span class="cx-text" data-variant="caption" data-dt-shown aria-live="polite"></span></div><div class="cx-hstack cx-dt-chips" hidden></div><div class="cx-dt-bulk" aria-live="polite"><div><span data-dt-count></span><div class="cx-hstack" style="gap:6px">${bulk.map((b) => ui.button({ label: b.label, variant: b.variant || 'outline', size: 'xs', attrs: `data-dt-bulk="${b.id}"` })).join('')}${ui.button({ label: T('Clear selection'), variant: 'ghost', size: 'xs', attrs: 'data-dt-unselect' })}</div></div></div><div class="cx-dt-scroll cx-scroll"><table class="cx-dt"><thead><tr>${picks ? `<th class="cx-dt-check">${cbHtml(T('Select all'), false).replace('class="cn-checkbox"', 'class="cn-checkbox" data-dt-all')}</th>` : ''}${COLS.map((c) => `<th aria-sort="${c.key === firstSort.key ? 'ascending' : 'none'}" ${c.type === 'money' ? 'class="cx-dt-num"' : ''}>${c.sortable === false ? ui.text(c.label, 'label') : `<button class="cn-button cn-button-variant-ghost cn-button-size-xs cx-th" data-sort="${c.key}">${c.label}<svg><use href="#${c.key === firstSort.key ? 'arrowup' : 'sort'}"/></svg></button>`}</th>`).join('')}</tr></thead><tbody></tbody></table></div></div>`;
|
|
44
48
|
const host = $('.cx-dt-host', root), dtTools = $('.cx-dt-tools', root), dtChips = $('.cx-dt-chips', root), dtCountEl = $('[data-dt-shown]', root), dtBody = $('tbody', root), dtAll = $('[data-dt-all]', root), dtBulk = $('.cx-dt-bulk', root);
|
|
45
49
|
const dtSel = new Set(); let dtSort = firstSort;
|
|
46
50
|
const passes = (r, except) => FILTERS.every((f) => f.id === except || PASS[f.type](f, fState[f.id], r));
|
|
@@ -53,7 +57,7 @@ export function dataTable(root, cfg, core, on) {
|
|
|
53
57
|
const paintTools = () => {
|
|
54
58
|
if (dtMobile) {
|
|
55
59
|
const n = activeCount(), focused = document.activeElement?.dataset?.filter === searchF?.id;
|
|
56
|
-
dtTools.innerHTML = searchHtml() + ui.button({ label: `${ui.icon('filter')}${T('Filters')}${n ? ui.badge(String(n), { variant: 'default' }) : ''}`, size: 'sm', cls: 'cx-fbtn-m', attrs: 'data-fmobile aria-haspopup="dialog"' });
|
|
60
|
+
dtTools.innerHTML = searchHtml() + (sheetFilters() ? ui.button({ label: `${ui.icon('filter')}${T('Filters')}${n ? ui.badge(String(n), { variant: 'default' }) : ''}`, size: 'sm', cls: 'cx-fbtn-m', attrs: 'data-fmobile aria-haspopup="dialog"' }) : '');
|
|
57
61
|
if (focused) { const i = dtTools.querySelector('[data-filter]'); i.focus(); i.setSelectionRange(i.value.length, i.value.length); }
|
|
58
62
|
return;
|
|
59
63
|
}
|
|
@@ -146,7 +150,7 @@ export function dataTable(root, cfg, core, on) {
|
|
|
146
150
|
return `<td class="cx-dt-svc" ${dateCol ? `data-when="${whenText(r[dateCol.key])}"` : ''}>${v ?? ''}</td>`;
|
|
147
151
|
};
|
|
148
152
|
const who = (r) => r[colOf('person')?.key ?? COLS[0]?.key] ?? '';
|
|
149
|
-
const rowCells = (r) => `<td class="cx-dt-check">${cbHtml(T('Select {0}', who(r)), dtSel.has(r.id))}</td
|
|
153
|
+
const rowCells = (r) => `${picks ? `<td class="cx-dt-check">${cbHtml(T('Select {0}', who(r)), dtSel.has(r.id))}</td>` : ''}${COLS.map((c) => cell(c, r)).join('')}`;
|
|
150
154
|
const EASE = 'cubic-bezier(.23, 1, .32, 1)';
|
|
151
155
|
let dtAnim = 0;
|
|
152
156
|
const dtPaint = () => {
|
|
@@ -170,7 +174,7 @@ export function dataTable(root, cfg, core, on) {
|
|
|
170
174
|
};
|
|
171
175
|
if (leaving.length && dtBody.children.length) { leaving.forEach((tr) => tr.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 130, easing: 'ease-out', fill: 'forwards' })); setTimeout(apply, 130); } else apply();
|
|
172
176
|
dtCountEl.textContent = rows.length === DT.length ? `${DT.length} ${DT.length === 1 ? ONE : MANY}` : `${rows.length} de ${DT.length} ${MANY}`; paintChips();
|
|
173
|
-
setCheck(dtAll, dtSel.size === 0 ? false : dtSel.size === DT.length ? true : 'mixed');
|
|
177
|
+
if (dtAll) setCheck(dtAll, dtSel.size === 0 ? false : dtSel.size === DT.length ? true : 'mixed');
|
|
174
178
|
dtBulk.toggleAttribute('data-open', dtSel.size > 0);
|
|
175
179
|
if (dtSel.size) $('[data-dt-count]', root).textContent = `${dtSel.size} ${dtSel.size === 1 ? 'seleccionada' : 'seleccionadas'}`;
|
|
176
180
|
syncMobileCount(rows.length);
|
|
@@ -181,10 +185,10 @@ export function dataTable(root, cfg, core, on) {
|
|
|
181
185
|
$$('thead [data-sort]', root).forEach((b) => { const on = b.dataset.sort === key; b.parentElement.setAttribute('aria-sort', on ? (dtSort.dir === 1 ? 'ascending' : 'descending') : 'none'); $('use', b).setAttribute('href', on ? (dtSort.dir === 1 ? '#arrowup' : '#arrowdown') : '#sort'); });
|
|
182
186
|
dtPaint();
|
|
183
187
|
});
|
|
184
|
-
dtAll
|
|
188
|
+
dtAll?.addEventListener('click', () => { if (dtSel.size === DT.length) dtSel.clear(); else DT.forEach((r) => dtSel.add(r.id)); dtPaint(); });
|
|
185
189
|
dtBody.addEventListener('click', (e) => {
|
|
186
190
|
if (e.target.closest('[data-fclear-empty]')) { dtTools.querySelector('[data-fclear]')?.click(); return; }
|
|
187
|
-
const tr = e.target.closest('tr'); if (!tr || tr.dataset.id === 'empty') return;
|
|
191
|
+
const tr = e.target.closest('tr'); if (!picks || !tr || tr.dataset.id === 'empty') return;
|
|
188
192
|
if (!e.target.closest('.cn-checkbox') && !host.hasAttribute('data-narrow') && !matchMedia('(max-width: 640px)').matches) return;
|
|
189
193
|
const raw = tr.dataset.id, id = DT.find((r) => String(r.id) === raw)?.id; dtSel.has(id) ? dtSel.delete(id) : dtSel.add(id); dtPaint();
|
|
190
194
|
});
|
|
@@ -196,5 +200,12 @@ export function dataTable(root, cfg, core, on) {
|
|
|
196
200
|
dtSel.clear(); dtPaint(); on?.action?.(JSON.stringify({ action: b.dataset.dtBulk, ids }));
|
|
197
201
|
});
|
|
198
202
|
paintTools(); dtPaint();
|
|
199
|
-
|
|
203
|
+
// new rows from the page (a live list): the same table takes them, keeping its sort, filters and selection
|
|
204
|
+
const setRows = (rows) => {
|
|
205
|
+
DT.splice(0, DT.length, ...list(rows).map((r, i) => ({ id: r.id ?? i, ...r })));
|
|
206
|
+
const ids = new Set(DT.map((r) => r.id));
|
|
207
|
+
[...dtSel].forEach((id) => { if (!ids.has(id)) dtSel.delete(id); });
|
|
208
|
+
dtPaint(); paintTools();
|
|
209
|
+
};
|
|
210
|
+
return { refresh: () => { dtPaint(); paintTools(); }, setRows };
|
|
200
211
|
}
|
package/registry/kit-widget.js
CHANGED
|
@@ -48,12 +48,14 @@ export function mount(el, inputs, on) {
|
|
|
48
48
|
el.addEventListener('input', () => on.action?.(JSON.stringify(wh.value())));
|
|
49
49
|
},
|
|
50
50
|
};
|
|
51
|
-
RENDER.table = (core, i) => import('@muten/shadcn/registry/kit/table.js').then(({ dataTable }) => dataTable(el, { columns: i.columns, rows: i.rows, filters: i.filters, statuses: i.statuses, noun: [i.one || 'fila', i.many || 'filas'], bulk: i.bulk }, core, on));
|
|
51
|
+
RENDER.table = (core, i) => import('@muten/shadcn/registry/kit/table.js').then(({ dataTable }) => (shown = dataTable(el, { columns: i.columns, rows: i.rows, filters: i.filters, statuses: i.statuses, noun: [i.one || 'fila', i.many || 'filas'], bulk: i.bulk }, core, on)));
|
|
52
52
|
// until the kit arrives the host keeps its final size with a shimmer (kit.css, :empty), then the widget fades in
|
|
53
53
|
el.classList.add('cx-kw-wait');
|
|
54
54
|
// its language: inputs.lang, else the nearest lang (the page's); a lang that changes repaints it in the new one
|
|
55
55
|
if (inputs.lang) el.setAttribute('lang', inputs.lang);
|
|
56
56
|
let core0 = null;
|
|
57
|
+
// what a widget hands back to be fed later (the table's setRows)
|
|
58
|
+
let shown = null;
|
|
57
59
|
import('@muten/shadcn/registry/kit/core.js').then((core) => {
|
|
58
60
|
core0 = core;
|
|
59
61
|
const render = RENDER[inputs.name]; if (!render) return;
|
|
@@ -80,5 +82,6 @@ export function mount(el, inputs, on) {
|
|
|
80
82
|
el.addEventListener('pointerdown', () => { stopped = true; clearTimeout(timer); }, { once: true, capture: true });
|
|
81
83
|
timer = setTimeout(tick, d.every);
|
|
82
84
|
}
|
|
83
|
-
|
|
85
|
+
// muten calls this when a bound state changes: a table takes its new rows in place, the rest keep their snapshot
|
|
86
|
+
return (next) => { if (inputs.name === 'table' && next && shown?.setRows) shown.setRows(next.rows); };
|
|
84
87
|
}
|