@muten/shadcn 0.0.2 → 0.0.3
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/package.json +1 -1
- package/registry/tree-view.js +57 -10
- package/registry/tree-view.muten +13 -7
package/package.json
CHANGED
package/registry/tree-view.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
// TreeView host (muten Custom).
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// TreeView host (muten Custom). TWO modes, chosen by whether you pass key names:
|
|
2
|
+
// nested (default): data = [{ name, children? }] - folders expand, a leaf emits its "/path".
|
|
3
|
+
// flat (pass keys): data = a FLAT list of records; `groupKey` buckets them into folders, `labelKey` is the
|
|
4
|
+
// visible text, `valueKey` is the payload - a leaf emits node[valueKey] (NOT a path). This is
|
|
5
|
+
// how you feed a `query` of rows straight in (e.g. tilesets) without nesting them in muten.
|
|
6
|
+
// `selected` (optional, controlled): highlights the matching leaf (its value in flat mode, its path in nested) and
|
|
7
|
+
// auto-expands the folder holding it - for the row your app auto-opens on load.
|
|
8
|
+
// Folders ONLY expand; only leaves emit `select`. inputs: { data, labelKey, valueKey, groupKey, selected }.
|
|
9
|
+
// handlers: { select(value) }. A `query` passed as data arrives as { loading, data:[…] } - unwrapped here.
|
|
4
10
|
export function mount(el, inputs, handlers) {
|
|
5
11
|
const FOLDER = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>';
|
|
6
12
|
const FILE = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/></svg>';
|
|
@@ -15,18 +21,55 @@ export function mount(el, inputs, handlers) {
|
|
|
15
21
|
{ name: 'package.json' },
|
|
16
22
|
];
|
|
17
23
|
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
// A query arrives as { loading, data:[…] }; a plain list as the array; else nothing.
|
|
25
|
+
const rows = (d) => Array.isArray(d) ? d : (d && Array.isArray(d.data) ? d.data : null);
|
|
26
|
+
|
|
27
|
+
// Normalize either mode into the internal nested shape: folder = { name, children:[…] }, leaf = { name, value }.
|
|
28
|
+
const toTree = (ins) => {
|
|
29
|
+
const lk = ins.labelKey, vk = ins.valueKey, gk = ins.groupKey;
|
|
30
|
+
const list = rows(ins.data);
|
|
31
|
+
if ((lk || vk || gk) && list) {
|
|
32
|
+
const groups = new Map(); const loose = [];
|
|
33
|
+
for (const row of list) {
|
|
34
|
+
const label = lk ? row[lk] : (row && row.name != null ? row.name : String(row));
|
|
35
|
+
const value = vk ? row[vk] : label;
|
|
36
|
+
const leaf = { name: String(label), value };
|
|
37
|
+
const g = gk ? row[gk] : '';
|
|
38
|
+
if (g == null || g === '') { loose.push(leaf); continue; }
|
|
39
|
+
if (!groups.has(g)) groups.set(g, { name: String(g), children: [] });
|
|
40
|
+
groups.get(g).children.push(leaf);
|
|
41
|
+
}
|
|
42
|
+
return [...groups.values(), ...loose];
|
|
43
|
+
}
|
|
44
|
+
return list && list.length ? list : SAMPLE;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const idOf = (node, path) => Array.isArray(node.children) ? path : (node.value != null ? String(node.value) : path);
|
|
48
|
+
|
|
49
|
+
let data = toTree(inputs);
|
|
50
|
+
let selected = inputs.selected != null ? String(inputs.selected) : '';
|
|
20
51
|
const expanded = new Set();
|
|
21
|
-
|
|
52
|
+
|
|
53
|
+
// expand the ancestor folders of the leaf whose id === selected; true if found in this subtree
|
|
54
|
+
const expandTo = (nodes, parent) => {
|
|
55
|
+
for (const node of nodes) {
|
|
56
|
+
const path = parent + '/' + node.name;
|
|
57
|
+
if (Array.isArray(node.children)) { if (expandTo(node.children, path)) { expanded.add(path); return true; } }
|
|
58
|
+
else if (idOf(node, path) === selected) return true;
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
};
|
|
62
|
+
const seed = () => { for (const n of data) if (Array.isArray(n.children)) expanded.add('/' + n.name); if (selected) expandTo(data, ''); };
|
|
63
|
+
seed();
|
|
22
64
|
|
|
23
65
|
const build = (nodes, container, depth, parent) => {
|
|
24
66
|
for (const node of nodes) {
|
|
25
67
|
const path = parent + '/' + node.name;
|
|
26
68
|
const isFolder = Array.isArray(node.children);
|
|
69
|
+
const id = idOf(node, path);
|
|
27
70
|
const row = document.createElement('button'); row.type = 'button'; row.className = 'tree-row';
|
|
28
71
|
row.style.paddingLeft = (depth * 16 + 6) + 'px';
|
|
29
|
-
if (
|
|
72
|
+
if (!isFolder && id === selected) row.classList.add('tree-row-selected');
|
|
30
73
|
const chev = document.createElement('span'); chev.className = 'tree-chevron' + (isFolder ? '' : ' tree-chevron-hidden');
|
|
31
74
|
// lucide chevron-down (open) / chevron-right (closed)
|
|
32
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>' : '';
|
|
@@ -34,8 +77,8 @@ export function mount(el, inputs, handlers) {
|
|
|
34
77
|
const label = document.createElement('span'); label.className = 'tree-label'; label.textContent = node.name;
|
|
35
78
|
row.appendChild(chev); row.appendChild(ic); row.appendChild(label);
|
|
36
79
|
row.addEventListener('click', () => {
|
|
37
|
-
if (isFolder) { expanded.has(path) ? expanded.delete(path) : expanded.add(path); }
|
|
38
|
-
selected =
|
|
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
|
|
39
82
|
render();
|
|
40
83
|
});
|
|
41
84
|
container.appendChild(row);
|
|
@@ -45,5 +88,9 @@ export function mount(el, inputs, handlers) {
|
|
|
45
88
|
const render = () => { el.innerHTML = ''; const root = document.createElement('div'); root.className = 'tree'; build(data, root, 0, ''); el.appendChild(root); };
|
|
46
89
|
|
|
47
90
|
render();
|
|
48
|
-
return (n) => {
|
|
91
|
+
return (n) => {
|
|
92
|
+
data = toTree(n);
|
|
93
|
+
if (n.selected != null) { selected = String(n.selected); if (selected) expandTo(data, ''); } // re-open to the controlled leaf
|
|
94
|
+
render();
|
|
95
|
+
};
|
|
49
96
|
}
|
package/registry/tree-view.muten
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
# Tree View - a file/folder tree (Custom). TWO modes:
|
|
2
|
+
# nested (default): pass a nested `data` of { name, children }; leaves emit their "/path". Leave the keys "".
|
|
3
|
+
# TreeView(data: fileTree, labelKey: "", valueKey: "", groupKey: "", selected: "", onSelect: pick)
|
|
4
|
+
# flat (pass keys): feed a FLAT list (e.g. a `query` of rows) and let the tree group + key it for you -
|
|
5
|
+
# groupKey buckets rows into folders, labelKey is the text, valueKey is the payload a leaf emits:
|
|
6
|
+
# state { tiles = query tilesets : list<Tile> tileUrl = "" : text }
|
|
7
|
+
# action pick(u: text) mutates tileUrl { tileUrl.set(u) }
|
|
8
|
+
# TreeView(data: tiles, labelKey: "tname", valueKey: "url", groupKey: "folder", selected: tileUrl, onSelect: pick)
|
|
9
|
+
# `selected` highlights + auto-opens that leaf. Folders only expand; only leaves emit select. (muten parts need
|
|
10
|
+
# every arg, so pass "" for the keys you don't use - an empty key = nested mode.)
|
|
11
|
+
# shadcn-for-muten · Custom · host JS is src/components/TreeView.js (copied by `muten add tree-view`).
|
|
12
|
+
part TreeView(data: list, labelKey: text, valueKey: text, groupKey: text, selected: text, onSelect: action) {
|
|
13
|
+
Custom TreeView inputs(data: $data, labelKey: $labelKey, valueKey: $valueKey, groupKey: $groupKey, selected: $selected) on(select: $onSelect) class("tree-view")
|
|
8
14
|
}
|