flexdesk 0.6.2 → 0.7.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/README.md +326 -0
- package/css/base.css +333 -0
- package/css/flexdesk.css +333 -0
- package/dist/charts.js +2 -2
- package/dist/{chunk-CPYBQN3Q.js → chunk-JLCJWCRS.js} +1617 -58
- package/dist/chunk-JLCJWCRS.js.map +7 -0
- package/dist/{chunk-XYUZF5CD.js → chunk-WKTYKXWF.js} +2 -2
- package/dist/flexdesk.css +333 -0
- package/dist/flow.js +24 -8
- package/dist/flow.js.map +2 -2
- package/dist/tiles.js +2 -2
- package/dist/widgets.js +672 -1
- package/dist/widgets.js.map +4 -4
- package/package.json +1 -1
- package/src/flow/outline/editor.js +46 -8
- package/src/ui/components/data_table.js +1695 -70
- package/src/ui/components/fact_table.js +342 -0
- package/src/ui/components/record_list.js +594 -0
- package/src/ui/components/table_filter_chips.js +153 -0
- package/dist/chunk-CPYBQN3Q.js.map +0 -7
- /package/dist/{chunk-XYUZF5CD.js.map → chunk-WKTYKXWF.js.map} +0 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* table_filter_chips.js — 0.7.0
|
|
3
|
+
*
|
|
4
|
+
* ONE-OF-N CHIPS WITH COUNTS, as a column filter control: the built-in kind
|
|
5
|
+
* behind `DataTable`'s `filterKinds: {[col]: {kind: 'chips', options}}`, and
|
|
6
|
+
* usable on its own.
|
|
7
|
+
*
|
|
8
|
+
* Each option is a chip — an optional glyph, its label, and a count badge that
|
|
9
|
+
* reads `–` until `setCounts` says otherwise. A chip whose count is 0 is
|
|
10
|
+
* DIMMED, never hidden: a chip that disappears at nought takes the information
|
|
11
|
+
* "there are none of these" with it, which is often the most reassuring thing
|
|
12
|
+
* on the screen.
|
|
13
|
+
*
|
|
14
|
+
* - An option whose value is `''` or `null` stands for NO FILTER ("All"); it
|
|
15
|
+
* is pressed exactly when nothing is chosen, and its count is the total.
|
|
16
|
+
* - One-of-N (the default): pressing a chip chooses it; pressing the chosen
|
|
17
|
+
* chip again clears the filter.
|
|
18
|
+
* - `multiple: true`: each chip toggles its value in or out of a list.
|
|
19
|
+
*
|
|
20
|
+
* What the values MEAN — which levels exist, their words, their glyphs, what a
|
|
21
|
+
* count counts — is the consumer's. This file is only the control.
|
|
22
|
+
*
|
|
23
|
+
* const chips = createChipsFilter({ header: 'Level', setValue: (v) => …, value: null },
|
|
24
|
+
* { options: [{ value: '', label: 'All', icon: 'list_alt' },
|
|
25
|
+
* { value: 'warn', label: 'Warn', icon: 'warning' }] });
|
|
26
|
+
* host.appendChild(chips.el);
|
|
27
|
+
* chips.setCounts({ '': 120, warn: 3 });
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const isAll = (v) => v === '' || v == null;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {object} [ctx] `{header, value, counts, setValue(v)}` — what a
|
|
34
|
+
* `DataTable` hands a filter control. Without `setValue` the control keeps
|
|
35
|
+
* its own value and reports it through `spec.onChange`.
|
|
36
|
+
* @param {object} spec `{options: [{value, label, icon?, title?}], multiple?,
|
|
37
|
+
* label?, counts? (false: no badges), onChange?}`
|
|
38
|
+
* @returns {{el: HTMLElement, setValue(v): void, setCounts(c): void,
|
|
39
|
+
* getValue(): *, destroy(): void}}
|
|
40
|
+
*/
|
|
41
|
+
export function createChipsFilter(ctx = {}, spec = {}) {
|
|
42
|
+
const options = Array.isArray(spec.options) ? spec.options : [];
|
|
43
|
+
const multiple = !!spec.multiple;
|
|
44
|
+
const showCounts = spec.counts !== false;
|
|
45
|
+
const el = document.createElement('div');
|
|
46
|
+
el.className = 'twm-dt-filter-chips' + (multiple ? ' twm-dt-filter-chips--multiple' : '');
|
|
47
|
+
el.setAttribute('role', 'group');
|
|
48
|
+
const name = spec.label || ctx.header;
|
|
49
|
+
if (name) el.setAttribute('aria-label', String(name));
|
|
50
|
+
|
|
51
|
+
/** Chip key (text) -> the option's own value. */
|
|
52
|
+
const valueOf = new Map();
|
|
53
|
+
const chips = new Map();
|
|
54
|
+
let value = null;
|
|
55
|
+
|
|
56
|
+
const keyOf = (v) => String(v ?? '');
|
|
57
|
+
const normalise = (v) => {
|
|
58
|
+
if (multiple) {
|
|
59
|
+
const list = Array.isArray(v) ? v : (isAll(v) ? [] : [v]);
|
|
60
|
+
return list.filter((x) => !isAll(x)).map(keyOf);
|
|
61
|
+
}
|
|
62
|
+
if (Array.isArray(v)) v = v[0];
|
|
63
|
+
return isAll(v) ? null : keyOf(v);
|
|
64
|
+
};
|
|
65
|
+
const chosen = () => (multiple ? value.length === 0 : value === null);
|
|
66
|
+
|
|
67
|
+
for (const opt of options) {
|
|
68
|
+
const key = keyOf(opt.value);
|
|
69
|
+
valueOf.set(key, opt.value);
|
|
70
|
+
const btn = document.createElement('button');
|
|
71
|
+
btn.type = 'button';
|
|
72
|
+
btn.className = 'twm-dt-filter-chip';
|
|
73
|
+
btn.dataset.value = key;
|
|
74
|
+
if (opt.title) btn.title = String(opt.title);
|
|
75
|
+
if (opt.icon) {
|
|
76
|
+
const icon = document.createElement('span');
|
|
77
|
+
icon.className = 'material-symbols-outlined twm-dt-filter-chip__icon';
|
|
78
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
79
|
+
icon.textContent = String(opt.icon);
|
|
80
|
+
btn.appendChild(icon);
|
|
81
|
+
}
|
|
82
|
+
const label = document.createElement('span');
|
|
83
|
+
label.className = 'twm-dt-filter-chip__label';
|
|
84
|
+
label.textContent = String(opt.label ?? key);
|
|
85
|
+
btn.appendChild(label);
|
|
86
|
+
let count = null;
|
|
87
|
+
if (showCounts) {
|
|
88
|
+
count = document.createElement('span');
|
|
89
|
+
count.className = 'twm-dt-filter-chip__count';
|
|
90
|
+
count.textContent = '–';
|
|
91
|
+
btn.appendChild(count);
|
|
92
|
+
}
|
|
93
|
+
btn.addEventListener('click', (ev) => {
|
|
94
|
+
ev.stopPropagation();
|
|
95
|
+
pick(key);
|
|
96
|
+
});
|
|
97
|
+
chips.set(key, { btn, count });
|
|
98
|
+
el.appendChild(btn);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function outward() {
|
|
102
|
+
if (multiple) return value.length ? value.map((k) => valueOf.get(k)) : null;
|
|
103
|
+
return value === null ? null : valueOf.get(value);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function pick(key) {
|
|
107
|
+
if (isAll(key)) value = multiple ? [] : null;
|
|
108
|
+
else if (multiple) {
|
|
109
|
+
value = value.includes(key) ? value.filter((k) => k !== key) : [...value, key];
|
|
110
|
+
} else {
|
|
111
|
+
value = value === key ? null : key;
|
|
112
|
+
}
|
|
113
|
+
paint();
|
|
114
|
+
const out = outward();
|
|
115
|
+
if (typeof ctx.setValue === 'function') ctx.setValue(out);
|
|
116
|
+
try { spec.onChange?.(out); } catch (err) { console.error('[chips filter] onChange threw', err); }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function paint() {
|
|
120
|
+
for (const [key, { btn }] of chips) {
|
|
121
|
+
const on = isAll(key) ? chosen() : (multiple ? value.includes(key) : value === key);
|
|
122
|
+
btn.classList.toggle('twm-dt-filter-chip--on', on);
|
|
123
|
+
btn.setAttribute('aria-pressed', on ? 'true' : 'false');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function setValue(v) {
|
|
128
|
+
value = normalise(v);
|
|
129
|
+
paint();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function setCounts(counts) {
|
|
133
|
+
for (const [key, { btn, count }] of chips) {
|
|
134
|
+
const n = counts == null ? undefined : counts[key];
|
|
135
|
+
const known = typeof n === 'number' && Number.isFinite(n);
|
|
136
|
+
if (count) count.textContent = known ? n.toLocaleString() : '–';
|
|
137
|
+
btn.classList.toggle('twm-dt-filter-chip--empty', known && n === 0);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
setValue(ctx.value);
|
|
142
|
+
if (ctx.counts) setCounts(ctx.counts);
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
el,
|
|
146
|
+
setValue,
|
|
147
|
+
setCounts,
|
|
148
|
+
getValue: outward,
|
|
149
|
+
destroy() { /* nothing outside its own element */ },
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export default createChipsFilter;
|