@qnc/qnc_data_tables 1.0.3 → 1.0.6-a
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 +36 -0
- package/dist/bound_stored_value.d.ts +39 -0
- package/dist/bound_stored_value.js +134 -0
- package/dist/bound_stored_value.ts +158 -0
- package/dist/column_manager.d.ts +43 -0
- package/dist/column_manager.js +124 -0
- package/dist/column_manager.ts +158 -0
- package/dist/column_resizing.d.ts +3 -0
- package/dist/column_resizing.js +30 -0
- package/dist/column_resizing.ts +52 -0
- package/dist/column_sorting.d.ts +11 -0
- package/dist/column_sorting.js +53 -0
- package/dist/column_sorting.ts +63 -0
- package/dist/conditionally_wrapped_element.d.ts +5 -0
- package/dist/conditionally_wrapped_element.js +14 -0
- package/dist/conditionally_wrapped_element.ts +17 -0
- package/dist/create_mithril_app.d.ts +3 -0
- package/dist/create_mithril_app.js +25 -0
- package/dist/create_mithril_app.ts +35 -0
- package/dist/create_style.d.ts +4 -0
- package/dist/create_style.js +9 -0
- package/dist/create_style.ts +10 -0
- package/dist/custom_element.d.ts +23 -0
- package/dist/custom_element.js +63 -0
- package/dist/custom_element.ts +71 -0
- package/dist/event_names.d.ts +1 -0
- package/dist/event_names.js +1 -0
- package/dist/event_names.ts +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/index.ts +5 -0
- package/dist/mithril_view.d.ts +16 -0
- package/dist/mithril_view.js +484 -0
- package/dist/mithril_view.ts +1014 -0
- package/dist/optional_storage.d.ts +6 -0
- package/dist/optional_storage.js +18 -0
- package/dist/optional_storage.ts +23 -0
- package/dist/overflow_class_manager.d.ts +20 -0
- package/dist/overflow_class_manager.js +30 -0
- package/dist/overflow_class_manager.ts +40 -0
- package/dist/renderer.d.ts +16 -0
- package/dist/renderer.js +51 -0
- package/dist/renderer.ts +86 -0
- package/dist/selection_fieldset_controller.d.ts +9 -0
- package/dist/selection_fieldset_controller.js +85 -0
- package/dist/selection_fieldset_controller.ts +104 -0
- package/dist/state_machine.d.ts +67 -0
- package/dist/state_machine.js +316 -0
- package/dist/state_machine.ts +434 -0
- package/dist/state_types.d.ts +62 -0
- package/dist/state_types.js +1 -0
- package/dist/state_types.ts +84 -0
- package/dist/table_manager.d.ts +9 -0
- package/dist/table_manager.js +16 -0
- package/dist/table_manager.ts +28 -0
- package/dist/table_options.d.ts +164 -0
- package/dist/table_options.js +97 -0
- package/dist/table_options.ts +132 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/watched_mutable_value.d.ts +15 -0
- package/dist/watched_mutable_value.js +23 -0
- package/dist/watched_mutable_value.ts +23 -0
- package/package.json +12 -5
- package/dist/qnc_data_tables.d.ts +0 -57
- package/dist/qnc_data_tables.js +0 -1136
- package/dist/qnc_data_tables_inline_css.js +0 -8
|
@@ -0,0 +1,1014 @@
|
|
|
1
|
+
import m, { Children, redraw, Vnode, VnodeDOM, trust } from "mithril";
|
|
2
|
+
import { AppState, SortState, SortStateInterpreter } from "./state_types.js";
|
|
3
|
+
import { Renderer } from "./renderer.js";
|
|
4
|
+
import { ExtraSortFunction } from "./table_options.js";
|
|
5
|
+
import { ConfiguredColumns, ConfiguredColumn } from "./column_manager.js";
|
|
6
|
+
import { enable } from "@qnc/drag_sorter";
|
|
7
|
+
import { begin_header_drag } from "./column_resizing.js";
|
|
8
|
+
import { WatchedMutableValue } from "./watched_mutable_value.js";
|
|
9
|
+
|
|
10
|
+
function classes(...values: (string | false | null)[]) {
|
|
11
|
+
return values.filter(Boolean).join(" ");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type Updaters = {
|
|
15
|
+
set_page_size: (page_size: number) => void;
|
|
16
|
+
set_page_number: (page_number: number) => void;
|
|
17
|
+
set_column_enabled: (column_key: string, enabled: boolean) => void;
|
|
18
|
+
sort_by_function: (sort_function_key: string) => void;
|
|
19
|
+
sort_by_column: (
|
|
20
|
+
column_key: string,
|
|
21
|
+
direction: "forward" | "reverse",
|
|
22
|
+
) => void;
|
|
23
|
+
set_column_width: (column_key: string, width: number) => void;
|
|
24
|
+
set_column_order: (keys: string[]) => void;
|
|
25
|
+
set_selected: (pk: string, selected: boolean) => void;
|
|
26
|
+
bulk_set_selected: (pks: Iterable<string>, selected: boolean) => void;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function app_view(
|
|
30
|
+
state: AppState<string>,
|
|
31
|
+
renderer: Renderer,
|
|
32
|
+
updaters: Updaters,
|
|
33
|
+
) {
|
|
34
|
+
return [
|
|
35
|
+
m("style", state.row_width_style_string.get()),
|
|
36
|
+
top_control_bar({
|
|
37
|
+
current_page: state.page_number,
|
|
38
|
+
set_page: updaters.set_page_number,
|
|
39
|
+
total_pages:
|
|
40
|
+
state.accessible_result_count !== null
|
|
41
|
+
? Math.ceil(state.accessible_result_count / state.page_size)
|
|
42
|
+
: null,
|
|
43
|
+
page_size: state.page_size,
|
|
44
|
+
set_page_size: updaters.set_page_size,
|
|
45
|
+
result_count: state.result_count,
|
|
46
|
+
result_count_loading: state.fetching_result_count,
|
|
47
|
+
accessible_result_count: state.accessible_result_count,
|
|
48
|
+
extra_sort_functions: state.extra_sort_functions,
|
|
49
|
+
sort_by_function: updaters.sort_by_function,
|
|
50
|
+
current_sort: state.current_sort,
|
|
51
|
+
}),
|
|
52
|
+
column_controls(state.columns, updaters, renderer),
|
|
53
|
+
m("form", [
|
|
54
|
+
render_with_loader(state.fetching_page_data, [
|
|
55
|
+
table(
|
|
56
|
+
state.columns,
|
|
57
|
+
updaters,
|
|
58
|
+
state.table_data,
|
|
59
|
+
renderer,
|
|
60
|
+
state.sort_state_interpreter,
|
|
61
|
+
state.fill_last_page,
|
|
62
|
+
state.page_size,
|
|
63
|
+
state.table_key,
|
|
64
|
+
state.include_selection_column,
|
|
65
|
+
state.table_overflows_left,
|
|
66
|
+
state.table_overflows_right,
|
|
67
|
+
state.row_width_style_string,
|
|
68
|
+
state.selected_pks,
|
|
69
|
+
),
|
|
70
|
+
state.include_selection_column &&
|
|
71
|
+
selection_controls(state, updaters),
|
|
72
|
+
]),
|
|
73
|
+
]),
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
type RowData = Readonly<
|
|
77
|
+
[
|
|
78
|
+
string, // row pk
|
|
79
|
+
ReadonlyMap<string, string>, // column key => cell html
|
|
80
|
+
]
|
|
81
|
+
>;
|
|
82
|
+
function table(
|
|
83
|
+
columns: ConfiguredColumns,
|
|
84
|
+
updaters: Updaters,
|
|
85
|
+
data: ReadonlyArray<RowData>,
|
|
86
|
+
renderer: Renderer,
|
|
87
|
+
sort_state_interpreter: SortStateInterpreter,
|
|
88
|
+
fill_last_page: boolean,
|
|
89
|
+
page_size: number,
|
|
90
|
+
table_key: string,
|
|
91
|
+
include_selection_column: boolean,
|
|
92
|
+
table_overflows_left: WatchedMutableValue<boolean>,
|
|
93
|
+
table_overflows_right: WatchedMutableValue<boolean>,
|
|
94
|
+
row_width_style_string: WatchedMutableValue<string>,
|
|
95
|
+
selected_pks: ReadonlySet<string>,
|
|
96
|
+
) {
|
|
97
|
+
return m(
|
|
98
|
+
"div",
|
|
99
|
+
{
|
|
100
|
+
class: classes(
|
|
101
|
+
`qnc-data-table qnc-data-table-${CSS.escape(table_key)}`,
|
|
102
|
+
table_overflows_left.get() && "qdt-overflows-left",
|
|
103
|
+
table_overflows_right.get() && "qdt-overflows-right",
|
|
104
|
+
),
|
|
105
|
+
role: "table",
|
|
106
|
+
// style: all_columns().map(c=>`${column_width_property_name(c.key)}: ${c.width}px`).join(';')
|
|
107
|
+
},
|
|
108
|
+
m(
|
|
109
|
+
"div",
|
|
110
|
+
{
|
|
111
|
+
class: "qnc-data-table-head",
|
|
112
|
+
role: "rowgroup",
|
|
113
|
+
},
|
|
114
|
+
header_row({
|
|
115
|
+
columns,
|
|
116
|
+
updaters,
|
|
117
|
+
sort_state_interpreter,
|
|
118
|
+
table_key,
|
|
119
|
+
include_selection_column,
|
|
120
|
+
renderer,
|
|
121
|
+
table_overflows_left,
|
|
122
|
+
table_overflows_right,
|
|
123
|
+
row_width_style_string,
|
|
124
|
+
}),
|
|
125
|
+
),
|
|
126
|
+
m(
|
|
127
|
+
"div",
|
|
128
|
+
{
|
|
129
|
+
class: "qnc-data-table-body",
|
|
130
|
+
role: "rowgroup",
|
|
131
|
+
},
|
|
132
|
+
table_rows(
|
|
133
|
+
columns,
|
|
134
|
+
data,
|
|
135
|
+
fill_last_page,
|
|
136
|
+
page_size,
|
|
137
|
+
table_key,
|
|
138
|
+
include_selection_column,
|
|
139
|
+
updaters,
|
|
140
|
+
selected_pks,
|
|
141
|
+
),
|
|
142
|
+
),
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
type HeaderAttrs = {
|
|
146
|
+
columns: ConfiguredColumns;
|
|
147
|
+
updaters: Updaters;
|
|
148
|
+
renderer: Renderer;
|
|
149
|
+
sort_state_interpreter: SortStateInterpreter;
|
|
150
|
+
table_key: string;
|
|
151
|
+
include_selection_column: boolean;
|
|
152
|
+
table_overflows_left: WatchedMutableValue<boolean>;
|
|
153
|
+
table_overflows_right: WatchedMutableValue<boolean>;
|
|
154
|
+
row_width_style_string: WatchedMutableValue<string>;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
function HeaderComponent(initialVnode: Vnode) {
|
|
158
|
+
let sticky_style_string = "";
|
|
159
|
+
|
|
160
|
+
function setup_detector(
|
|
161
|
+
detector: Element,
|
|
162
|
+
watched_value: WatchedMutableValue<boolean>,
|
|
163
|
+
) {
|
|
164
|
+
const table = detector.closest(".qnc-data-table");
|
|
165
|
+
if (!table) throw new Error("could not find parent table element");
|
|
166
|
+
new IntersectionObserver(
|
|
167
|
+
function (entries, observer) {
|
|
168
|
+
watched_value.set(!entries[0]?.isIntersecting);
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
root: table,
|
|
172
|
+
},
|
|
173
|
+
).observe(detector);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Note: layout tightlty coupled to recompute_sticky_style_string
|
|
177
|
+
function raw_header_row(
|
|
178
|
+
columns: ConfiguredColumns,
|
|
179
|
+
updaters: Updaters,
|
|
180
|
+
renderer: Renderer,
|
|
181
|
+
sort_state_interpreter: SortStateInterpreter,
|
|
182
|
+
table_key: string,
|
|
183
|
+
table_overflows_left: WatchedMutableValue<boolean>,
|
|
184
|
+
table_overflows_right: WatchedMutableValue<boolean>,
|
|
185
|
+
include_selection_column: boolean,
|
|
186
|
+
) {
|
|
187
|
+
return m(
|
|
188
|
+
"div",
|
|
189
|
+
{
|
|
190
|
+
role: "row",
|
|
191
|
+
className: `qnc-data-table-row qnc-data-table-header-row qnc-data-table-row qnc-data-table-row-${CSS.escape(
|
|
192
|
+
table_key,
|
|
193
|
+
)}`,
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
// "overflow-left detector"
|
|
197
|
+
m(
|
|
198
|
+
"div",
|
|
199
|
+
{
|
|
200
|
+
role: "columnheader",
|
|
201
|
+
style: "padding: 0; width: 0; border: none;",
|
|
202
|
+
},
|
|
203
|
+
m("div", {
|
|
204
|
+
style: "width: 1px; height: 1px; background-color: transparent; pointerEvents: none;",
|
|
205
|
+
oncreate: (vnode) => {
|
|
206
|
+
setup_detector(vnode.dom, table_overflows_left);
|
|
207
|
+
},
|
|
208
|
+
}),
|
|
209
|
+
),
|
|
210
|
+
|
|
211
|
+
include_selection_column && empty_checkbox_cell(table_key),
|
|
212
|
+
|
|
213
|
+
columns.fixed_enabled_columns.map((c) =>
|
|
214
|
+
header_cell(
|
|
215
|
+
renderer,
|
|
216
|
+
c,
|
|
217
|
+
sort_state_interpreter,
|
|
218
|
+
updaters,
|
|
219
|
+
true,
|
|
220
|
+
table_key,
|
|
221
|
+
),
|
|
222
|
+
),
|
|
223
|
+
m("div", {
|
|
224
|
+
role: "columnheader",
|
|
225
|
+
class: `qdt-overflow-indicator-cell qdt-overflow-indicator-cell-left qnc-data-table-fixed-cell-${CSS.escape(
|
|
226
|
+
table_key,
|
|
227
|
+
)}`,
|
|
228
|
+
}),
|
|
229
|
+
columns.sortable_enabled_columns.map((c) =>
|
|
230
|
+
header_cell(
|
|
231
|
+
renderer,
|
|
232
|
+
c,
|
|
233
|
+
sort_state_interpreter,
|
|
234
|
+
updaters,
|
|
235
|
+
false,
|
|
236
|
+
table_key,
|
|
237
|
+
),
|
|
238
|
+
),
|
|
239
|
+
empty_expanding_cell("columnheader"),
|
|
240
|
+
m("div", {
|
|
241
|
+
role: "columnheader",
|
|
242
|
+
class: `qdt-overflow-indicator-cell qdt-overflow-indicator-cell-right`,
|
|
243
|
+
}),
|
|
244
|
+
|
|
245
|
+
// "overflow-right detector"
|
|
246
|
+
m(
|
|
247
|
+
"div",
|
|
248
|
+
{
|
|
249
|
+
role: "columnheader",
|
|
250
|
+
style: "padding: 0; width: 0; border: none; position: relative;",
|
|
251
|
+
},
|
|
252
|
+
m("div", {
|
|
253
|
+
style: "position: absolute; right: 0; width: 1px; height: 1px; background-color: transparent; pointerEvents: none;",
|
|
254
|
+
oncreate: (vnode) => {
|
|
255
|
+
setup_detector(vnode.dom, table_overflows_right);
|
|
256
|
+
},
|
|
257
|
+
}),
|
|
258
|
+
),
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
// Note: tightlty coupled to raw_header_row layout
|
|
262
|
+
function recompute_sticky_style_string(
|
|
263
|
+
header_element: Element,
|
|
264
|
+
include_selection_column: boolean,
|
|
265
|
+
columns: ConfiguredColumns,
|
|
266
|
+
table_key: string,
|
|
267
|
+
) {
|
|
268
|
+
const old_value = sticky_style_string;
|
|
269
|
+
sticky_style_string = "";
|
|
270
|
+
const fixed_column_count =
|
|
271
|
+
(include_selection_column ? 1 : 0) +
|
|
272
|
+
columns.fixed_enabled_columns.length +
|
|
273
|
+
1; // the "sticky divider" column, where box-shadow is rendered
|
|
274
|
+
let previous_width = 0;
|
|
275
|
+
const start_index = 1; // skip over the "scroll detector" column
|
|
276
|
+
let i = start_index; // i will range over "element index" of all fixed column headers
|
|
277
|
+
const end_index =
|
|
278
|
+
start_index +
|
|
279
|
+
fixed_column_count + // all fixed columns
|
|
280
|
+
1; // left overflow indicator cell
|
|
281
|
+
while (i < end_index) {
|
|
282
|
+
const cell = header_element.children[i];
|
|
283
|
+
if (!cell) break;
|
|
284
|
+
|
|
285
|
+
sticky_style_string += `.qnc-data-table-fixed-cell-${CSS.escape(
|
|
286
|
+
table_key,
|
|
287
|
+
)}:nth-child(${i + 1}) {
|
|
288
|
+
position: sticky;
|
|
289
|
+
left: ${previous_width}px;
|
|
290
|
+
z-index: 2;
|
|
291
|
+
}`;
|
|
292
|
+
previous_width += cell.clientWidth;
|
|
293
|
+
i++;
|
|
294
|
+
}
|
|
295
|
+
return sticky_style_string != old_value;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/*
|
|
299
|
+
Rows need explicit min-width.
|
|
300
|
+
Otherwise, they are only as wide as table, and cells overflow.
|
|
301
|
+
This causes problems if:
|
|
302
|
+
1. there are any background colours/borders on rows
|
|
303
|
+
2. there are sticky columns, and row content is more than two table-widths wide (eventually, the sticky columns get pushed out view)
|
|
304
|
+
|
|
305
|
+
#1. can be solved by putting all colours/borders on cells, rather than rows, but issue #2 still requires fixed row widths.
|
|
306
|
+
|
|
307
|
+
While we _could_ compute the expected width based on columns.map(c => c.width), that approach would also require us to know the padding per cell (which might change based on media query). So let's just _measure_ the actual rendered width, instead.
|
|
308
|
+
|
|
309
|
+
Note: this isn't foolproof, either. In hindsight, I'd have preferred to just calculate this based on column widths (perhaps setting `box-sizing: border-box` on them, so we wouldn't have to worry about padding).
|
|
310
|
+
*/
|
|
311
|
+
function get_row_width_style(header: Element, table_key: string): string {
|
|
312
|
+
let row_width = 0;
|
|
313
|
+
let cell = header.firstElementChild;
|
|
314
|
+
|
|
315
|
+
while (cell) {
|
|
316
|
+
// skip the "empty expanding column"; otherwise, row width will never shrink, even when it should
|
|
317
|
+
if (!cell.classList.contains("qdt-empty-expanding-cell")) {
|
|
318
|
+
row_width += cell.clientWidth;
|
|
319
|
+
}
|
|
320
|
+
cell = cell.nextElementSibling;
|
|
321
|
+
}
|
|
322
|
+
return `.qnc-data-table-row-${CSS.escape(
|
|
323
|
+
table_key,
|
|
324
|
+
)} {min-width: ${row_width}px;}`;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
oncreate(vnode: VnodeDOM) {
|
|
329
|
+
const {
|
|
330
|
+
columns,
|
|
331
|
+
include_selection_column,
|
|
332
|
+
table_key,
|
|
333
|
+
row_width_style_string,
|
|
334
|
+
} = vnode.attrs as HeaderAttrs;
|
|
335
|
+
row_width_style_string.set(
|
|
336
|
+
get_row_width_style(vnode.dom, table_key),
|
|
337
|
+
);
|
|
338
|
+
if (
|
|
339
|
+
recompute_sticky_style_string(
|
|
340
|
+
vnode.dom,
|
|
341
|
+
include_selection_column,
|
|
342
|
+
columns,
|
|
343
|
+
table_key,
|
|
344
|
+
)
|
|
345
|
+
)
|
|
346
|
+
redraw();
|
|
347
|
+
},
|
|
348
|
+
onupdate(vnode: VnodeDOM) {
|
|
349
|
+
const {
|
|
350
|
+
columns,
|
|
351
|
+
include_selection_column,
|
|
352
|
+
table_key,
|
|
353
|
+
row_width_style_string,
|
|
354
|
+
} = vnode.attrs as HeaderAttrs;
|
|
355
|
+
row_width_style_string.set(
|
|
356
|
+
get_row_width_style(vnode.dom, table_key),
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
// Optimization opportunity: we _could_ hash (column order, column widths, include_selection_count)
|
|
360
|
+
// and return early whenever unchanged
|
|
361
|
+
if (
|
|
362
|
+
recompute_sticky_style_string(
|
|
363
|
+
vnode.dom,
|
|
364
|
+
include_selection_column,
|
|
365
|
+
columns,
|
|
366
|
+
table_key,
|
|
367
|
+
)
|
|
368
|
+
)
|
|
369
|
+
redraw();
|
|
370
|
+
},
|
|
371
|
+
view(vnode: Vnode) {
|
|
372
|
+
const {
|
|
373
|
+
columns,
|
|
374
|
+
updaters,
|
|
375
|
+
renderer,
|
|
376
|
+
sort_state_interpreter,
|
|
377
|
+
table_key,
|
|
378
|
+
table_overflows_left,
|
|
379
|
+
table_overflows_right,
|
|
380
|
+
include_selection_column,
|
|
381
|
+
} = vnode.attrs as HeaderAttrs;
|
|
382
|
+
return [
|
|
383
|
+
raw_header_row(
|
|
384
|
+
columns,
|
|
385
|
+
updaters,
|
|
386
|
+
renderer,
|
|
387
|
+
sort_state_interpreter,
|
|
388
|
+
table_key,
|
|
389
|
+
table_overflows_left,
|
|
390
|
+
table_overflows_right,
|
|
391
|
+
include_selection_column,
|
|
392
|
+
),
|
|
393
|
+
m("style", sticky_style_string),
|
|
394
|
+
];
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
function header_row(attrs: HeaderAttrs) {
|
|
399
|
+
return (m as any)(HeaderComponent, attrs);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function header_cell(
|
|
403
|
+
renderer: Renderer,
|
|
404
|
+
column: ConfiguredColumn,
|
|
405
|
+
sort_state_interpreter: SortStateInterpreter,
|
|
406
|
+
updaters: Updaters,
|
|
407
|
+
fixed: boolean,
|
|
408
|
+
table_key: string,
|
|
409
|
+
) {
|
|
410
|
+
const sort_direction = sort_state_interpreter(column.key);
|
|
411
|
+
return m(
|
|
412
|
+
"div",
|
|
413
|
+
{
|
|
414
|
+
role: "columnheader",
|
|
415
|
+
style: {
|
|
416
|
+
width: column.width + "px",
|
|
417
|
+
},
|
|
418
|
+
class: classes(
|
|
419
|
+
"qdt-cell",
|
|
420
|
+
"qdt-resizable-header",
|
|
421
|
+
fixed && `qnc-data-table-fixed-cell-${CSS.escape(table_key)}`,
|
|
422
|
+
),
|
|
423
|
+
},
|
|
424
|
+
|
|
425
|
+
m(
|
|
426
|
+
"div", // the only reason this div is here is so that it has "overflow: hidden;", while the parent element has "overflow: visible" (to allow resize handle to overflow)
|
|
427
|
+
renderer.render_header({
|
|
428
|
+
label: renderer.render_with_optional_help_text(
|
|
429
|
+
column.display,
|
|
430
|
+
column.help_text,
|
|
431
|
+
),
|
|
432
|
+
sortable: column.sortable,
|
|
433
|
+
sorted: sort_direction || "no",
|
|
434
|
+
set_sort: function (direction: "forward" | "reverse") {
|
|
435
|
+
updaters.sort_by_column(column.key, direction);
|
|
436
|
+
},
|
|
437
|
+
}),
|
|
438
|
+
),
|
|
439
|
+
|
|
440
|
+
m("span", {
|
|
441
|
+
onmousedown: (e: MouseEvent) => {
|
|
442
|
+
e.preventDefault(); // prevent text highlighting
|
|
443
|
+
|
|
444
|
+
begin_header_drag(
|
|
445
|
+
e,
|
|
446
|
+
(width) => {
|
|
447
|
+
updaters.set_column_width(column.key, width);
|
|
448
|
+
m.redraw();
|
|
449
|
+
},
|
|
450
|
+
column.width,
|
|
451
|
+
() => {
|
|
452
|
+
m.redraw();
|
|
453
|
+
},
|
|
454
|
+
);
|
|
455
|
+
},
|
|
456
|
+
class: "qdt-column-resizer",
|
|
457
|
+
}),
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
function table_rows(
|
|
461
|
+
columns: ConfiguredColumns,
|
|
462
|
+
data: ReadonlyArray<RowData>,
|
|
463
|
+
fill_last_page: boolean,
|
|
464
|
+
page_size: number,
|
|
465
|
+
table_key: string,
|
|
466
|
+
include_selection_column: boolean,
|
|
467
|
+
updaters: Updaters,
|
|
468
|
+
selected_pks: ReadonlySet<string>,
|
|
469
|
+
) {
|
|
470
|
+
return [
|
|
471
|
+
data.map((row) =>
|
|
472
|
+
table_row(
|
|
473
|
+
row,
|
|
474
|
+
columns,
|
|
475
|
+
table_key,
|
|
476
|
+
include_selection_column
|
|
477
|
+
? checkbox_cell(row[0], updaters, selected_pks, table_key)
|
|
478
|
+
: null,
|
|
479
|
+
),
|
|
480
|
+
),
|
|
481
|
+
fill_last_page &&
|
|
482
|
+
Array.from(Array(Math.max(0, page_size - data.length))).map((x) =>
|
|
483
|
+
raw_table_row(
|
|
484
|
+
columns.fixed_enabled_columns.map((c) =>
|
|
485
|
+
data_cell(c, null, true, table_key),
|
|
486
|
+
),
|
|
487
|
+
columns.sortable_enabled_columns.map((c) =>
|
|
488
|
+
data_cell(c, null, false, table_key),
|
|
489
|
+
),
|
|
490
|
+
table_key,
|
|
491
|
+
null,
|
|
492
|
+
),
|
|
493
|
+
),
|
|
494
|
+
];
|
|
495
|
+
}
|
|
496
|
+
function raw_table_row(
|
|
497
|
+
fixed_cells: Children,
|
|
498
|
+
sortable_cells: Children,
|
|
499
|
+
table_key: string,
|
|
500
|
+
checkbox_cell: Vnode | null,
|
|
501
|
+
) {
|
|
502
|
+
return m(
|
|
503
|
+
"div",
|
|
504
|
+
{
|
|
505
|
+
role: "row",
|
|
506
|
+
className: `qnc-data-table-row qnc-data-table-body-row qnc-data-table-row qnc-data-table-row-${CSS.escape(
|
|
507
|
+
table_key,
|
|
508
|
+
)}`,
|
|
509
|
+
},
|
|
510
|
+
|
|
511
|
+
// Empty "scroll detector" column
|
|
512
|
+
m("div", {
|
|
513
|
+
role: "cell",
|
|
514
|
+
style: "width: 0px; padding: 0; border: none;",
|
|
515
|
+
}),
|
|
516
|
+
|
|
517
|
+
checkbox_cell,
|
|
518
|
+
// include_selection_column && checkbox_cell(pk, updaters, selected_pks),
|
|
519
|
+
|
|
520
|
+
fixed_cells,
|
|
521
|
+
m("div", {
|
|
522
|
+
role: "cell",
|
|
523
|
+
class: `qdt-overflow-indicator-cell qdt-overflow-indicator-cell-left qnc-data-table-fixed-cell-${CSS.escape(
|
|
524
|
+
table_key,
|
|
525
|
+
)}`,
|
|
526
|
+
}),
|
|
527
|
+
sortable_cells,
|
|
528
|
+
|
|
529
|
+
empty_expanding_cell(),
|
|
530
|
+
|
|
531
|
+
m("div", {
|
|
532
|
+
role: "cell",
|
|
533
|
+
class: `qdt-overflow-indicator-cell qdt-overflow-indicator-cell-right`,
|
|
534
|
+
}),
|
|
535
|
+
|
|
536
|
+
// Empty "overflow detector" column
|
|
537
|
+
m("div", {
|
|
538
|
+
role: "cell",
|
|
539
|
+
style: "width: 0px; padding: 0; border: none;",
|
|
540
|
+
}),
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// #coupled-cell-functions: these functions are tightly coupled. TODO: move to a separate module
|
|
545
|
+
function data_cell(
|
|
546
|
+
column: ConfiguredColumn,
|
|
547
|
+
content: Children,
|
|
548
|
+
fixed: boolean,
|
|
549
|
+
table_key: string,
|
|
550
|
+
) {
|
|
551
|
+
return m(
|
|
552
|
+
"div",
|
|
553
|
+
{
|
|
554
|
+
role: "cell",
|
|
555
|
+
style: {
|
|
556
|
+
width: column.width + "px",
|
|
557
|
+
},
|
|
558
|
+
class: classes(
|
|
559
|
+
"qdt-cell",
|
|
560
|
+
fixed && `qnc-data-table-fixed-cell-${CSS.escape(table_key)}`,
|
|
561
|
+
),
|
|
562
|
+
},
|
|
563
|
+
content,
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
// Note: layout tightly coupled to HeaderRow
|
|
567
|
+
function table_row(
|
|
568
|
+
row: readonly [string, ReadonlyMap<string, string>],
|
|
569
|
+
columns: ConfiguredColumns,
|
|
570
|
+
table_key: string,
|
|
571
|
+
checkbox_cell: Vnode | null,
|
|
572
|
+
) {
|
|
573
|
+
const [id, cell_map] = row;
|
|
574
|
+
|
|
575
|
+
return raw_table_row(
|
|
576
|
+
columns.fixed_enabled_columns.map((c) =>
|
|
577
|
+
data_cell(c, trust(cell_map.get(c.key) || ""), true, table_key),
|
|
578
|
+
),
|
|
579
|
+
columns.sortable_enabled_columns.map((c) =>
|
|
580
|
+
data_cell(c, trust(cell_map.get(c.key) || ""), false, table_key),
|
|
581
|
+
),
|
|
582
|
+
table_key,
|
|
583
|
+
checkbox_cell,
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// #coupled-cell-functions: these functions are tightly coupled. TODO: move to a separate module
|
|
588
|
+
function empty_checkbox_cell(table_key: string) {
|
|
589
|
+
return m(
|
|
590
|
+
"div",
|
|
591
|
+
{
|
|
592
|
+
role: "columnheader",
|
|
593
|
+
style: { width: "auto" }, // This is the only auto-width column; it works because every row has the same content
|
|
594
|
+
className: `qdt-cell qnc-data-table-fixed-cell-${CSS.escape(
|
|
595
|
+
table_key,
|
|
596
|
+
)}`,
|
|
597
|
+
},
|
|
598
|
+
// Render an invisible checkbox, so this header cell takes up the same width as the rest of the checkbox cells
|
|
599
|
+
m(
|
|
600
|
+
"label",
|
|
601
|
+
{ className: "qdt-row-checkbox-label" },
|
|
602
|
+
m("input", {
|
|
603
|
+
type: "checkbox",
|
|
604
|
+
style: {
|
|
605
|
+
visibility: "hidden",
|
|
606
|
+
pointerEvents: "none",
|
|
607
|
+
},
|
|
608
|
+
tabIndex: -1,
|
|
609
|
+
}),
|
|
610
|
+
),
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// #coupled-cell-functions: these functions are tightly coupled. TODO: move to a separate module
|
|
615
|
+
function checkbox_cell(
|
|
616
|
+
pk: string,
|
|
617
|
+
updaters: Updaters,
|
|
618
|
+
selected_pks: ReadonlySet<string>,
|
|
619
|
+
table_key: string,
|
|
620
|
+
) {
|
|
621
|
+
return m(
|
|
622
|
+
"div",
|
|
623
|
+
{
|
|
624
|
+
role: "cell",
|
|
625
|
+
style: { width: "auto" }, // This is the only auto-width column; it works because every row has the same content
|
|
626
|
+
className: `qdt-cell qnc-data-table-fixed-cell-${CSS.escape(
|
|
627
|
+
table_key,
|
|
628
|
+
)}`,
|
|
629
|
+
},
|
|
630
|
+
m(
|
|
631
|
+
"label",
|
|
632
|
+
{ className: "qdt-row-checkbox-label" },
|
|
633
|
+
m("input", {
|
|
634
|
+
type: "checkbox",
|
|
635
|
+
checked: selected_pks.has(pk),
|
|
636
|
+
onchange: (e: MouseEvent) => {
|
|
637
|
+
updaters.set_selected(pk, !selected_pks.has(pk));
|
|
638
|
+
// TODO: handle shift+clicking, like django_data_tables did
|
|
639
|
+
},
|
|
640
|
+
}),
|
|
641
|
+
),
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
function empty_expanding_cell(role = "cell") {
|
|
645
|
+
return m("div", {
|
|
646
|
+
style: { flexGrow: "1", padding: "0", flexBasis: "0px" },
|
|
647
|
+
class: "qdt-empty-expanding-cell",
|
|
648
|
+
// Just in case users are using this in their selector for default cell background/border
|
|
649
|
+
role: role,
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function extra_sort_function_widget(
|
|
654
|
+
current_sort: SortState | null,
|
|
655
|
+
extra_sort_functions: ExtraSortFunction[],
|
|
656
|
+
sort_by_function: (function_key: string) => void,
|
|
657
|
+
) {
|
|
658
|
+
// TODO: update checkbox-dropdown to work with radio buttons, publish on npm, document that it should always be included as a peer dependency
|
|
659
|
+
// use that, so we can make help text smaller, and not show in "summary"
|
|
660
|
+
const current_function_key =
|
|
661
|
+
current_sort &&
|
|
662
|
+
current_sort.type == "function" &&
|
|
663
|
+
current_sort.function_key;
|
|
664
|
+
return m(
|
|
665
|
+
"label",
|
|
666
|
+
"sort by: ",
|
|
667
|
+
m(
|
|
668
|
+
"select",
|
|
669
|
+
{
|
|
670
|
+
onchange: (e: Event) => {
|
|
671
|
+
const key = (e.target as HTMLSelectElement).value;
|
|
672
|
+
if (key) sort_by_function(key);
|
|
673
|
+
},
|
|
674
|
+
},
|
|
675
|
+
current_sort == null && m("option", "-----"),
|
|
676
|
+
current_sort &&
|
|
677
|
+
current_sort.type == "column" &&
|
|
678
|
+
m("option", "(selected column)"),
|
|
679
|
+
extra_sort_functions.map((esf) =>
|
|
680
|
+
m(
|
|
681
|
+
"option",
|
|
682
|
+
{
|
|
683
|
+
selected: current_function_key == esf.key,
|
|
684
|
+
value: esf.key,
|
|
685
|
+
},
|
|
686
|
+
esf.display,
|
|
687
|
+
esf.help_text && ` (${esf.help_text})`,
|
|
688
|
+
),
|
|
689
|
+
),
|
|
690
|
+
),
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function top_control_bar(options: {
|
|
695
|
+
current_page: number;
|
|
696
|
+
set_page: (page: number) => void;
|
|
697
|
+
total_pages: number | null;
|
|
698
|
+
page_size: number;
|
|
699
|
+
set_page_size: (size: number) => void;
|
|
700
|
+
result_count: number | null;
|
|
701
|
+
result_count_loading: boolean;
|
|
702
|
+
accessible_result_count: number | null;
|
|
703
|
+
sort_by_function: (function_key: string) => void;
|
|
704
|
+
extra_sort_functions: ExtraSortFunction[];
|
|
705
|
+
current_sort: SortState | null;
|
|
706
|
+
}) {
|
|
707
|
+
function space_horizontally(gap: string, items: m.Children[]) {
|
|
708
|
+
return m(
|
|
709
|
+
"div",
|
|
710
|
+
{ style: { overflow: "hidden" } },
|
|
711
|
+
m(
|
|
712
|
+
"div",
|
|
713
|
+
{ style: { display: "flex", marginRight: "-" + gap } },
|
|
714
|
+
items.map((item) =>
|
|
715
|
+
m("div", { style: { marginRight: gap } }, item, " "),
|
|
716
|
+
),
|
|
717
|
+
),
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const {
|
|
722
|
+
current_page,
|
|
723
|
+
set_page,
|
|
724
|
+
total_pages,
|
|
725
|
+
page_size,
|
|
726
|
+
set_page_size,
|
|
727
|
+
result_count,
|
|
728
|
+
result_count_loading,
|
|
729
|
+
accessible_result_count,
|
|
730
|
+
} = options;
|
|
731
|
+
|
|
732
|
+
return [
|
|
733
|
+
space_horizontally("2em", [
|
|
734
|
+
// Paginator
|
|
735
|
+
[
|
|
736
|
+
m(
|
|
737
|
+
"button",
|
|
738
|
+
{
|
|
739
|
+
type: "button",
|
|
740
|
+
disabled: current_page <= 1,
|
|
741
|
+
onclick: (e: MouseEvent) => set_page(current_page - 1),
|
|
742
|
+
},
|
|
743
|
+
"<",
|
|
744
|
+
),
|
|
745
|
+
" Page ",
|
|
746
|
+
m("input", {
|
|
747
|
+
type: "number",
|
|
748
|
+
value: current_page,
|
|
749
|
+
max: total_pages,
|
|
750
|
+
style: { width: "3.5em" },
|
|
751
|
+
onchange: function (e: Event) {
|
|
752
|
+
set_page(
|
|
753
|
+
parseInt((e.target as HTMLInputElement).value),
|
|
754
|
+
);
|
|
755
|
+
},
|
|
756
|
+
}),
|
|
757
|
+
" of ",
|
|
758
|
+
total_pages ?? "?",
|
|
759
|
+
" ",
|
|
760
|
+
m(
|
|
761
|
+
"button",
|
|
762
|
+
{
|
|
763
|
+
type: "button",
|
|
764
|
+
disabled: current_page >= (total_pages ?? 1),
|
|
765
|
+
onclick: (e: MouseEvent) => set_page(current_page + 1),
|
|
766
|
+
},
|
|
767
|
+
">",
|
|
768
|
+
),
|
|
769
|
+
],
|
|
770
|
+
|
|
771
|
+
// Results per page
|
|
772
|
+
m(
|
|
773
|
+
"label",
|
|
774
|
+
m("input", {
|
|
775
|
+
value: page_size,
|
|
776
|
+
type: "number",
|
|
777
|
+
style: { width: "3.5em" },
|
|
778
|
+
onchange: function (e: Event) {
|
|
779
|
+
set_page_size(
|
|
780
|
+
parseInt((e.target as HTMLInputElement).value),
|
|
781
|
+
);
|
|
782
|
+
},
|
|
783
|
+
}),
|
|
784
|
+
" results per page",
|
|
785
|
+
),
|
|
786
|
+
|
|
787
|
+
// Result count
|
|
788
|
+
render_with_loader(
|
|
789
|
+
result_count_loading,
|
|
790
|
+
m("span", m("b", result_count), " result(s) total."),
|
|
791
|
+
),
|
|
792
|
+
|
|
793
|
+
options.extra_sort_functions.length &&
|
|
794
|
+
extra_sort_function_widget(
|
|
795
|
+
options.current_sort,
|
|
796
|
+
options.extra_sort_functions,
|
|
797
|
+
options.sort_by_function,
|
|
798
|
+
),
|
|
799
|
+
]),
|
|
800
|
+
|
|
801
|
+
m(
|
|
802
|
+
"div",
|
|
803
|
+
accessible_result_count !== null &&
|
|
804
|
+
result_count !== null &&
|
|
805
|
+
accessible_result_count < result_count &&
|
|
806
|
+
m(
|
|
807
|
+
"div",
|
|
808
|
+
{ style: { color: "red", fontWeight: "bold" } },
|
|
809
|
+
m("sup", "*"),
|
|
810
|
+
"The result table is limited to the first ",
|
|
811
|
+
accessible_result_count,
|
|
812
|
+
" results.",
|
|
813
|
+
),
|
|
814
|
+
),
|
|
815
|
+
];
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function column_controls(
|
|
819
|
+
columns: ConfiguredColumns,
|
|
820
|
+
updaters: Updaters,
|
|
821
|
+
renderer: Renderer,
|
|
822
|
+
) {
|
|
823
|
+
return m(
|
|
824
|
+
"details",
|
|
825
|
+
{ className: "qdt-column-details" },
|
|
826
|
+
m("summary", "Columns"),
|
|
827
|
+
m(
|
|
828
|
+
"ol",
|
|
829
|
+
{
|
|
830
|
+
oncreate: (vnode) =>
|
|
831
|
+
enable(vnode.dom as HTMLElement, {
|
|
832
|
+
onchange: (container: Element) => {
|
|
833
|
+
updaters.set_column_order(
|
|
834
|
+
Array.from(container.children).map(function (
|
|
835
|
+
li: Element,
|
|
836
|
+
) {
|
|
837
|
+
if (!(li instanceof HTMLElement))
|
|
838
|
+
throw new Error(
|
|
839
|
+
`unexpected child: ${li}`,
|
|
840
|
+
);
|
|
841
|
+
const value = li.dataset.key;
|
|
842
|
+
if (typeof value == "undefined")
|
|
843
|
+
throw new Error(
|
|
844
|
+
"column li missing [data-key]",
|
|
845
|
+
);
|
|
846
|
+
return value;
|
|
847
|
+
}),
|
|
848
|
+
);
|
|
849
|
+
},
|
|
850
|
+
stationary_list_item_markers: true,
|
|
851
|
+
handle_selector: ".handle",
|
|
852
|
+
}),
|
|
853
|
+
},
|
|
854
|
+
columns.all_sortable_columns.map((c) =>
|
|
855
|
+
m(
|
|
856
|
+
"li",
|
|
857
|
+
{
|
|
858
|
+
"data-key": c.key,
|
|
859
|
+
key: c.key,
|
|
860
|
+
className: "qnc-data-table-column-item",
|
|
861
|
+
},
|
|
862
|
+
m(
|
|
863
|
+
"label",
|
|
864
|
+
m("input", {
|
|
865
|
+
type: "checkbox",
|
|
866
|
+
checked: c.enabled,
|
|
867
|
+
onclick: (e: MouseEvent) =>
|
|
868
|
+
updaters.set_column_enabled(c.key, !c.enabled),
|
|
869
|
+
}),
|
|
870
|
+
" ",
|
|
871
|
+
c.display,
|
|
872
|
+
" ",
|
|
873
|
+
// don't wrap c.display in help text, because it's part of the drag handle
|
|
874
|
+
c.help_text &&
|
|
875
|
+
renderer.render_with_help_text("", c.help_text),
|
|
876
|
+
" ",
|
|
877
|
+
m(
|
|
878
|
+
"span.handle",
|
|
879
|
+
{ style: { padding: "0 0.5em", cursor: "grab" } },
|
|
880
|
+
"↕︎",
|
|
881
|
+
),
|
|
882
|
+
),
|
|
883
|
+
),
|
|
884
|
+
),
|
|
885
|
+
),
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// I'd like to replace this with some kind of <qdt-loading-container> (make a new package on npm) which is user-styleable
|
|
890
|
+
// Alternatively (or as part of that work), we could make use of <qnc-spinner-uppercase>
|
|
891
|
+
// https://tracker.quadrant.net/issues/271/
|
|
892
|
+
function render_with_loader(loading: boolean, content: m.Children) {
|
|
893
|
+
return m(
|
|
894
|
+
"div",
|
|
895
|
+
{
|
|
896
|
+
style: {
|
|
897
|
+
"--qdt-loading-spinner-size": "1em",
|
|
898
|
+
minWidth: "var(--qdt-loading-spinner-size)",
|
|
899
|
+
minHeight: "var(--qdt-loading-spinner-size)",
|
|
900
|
+
position: "relative",
|
|
901
|
+
isolation: "isolate", // force new stacking context
|
|
902
|
+
},
|
|
903
|
+
},
|
|
904
|
+
// content wrapper
|
|
905
|
+
m(
|
|
906
|
+
"div",
|
|
907
|
+
{
|
|
908
|
+
style: {
|
|
909
|
+
opacity: loading ? "50%" : "",
|
|
910
|
+
},
|
|
911
|
+
},
|
|
912
|
+
|
|
913
|
+
content,
|
|
914
|
+
),
|
|
915
|
+
// spinner
|
|
916
|
+
m("div", {
|
|
917
|
+
style: {
|
|
918
|
+
display: loading ? "block" : "none",
|
|
919
|
+
pointerEvents: "none",
|
|
920
|
+
zIndex: 2147483647,
|
|
921
|
+
position: "absolute",
|
|
922
|
+
top: "calc(50% - calc(var(--qdt-loading-spinner-size)/2))",
|
|
923
|
+
left: "calc(50% - calc(var(--qdt-loading-spinner-size)/2))",
|
|
924
|
+
borderRadius: "1000px",
|
|
925
|
+
border: "1px solid black",
|
|
926
|
+
borderWidth: "calc(var(--qdt-loading-spinner-size)/2)",
|
|
927
|
+
borderLeftColor: "white",
|
|
928
|
+
borderRightColor: "white",
|
|
929
|
+
opacity: "0.5",
|
|
930
|
+
animation: "qdt-loader-spin 1.2s linear infinite",
|
|
931
|
+
},
|
|
932
|
+
}),
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
function selection_controls(state: AppState<string>, updaters: Updaters) {
|
|
936
|
+
const page_ids = state.table_data.map(([pk, cells]) => pk);
|
|
937
|
+
const entire_page_selected = contains_all(
|
|
938
|
+
state.selected_pks,
|
|
939
|
+
new Set(page_ids),
|
|
940
|
+
);
|
|
941
|
+
const all_results_selected = contains_all(
|
|
942
|
+
state.selected_pks,
|
|
943
|
+
new Set(state.filtered_pks),
|
|
944
|
+
);
|
|
945
|
+
|
|
946
|
+
function clear_page() {
|
|
947
|
+
updaters.bulk_set_selected(page_ids, false);
|
|
948
|
+
}
|
|
949
|
+
function select_page() {
|
|
950
|
+
updaters.bulk_set_selected(page_ids, true);
|
|
951
|
+
}
|
|
952
|
+
function clear_table() {
|
|
953
|
+
updaters.bulk_set_selected(state.filtered_pks, false);
|
|
954
|
+
}
|
|
955
|
+
function select_table() {
|
|
956
|
+
updaters.bulk_set_selected(state.filtered_pks, true);
|
|
957
|
+
}
|
|
958
|
+
function clear_selection() {
|
|
959
|
+
updaters.bulk_set_selected(state.selected_pks, false);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
const selection_operators = [
|
|
963
|
+
entire_page_selected
|
|
964
|
+
? m(
|
|
965
|
+
"button",
|
|
966
|
+
{ onclick: clear_page, type: "button" },
|
|
967
|
+
"Deselect Current Page",
|
|
968
|
+
)
|
|
969
|
+
: m(
|
|
970
|
+
"button",
|
|
971
|
+
{ onclick: select_page, type: "button" },
|
|
972
|
+
"Select Current Page",
|
|
973
|
+
),
|
|
974
|
+
all_results_selected
|
|
975
|
+
? m(
|
|
976
|
+
"button",
|
|
977
|
+
{ onclick: clear_table, type: "button" },
|
|
978
|
+
"Deselect All Results",
|
|
979
|
+
)
|
|
980
|
+
: m(
|
|
981
|
+
"button",
|
|
982
|
+
{ onclick: select_table, type: "button" },
|
|
983
|
+
"Select All Results",
|
|
984
|
+
),
|
|
985
|
+
m(
|
|
986
|
+
"button",
|
|
987
|
+
{
|
|
988
|
+
onclick: clear_selection,
|
|
989
|
+
disabled: state.selected_pks.size == 0,
|
|
990
|
+
type: "button",
|
|
991
|
+
},
|
|
992
|
+
"Clear Selection",
|
|
993
|
+
),
|
|
994
|
+
];
|
|
995
|
+
return m("div.qdt-selection-controls", selection_operators);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
const contains_all = (Set.prototype as any).isSubsetOf
|
|
999
|
+
? function <U>(
|
|
1000
|
+
possible_superset: ReadonlySet<U>,
|
|
1001
|
+
possible_subset: ReadonlySet<U>,
|
|
1002
|
+
): boolean {
|
|
1003
|
+
// @ts-ignore
|
|
1004
|
+
return possible_subset.isSubsetOf(possible_superset);
|
|
1005
|
+
}
|
|
1006
|
+
: function <U>(
|
|
1007
|
+
possible_superset: ReadonlySet<U>,
|
|
1008
|
+
possible_subset: ReadonlySet<U>,
|
|
1009
|
+
): boolean {
|
|
1010
|
+
for (const value of possible_subset) {
|
|
1011
|
+
if (!possible_superset.has(value)) return false;
|
|
1012
|
+
}
|
|
1013
|
+
return true;
|
|
1014
|
+
};
|