@xiee/utils 1.14.91 → 1.14.93
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/css/lt-interactive.css +39 -28
- package/css/lt-interactive.min.css +1 -1
- package/css/lt-interactive.min.css.map +1 -1
- package/js/lt-interactive.js +262 -75
- package/js/lt-interactive.min.js +1 -1
- package/js/lt-interactive.min.js.map +1 -1
- package/js/lt.js +87 -32
- package/js/lt.min.js +1 -1
- package/js/lt.min.js.map +1 -1
- package/package.json +1 -1
package/js/lt-interactive.js
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* table whose spec carries `interactive`, it adds the requested controls and
|
|
6
6
|
* re-renders <tbody> through the core `spec._viewRows` seam. Every control is a
|
|
7
7
|
* row of the table itself — the search box in <thead>, the pager in <tfoot> —
|
|
8
|
-
* so it is exactly as wide as the table and scrolls with it.
|
|
9
|
-
* row groups
|
|
8
|
+
* so it is exactly as wide as the table and scrolls with it. Row structure is
|
|
9
|
+
* honored: separator row groups, indentation, and rowspan row groups all
|
|
10
|
+
* sort/filter within each group or subtree, never across it (see computeView).
|
|
10
11
|
*/
|
|
11
12
|
(root => {
|
|
12
13
|
"use strict";
|
|
@@ -25,12 +26,25 @@
|
|
|
25
26
|
// explicit `{col, dir}` object. Normalizes either to `{col, dir}`.
|
|
26
27
|
const parseKey = k => typeof k !== "string" ? { ...k } :
|
|
27
28
|
k[0] === "-" ? { col: k.slice(1), dir: "desc" } : { col: k, dir: "asc" };
|
|
29
|
+
// Compare two non-null values: numeric subtraction for a numeric column, else
|
|
30
|
+
// a locale-aware string compare (the collator built once, lazily reused).
|
|
31
|
+
const cmpVals = (x, y, num) => {
|
|
32
|
+
coll ||= new Intl.Collator();
|
|
33
|
+
return num ? x - y : coll.compare(String(x), String(y));
|
|
34
|
+
};
|
|
28
35
|
|
|
29
36
|
// --- Small DOM helpers, so building the controls stays terse ---
|
|
30
37
|
const $ = (el, sel) => el.querySelector(sel),
|
|
31
38
|
$$ = (el, sel) => el.querySelectorAll(sel),
|
|
32
39
|
on = (t, type, fn, opts) => t.addEventListener(type, fn, opts),
|
|
33
40
|
off = (t, type, fn) => t.removeEventListener(type, fn);
|
|
41
|
+
// A row's data cells, in column order: its children minus the leading rowspan
|
|
42
|
+
// group-column cells (class `lt-row-group`), which are not in `spec._cols` and,
|
|
43
|
+
// in rowspan mode, appear on some rows but not others (a run's later rows have
|
|
44
|
+
// none). Selecting by class — not position — is what lets the positional code
|
|
45
|
+
// (hide, detail, display capture) stay correct whatever leads the row.
|
|
46
|
+
const dataCells = tr =>
|
|
47
|
+
[...tr.children].filter(c => !c.classList.contains("lt-row-group"));
|
|
34
48
|
// Create a <tag> and assign `props`: a dashed key ("aria-label") sets an
|
|
35
49
|
// attribute, any other key ("className", "type", "textContent") a DOM
|
|
36
50
|
// property. Appends to `parent` when given, and returns the new element.
|
|
@@ -97,51 +111,138 @@
|
|
|
97
111
|
// predicates are combined with AND: a row must pass all of them.
|
|
98
112
|
function computeView(spec, disp, state) {
|
|
99
113
|
const cols = spec._cols || [], data = spec.data || {},
|
|
114
|
+
nRow = (data[cols[0]] || []).length,
|
|
100
115
|
cell = (c, r) => ({
|
|
101
116
|
raw: data[c]?.[r - 1] ?? null, disp: disp[c]?.[r - 1] ?? ""
|
|
102
117
|
});
|
|
103
|
-
let idx = Array.from({ length: (data[cols[0]] || []).length }, (_, i) => i + 1);
|
|
104
118
|
|
|
105
|
-
//
|
|
119
|
+
// The per-row keep test: the column filters, the table-wide search, then the
|
|
120
|
+
// externally registered predicates, combined with AND (a row must pass all).
|
|
121
|
+
// A filter looks at one cell, so the cheap tests come first.
|
|
122
|
+
const tests = [];
|
|
106
123
|
for (const c in state.filters || {}) {
|
|
107
124
|
const pred = matcher(state.filters[c]);
|
|
108
|
-
if (pred)
|
|
125
|
+
if (pred) tests.push(r => pred([cell(c, r)]));
|
|
109
126
|
}
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
112
|
-
|
|
127
|
+
const sPred = matcher(state.term);
|
|
128
|
+
if (sPred) tests.push(r => sPred(cols.map(c => cell(c, r))));
|
|
113
129
|
// predicates registered from outside (el._lt.filter) run last, each a
|
|
114
|
-
// function of the row's raw values keyed by column
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
130
|
+
// function of the row's raw values keyed by column. The row carries *every*
|
|
131
|
+
// column (not just the visible `cols`), so a widget can filter on a hidden
|
|
132
|
+
// helper column — e.g. forestly's incidence slider reads the hidden
|
|
133
|
+
// `hide_prop`, its parameter dropdown the hidden `parameter`.
|
|
118
134
|
const fns = Object.values(state.predicates || {});
|
|
119
135
|
if (fns.length) {
|
|
120
|
-
const all = Object.keys(data)
|
|
121
|
-
|
|
122
|
-
|
|
136
|
+
const all = Object.keys(data),
|
|
137
|
+
row = r => Object.fromEntries(all.map(c => [c, data[c]?.[r - 1] ?? null]));
|
|
138
|
+
tests.push(r => { const o = row(r); return fns.every(f => f(o)); });
|
|
123
139
|
}
|
|
140
|
+
const keep = r => tests.every(t => t(r));
|
|
124
141
|
|
|
125
|
-
// sort
|
|
126
|
-
// column (resolved once) carries its
|
|
127
|
-
// last regardless of direction, so the
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
142
|
+
// Build a comparator from a list of sort keys: each key in turn, falling
|
|
143
|
+
// through to the next on a tie; a key's column (resolved once) carries its
|
|
144
|
+
// numeric test and direction. nulls sort last regardless of direction, so the
|
|
145
|
+
// direction never applies to them. Null when the list sorts nothing.
|
|
146
|
+
const makeCmp = sortKeys => {
|
|
147
|
+
const keys = sortKeys.map(k => {
|
|
148
|
+
const col = data[k.col];
|
|
149
|
+
return col && { col, num: numCol(col), dir: k.dir === "desc" ? -1 : 1 };
|
|
150
|
+
}).filter(Boolean);
|
|
151
|
+
return keys.length ? (a, b) => {
|
|
135
152
|
for (const { col, num, dir } of keys) {
|
|
136
153
|
const x = col[a - 1], y = col[b - 1], xn = x == null, yn = y == null;
|
|
137
154
|
if (xn || yn) { if (xn !== yn) return xn ? 1 : -1; continue; }
|
|
138
|
-
const c =
|
|
155
|
+
const c = cmpVals(x, y, num);
|
|
139
156
|
if (c) return dir * c;
|
|
140
157
|
}
|
|
141
158
|
return 0;
|
|
142
|
-
}
|
|
159
|
+
} : null;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Reduce a list of rows (in document order) to the kept rows in view order,
|
|
163
|
+
// sorted by `cmp`. With no indentation this is a plain filter + sort. With
|
|
164
|
+
// indentation the rows form a tree (a row's parent is the nearest earlier row
|
|
165
|
+
// one level up): filtering keeps any row with a surviving descendant, so a
|
|
166
|
+
// match stays in context, and a sort reorders siblings while each subtree
|
|
167
|
+
// travels with its parent.
|
|
168
|
+
const indent = spec._indent;
|
|
169
|
+
const reduce = (rows, cmp) => {
|
|
170
|
+
if (!indent) {
|
|
171
|
+
const out = rows.filter(keep);
|
|
172
|
+
return cmp ? out.sort(cmp) : out;
|
|
173
|
+
}
|
|
174
|
+
const roots = [], stack = [];
|
|
175
|
+
for (const r of rows) {
|
|
176
|
+
const node = { r, kids: [] }, lv = indent[r - 1] || 0;
|
|
177
|
+
while (stack.length && stack[stack.length - 1].lv >= lv) stack.pop();
|
|
178
|
+
(stack.length ? stack[stack.length - 1].node.kids : roots).push(node);
|
|
179
|
+
stack.push({ node, lv });
|
|
180
|
+
}
|
|
181
|
+
const prune = ns => ns.map(n => {
|
|
182
|
+
const kids = prune(n.kids);
|
|
183
|
+
return keep(n.r) || kids.length ? { r: n.r, kids } : null;
|
|
184
|
+
}).filter(Boolean);
|
|
185
|
+
const sortNodes = ns => {
|
|
186
|
+
if (cmp) ns.sort((a, b) => cmp(a.r, b.r));
|
|
187
|
+
ns.forEach(n => sortNodes(n.kids));
|
|
188
|
+
return ns;
|
|
189
|
+
};
|
|
190
|
+
const flat = [], walk = ns => ns.forEach(n => { flat.push(n.r); walk(n.kids); });
|
|
191
|
+
walk(sortNodes(prune(roots)));
|
|
192
|
+
return flat;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const all = () => Array.from({ length: nRow }, (_, i) => i + 1);
|
|
196
|
+
|
|
197
|
+
// Rowspan row groups: the group columns (outermost first) nest, so the view
|
|
198
|
+
// must stay a hierarchy — sorting a data column may only reorder rows within
|
|
199
|
+
// the innermost group, never across a group boundary, or a spanning cell
|
|
200
|
+
// would have to split. So partition by group column 1, then recurse on
|
|
201
|
+
// column 2 within each block, …, and only at the leaves sort by the data
|
|
202
|
+
// keys. A group column the reader clicked orders its blocks by that column
|
|
203
|
+
// (its key is pulled out of the leaf sort); untouched, blocks keep their
|
|
204
|
+
// first-appearance order. Rendering recomputes the spans from this order.
|
|
205
|
+
const gcols = spec._rowspan;
|
|
206
|
+
if (gcols) {
|
|
207
|
+
const dir = {}, leaf = [];
|
|
208
|
+
for (const k of state.sort || [])
|
|
209
|
+
gcols.includes(k.col) ? (dir[k.col] = k.dir) : leaf.push(k);
|
|
210
|
+
const leafCmp = makeCmp(leaf);
|
|
211
|
+
const partition = (rows, depth) => {
|
|
212
|
+
if (depth === gcols.length) return reduce(rows, leafCmp);
|
|
213
|
+
const col = data[gcols[depth]], blocks = new Map();
|
|
214
|
+
for (const r of rows) {
|
|
215
|
+
const v = col[r - 1], key = v == null ? "\0" : String(v);
|
|
216
|
+
(blocks.get(key) || blocks.set(key, { v, rows: [] }).get(key)).rows.push(r);
|
|
217
|
+
}
|
|
218
|
+
let order = [...blocks.values()];
|
|
219
|
+
if (dir[gcols[depth]]) {
|
|
220
|
+
const num = numCol(col), d = dir[gcols[depth]] === "desc" ? -1 : 1;
|
|
221
|
+
order.sort((a, b) => {
|
|
222
|
+
const an = a.v == null, bn = b.v == null;
|
|
223
|
+
if (an || bn) return an === bn ? 0 : an ? 1 : -1;
|
|
224
|
+
return d * cmpVals(a.v, b.v, num);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return order.flatMap(b => partition(b.rows, depth + 1));
|
|
228
|
+
};
|
|
229
|
+
return partition(all(), 0);
|
|
143
230
|
}
|
|
144
|
-
|
|
231
|
+
|
|
232
|
+
// Separator row groups keep their place (the group is the unit that does not
|
|
233
|
+
// move): each group's rows filter/sort within it, and the surviving rows of
|
|
234
|
+
// all groups concatenate in group order, then any ungrouped rows (manual
|
|
235
|
+
// groups need not cover every row). A group left with no row simply
|
|
236
|
+
// contributes nothing — its header is re-emitted at render only when a row
|
|
237
|
+
// remains.
|
|
238
|
+
const cmp = makeCmp(state.sort || []);
|
|
239
|
+
const groups = spec._groups;
|
|
240
|
+
if (!groups) return reduce(all(), cmp);
|
|
241
|
+
const grouped = new Set(), out = [];
|
|
242
|
+
for (const g of groups) { g.rows.forEach(r => grouped.add(r)); out.push(...reduce(g.rows, cmp)); }
|
|
243
|
+
const rest = [];
|
|
244
|
+
for (let r = 1; r <= nRow; r++) if (!grouped.has(r)) rest.push(r);
|
|
245
|
+
return [...out, ...reduce(rest, cmp)];
|
|
145
246
|
}
|
|
146
247
|
|
|
147
248
|
// The rows of `view` on the current page. `state.page` is clamped into range
|
|
@@ -156,33 +257,60 @@
|
|
|
156
257
|
return view.slice(state.page * n, (state.page + 1) * n);
|
|
157
258
|
}
|
|
158
259
|
|
|
159
|
-
// Enhanceable only when the rows are flat: row groups and indentation give the
|
|
160
|
-
// row order a meaning of its own (grouping, hierarchy) that reordering or
|
|
161
|
-
// dropping rows would destroy. Column spanners, explicit or from `auto_span`,
|
|
162
|
-
// are no obstacle: they are <thead> rows, and only <tbody> is ever re-rendered.
|
|
163
|
-
// Row-keyed styles and footnotes are fine too — core keys them to the original
|
|
164
|
-
// row indices, which is what the view carries.
|
|
165
|
-
const isFlat = spec => !spec.row_group &&
|
|
166
|
-
!(spec.ops || []).some(o => o.type === "row_group" || o.type === "indent");
|
|
167
|
-
|
|
168
260
|
// Displayed text per column, keyed to the original row order. It does not
|
|
169
261
|
// change with the view, so capture it once from the initial full render.
|
|
170
|
-
|
|
262
|
+
// `order[i]` is the original 1-based index of the i-th body row as rendered
|
|
263
|
+
// (for a grouped table whose groups do not follow file order); absent ⇒ body
|
|
264
|
+
// rows are in index order. Separator group-header rows are skipped — they are
|
|
265
|
+
// no column's cell. `dataCells` drops any leading rowspan group cell, so the
|
|
266
|
+
// index lines up with `cols` whatever precedes the data cells.
|
|
267
|
+
function captureDisplay(el, cols, order) {
|
|
171
268
|
const disp = {};
|
|
172
269
|
cols.forEach(c => disp[c] = []);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
270
|
+
let i = 0;
|
|
271
|
+
$$(el, "tbody tr").forEach(tr => {
|
|
272
|
+
if (tr.classList.contains("lt-row-group")) return;
|
|
273
|
+
const r = order ? order[i] : i + 1, cells = dataCells(tr);
|
|
274
|
+
cols.forEach((c, ci) => disp[c][r - 1] = cells[ci]?.textContent ?? "");
|
|
275
|
+
i++;
|
|
276
|
+
});
|
|
176
277
|
return disp;
|
|
177
278
|
}
|
|
178
279
|
|
|
179
280
|
function enhance(el, spec) {
|
|
180
|
-
if (!isFlat(spec)) return console.warn(
|
|
181
|
-
"lt: interactive tables need flat rows (no row groups or indentation); " +
|
|
182
|
-
"rendering a static table."
|
|
183
|
-
);
|
|
184
281
|
const opts = spec.interactive, cols = spec._cols || [],
|
|
185
|
-
|
|
282
|
+
data = spec.data || {}, nRow = (data[cols[0]] || []).length,
|
|
283
|
+
groups = spec._groups,
|
|
284
|
+
// each row's group (for re-emitting a surviving header), and the body
|
|
285
|
+
// render order: groups in order, then any ungrouped rows (so a manual
|
|
286
|
+
// group listed out of file order maps the display text correctly)
|
|
287
|
+
groupOf = {}, order = groups ? [] : null;
|
|
288
|
+
if (groups) {
|
|
289
|
+
const seen = new Set();
|
|
290
|
+
groups.forEach(g => g.rows.forEach(r => { groupOf[r] = g; order.push(r); seen.add(r); }));
|
|
291
|
+
for (let r = 1; r <= nRow; r++) if (!seen.has(r)) order.push(r);
|
|
292
|
+
}
|
|
293
|
+
// turn a page's data rows into the row list core renders: a group header
|
|
294
|
+
// (an object core draws as a full-width label row) is inserted whenever the
|
|
295
|
+
// group changes, so a group spanning a page boundary re-shows its header at
|
|
296
|
+
// the top of the next page; ungrouped rows get none.
|
|
297
|
+
const withGroups = rows => {
|
|
298
|
+
if (!groups) return rows;
|
|
299
|
+
const out = []; let prev;
|
|
300
|
+
for (const r of rows) {
|
|
301
|
+
const g = groupOf[r];
|
|
302
|
+
if (g !== prev) { if (g) out.push({ label: g.label, raw: g.raw }); prev = g; }
|
|
303
|
+
out.push(r);
|
|
304
|
+
}
|
|
305
|
+
return out;
|
|
306
|
+
};
|
|
307
|
+
// the rowspan group columns lead the header row (they are hidden from
|
|
308
|
+
// `cols`); the full header order drives sort, so clicking a group header
|
|
309
|
+
// reorders its blocks (computeView pulls a group-column key out of the leaf
|
|
310
|
+
// sort — see the `_rowspan` branch there)
|
|
311
|
+
const nGroup = spec._rowspan ? spec._rowspan.length : 0,
|
|
312
|
+
allCols = nGroup ? [...spec._rowspan, ...cols] : cols;
|
|
313
|
+
const disp = captureDisplay(el, cols, order),
|
|
186
314
|
// the bottom header row: the one whose cells line up with `cols`
|
|
187
315
|
hrow = [...$$(el, "thead tr")].pop(),
|
|
188
316
|
// an array `sort` on the options is an initial sort (a list of key
|
|
@@ -204,13 +332,16 @@
|
|
|
204
332
|
const refresh = (stale = true) => {
|
|
205
333
|
if (stale) { view = computeView(spec, disp, state); state.page = 0; }
|
|
206
334
|
const rows = pageSlice(view, state),
|
|
335
|
+
// _viewRows carries the page's data rows with group headers spliced
|
|
336
|
+
// back in; postSwap hooks get the same list, one entry per <tbody> row
|
|
337
|
+
vr = withGroups(rows),
|
|
207
338
|
tmp = elem(el.ownerDocument, "template", {
|
|
208
|
-
innerHTML: LT.buildHtml({ ...spec, _viewRows:
|
|
339
|
+
innerHTML: LT.buildHtml({ ...spec, _viewRows: vr })
|
|
209
340
|
});
|
|
210
341
|
const body = $(tmp.content, "tbody");
|
|
211
342
|
if (!rows.length) // no matches: a neutral symbol spanning all columns
|
|
212
343
|
body.innerHTML = `<tr class="lti-empty"><td colspan="${cols.length}">—</td></tr>`;
|
|
213
|
-
else postSwap.forEach(f => f(body,
|
|
344
|
+
else postSwap.forEach(f => f(body, vr));
|
|
214
345
|
$(el, "tbody").replaceWith(body);
|
|
215
346
|
sync(view.length);
|
|
216
347
|
};
|
|
@@ -242,26 +373,39 @@
|
|
|
242
373
|
const flt = opts.filter,
|
|
243
374
|
barCols = flt?.cols ?
|
|
244
375
|
Object.keys(flt.cols).filter(c => flt.cols[c] !== true && !cols.includes(c)) : [];
|
|
245
|
-
// the table-wide controls share one full-width head row
|
|
246
|
-
// (
|
|
247
|
-
|
|
376
|
+
// the table-wide controls share one full-width head row, laid out left to
|
|
377
|
+
// right: an icon group (the column menu and the download button), then the
|
|
378
|
+
// head-bar filter chips, then the search box (which absorbs the free space)
|
|
379
|
+
const headBar = (opts.search !== false || opts.hide || barCols.length || opts.download) ?
|
|
248
380
|
elem(el.ownerDocument, "div", { className: "lti-bar" },
|
|
249
381
|
fullRow(el.tHead || el.createTHead(), "lti-head", cols.length, 0)) : null;
|
|
250
|
-
// the header labels
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
382
|
+
// the data-column header labels (skipping any leading group cell), read
|
|
383
|
+
// before sort/resize decorate the cells, so the column menu can list the
|
|
384
|
+
// displayed labels rather than the raw names
|
|
385
|
+
const labels = dataCells(hrow).map(th => th.textContent);
|
|
386
|
+
// wire sort before adding the filter row, so it sees the header row only;
|
|
387
|
+
// `allCols` so every header cell binds (a group header is sortable too)
|
|
388
|
+
if (opts.sort !== false) addSort(hrow, allCols, state, refresh);
|
|
255
389
|
// the under-header filter row: needed when a default applies to visible
|
|
256
390
|
// columns or any named column is itself visible
|
|
257
391
|
if (flt && (flt.default || (flt.cols && Object.keys(flt.cols).some(c => cols.includes(c)))))
|
|
258
392
|
addFilter(hrow, cols, flt, spec.data, state, refresh);
|
|
259
|
-
const layout = opts.resize ? fixedLayout(el, hrow, cols.length) : null;
|
|
393
|
+
const layout = opts.resize ? fixedLayout(el, hrow, cols.length, nGroup) : null;
|
|
260
394
|
if (opts.resize) addResize(el, layout);
|
|
395
|
+
// the icon buttons sit together in a group that keeps its natural width;
|
|
396
|
+
// append them (menu first, download second) before the chips and search so
|
|
397
|
+
// the DOM order is the visual order
|
|
398
|
+
const icons = (opts.hide || opts.download) ?
|
|
399
|
+
elem(el.ownerDocument, "div", { className: "lti-icons" }, headBar) : null;
|
|
261
400
|
if (opts.hide)
|
|
262
|
-
addColumnToggle(
|
|
401
|
+
addColumnToggle(icons, el, hrow, cols, labels, opts.hide, layout, postSwap);
|
|
402
|
+
if (opts.download) addDownload(icons, el, spec, cols, labels, opts.download);
|
|
403
|
+
// the chips share a group so the bar has three parts (icons, chips, search)
|
|
404
|
+
// with a wider gap between them than within each
|
|
405
|
+
if (barCols.length) addControlFilters(
|
|
406
|
+
elem(el.ownerDocument, "div", { className: "lti-chips" }, headBar),
|
|
407
|
+
barCols, flt.cols, spec.data, state, refresh);
|
|
263
408
|
if (opts.search !== false) addSearch(headBar, el, state, refresh);
|
|
264
|
-
if (barCols.length) addControlFilters(headBar, barCols, flt.cols, spec.data, state, refresh);
|
|
265
409
|
// row detail re-renders through the same seam: toggling a row only changes
|
|
266
410
|
// which rows carry a detail block, so a plain re-render (no new view) is
|
|
267
411
|
// enough
|
|
@@ -313,6 +457,40 @@
|
|
|
313
457
|
onType(input, v => { state.term = v; refresh(); });
|
|
314
458
|
}
|
|
315
459
|
|
|
460
|
+
// Escape one value for a CSV field: wrap in quotes (doubling any inside) only
|
|
461
|
+
// when it holds a comma, quote, or newline, so plain values stay bare.
|
|
462
|
+
function csvField(v) {
|
|
463
|
+
const s = v == null ? "" : String(v);
|
|
464
|
+
return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// A button that downloads the table's current view as a CSV file. The header
|
|
468
|
+
// row is the column labels; each body row is a view row (every row the filters
|
|
469
|
+
// and search keep, in sort order, across all pages — `el._lt.view()` gives the
|
|
470
|
+
// 1-based original indices) rendered with the displayed cell text from
|
|
471
|
+
// `spec._display`. `name` is the file name (`true` = a default); group columns
|
|
472
|
+
// (null in `cols`) are skipped. No library, no network: a Blob and an <a>.
|
|
473
|
+
function addDownload(cell, el, spec, cols, labels, name) {
|
|
474
|
+
const doc = el.ownerDocument,
|
|
475
|
+
file = (typeof name === "string" && name ? name : "table")
|
|
476
|
+
.replace(/(\.csv)?$/i, ".csv"),
|
|
477
|
+
keep = cols.map((c, i) => i).filter(i => cols[i] != null),
|
|
478
|
+
btn = elem(doc, "button", {
|
|
479
|
+
type: "button", className: "lti-download", title: "Download CSV",
|
|
480
|
+
"aria-label": "Download table as CSV"
|
|
481
|
+
}, cell);
|
|
482
|
+
btn.onclick = () => {
|
|
483
|
+
const disp = spec._display || {},
|
|
484
|
+
lines = [keep.map(i => csvField(labels[i] ?? cols[i])).join(",")];
|
|
485
|
+
for (const r of el._lt.view())
|
|
486
|
+
lines.push(keep.map(i => csvField((disp[cols[i]] || [])[r - 1])).join(","));
|
|
487
|
+
const url = URL.createObjectURL(
|
|
488
|
+
new Blob([lines.join("\n")], { type: "text/csv" }));
|
|
489
|
+
elem(doc, "a", { href: url, download: file }).click();
|
|
490
|
+
URL.revokeObjectURL(url);
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
316
494
|
// Advance one column through asc → desc → unsorted, updating `state.sort` (an
|
|
317
495
|
// ordered list of `{col, dir}` keys). A plain click sorts by that column
|
|
318
496
|
// alone; a shift-click adds it as a further tie-breaker (or re-cycles it where
|
|
@@ -426,7 +604,11 @@
|
|
|
426
604
|
rerender();
|
|
427
605
|
};
|
|
428
606
|
return (body, rows) => [...body.rows].forEach((tr, i) => {
|
|
429
|
-
const r = rows[i]
|
|
607
|
+
const r = rows[i];
|
|
608
|
+
if (typeof r !== "number") return; // a separator group-header row
|
|
609
|
+
// the first data cell (past any leading rowspan group cell), so the caret
|
|
610
|
+
// sits in the row's own first column, not a group label
|
|
611
|
+
const td0 = dataCells(tr)[0], open = state.expanded.has(r);
|
|
430
612
|
if (!td0) return;
|
|
431
613
|
const btn = elem(doc, "button", {
|
|
432
614
|
type: "button", className: "lti-expand", textContent: open ? "▾" : "▸",
|
|
@@ -444,14 +626,16 @@
|
|
|
444
626
|
});
|
|
445
627
|
}
|
|
446
628
|
|
|
447
|
-
// The table's <colgroup>, created
|
|
448
|
-
//
|
|
449
|
-
|
|
629
|
+
// The table's <colgroup>, created when core emitted none (it only does so for
|
|
630
|
+
// a table given explicit widths on the R side). It gets `nGroup` leading <col>s
|
|
631
|
+
// for the rowspan group columns (which lead each header/body row) then one per
|
|
632
|
+
// data column, matching core's own layout so widths line up.
|
|
633
|
+
function colGroup(el, nCol, nGroup = 0) {
|
|
450
634
|
let g = $(el, "colgroup");
|
|
451
635
|
if (!g) {
|
|
452
636
|
const doc = el.ownerDocument;
|
|
453
637
|
g = elem(doc, "colgroup");
|
|
454
|
-
for (let i = 0; i < nCol; i++) elem(doc, "col", {}, g);
|
|
638
|
+
for (let i = 0; i < nGroup + nCol; i++) elem(doc, "col", {}, g);
|
|
455
639
|
// <colgroup> comes after <caption> (the title), before <thead>
|
|
456
640
|
el.caption ? el.caption.after(g) : el.prepend(g);
|
|
457
641
|
}
|
|
@@ -466,9 +650,9 @@
|
|
|
466
650
|
// content width (an unconstrained reflow, with the widths put back after).
|
|
467
651
|
// `setWidth(i, w, min)` sets column i to `w` px (clamped to `min`), widening or
|
|
468
652
|
// narrowing the table by as much; the other columns keep their widths.
|
|
469
|
-
function fixedLayout(el, hrow, nCol) {
|
|
653
|
+
function fixedLayout(el, hrow, nCol, nGroup = 0) {
|
|
470
654
|
const ths = [...$$(hrow, "th")],
|
|
471
|
-
cs = colGroup(el, nCol), wOf = e => e.getBoundingClientRect().width;
|
|
655
|
+
cs = colGroup(el, nCol, nGroup), wOf = e => e.getBoundingClientRect().width;
|
|
472
656
|
const freeze = () => {
|
|
473
657
|
if (el.classList.contains("lti-fixed")) return;
|
|
474
658
|
const w = ths.map(wOf);
|
|
@@ -491,9 +675,11 @@
|
|
|
491
675
|
const old = parseFloat(cs[i].style.width);
|
|
492
676
|
cs[i].style.width = Math.max(w, min) + "px";
|
|
493
677
|
el.style.width =
|
|
494
|
-
parseFloat(el.style.width) + parseFloat(cs[i].style.width) - old
|
|
678
|
+
`${parseFloat(el.style.width) + parseFloat(cs[i].style.width) - old}px`;
|
|
495
679
|
};
|
|
496
|
-
|
|
680
|
+
// `dataCs` drops the leading group <col>s, so a data-column consumer (the
|
|
681
|
+
// column-hide menu) indexes it by data-column position
|
|
682
|
+
return { ths, cs, dataCs: cs.slice(nGroup), freeze, natural, setWidth };
|
|
497
683
|
}
|
|
498
684
|
|
|
499
685
|
// Drag-to-resize column edges: a grip on the right edge of each header cell.
|
|
@@ -514,8 +700,8 @@
|
|
|
514
700
|
freeze();
|
|
515
701
|
const x0 = e.clientX, w0 = parseFloat(cs[i].style.width);
|
|
516
702
|
el.classList.add("lti-resizing");
|
|
517
|
-
//
|
|
518
|
-
//
|
|
703
|
+
// no label floor: omit setWidth's min arg to take its default, and the
|
|
704
|
+
// cell clips its overflow (CSS) instead of spilling when shrunk
|
|
519
705
|
drag(e, ev => setWidth(i, w0 + ev.clientX - x0),
|
|
520
706
|
() => el.classList.remove("lti-resizing"));
|
|
521
707
|
};
|
|
@@ -532,10 +718,11 @@
|
|
|
532
718
|
function addColumnToggle(cell, el, hrow, cols, labels, opt, layout, postSwap) {
|
|
533
719
|
const doc = el.ownerDocument,
|
|
534
720
|
start = (opt && opt.hidden) || [], hidden = new Set();
|
|
535
|
-
// show/hide column i's cell in one row
|
|
536
|
-
//
|
|
721
|
+
// show/hide column i's cell in one row (indexing the row's data cells, past
|
|
722
|
+
// any leading rowspan group cell), skipping the single colspan cell of a
|
|
723
|
+
// detail or empty row (which spans every column and is no column's own)
|
|
537
724
|
const setCell = (tr, i, on) => {
|
|
538
|
-
const c = tr
|
|
725
|
+
const c = dataCells(tr)[i];
|
|
539
726
|
if (c && c.colSpan === 1) c.hidden = on;
|
|
540
727
|
};
|
|
541
728
|
// re-hide every hidden column's cells on each freshly-built <tbody>
|
|
@@ -551,7 +738,7 @@
|
|
|
551
738
|
// keeps them in sync; only the fresh <tbody> is re-hidden (via postSwap).
|
|
552
739
|
const apply = i => {
|
|
553
740
|
const on = hidden.has(i);
|
|
554
|
-
if (layout?.
|
|
741
|
+
if (layout?.dataCs[i]) layout.dataCs[i].hidden = on;
|
|
555
742
|
for (const tr of el.rows)
|
|
556
743
|
if (!tr.classList.contains("lt-spanner-row")) setCell(tr, i, on);
|
|
557
744
|
};
|
|
@@ -671,7 +858,7 @@
|
|
|
671
858
|
t.setAttribute("aria-valuenow", val[i]);
|
|
672
859
|
});
|
|
673
860
|
fill.style.left = pct(val[0]) + "%";
|
|
674
|
-
fill.style.right = 100 - pct(val[1])
|
|
861
|
+
fill.style.right = `${100 - pct(val[1])}%`;
|
|
675
862
|
},
|
|
676
863
|
setOne = (i, v) => {
|
|
677
864
|
val[i] = snap(v);
|
package/js/lt-interactive.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(t=>{"use strict";const e=t.LT;if(!e?.onMount||e.plugins.interactive)return;let n;const o=t=>"number"==typeof t.find(t=>null!=t),s=t=>"string"!=typeof t?{...t}:"-"===t[0]?{col:t.slice(1),dir:"desc"}:{col:t,dir:"asc"},i=(t,e)=>t.querySelector(e),l=(t,e)=>t.querySelectorAll(e),r=(t,e,n,o)=>t.addEventListener(e,n,o);function a(t,e,n={},o){const s=t.createElement(e);for(const t in n)t.includes("-")?s.setAttribute(t,n[t]):s[t]=n[t];return o?o.appendChild(s):s}function c(t,e,n){t.preventDefault();const o=t.target.ownerDocument||document,s=t=>e(t);r(o,"pointermove",s),r(o,"pointerup",()=>{var t,e;t="pointermove",e=s,o.removeEventListener(t,e),n&&n()},{once:!0})}function d(t){const e=String(t??"").trim();if(""===e)return null;if(/(^|[^\w$])x([^\w$]|$)/.test(e)){let t;try{t=new Function("x",`return (${e});`)}catch(t){}if(t){const n=/^\s*!|!=/.test(e),o=e=>{if(null==e)return n;try{const n=Number(e);return!!t(String(e))||""!==e&&isFinite(n)&&!!t(n)}catch(t){return!1}};return t=>t[n?"every":"some"](t=>o(t.raw))}}const n="!"===e[0],o=(n?e.slice(1).trim():e).toLowerCase();return""===o?null:t=>n!==t.some(t=>null!=t.disp&&String(t.disp).toLowerCase().includes(o))}function u(t,e,s){const i=t._cols||[],l=t.data||{},r=(t,n)=>({raw:l[t]?.[n-1]??null,disp:e[t]?.[n-1]??""});let a=Array.from({length:(l[i[0]]||[]).length},(t,e)=>e+1);for(const t in s.filters||{}){const e=d(s.filters[t]);e&&(a=a.filter(n=>e([r(t,n)])))}const c=d(s.term);c&&(a=a.filter(t=>c(i.map(e=>r(e,t)))));const u=Object.values(s.predicates||{});if(u.length){const t=Object.keys(l),e=e=>Object.fromEntries(t.map(t=>[t,l[t]?.[e-1]??null]));a=a.filter(t=>{const n=e(t);return u.every(t=>t(n))})}const p=(s.sort||[]).map(t=>{const e=l[t.col];return e&&{col:e,num:o(e),dir:"desc"===t.dir?-1:1}}).filter(Boolean);return p.length&&(n||=new Intl.Collator,a.sort((t,e)=>{for(const{col:o,num:s,dir:i}of p){const l=o[t-1],r=o[e-1],a=null==l,c=null==r;if(a||c){if(a!==c)return a?1:-1;continue}const d=s?l-r:n.compare(String(l),String(r));if(d)return i*d}return 0})),a}function p(t,e){const n=e.pageSize;if(!n)return e.page=0,t;const o=Math.max(0,Math.ceil(t.length/n)-1);return e.page=Math.min(Math.max(e.page||0,0),o),t.slice(e.page*n,(e.page+1)*n)}function f(n,o){if(!(t=>!t.row_group&&!(t.ops||[]).some(t=>"row_group"===t.type||"indent"===t.type))(o))return console.warn("lt: interactive tables need flat rows (no row groups or indentation); rendering a static table.");const d=o.interactive,f=o._cols||[],w=function(t,e){const n={};return e.forEach(t=>n[t]=[]),l(t,"tbody tr").forEach((t,o)=>e.forEach((e,s)=>n[e][o]=t.children[s]?.textContent??"")),n}(n,f),y=[...l(n,"thead tr")].pop(),b={filters:{},predicates:{},page:0,pageSize:0,sort:Array.isArray(d.sort)?d.sort.map(s):[]};let x,v=()=>{};const S=[],N=(t=!0)=>{t&&(x=u(o,w,b),b.page=0);const s=p(x,b),l=a(n.ownerDocument,"template",{innerHTML:e.buildHtml({...o,_viewRows:s})}),r=i(l.content,"tbody");s.length?S.forEach(t=>t(r,s)):r.innerHTML=`<tr class="lti-empty"><td colspan="${f.length}">—</td></tr>`,i(n,"tbody").replaceWith(r),v(x.length)};n._lt={spec:o,state:b,refresh:()=>N(),view:()=>x||u(o,w,b),filter(t,e){e?b.predicates[t]=e:delete b.predicates[t],N()}};const E=d.filter,k=E?.cols?Object.keys(E.cols).filter(t=>!0!==E.cols[t]&&!f.includes(t)):[],M=!1!==d.search||d.hide||k.length?a(n.ownerDocument,"div",{className:"lti-bar"},m(n.tHead||n.createTHead(),"lti-head",f.length,0)):null,$=[...l(y,"th")].map(t=>t.textContent);!1!==d.sort&&function(t,e,n,o){const s=t.ownerDocument,i={},r=()=>{for(const t in i)i[t].th.removeAttribute("aria-sort"),i[t].ind.textContent="";const t=n.sort||[];t.forEach(({col:e,dir:n},o)=>{const s=i[e];s&&(s.th.setAttribute("aria-sort","desc"===n?"descending":"ascending"),s.ind.textContent=("desc"===n?"▼":"▲")+(t.length>1?String(o+1).replace(/\d/g,t=>"₀₁₂₃₄₅₆₇₈₉"[t]):""))})};l(t,"th").forEach((t,l)=>{const c=e[l];if(null==c)return;t.classList.add("lti-sortable");const d=a(s,"span",{className:"lti-label"},t);for(;t.firstChild!==d;)d.append(t.firstChild);i[c]={th:t,ind:a(s,"span",{className:"lti-sort"},d)},d.onclick=t=>{!function(t,e,n){const o=(t.sort||[]).slice(),s=o.findIndex(t=>t.col===e),i=s<0?"asc":"asc"===o[s].dir?"desc":null;if(!n)return void(t.sort=i?[{col:e,dir:i}]:[]);s<0?o.push({col:e,dir:i}):i?o[s]={col:e,dir:i}:o.splice(s,1);t.sort=o}(n,c,t.shiftKey),r(),o()}}),r()}(y,f,b,N),E&&(E.default||E.cols&&Object.keys(E.cols).some(t=>f.includes(t)))&&function(t,e,n,o,s,i){const l=t.ownerDocument,r=a(l,"tr",{className:"lti-filters"}),c=n.default,d=n.cols||{};e.forEach(t=>{const e=a(l,"td",{},r),n=t in d?d[t]:c;if(n)if(!0===n){g(e.appendChild(h(l,`Filter ${t}`)),e=>{e?s.filters[t]=e:delete s.filters[t],i()})}else e.appendChild(C(l,t,n,o,s,i,null))}),t.parentNode.appendChild(r)}(y,f,E,o.data,b,N);const A=d.resize?function(t,e,n){const o=[...l(e,"th")],s=function(t,e){let n=i(t,"colgroup");if(!n){const o=t.ownerDocument;n=a(o,"colgroup");for(let t=0;t<e;t++)a(o,"col",{},n);t.caption?t.caption.after(n):t.prepend(n)}return[...n.children]}(t,n),r=t=>t.getBoundingClientRect().width,c=()=>{if(t.classList.contains("lti-fixed"))return;const e=o.map(r);t.style.width=r(t)+"px",s.forEach((t,n)=>t.style.width=e[n]+"px"),t.classList.add("lti-fixed")},d=e=>{const n=s.map(t=>t.style.width),i=t.style.width;s.forEach(t=>t.style.width=""),t.style.width="",t.classList.remove("lti-fixed");const l=r(o[e]);return s.forEach((t,e)=>t.style.width=n[e]),t.style.width=i,t.classList.add("lti-fixed"),l},u=(e,n,o=24)=>{const i=parseFloat(s[e].style.width);s[e].style.width=Math.max(n,o)+"px",t.style.width=parseFloat(t.style.width)+parseFloat(s[e].style.width)-i+"px"};return{ths:o,cs:s,freeze:c,natural:d,setWidth:u}}(n,y,f.length):null;if(d.resize&&function(t,e){const n=t.ownerDocument,{ths:o,cs:s,freeze:i,natural:l,setWidth:r}=e;o.forEach((e,o)=>{const d=a(n,"div",{className:"lti-resizer"},e);d.ondblclick=t=>{t.stopPropagation(),i(),r(o,l(o))},d.onpointerdown=e=>{e.stopPropagation(),i();const n=e.clientX,l=parseFloat(s[o].style.width);t.classList.add("lti-resizing"),c(e,t=>r(o,l+t.clientX-n),()=>t.classList.remove("lti-resizing"))}})}(n,A),d.hide&&function(t,e,n,o,s,i,l,c){const d=e.ownerDocument,u=i&&i.hidden||[],p=new Set,f=(t,e,n)=>{const o=t.children[e];o&&1===o.colSpan&&(o.hidden=n)};c.push(t=>p.forEach(e=>{for(const n of t.rows)f(n,e,!0)}));const h=t=>{const n=p.has(t);l?.cs[t]&&(l.cs[t].hidden=n);for(const o of e.rows)o.classList.contains("lt-spanner-row")||f(o,t,n)},m=a(d,"span",{className:"lti-cols"},t),g=a(d,"button",{type:"button",title:"Columns","aria-label":"Show or hide columns","aria-expanded":"false"},m),w=a(d,"div",{className:"lti-menu",hidden:!0},m);o.forEach((t,e)=>{if(null==t)return;const n=a(d,"label",{},w),o=a(d,"input",{type:"checkbox",checked:!0},n);n.append(s[e]??t),u.includes(t)&&(o.checked=!1,p.add(e)),o.onchange=()=>{o.checked?p.delete(e):p.add(e),h(e)}}),o.forEach((t,e)=>h(e));const y=t=>{w.hidden=!t,g.setAttribute("aria-expanded",String(t))};g.onclick=t=>{t.stopPropagation(),y(w.hidden)},r(d,"click",t=>{m.contains(t.target)||y(!1)}),r(d,"keydown",t=>{"Escape"===t.key&&y(!1)})}(M,n,0,f,$,d.hide,A,S),!1!==d.search&&function(t,e,n,o){const s=t.appendChild(h(e.ownerDocument,"Search"));g(s,t=>{n.term=t,o()})}(M,n,b,N),k.length&&function(t,e,n,o,s,i){const l=t.ownerDocument;for(const r of e)t.appendChild(C(l,r,n[r],o,s,i,n[r].label||r))}(M,k,E.cols,o.data,b,N),d.detail&&S.push(function(n,o,s,i,l,r){const c=n.ownerDocument,d=s.length,u={};i.expanded=new Set;const p=(t,e)=>{const n={};for(const o in t)n[o]=t[o]?.[e-1]??null;return n},f=e=>{if(e in u)return u[e];const n=p(o._display||{},e);let s=null;if(Array.isArray(l)){const t={};for(const e of l)t[e]=[n[e]??null];s={data:t}}else{const i="function"==typeof l?l:t[l];"function"==typeof i&&(s=i(p(o.data||{},e),e,n))}return u[e]=s},h=t=>{i.expanded.has(t)?i.expanded.delete(t):i.expanded.add(t),r()};return(t,n)=>[...t.rows].forEach((t,o)=>{const s=n[o],l=t.cells[0],r=i.expanded.has(s);if(!l)return;const u=a(c,"button",{type:"button",className:"lti-expand",textContent:r?"▾":"▸","aria-expanded":String(r),"aria-label":"Toggle detail"});u.onclick=()=>h(s),l.prepend(u);const p=r&&f(s);if(p){const n=a(c,"tr",{className:"lti-detail"});t.after(n);const o=a(c,"td",{colSpan:d},n);e.render(a(c,"div",{},o),p)}})}(n,o,f,b,d.detail,()=>N(!1))),d.pager){const t=Array.isArray(d.pager)?d.pager:[10,25,50,100];v=function(t,e,n,o,s){const i=t.ownerDocument;let l=t.tFoot;l||((l=t.createTFoot()).className="lt-footer");const r=a(i,"div",{className:"lti-pager"},m(l,"lti-pager-row",e.length,-1)),c=a(i,"span",{className:"lti-pos"});o.pageSize=n[0];const d=[()=>0,t=>t-1,t=>t+1,()=>1e9],u=["«","‹","›","»"].map((t,e)=>{const n=a(i,"button",{type:"button",textContent:t,"aria-label":["First","Previous","Next","Last"][e]},r);return n.onclick=()=>{o.page=d[e](o.page),s()},n});if(r.appendChild(c),n.length>1){const t=a(i,"select",{"aria-label":"Rows per page"},r);n.forEach(e=>a(i,"option",{value:e,textContent:e||"∞"},t)),t.onchange=()=>{o.pageSize=+t.value,o.page=0,s()}}return t=>{const e=o.pageSize||t||1,n=Math.max(0,Math.ceil(t/e)-1);c.textContent=t?`${o.page*e+1}–${Math.min(t,(o.page+1)*e)} / ${t}`:"0 / 0",u.forEach((t,e)=>t.disabled=e<2?!o.page:o.page===n)}}(n,f,t,b,()=>N(!1)),N()}else(b.sort.length||d.detail)&&N()}function h(t,e){return a(t,"input",{type:"search",className:"lti-search","aria-label":e})}function m(t,e,n,o){const s=t.insertRow(o);return s.className=e,a(t.ownerDocument,"td",{colSpan:n},s)}function g(t,e){let n;const o=()=>e(t.value);t.oninput=()=>{clearTimeout(n),n=setTimeout(o,150)},t.onchange=()=>{clearTimeout(n),o()}}const w=t=>""===t?"":`x == ${JSON.stringify(String(t))}`,y=t=>{const e=/^\s*x\s*==\s*(['"])([\s\S]*)\1\s*$/.exec(t||"");return e?e[2]:null},b=(t,e,n,o)=>t<=n&&e>=o?"":`x >= ${t} && x <= ${e}`,x=t=>{const e=/^\s*x\s*>=\s*(-?[\d.]+)\s*&&\s*x\s*<=\s*(-?[\d.]+)\s*$/.exec(t||"");return e?[parseFloat(e[1]),parseFloat(e[2])]:null};function v(t,e,n){const o=e.min,s=e.max,i=s-o||1,l=e.step||i/100,r=a(t,"div",{className:"lti-slider"}),d=a(t,"div",{className:"lti-slider-fill"},r),u=e=>a(t,"button",{type:"button",className:"lti-thumb",role:"slider","aria-label":e,"aria-valuemin":o,"aria-valuemax":s},r),p=[u("Minimum"),u("Maximum")];let f=[o,s];const h=t=>(t-o)/i*100,m=()=>{p.forEach((t,e)=>{t.style.left=h(f[e])+"%",t.setAttribute("aria-valuenow",f[e])}),d.style.left=h(f[0])+"%",d.style.right=100-h(f[1])+"%"},g=(t,e)=>{f[t]=(t=>{const e=Math.round((t-o)/l)*l+o;return Math.min(s,Math.max(o,Math.round(1e6*e)/1e6))})(e),f[0]>f[1]&&(f=[Math.min(...f),Math.max(...f)]),m(),n(f.slice())};return p.forEach((t,e)=>{t.onpointerdown=n=>{t.focus(),c(n,t=>g(e,(t=>{const e=r.getBoundingClientRect();return o+i*Math.min(1,Math.max(0,(t-e.left)/e.width))})(t.clientX)))},t.onkeydown=t=>{const n={ArrowLeft:-1,ArrowDown:-1,ArrowRight:1,ArrowUp:1}[t.key];if(n)g(e,f[e]+n*l);else if("Home"===t.key)g(e,o);else{if("End"!==t.key)return;g(e,s)}t.preventDefault()}}),m(),{el:r,set:t=>{f=t.slice(),m()}}}function S(t,e,n){const o=a(t,"span",{className:"lti-pop"}),s=a(t,"button",{type:"button",className:"lti-funnel",title:e,"aria-label":e,"aria-expanded":"false"},o),i=a(t,"div",{className:"lti-pop-panel",hidden:!0},o);n(i);const l=e=>{i.hidden=!e,s.setAttribute("aria-expanded",String(e)),e&&(()=>{i.style.left=i.style.right="";const e=i.getBoundingClientRect(),n=(t.defaultView||window).innerWidth;e.right>n-4&&(i.style.left="auto",i.style.right="0")})()};return s.onclick=t=>{t.stopPropagation(),l(i.hidden)},r(t,"click",t=>{o.contains(t.target)||l(!1)}),r(t,"keydown",t=>{"Escape"===t.key&&l(!1)}),o}const N={select:{describe:(t,e)=>{const n=y(t);if(null==n)return t?"⋯":"";const o=e.choices.find(t=>t.value===n);return o?o.label:n},init:t=>w(t.selected??(t.choices[0]||{}).value??""),build:(t,e,n)=>{const o=function(t,e,n){const o=a(t,"select",{className:"lti-filter-pick"});return e.forEach(e=>a(t,"option",{value:e.value,textContent:e.label},o)),o.onchange=()=>n(o.value),{el:o,set:t=>o.value=t}}(t,e.choices,t=>n(w(t),s)),s={el:o.el,reflect:t=>{const e=y(t);o.set(null==e?"":e)}};return s}},range:{describe:t=>{const e=x(t);return e?`${e[0]} – ${e[1]}`:t?"⋯":""},init:t=>t.value?b(t.value[0],t.value[1],t.min,t.max):"",build:(t,e,n)=>{const o=a(t,"span",{className:"lti-slider-out"}),s=t=>o.textContent=`${t[0]} – ${t[1]}`,i=v(t,e,t=>{s(t),n(b(t[0],t[1],e.min,e.max),l)}),l={el:[i.el,o],reflect:t=>{const n=x(t)||[e.min,e.max];i.set(n),s(n)}};return l}}};function C(t,e,n,o,s,i,l){const r=function(t,e){if("select"===t.type){let n=t.choices;if(!n){const t=new Set,o=[];for(const n of e)null==n||t.has(n)||(t.add(n),o.push(n));o.sort((t,e)=>"number"==typeof t&&"number"==typeof e?t-e:String(t).localeCompare(String(e))),n=o.map(t=>({value:String(t),label:String(t)}))}return{...t,choices:n}}let{min:n,max:o}=t;if(null==n||null==o){let t=1/0,s=-1/0;for(const n of e){const e=Number(n);isFinite(e)&&(e<t&&(t=e),e>s&&(s=e))}null==n&&(n=isFinite(t)?t:0),null==o&&(o=isFinite(s)?s:1)}return{...t,min:n,max:o}}(n,o[e]||[]),c=N[r.type],d=[];let u=()=>{};const p=(t,n)=>{t?s.filters[e]=t:delete s.filters[e],i();const o=s.filters[e]||"";d.forEach(t=>t!==n&&t.reflect(o)),u(o)},f=S(t,`Filter ${r.label||e}`,n=>{const o=a(t,"input",{type:"search",className:"lti-search","aria-label":`${r.label||e} filter expression`},n),s={reflect:e=>{t.activeElement!==o&&(o.value=e)}};d.push(s),g(o,t=>p(t.trim(),s));const i=c.build(t,r,p);d.push(i),n.append(...[].concat(i.el)),p(c.init(r),null)});let h,m=f;return null!=l&&(m=a(t,"span",{className:"lti-chip"}),a(t,"span",{className:"lti-chip-name",textContent:l},m),h=a(t,"span",{className:"lti-chip-cur"},m),m.appendChild(f)),u=t=>{m.classList.toggle("lti-on",!!t),h&&(h.textContent=c.describe(t,r))},u(s.filters[e]||""),m}const E=(t,e)=>{e?.interactive&&!t.dataset.ltiOn&&(t.dataset.ltiOn="1",f(t,e))};e.plugins.interactive={matcher:d,computeView:u,pageSlice:p,enhance:f},e.onMount.push(E),"undefined"!=typeof document&&l(document,".lt-table").forEach(t=>E(t,t._ltSpec))})("undefined"!=typeof window?window:globalThis);
|
|
1
|
+
(t=>{"use strict";const e=t.LT;if(!e?.onMount||e.plugins.interactive)return;let n;const o=t=>"number"==typeof t.find(t=>null!=t),s=t=>"string"!=typeof t?{...t}:"-"===t[0]?{col:t.slice(1),dir:"desc"}:{col:t,dir:"asc"},l=(t,e,o)=>(n||=new Intl.Collator,o?t-e:n.compare(String(t),String(e))),r=(t,e)=>t.querySelector(e),i=(t,e)=>t.querySelectorAll(e),a=(t,e,n,o)=>t.addEventListener(e,n,o),c=t=>[...t.children].filter(t=>!t.classList.contains("lt-row-group"));function u(t,e,n={},o){const s=t.createElement(e);for(const t in n)t.includes("-")?s.setAttribute(t,n[t]):s[t]=n[t];return o?o.appendChild(s):s}function d(t,e,n){t.preventDefault();const o=t.target.ownerDocument||document,s=t=>e(t);a(o,"pointermove",s),a(o,"pointerup",()=>{var t,e;t="pointermove",e=s,o.removeEventListener(t,e),n&&n()},{once:!0})}function p(t){const e=String(t??"").trim();if(""===e)return null;if(/(^|[^\w$])x([^\w$]|$)/.test(e)){let t;try{t=new Function("x",`return (${e});`)}catch(t){}if(t){const n=/^\s*!|!=/.test(e),o=e=>{if(null==e)return n;try{const n=Number(e);return!!t(String(e))||""!==e&&isFinite(n)&&!!t(n)}catch(t){return!1}};return t=>t[n?"every":"some"](t=>o(t.raw))}}const n="!"===e[0],o=(n?e.slice(1).trim():e).toLowerCase();return""===o?null:t=>n!==t.some(t=>null!=t.disp&&String(t.disp).toLowerCase().includes(o))}function f(t,e,n){const s=t._cols||[],r=t.data||{},i=(r[s[0]]||[]).length,a=(t,n)=>({raw:r[t]?.[n-1]??null,disp:e[t]?.[n-1]??""}),c=[];for(const t in n.filters||{}){const e=p(n.filters[t]);e&&c.push(n=>e([a(t,n)]))}const u=p(n.term);u&&c.push(t=>u(s.map(e=>a(e,t))));const d=Object.values(n.predicates||{});if(d.length){const t=Object.keys(r),e=e=>Object.fromEntries(t.map(t=>[t,r[t]?.[e-1]??null]));c.push(t=>{const n=e(t);return d.every(t=>t(n))})}const f=t=>c.every(e=>e(t)),h=t=>{const e=t.map(t=>{const e=r[t.col];return e&&{col:e,num:o(e),dir:"desc"===t.dir?-1:1}}).filter(Boolean);return e.length?(t,n)=>{for(const{col:o,num:s,dir:r}of e){const e=o[t-1],i=o[n-1],a=null==e,c=null==i;if(a||c){if(a!==c)return a?1:-1;continue}const u=l(e,i,s);if(u)return r*u}return 0}:null},m=t._indent,g=(t,e)=>{if(!m){const n=t.filter(f);return e?n.sort(e):n}const n=[],o=[];for(const e of t){const t={r:e,kids:[]},s=m[e-1]||0;for(;o.length&&o[o.length-1].lv>=s;)o.pop();(o.length?o[o.length-1].node.kids:n).push(t),o.push({node:t,lv:s})}const s=t=>t.map(t=>{const e=s(t.kids);return f(t.r)||e.length?{r:t.r,kids:e}:null}).filter(Boolean),l=t=>(e&&t.sort((t,n)=>e(t.r,n.r)),t.forEach(t=>l(t.kids)),t),r=[],i=t=>t.forEach(t=>{r.push(t.r),i(t.kids)});return i(l(s(n))),r},w=()=>Array.from({length:i},(t,e)=>e+1),y=t._rowspan;if(y){const t={},e=[];for(const o of n.sort||[])y.includes(o.col)?t[o.col]=o.dir:e.push(o);const s=h(e),i=(e,n)=>{if(n===y.length)return g(e,s);const a=r[y[n]],c=new Map;for(const t of e){const e=a[t-1],n=null==e?"\0":String(e);(c.get(n)||c.set(n,{v:e,rows:[]}).get(n)).rows.push(t)}let u=[...c.values()];if(t[y[n]]){const e=o(a),s="desc"===t[y[n]]?-1:1;u.sort((t,n)=>{const o=null==t.v,r=null==n.v;return o||r?o===r?0:o?1:-1:s*l(t.v,n.v,e)})}return u.flatMap(t=>i(t.rows,n+1))};return i(w(),0)}const b=h(n.sort||[]),v=t._groups;if(!v)return g(w(),b);const x=new Set,S=[];for(const t of v)t.rows.forEach(t=>x.add(t)),S.push(...g(t.rows,b));const C=[];for(let t=1;t<=i;t++)x.has(t)||C.push(t);return[...S,...g(C,b)]}function h(t,e){const n=e.pageSize;if(!n)return e.page=0,t;const o=Math.max(0,Math.ceil(t.length/n)-1);return e.page=Math.min(Math.max(e.page||0,0),o),t.slice(e.page*n,(e.page+1)*n)}function m(n,o){const l=o.interactive,p=o._cols||[],m=((o.data||{})[p[0]]||[]).length,v=o._groups,x={},S=v?[]:null;if(v){const t=new Set;v.forEach(e=>e.rows.forEach(n=>{x[n]=e,S.push(n),t.add(n)}));for(let e=1;e<=m;e++)t.has(e)||S.push(e)}const C=o._rowspan?o._rowspan.length:0,E=C?[...o._rowspan,...p]:p,N=function(t,e,n){const o={};e.forEach(t=>o[t]=[]);let s=0;return i(t,"tbody tr").forEach(t=>{if(t.classList.contains("lt-row-group"))return;const l=n?n[s]:s+1,r=c(t);e.forEach((t,e)=>o[t][l-1]=r[e]?.textContent??""),s++}),o}(n,p,S),k=[...i(n,"thead tr")].pop(),M={filters:{},predicates:{},page:0,pageSize:0,sort:Array.isArray(l.sort)?l.sort.map(s):[]};let D,$=()=>{};const A=[],F=(t=!0)=>{t&&(D=f(o,N,M),M.page=0);const s=h(D,M),l=(t=>{if(!v)return t;const e=[];let n;for(const o of t){const t=x[o];t!==n&&(t&&e.push({label:t.label,raw:t.raw}),n=t),e.push(o)}return e})(s),i=u(n.ownerDocument,"template",{innerHTML:e.buildHtml({...o,_viewRows:l})}),a=r(i.content,"tbody");s.length?A.forEach(t=>t(a,l)):a.innerHTML=`<tr class="lti-empty"><td colspan="${p.length}">—</td></tr>`,r(n,"tbody").replaceWith(a),$(D.length)};n._lt={spec:o,state:M,refresh:()=>F(),view:()=>D||f(o,N,M),filter(t,e){e?M.predicates[t]=e:delete M.predicates[t],F()}};const _=l.filter,z=_?.cols?Object.keys(_.cols).filter(t=>!0!==_.cols[t]&&!p.includes(t)):[],R=!1!==l.search||l.hide||z.length||l.download?u(n.ownerDocument,"div",{className:"lti-bar"},w(n.tHead||n.createTHead(),"lti-head",p.length,0)):null,j=c(k).map(t=>t.textContent);!1!==l.sort&&function(t,e,n,o){const s=t.ownerDocument,l={},r=()=>{for(const t in l)l[t].th.removeAttribute("aria-sort"),l[t].ind.textContent="";const t=n.sort||[];t.forEach(({col:e,dir:n},o)=>{const s=l[e];s&&(s.th.setAttribute("aria-sort","desc"===n?"descending":"ascending"),s.ind.textContent=("desc"===n?"▼":"▲")+(t.length>1?String(o+1).replace(/\d/g,t=>"₀₁₂₃₄₅₆₇₈₉"[t]):""))})};i(t,"th").forEach((t,i)=>{const a=e[i];if(null==a)return;t.classList.add("lti-sortable");const c=u(s,"span",{className:"lti-label"},t);for(;t.firstChild!==c;)c.append(t.firstChild);l[a]={th:t,ind:u(s,"span",{className:"lti-sort"},c)},c.onclick=t=>{!function(t,e,n){const o=(t.sort||[]).slice(),s=o.findIndex(t=>t.col===e),l=s<0?"asc":"asc"===o[s].dir?"desc":null;if(!n)return void(t.sort=l?[{col:e,dir:l}]:[]);s<0?o.push({col:e,dir:l}):l?o[s]={col:e,dir:l}:o.splice(s,1);t.sort=o}(n,a,t.shiftKey),r(),o()}}),r()}(k,E,M,F),_&&(_.default||_.cols&&Object.keys(_.cols).some(t=>p.includes(t)))&&function(t,e,n,o,s,l){const r=t.ownerDocument,i=u(r,"tr",{className:"lti-filters"}),a=n.default,c=n.cols||{};e.forEach(t=>{const e=u(r,"td",{},i),n=t in c?c[t]:a;if(n)if(!0===n){y(e.appendChild(g(r,`Filter ${t}`)),e=>{e?s.filters[t]=e:delete s.filters[t],l()})}else e.appendChild(L(r,t,n,o,s,l,null))}),t.parentNode.appendChild(i)}(k,p,_,o.data,M,F);const O=l.resize?function(t,e,n,o=0){const s=[...i(e,"th")],l=function(t,e,n=0){let o=r(t,"colgroup");if(!o){const s=t.ownerDocument;o=u(s,"colgroup");for(let t=0;t<n+e;t++)u(s,"col",{},o);t.caption?t.caption.after(o):t.prepend(o)}return[...o.children]}(t,n,o),a=t=>t.getBoundingClientRect().width,c=()=>{if(t.classList.contains("lti-fixed"))return;const e=s.map(a);t.style.width=a(t)+"px",l.forEach((t,n)=>t.style.width=e[n]+"px"),t.classList.add("lti-fixed")},d=e=>{const n=l.map(t=>t.style.width),o=t.style.width;l.forEach(t=>t.style.width=""),t.style.width="",t.classList.remove("lti-fixed");const r=a(s[e]);return l.forEach((t,e)=>t.style.width=n[e]),t.style.width=o,t.classList.add("lti-fixed"),r},p=(e,n,o=24)=>{const s=parseFloat(l[e].style.width);l[e].style.width=Math.max(n,o)+"px",t.style.width=parseFloat(t.style.width)+parseFloat(l[e].style.width)-s+"px"};return{ths:s,cs:l,dataCs:l.slice(o),freeze:c,natural:d,setWidth:p}}(n,k,p.length,C):null;l.resize&&function(t,e){const n=t.ownerDocument,{ths:o,cs:s,freeze:l,natural:r,setWidth:i}=e;o.forEach((e,o)=>{const a=u(n,"div",{className:"lti-resizer"},e);a.ondblclick=t=>{t.stopPropagation(),l(),i(o,r(o))},a.onpointerdown=e=>{e.stopPropagation(),l();const n=e.clientX,r=parseFloat(s[o].style.width);t.classList.add("lti-resizing"),d(e,t=>i(o,r+t.clientX-n),()=>t.classList.remove("lti-resizing"))}})}(n,O);const T=l.hide||l.download?u(n.ownerDocument,"div",{className:"lti-icons"},R):null;if(l.hide&&function(t,e,n,o,s,l,r,i){const d=e.ownerDocument,p=l&&l.hidden||[],f=new Set,h=(t,e,n)=>{const o=c(t)[e];o&&1===o.colSpan&&(o.hidden=n)};i.push(t=>f.forEach(e=>{for(const n of t.rows)h(n,e,!0)}));const m=t=>{const n=f.has(t);r?.dataCs[t]&&(r.dataCs[t].hidden=n);for(const o of e.rows)o.classList.contains("lt-spanner-row")||h(o,t,n)},g=u(d,"span",{className:"lti-cols"},t),w=u(d,"button",{type:"button",title:"Columns","aria-label":"Show or hide columns","aria-expanded":"false"},g),y=u(d,"div",{className:"lti-menu",hidden:!0},g);o.forEach((t,e)=>{if(null==t)return;const n=u(d,"label",{},y),o=u(d,"input",{type:"checkbox",checked:!0},n);n.append(s[e]??t),p.includes(t)&&(o.checked=!1,f.add(e)),o.onchange=()=>{o.checked?f.delete(e):f.add(e),m(e)}}),o.forEach((t,e)=>m(e));const b=t=>{y.hidden=!t,w.setAttribute("aria-expanded",String(t))};w.onclick=t=>{t.stopPropagation(),b(y.hidden)},a(d,"click",t=>{g.contains(t.target)||b(!1)}),a(d,"keydown",t=>{"Escape"===t.key&&b(!1)})}(T,n,0,p,j,l.hide,O,A),l.download&&function(t,e,n,o,s,l){const r=e.ownerDocument,i=("string"==typeof l&&l?l:"table").replace(/(\.csv)?$/i,".csv"),a=o.map((t,e)=>e).filter(t=>null!=o[t]);u(r,"button",{type:"button",className:"lti-download",title:"Download CSV","aria-label":"Download table as CSV"},t).onclick=()=>{const t=n._display||{},l=[a.map(t=>b(s[t]??o[t])).join(",")];for(const n of e._lt.view())l.push(a.map(e=>b((t[o[e]]||[])[n-1])).join(","));const c=URL.createObjectURL(new Blob([l.join("\n")],{type:"text/csv"}));u(r,"a",{href:c,download:i}).click(),URL.revokeObjectURL(c)}}(T,n,o,p,j,l.download),z.length&&function(t,e,n,o,s,l){const r=t.ownerDocument;for(const i of e)t.appendChild(L(r,i,n[i],o,s,l,n[i].label||i))}(u(n.ownerDocument,"div",{className:"lti-chips"},R),z,_.cols,o.data,M,F),!1!==l.search&&function(t,e,n,o){const s=t.appendChild(g(e.ownerDocument,"Search"));y(s,t=>{n.term=t,o()})}(R,n,M,F),l.detail&&A.push(function(n,o,s,l,r,i){const a=n.ownerDocument,d=s.length,p={};l.expanded=new Set;const f=(t,e)=>{const n={};for(const o in t)n[o]=t[o]?.[e-1]??null;return n},h=e=>{if(e in p)return p[e];const n=f(o._display||{},e);let s=null;if(Array.isArray(r)){const t={};for(const e of r)t[e]=[n[e]??null];s={data:t}}else{const l="function"==typeof r?r:t[r];"function"==typeof l&&(s=l(f(o.data||{},e),e,n))}return p[e]=s},m=t=>{l.expanded.has(t)?l.expanded.delete(t):l.expanded.add(t),i()};return(t,n)=>[...t.rows].forEach((t,o)=>{const s=n[o];if("number"!=typeof s)return;const r=c(t)[0],i=l.expanded.has(s);if(!r)return;const p=u(a,"button",{type:"button",className:"lti-expand",textContent:i?"▾":"▸","aria-expanded":String(i),"aria-label":"Toggle detail"});p.onclick=()=>m(s),r.prepend(p);const f=i&&h(s);if(f){const n=u(a,"tr",{className:"lti-detail"});t.after(n);const o=u(a,"td",{colSpan:d},n);e.render(u(a,"div",{},o),f)}})}(n,o,p,M,l.detail,()=>F(!1))),l.pager){const t=Array.isArray(l.pager)?l.pager:[10,25,50,100];$=function(t,e,n,o,s){const l=t.ownerDocument;let r=t.tFoot;r||((r=t.createTFoot()).className="lt-footer");const i=u(l,"div",{className:"lti-pager"},w(r,"lti-pager-row",e.length,-1)),a=u(l,"span",{className:"lti-pos"});o.pageSize=n[0];const c=[()=>0,t=>t-1,t=>t+1,()=>1e9],d=["«","‹","›","»"].map((t,e)=>{const n=u(l,"button",{type:"button",textContent:t,"aria-label":["First","Previous","Next","Last"][e]},i);return n.onclick=()=>{o.page=c[e](o.page),s()},n});if(i.appendChild(a),n.length>1){const t=u(l,"select",{"aria-label":"Rows per page"},i);n.forEach(e=>u(l,"option",{value:e,textContent:e||"∞"},t)),t.onchange=()=>{o.pageSize=+t.value,o.page=0,s()}}return t=>{const e=o.pageSize||t||1,n=Math.max(0,Math.ceil(t/e)-1);a.textContent=t?`${o.page*e+1}–${Math.min(t,(o.page+1)*e)} / ${t}`:"0 / 0",d.forEach((t,e)=>t.disabled=e<2?!o.page:o.page===n)}}(n,p,t,M,()=>F(!1)),F()}else(M.sort.length||l.detail)&&F()}function g(t,e){return u(t,"input",{type:"search",className:"lti-search","aria-label":e})}function w(t,e,n,o){const s=t.insertRow(o);return s.className=e,u(t.ownerDocument,"td",{colSpan:n},s)}function y(t,e){let n;const o=()=>e(t.value);t.oninput=()=>{clearTimeout(n),n=setTimeout(o,150)},t.onchange=()=>{clearTimeout(n),o()}}function b(t){const e=null==t?"":String(t);return/[",\n]/.test(e)?'"'+e.replace(/"/g,'""')+'"':e}const v=t=>""===t?"":`x == ${JSON.stringify(String(t))}`,x=t=>{const e=/^\s*x\s*==\s*(['"])([\s\S]*)\1\s*$/.exec(t||"");return e?e[2]:null},S=(t,e,n,o)=>t<=n&&e>=o?"":`x >= ${t} && x <= ${e}`,C=t=>{const e=/^\s*x\s*>=\s*(-?[\d.]+)\s*&&\s*x\s*<=\s*(-?[\d.]+)\s*$/.exec(t||"");return e?[parseFloat(e[1]),parseFloat(e[2])]:null};function E(t,e,n){const o=e.min,s=e.max,l=s-o||1,r=e.step||l/100,i=u(t,"div",{className:"lti-slider"}),a=u(t,"div",{className:"lti-slider-fill"},i),c=e=>u(t,"button",{type:"button",className:"lti-thumb",role:"slider","aria-label":e,"aria-valuemin":o,"aria-valuemax":s},i),p=[c("Minimum"),c("Maximum")];let f=[o,s];const h=t=>(t-o)/l*100,m=()=>{p.forEach((t,e)=>{t.style.left=h(f[e])+"%",t.setAttribute("aria-valuenow",f[e])}),a.style.left=h(f[0])+"%",a.style.right=100-h(f[1])+"%"},g=(t,e)=>{f[t]=(t=>{const e=Math.round((t-o)/r)*r+o;return Math.min(s,Math.max(o,Math.round(1e6*e)/1e6))})(e),f[0]>f[1]&&(f=[Math.min(...f),Math.max(...f)]),m(),n(f.slice())};return p.forEach((t,e)=>{t.onpointerdown=n=>{t.focus(),d(n,t=>g(e,(t=>{const e=i.getBoundingClientRect();return o+l*Math.min(1,Math.max(0,(t-e.left)/e.width))})(t.clientX)))},t.onkeydown=t=>{const n={ArrowLeft:-1,ArrowDown:-1,ArrowRight:1,ArrowUp:1}[t.key];if(n)g(e,f[e]+n*r);else if("Home"===t.key)g(e,o);else{if("End"!==t.key)return;g(e,s)}t.preventDefault()}}),m(),{el:i,set:t=>{f=t.slice(),m()}}}function N(t,e,n){const o=u(t,"span",{className:"lti-pop"}),s=u(t,"button",{type:"button",className:"lti-funnel",title:e,"aria-label":e,"aria-expanded":"false"},o),l=u(t,"div",{className:"lti-pop-panel",hidden:!0},o);n(l);const r=e=>{l.hidden=!e,s.setAttribute("aria-expanded",String(e)),e&&(()=>{l.style.left=l.style.right="";const e=l.getBoundingClientRect(),n=(t.defaultView||window).innerWidth;e.right>n-4&&(l.style.left="auto",l.style.right="0")})()};return s.onclick=t=>{t.stopPropagation(),r(l.hidden)},a(t,"click",t=>{o.contains(t.target)||r(!1)}),a(t,"keydown",t=>{"Escape"===t.key&&r(!1)}),o}const k={select:{describe:(t,e)=>{const n=x(t);if(null==n)return t?"⋯":"";const o=e.choices.find(t=>t.value===n);return o?o.label:n},init:t=>v(t.selected??(t.choices[0]||{}).value??""),build:(t,e,n)=>{const o=function(t,e,n){const o=u(t,"select",{className:"lti-filter-pick"});return e.forEach(e=>u(t,"option",{value:e.value,textContent:e.label},o)),o.onchange=()=>n(o.value),{el:o,set:t=>o.value=t}}(t,e.choices,t=>n(v(t),s)),s={el:o.el,reflect:t=>{const e=x(t);o.set(null==e?"":e)}};return s}},range:{describe:t=>{const e=C(t);return e?`${e[0]} – ${e[1]}`:t?"⋯":""},init:t=>t.value?S(t.value[0],t.value[1],t.min,t.max):"",build:(t,e,n)=>{const o=u(t,"span",{className:"lti-slider-out"}),s=t=>o.textContent=`${t[0]} – ${t[1]}`,l=E(t,e,t=>{s(t),n(S(t[0],t[1],e.min,e.max),r)}),r={el:[l.el,o],reflect:t=>{const n=C(t)||[e.min,e.max];l.set(n),s(n)}};return r}}};function L(t,e,n,o,s,l,r){const i=function(t,e){if("select"===t.type){let n=t.choices;if(!n){const t=new Set,o=[];for(const n of e)null==n||t.has(n)||(t.add(n),o.push(n));o.sort((t,e)=>"number"==typeof t&&"number"==typeof e?t-e:String(t).localeCompare(String(e))),n=o.map(t=>({value:String(t),label:String(t)}))}return{...t,choices:n}}let{min:n,max:o}=t;if(null==n||null==o){let t=1/0,s=-1/0;for(const n of e){const e=Number(n);isFinite(e)&&(e<t&&(t=e),e>s&&(s=e))}null==n&&(n=isFinite(t)?t:0),null==o&&(o=isFinite(s)?s:1)}return{...t,min:n,max:o}}(n,o[e]||[]),a=k[i.type],c=[];let d=()=>{};const p=(t,n)=>{t?s.filters[e]=t:delete s.filters[e],l();const o=s.filters[e]||"";c.forEach(t=>t!==n&&t.reflect(o)),d(o)},f=N(t,`Filter ${i.label||e}`,n=>{const o=u(t,"input",{type:"search",className:"lti-search","aria-label":`${i.label||e} filter expression`},n),s={reflect:e=>{t.activeElement!==o&&(o.value=e)}};c.push(s),y(o,t=>p(t.trim(),s));const l=a.build(t,i,p);c.push(l),n.append(...[].concat(l.el)),p(a.init(i),null)});let h,m=f;return null!=r&&(m=u(t,"span",{className:"lti-chip"}),u(t,"span",{className:"lti-chip-name",textContent:r},m),h=u(t,"span",{className:"lti-chip-cur"},m),m.appendChild(f)),d=t=>{m.classList.toggle("lti-on",!!t),h&&(h.textContent=a.describe(t,i))},d(s.filters[e]||""),m}const M=(t,e)=>{e?.interactive&&!t.dataset.ltiOn&&(t.dataset.ltiOn="1",m(t,e))};e.plugins.interactive={matcher:p,computeView:f,pageSlice:h,enhance:m},e.onMount.push(M),"undefined"!=typeof document&&i(document,".lt-table").forEach(t=>M(t,t._ltSpec))})("undefined"!=typeof window?window:globalThis);
|
|
2
2
|
//# sourceMappingURL=lt-interactive.min.js.map
|