@muten/shadcn 0.1.15 → 0.2.0
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/AGENTS.md +88 -1
- package/globals.css +1 -0
- package/kit.css +328 -123
- package/motion.css +3 -0
- package/package.json +1 -1
- package/registry/accordion.muten +3 -3
- package/registry/agenda.js +19 -16
- package/registry/agenda.muten +10 -10
- package/registry/alert-dialog.muten +2 -2
- package/registry/alert.muten +1 -1
- package/registry/app-shell.muten +1 -1
- package/registry/attachment.muten +1 -1
- package/registry/avatar.muten +4 -4
- package/registry/badge.muten +14 -3
- package/registry/band.muten +2 -2
- package/registry/bar-chart.muten +2 -2
- package/registry/bio-play.muten +2 -2
- package/registry/booking-play.muten +5 -5
- package/registry/booking.muten +2 -2
- package/registry/breadcrumb.muten +4 -4
- package/registry/button.muten +9 -7
- package/registry/calendar.js +15 -10
- package/registry/calendar.muten +1 -1
- package/registry/card.muten +1 -1
- package/registry/carousel.muten +1 -1
- package/registry/chat-play.muten +8 -8
- package/registry/chatbot.muten +5 -4
- package/registry/checklist.muten +1 -1
- package/registry/choice-list.muten +3 -3
- package/registry/code.js +10 -4
- package/registry/collapsible.muten +2 -2
- package/registry/color-picker.js +37 -36
- package/registry/color-picker.muten +1 -1
- package/registry/combobox.js +1 -1
- package/registry/combobox.muten +2 -2
- package/registry/command-palette.muten +2 -2
- package/registry/command.js +1 -1
- package/registry/command.muten +7 -7
- package/registry/context-menu.muten +3 -3
- package/registry/count-up.js +33 -31
- package/registry/count-up.muten +6 -4
- package/registry/date-field.js +1 -1
- package/registry/date-field.muten +3 -3
- package/registry/day-plan.muten +5 -5
- package/registry/device-frame.muten +3 -3
- package/registry/dialog.muten +1 -1
- package/registry/dirty-bar.js +2 -2
- package/registry/dirty-bar.muten +3 -3
- package/registry/drawer.muten +1 -1
- package/registry/dropdown-menu.muten +4 -4
- package/registry/empty.muten +1 -1
- package/registry/feature-grid.muten +1 -1
- package/registry/feature-rows.muten +2 -2
- package/registry/field-behavior.js +13 -3
- package/registry/field-behavior.muten +3 -3
- package/registry/field.muten +5 -5
- package/registry/file-input.js +8 -4
- package/registry/file-input.muten +3 -3
- package/registry/fixed-card.muten +4 -4
- package/registry/folder-picker.js +6 -2
- package/registry/folder-picker.muten +2 -2
- package/registry/heatmap.muten +5 -5
- package/registry/image-upload.js +1 -1
- package/registry/image-upload.muten +2 -2
- package/registry/inbox-play.muten +5 -5
- package/registry/indicator.muten +1 -1
- package/registry/info-table.muten +1 -1
- package/registry/input-group.muten +11 -11
- package/registry/input-otp.muten +2 -2
- package/registry/input.muten +1 -1
- package/registry/inspector.muten +2 -2
- package/registry/item.muten +2 -2
- package/registry/kit/board.js +36 -34
- package/registry/kit/collections.js +24 -24
- package/registry/kit/core.js +197 -114
- package/registry/kit/dates.js +29 -27
- package/registry/kit/i18n.js +172 -0
- package/registry/kit/menus.js +14 -13
- package/registry/kit/overlays.js +7 -2
- package/registry/kit/structure.js +66 -61
- package/registry/kit/table.js +19 -17
- package/registry/kit/values.js +36 -28
- package/registry/kit-skeleton.muten +1 -1
- package/registry/kit-widget.js +9 -4
- package/registry/lang-scope.js +8 -0
- package/registry/lang-scope.muten +5 -0
- package/registry/legal-doc.muten +5 -5
- package/registry/link-btn.muten +1 -1
- package/registry/list-motion.js +46 -0
- package/registry/list-motion.muten +4 -0
- package/registry/live-stack.muten +2 -2
- package/registry/logo-cloud.muten +2 -2
- package/registry/marketing.muten +7 -7
- package/registry/member-list.muten +2 -2
- package/registry/menubar.muten +3 -3
- package/registry/message-scroller.muten +3 -3
- package/registry/message.muten +9 -9
- package/registry/messaging.muten +1 -1
- package/registry/modal.muten +4 -4
- package/registry/money-field.js +1 -1
- package/registry/money-field.muten +2 -2
- package/registry/nav-button.muten +4 -4
- package/registry/navigation-menu.muten +4 -4
- package/registry/navpill.muten +6 -6
- package/registry/new-appointment.js +1 -1
- package/registry/new-appointment.muten +4 -4
- package/registry/number-field.js +6 -4
- package/registry/number-field.muten +1 -1
- package/registry/order-board.js +1 -1
- package/registry/order-board.muten +2 -2
- package/registry/order-play.muten +4 -4
- package/registry/pagination-nav.js +1 -1
- package/registry/pagination-nav.muten +3 -3
- package/registry/pagination.muten +4 -4
- package/registry/password-field.muten +2 -2
- package/registry/permission-matrix.muten +4 -4
- package/registry/phone-bar.muten +6 -6
- package/registry/phone-field.js +1 -1
- package/registry/phone-field.muten +1 -1
- package/registry/phone-shell.js +1 -1
- package/registry/pill.muten +2 -2
- package/registry/plans.muten +1 -1
- package/registry/popover.muten +2 -2
- package/registry/pricing.muten +4 -4
- package/registry/product-play.muten +3 -3
- package/registry/progress.muten +3 -3
- package/registry/queue.muten +1 -1
- package/registry/radio-group.muten +1 -1
- package/registry/rank-item.muten +4 -4
- package/registry/resizable.muten +1 -1
- package/registry/row-menu.js +1 -1
- package/registry/row-menu.muten +2 -2
- package/registry/save-button.muten +5 -5
- package/registry/scale-slider.js +1 -1
- package/registry/scale-slider.muten +2 -2
- package/registry/screen-tour.muten +1 -1
- package/registry/share-bar.muten +2 -2
- package/registry/share-button.muten +4 -4
- package/registry/sheet.muten +1 -1
- package/registry/sidebar.muten +12 -6
- package/registry/site-footer.muten +6 -6
- package/registry/sortable-behavior.js +67 -20
- package/registry/sortable-list.js +3 -1
- package/registry/sortable-list.muten +2 -2
- package/registry/sortable.muten +5 -4
- package/registry/spinner.muten +2 -2
- package/registry/split-button.muten +2 -2
- package/registry/stacked-bars.js +3 -3
- package/registry/stacked-bars.muten +2 -2
- package/registry/stamp-card.muten +2 -2
- package/registry/stat-band.muten +3 -3
- package/registry/stat.muten +3 -3
- package/registry/status-bar.muten +2 -2
- package/registry/stepper.muten +4 -4
- package/registry/store-qr.js +1 -1
- package/registry/store-qr.muten +2 -2
- package/registry/swatch.muten +1 -1
- package/registry/switch.muten +8 -7
- package/registry/table-view.muten +3 -3
- package/registry/tabs-behavior.js +9 -1
- package/registry/tabs.muten +2 -2
- package/registry/tag-input.js +1 -1
- package/registry/tag-input.muten +2 -2
- package/registry/text.muten +2 -2
- package/registry/time-field.js +1 -1
- package/registry/time-field.muten +3 -3
- package/registry/timeline.muten +4 -4
- package/registry/toaster.muten +2 -2
- package/registry/toggle-group.muten +1 -1
- package/registry/toggle.muten +3 -3
- package/registry/toolbar.muten +4 -4
- package/registry/tooltip.muten +2 -2
- package/registry/tree-view.js +38 -25
- package/registry/tree-view.muten +1 -1
- package/registry/veil.muten +1 -1
- package/registry/virtual-list.js +1 -1
- package/registry/virtual-list.muten +2 -2
- package/registry/virtual-window.js +4 -4
- package/registry/virtual-window.muten +2 -2
- package/registry/wordline.muten +1 -1
- package/registry/zoom-control.muten +1 -1
- package/registry.json +22 -2
package/registry/tree-view.js
CHANGED
|
@@ -62,55 +62,68 @@ export function mount(el, inputs, handlers) {
|
|
|
62
62
|
const seed = () => { for (const n of data) if (Array.isArray(n.children)) expanded.add('/' + n.name); if (selected) expandTo(data, ''); };
|
|
63
63
|
seed();
|
|
64
64
|
|
|
65
|
+
// Built ONCE per data: a folder is its row plus a group that folds open and closed (grid rows 0fr ↔ 1fr, the
|
|
66
|
+
// accordion's 200 ms), its chevron turns, and picking a leaf only moves aria-selected. Nothing is rebuilt on a
|
|
67
|
+
// click, so the row under the pointer keeps its hover, the tree never jumps in height and keeps its width.
|
|
68
|
+
const CHEV = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>';
|
|
69
|
+
let root = null;
|
|
65
70
|
const build = (nodes, container, depth, parent) => {
|
|
66
71
|
for (const node of nodes) {
|
|
67
72
|
const path = parent + '/' + node.name;
|
|
68
73
|
const isFolder = Array.isArray(node.children);
|
|
69
74
|
const id = idOf(node, path);
|
|
70
|
-
const row = document.createElement('button'); row.type = 'button'; row.className = 'cx-tree-row'; row.setAttribute('role', 'treeitem'); row.tabIndex = -1; row.dataset.path = path;
|
|
75
|
+
const row = document.createElement('button'); row.type = 'button'; row.className = 'cx-tree-row'; row.setAttribute('role', 'treeitem'); row.tabIndex = -1; row.dataset.path = path; row.dataset.id = id; row.setAttribute('aria-level', depth + 1);
|
|
71
76
|
row.style.paddingLeft = (depth * 16 + 8) + 'px';
|
|
72
|
-
if (!isFolder
|
|
73
|
-
const chev = document.createElement('span'); chev.className = 'cx-tree-chev' + (isFolder ? '' : ' cx-tree-chev-none');
|
|
74
|
-
// lucide chevron-down (open) / chevron-right (closed)
|
|
75
|
-
chev.innerHTML = isFolder ? '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="' + (expanded.has(path) ? 'm6 9 6 6 6-6' : 'm9 18 6-6-6-6') + '"/></svg>' : '';
|
|
77
|
+
if (!isFolder) row.dataset.leaf = '';
|
|
78
|
+
const chev = document.createElement('span'); chev.className = 'cx-tree-chev' + (isFolder ? '' : ' cx-tree-chev-none'); chev.innerHTML = isFolder ? CHEV : '';
|
|
76
79
|
const ic = document.createElement('span'); ic.className = 'cx-tree-ic'; ic.innerHTML = isFolder ? FOLDER : FILE;
|
|
77
80
|
const label = document.createElement('span'); label.className = 'cx-tree-label'; label.textContent = node.name;
|
|
78
|
-
row.
|
|
79
|
-
row.addEventListener('click', () => {
|
|
80
|
-
if (isFolder) { expanded.has(path) ? expanded.delete(path) : expanded.add(path); } // folders: expand only
|
|
81
|
-
else { selected = id; if (handlers.select) handlers.select(node.value != null ? node.value : path); } // leaves emit
|
|
82
|
-
render();
|
|
83
|
-
});
|
|
81
|
+
row.append(chev, ic, label);
|
|
84
82
|
container.appendChild(row);
|
|
85
|
-
if (isFolder
|
|
83
|
+
if (isFolder) {
|
|
84
|
+
row.setAttribute('aria-expanded', String(expanded.has(path)));
|
|
85
|
+
const fold = document.createElement('div'); fold.className = 'cx-tree-fold'; fold.setAttribute('role', 'group');
|
|
86
|
+
const inner = document.createElement('div'); inner.className = 'cx-tree-inner';
|
|
87
|
+
fold.appendChild(inner); container.appendChild(fold);
|
|
88
|
+
build(node.children, inner, depth + 1, path);
|
|
89
|
+
}
|
|
90
|
+
if (!isFolder) row.addEventListener('click', () => { selected = id; mark(); if (handlers.select) handlers.select(node.value != null ? node.value : path); });
|
|
91
|
+
else row.addEventListener('click', () => { expanded.has(path) ? expanded.delete(path) : expanded.add(path); row.setAttribute('aria-expanded', String(expanded.has(path))); visible(); });
|
|
86
92
|
}
|
|
87
93
|
};
|
|
94
|
+
// the rows a keyboard can reach: not inside a closed folder
|
|
95
|
+
const visible = () => { const all = [...root.querySelectorAll('.cx-tree-row')]; const shown = all.filter((r) => !r.parentElement.closest('.cx-tree-fold') || ![...ancestorsOf(r)].some((f) => f.previousElementSibling?.getAttribute('aria-expanded') !== 'true')); all.forEach((r) => { r.inert = !shown.includes(r); }); return shown; };
|
|
96
|
+
const ancestorsOf = function* (r) { let f = r.parentElement.closest('.cx-tree-fold'); while (f) { yield f; f = f.parentElement.closest('.cx-tree-fold'); } };
|
|
97
|
+
const mark = () => { root.querySelectorAll('.cx-tree-row').forEach((r) => { const on = r.hasAttribute('data-leaf') && r.dataset.id === selected; if (on) r.setAttribute('aria-selected', 'true'); else r.removeAttribute('aria-selected'); }); roving(); };
|
|
98
|
+
const roving = () => { const rows = visible(); const cur = rows.find((r) => r.dataset.path === focusPath) || rows.find((r) => r.getAttribute('aria-selected') === 'true') || rows[0]; rows.forEach((r) => { r.tabIndex = r === cur ? 0 : -1; }); return cur; };
|
|
88
99
|
let focusPath = null;
|
|
89
100
|
const render = () => {
|
|
90
|
-
el.innerHTML = '';
|
|
91
|
-
build(data, root, 0, ''); el.appendChild(root);
|
|
92
|
-
const rows = [...root.querySelectorAll('.cx-tree-row')];
|
|
93
|
-
if (!rows.some((r) => r.tabIndex === 0) && rows[0]) rows[0].tabIndex = 0;
|
|
94
|
-
if (focusPath) { const r = rows.find((x) => x.dataset.path === focusPath); if (r) { rows.forEach((x) => { x.tabIndex = -1; }); r.tabIndex = 0; r.focus({ preventScroll: true }); } }
|
|
101
|
+
el.innerHTML = ''; root = document.createElement('div'); root.className = 'cx-tree'; root.setAttribute('role', 'tree'); root.setAttribute('aria-label', inputs.label || 'Archivos');
|
|
102
|
+
build(data, root, 0, ''); el.appendChild(root); mark();
|
|
95
103
|
};
|
|
96
104
|
// keyboard (WAI tree): Up/Down move, Right opens a folder (or steps in), Left closes it (or goes to its parent), Enter/Space pick
|
|
97
105
|
el.addEventListener('keydown', (e) => {
|
|
98
|
-
const rows =
|
|
106
|
+
const rows = visible(), k = rows.indexOf(document.activeElement); if (k < 0) return;
|
|
99
107
|
const row = rows[k], folder = row.hasAttribute('aria-expanded'), open = row.getAttribute('aria-expanded') === 'true';
|
|
100
|
-
const go = (r) => { if (!r) return;
|
|
108
|
+
const go = (r) => { if (!r) return; focusPath = r.dataset.path; roving(); r.focus(); };
|
|
101
109
|
if (e.key === 'ArrowDown') { e.preventDefault(); go(rows[k + 1]); }
|
|
102
110
|
else if (e.key === 'ArrowUp') { e.preventDefault(); go(rows[k - 1]); }
|
|
103
111
|
else if (e.key === 'Home') { e.preventDefault(); go(rows[0]); }
|
|
104
112
|
else if (e.key === 'End') { e.preventDefault(); go(rows[rows.length - 1]); }
|
|
105
|
-
else if (e.key === 'ArrowRight' && folder) { e.preventDefault(); if (!open)
|
|
106
|
-
else if (e.key === 'ArrowLeft') { e.preventDefault(); if (folder && open)
|
|
113
|
+
else if (e.key === 'ArrowRight' && folder) { e.preventDefault(); if (!open) row.click(); else go(rows[k + 1]); }
|
|
114
|
+
else if (e.key === 'ArrowLeft') { e.preventDefault(); if (folder && open) row.click(); else { const parent = row.dataset.path.split('/').slice(0, -1).join('/'); go(rows.find((x) => x.dataset.path === parent)); } }
|
|
107
115
|
});
|
|
108
|
-
el.addEventListener('click', (e) => { const r = e.target.closest('.cx-tree-row'); if (r) focusPath = r.dataset.path; }, true);
|
|
116
|
+
el.addEventListener('click', (e) => { const r = e.target.closest('.cx-tree-row'); if (r) { focusPath = r.dataset.path; roving(); } }, true);
|
|
109
117
|
|
|
110
118
|
render();
|
|
119
|
+
// new data rebuilds (once); a new `selected` only moves the mark and opens the folders down to it
|
|
120
|
+
let lastData = inputs.data;
|
|
111
121
|
return (n) => {
|
|
112
|
-
data = toTree(n);
|
|
113
|
-
if (n.selected != null
|
|
114
|
-
|
|
122
|
+
if (n.data !== lastData) { lastData = n.data; data = toTree(n); if (n.selected != null) selected = String(n.selected); if (selected) expandTo(data, ''); render(); return; }
|
|
123
|
+
if (n.selected != null && String(n.selected) !== selected) {
|
|
124
|
+
selected = String(n.selected); if (selected) expandTo(data, '');
|
|
125
|
+
root.querySelectorAll('.cx-tree-row[aria-expanded]').forEach((r) => r.setAttribute('aria-expanded', String(expanded.has(r.dataset.path))));
|
|
126
|
+
mark();
|
|
127
|
+
}
|
|
115
128
|
};
|
|
116
129
|
}
|
package/registry/tree-view.muten
CHANGED
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
# flat: a list of rows grouped by a key - groupKey makes the folders, labelKey the text, valueKey what a file emits.
|
|
5
5
|
# TreeView(data: @tiles, labelKey: "name", valueKey: "url", groupKey: "folder", selected: tileUrl, onSelect: pick)
|
|
6
6
|
# selected highlights that file and opens the folders above it. shadcn-for-muten · Custom · kit.css (.cx-tree) · tree-view.js.
|
|
7
|
-
part TreeView(data: list, labelKey: text = "", valueKey: text = "", groupKey: text = "", selected: text = "", label: text = "
|
|
7
|
+
part TreeView(data: list, labelKey: text = "", valueKey: text = "", groupKey: text = "", selected: text = "", label: text = "Files", onSelect: action) {
|
|
8
8
|
Custom TreeView inputs(data: $data, labelKey: $labelKey, valueKey: $valueKey, groupKey: $groupKey, selected: $selected, label: $label) on(select: $onSelect) class("cx-tree-host")
|
|
9
9
|
}
|
package/registry/veil.muten
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Veil - a calm note over a card's content while it has nothing to show yet: the content stays behind, faded (ghost
|
|
2
2
|
# rows, an empty chart), and a title and a line say what will appear. With `on` false it is the content alone.
|
|
3
|
-
# Veil(on: noSales, title: "
|
|
3
|
+
# Veil(on: noSales, title: "Nothing sold yet", text: "What people book and buy from you most shows here.") { … }
|
|
4
4
|
# StackedBars and Heatmap carry their own (empty / emptyTitle); use Veil for anything else.
|
|
5
5
|
# shadcn-for-muten · part · no deps · kit.css (.cx-veil).
|
|
6
6
|
part Veil(on: bool, title: text = "", text: text = "") {
|
package/registry/virtual-list.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// VirtualList (muten Custom) - only the visible rows exist in the DOM (kit/collections.js virtualList).
|
|
2
2
|
export function mount(el, inputs) {
|
|
3
|
-
Promise.all([import('@muten/shadcn/registry/kit/core.js'), import('@muten/shadcn/registry/kit/collections.js')]).then(([core, c]) => c.virtualList(el, inputs, core));
|
|
3
|
+
Promise.all([import('@muten/shadcn/registry/kit/core.js'), import('@muten/shadcn/registry/kit/collections.js')]).then(([core, c]) => core.relang(el, inputs, () => c.virtualList(el, inputs, core)));
|
|
4
4
|
return () => {};
|
|
5
5
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# VirtualList - thousands of rows, only the visible ones (+ a margin) in the DOM; arrows, Page Up/Down, Home/End.
|
|
2
|
-
# VirtualList(rows: @clients, label: "
|
|
2
|
+
# VirtualList(rows: @clients, label: "Customers", jump: 5000) rows: {title, subtitle, value}
|
|
3
3
|
# total > rows.length repeats the rows up to it (a long demo). shadcn-for-muten · Custom (kit collections).
|
|
4
|
-
part VirtualList(rows: list, total: number = 0, rowHeight: number = 52, label: text = "
|
|
4
|
+
part VirtualList(rows: list, total: number = 0, rowHeight: number = 52, label: text = "List", jump: number = 0) {
|
|
5
5
|
Custom VirtualList inputs(rows: $rows, total: $total, rowHeight: $rowHeight, label: $label, jump: $jump) class("cx-vl-host")
|
|
6
6
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// average from the rows on screen. Plain DOM, no kit.
|
|
6
6
|
export function mount(el, inputs, on) {
|
|
7
7
|
let count = Math.max(0, Number(inputs.count) || 0), guess = Number(inputs.item) || 72, over = Math.max(0, Number(inputs.over ?? 6));
|
|
8
|
-
// -1: the first range is always said, even
|
|
8
|
+
// -1: the first range is always said, even “0,0” (nothing on screen yet), or the page keeps its own default
|
|
9
9
|
let from = -1, to = -1, step = 0, raf = 0, list = null, scroller = null, basePad = 0, wired = false;
|
|
10
10
|
// the real height (plus gap, screen px) of every row already drawn, by its place in the list. Rows of very different
|
|
11
11
|
// heights (a group of 2 next to one of 50) no longer move the scrollbar as they come and go: a row seen once keeps its
|
|
@@ -44,7 +44,7 @@ export function mount(el, inputs, on) {
|
|
|
44
44
|
if (to > from && parts.length === to - from) {
|
|
45
45
|
for (let i = 0; i < parts.length; i++) {
|
|
46
46
|
const shown = parts[i].filter((n) => n.getClientRects().length);
|
|
47
|
-
// a row with nothing shown (an empty group) takes no room and no gap: it is 0, not
|
|
47
|
+
// a row with nothing shown (an empty group) takes no room and no gap: it is 0, not “unknown”
|
|
48
48
|
if (!shown.length) { sizes[from + i] = 0; continue; }
|
|
49
49
|
const h = shown[shown.length - 1].getBoundingClientRect().bottom - shown[0].getBoundingClientRect().top;
|
|
50
50
|
if (h > 0) sizes[from + i] = h + gap;
|
|
@@ -71,7 +71,7 @@ export function mount(el, inputs, on) {
|
|
|
71
71
|
raf = 0; if (!list) return;
|
|
72
72
|
gapNow = -1;
|
|
73
73
|
// a list that is not on the page right now (a hidden panel, a closed tab: no box at all) keeps the rows it has. Its
|
|
74
|
-
// scroller measures 0 × 0, which read as
|
|
74
|
+
// scroller measures 0 × 0, which read as “scrolled to the end” and drew EVERY row into a panel nobody could see
|
|
75
75
|
if (!list.getClientRects().length || (scroller && !scroller.clientHeight)) return;
|
|
76
76
|
learn();
|
|
77
77
|
const [top, bottom] = view();
|
|
@@ -100,7 +100,7 @@ export function mount(el, inputs, on) {
|
|
|
100
100
|
list = el.parentElement; if (!list || wired) return; wired = true;
|
|
101
101
|
for (let n = list.parentElement; n && n !== document.body && n !== document.documentElement; n = n.parentElement) { const o = getComputedStyle(n).overflowY; if (o === 'auto' || o === 'scroll') { scroller = n; break; } }
|
|
102
102
|
basePad = parseFloat(getComputedStyle(list).paddingBottom) || 0;
|
|
103
|
-
// the browser must not
|
|
103
|
+
// the browser must not “hold” a row in place while rows above it come and go: that fights the window
|
|
104
104
|
list.style.overflowAnchor = 'none';
|
|
105
105
|
// and a flex parent must not shrink the list to fit: its length IS the scrollbar's (the space for the rows not drawn)
|
|
106
106
|
list.style.flexShrink = '0';
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
# The list stays the real typed list (rows keep their keys, fields and drag); only the `where` changes. In lib.ts:
|
|
12
12
|
# export function windowIds(rows: { id: string }[], seen: string): string { const [a, b] = String(seen).split(',').map(Number); return ' ' + (rows || []).slice(Number.isFinite(a) ? a : 0, Number.isFinite(b) ? b : 20).map((r) => r.id).join(' ') + ' '; }
|
|
13
13
|
# export function rowKey(id: string): string { return ` ${id} `; }
|
|
14
|
-
# (A sorted or filtered list: give windowIds the same list, in the same order, that the `each` draws. And
|
|
15
|
-
# real answer, nothing on screen yet: never read a 0 as
|
|
14
|
+
# (A sorted or filtered list: give windowIds the same list, in the same order, that the `each` draws. And “0,0” is a
|
|
15
|
+
# real answer, nothing on screen yet: never read a 0 as “missing”.)
|
|
16
16
|
# It stands in for the rows above (its own height) and pads the list below, so the scrollbar keeps the whole length.
|
|
17
17
|
# count = how many rows the list has (after any filter) · item = a first guess of one row's height in px (it learns the
|
|
18
18
|
# real one) · over = rows kept beyond each edge (6). The list scrolls inside its nearest `overflow: auto` parent or the
|
package/registry/wordline.muten
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# WordLine - a line of words set as big type: who a product is for, what a shop does. The words sit in a soft grey,
|
|
2
2
|
# joined by dots in ink; a Word with tone "strong" stands out in ink too. It wraps like text.
|
|
3
|
-
# WordLine(label: "
|
|
3
|
+
# WordLine(label: "Who it is for") { Word(label: "Barbershops") Word(label: "Classes", tone: "strong") Word(label: "Spa") }
|
|
4
4
|
# shadcn-for-muten · parts · no deps · kit.css (.cx-wordline).
|
|
5
5
|
part WordLine(label: text = "") { Stack class("cx-wordline") aria(label: $label) { slot } }
|
|
6
6
|
part Word(label: text, tone: text = "") { Span "{$label}" class("cx-word") data(tone: $tone) }
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
part ZoomControl(value: number, onOut: action, onIn: action, onReset: action) {
|
|
6
6
|
Stack class("cx-zoom") data(slot: "zoom-control") aria(role: "group", label: "Zoom") {
|
|
7
7
|
Btn(variant: "ghost", size: "icon-sm", label: "Alejar", onClick: $onOut) { Icon "lucide:minus" }
|
|
8
|
-
Button "{$value} %" -> $onReset class("cn-button cn-button-variant-ghost cn-button-size-sm group/button cx-zoom-value") data(name: "
|
|
8
|
+
Button "{$value} %" -> $onReset class("cn-button cn-button-variant-ghost cn-button-size-sm group/button cx-zoom-value") data(name: "Back to 100 %", trn: "Back to 100 %", trl: "Zoom {0} %, back to 100", tra: $value) aria(label: "Zoom {$value} %, back to 100") # its words carry $value: a part arg cannot interpolate an outer param, so it stays a Button
|
|
9
9
|
Btn(variant: "ghost", size: "icon-sm", label: "Acercar", onClick: $onIn) { Icon "lucide:plus" }
|
|
10
10
|
}
|
|
11
11
|
}
|
package/registry.json
CHANGED
|
@@ -483,7 +483,9 @@
|
|
|
483
483
|
},
|
|
484
484
|
{
|
|
485
485
|
"name": "item",
|
|
486
|
-
"deps": [
|
|
486
|
+
"deps": [
|
|
487
|
+
"list-motion"
|
|
488
|
+
],
|
|
487
489
|
"part": "Item",
|
|
488
490
|
"file": "registry/item.muten",
|
|
489
491
|
"description": "Item(variant, size) row with ItemMedia / ItemContent / ItemTitle / ItemDescription / ItemActions / ItemFooter; lists = ItemGroup + ItemSeparator."
|
|
@@ -519,6 +521,14 @@
|
|
|
519
521
|
"component": "KitWidget",
|
|
520
522
|
"description": "Host of the system kit widgets (Custom, kit core). Use the named parts."
|
|
521
523
|
},
|
|
524
|
+
{
|
|
525
|
+
"name": "lang-scope",
|
|
526
|
+
"deps": [],
|
|
527
|
+
"part": "LangScope",
|
|
528
|
+
"file": "registry/lang-scope.muten",
|
|
529
|
+
"component": "LangScope",
|
|
530
|
+
"description": "LangScope - everything inside speaks this language (es | en …): the library's words, its dates and its Customs follow"
|
|
531
|
+
},
|
|
522
532
|
{
|
|
523
533
|
"name": "legal-doc",
|
|
524
534
|
"deps": [],
|
|
@@ -533,6 +543,14 @@
|
|
|
533
543
|
"file": "registry/link-btn.muten",
|
|
534
544
|
"description": "A navigation link styled as a button. `LinkBtn(to: \"/signup\", variant: \"default\") { Span \"Get started\" }`."
|
|
535
545
|
},
|
|
546
|
+
{
|
|
547
|
+
"name": "list-motion",
|
|
548
|
+
"deps": [],
|
|
549
|
+
"part": "ListMotion",
|
|
550
|
+
"file": "registry/list-motion.muten",
|
|
551
|
+
"component": "ListMotion",
|
|
552
|
+
"description": "Rows that open and close their place (Custom); ItemGroup carries it."
|
|
553
|
+
},
|
|
536
554
|
{
|
|
537
555
|
"name": "live-stack",
|
|
538
556
|
"deps": [],
|
|
@@ -1109,7 +1127,9 @@
|
|
|
1109
1127
|
},
|
|
1110
1128
|
{
|
|
1111
1129
|
"name": "timeline",
|
|
1112
|
-
"deps": [
|
|
1130
|
+
"deps": [
|
|
1131
|
+
"list-motion"
|
|
1132
|
+
],
|
|
1113
1133
|
"part": "Timeline",
|
|
1114
1134
|
"file": "registry/timeline.muten",
|
|
1115
1135
|
"description": "Vertical event timeline: a dot on a connector line + title/time/description per item (parts: Timeline, TimelineItem)."
|