@qnc/qnc_data_tables 1.0.4 → 1.0.7-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.
Files changed (68) hide show
  1. package/README.md +36 -0
  2. package/dist/bound_stored_value.d.ts +39 -0
  3. package/dist/bound_stored_value.js +134 -0
  4. package/dist/bound_stored_value.ts +158 -0
  5. package/dist/column_manager.d.ts +43 -0
  6. package/dist/column_manager.js +124 -0
  7. package/dist/column_manager.ts +158 -0
  8. package/dist/column_resizing.d.ts +3 -0
  9. package/dist/column_resizing.js +30 -0
  10. package/dist/column_resizing.ts +52 -0
  11. package/dist/column_sorting.d.ts +11 -0
  12. package/dist/column_sorting.js +53 -0
  13. package/dist/column_sorting.ts +63 -0
  14. package/dist/conditionally_wrapped_element.d.ts +5 -0
  15. package/dist/conditionally_wrapped_element.js +14 -0
  16. package/dist/conditionally_wrapped_element.ts +17 -0
  17. package/dist/create_mithril_app.d.ts +3 -0
  18. package/dist/create_mithril_app.js +25 -0
  19. package/dist/create_mithril_app.ts +35 -0
  20. package/dist/create_style.d.ts +4 -0
  21. package/dist/create_style.js +9 -0
  22. package/dist/create_style.ts +10 -0
  23. package/dist/custom_element.d.ts +23 -0
  24. package/dist/custom_element.js +63 -0
  25. package/dist/custom_element.ts +71 -0
  26. package/dist/event_names.d.ts +1 -0
  27. package/dist/event_names.js +1 -0
  28. package/dist/event_names.ts +1 -0
  29. package/dist/index.d.ts +5 -0
  30. package/dist/index.js +4 -0
  31. package/dist/index.ts +5 -0
  32. package/dist/mithril_view.d.ts +16 -0
  33. package/dist/mithril_view.js +484 -0
  34. package/dist/mithril_view.ts +1014 -0
  35. package/dist/optional_storage.d.ts +6 -0
  36. package/dist/optional_storage.js +18 -0
  37. package/dist/optional_storage.ts +23 -0
  38. package/dist/overflow_class_manager.d.ts +20 -0
  39. package/dist/overflow_class_manager.js +30 -0
  40. package/dist/overflow_class_manager.ts +40 -0
  41. package/dist/renderer.d.ts +16 -0
  42. package/dist/renderer.js +51 -0
  43. package/dist/renderer.ts +86 -0
  44. package/dist/selection_fieldset_controller.d.ts +9 -0
  45. package/dist/selection_fieldset_controller.js +85 -0
  46. package/dist/selection_fieldset_controller.ts +104 -0
  47. package/dist/state_machine.d.ts +67 -0
  48. package/dist/state_machine.js +316 -0
  49. package/dist/state_machine.ts +434 -0
  50. package/dist/state_types.d.ts +62 -0
  51. package/dist/state_types.js +1 -0
  52. package/dist/state_types.ts +84 -0
  53. package/dist/table_manager.d.ts +9 -0
  54. package/dist/table_manager.js +16 -0
  55. package/dist/table_manager.ts +28 -0
  56. package/dist/table_options.d.ts +164 -0
  57. package/dist/table_options.js +97 -0
  58. package/dist/table_options.ts +132 -0
  59. package/dist/tsconfig.tsbuildinfo +1 -1
  60. package/dist/watched_mutable_value.d.ts +15 -0
  61. package/dist/watched_mutable_value.js +23 -0
  62. package/dist/watched_mutable_value.ts +23 -0
  63. package/intermediate/qnc_data_tables_inline_css.d.ts +2 -0
  64. package/intermediate/qnc_data_tables_inline_css.js +8 -0
  65. package/package.json +14 -5
  66. package/dist/qnc_data_tables.d.ts +0 -57
  67. package/dist/qnc_data_tables.js +0 -1136
  68. package/dist/qnc_data_tables_inline_css.js +0 -8
@@ -0,0 +1 @@
1
+ export const SELECTION_CHANGE = "qnc-data-tables-selection-change";
@@ -0,0 +1,5 @@
1
+ export { define_custom_element } from "./custom_element.js";
2
+ export { Renderer } from "./renderer.js";
3
+ export type { TableOptions } from "./table_options.js";
4
+ export { create_style } from "./create_style.js";
5
+ export { register_selection_fieldset_controller_custom_element } from "./selection_fieldset_controller.js";
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { define_custom_element } from "./custom_element.js";
2
+ export { Renderer } from "./renderer.js";
3
+ export { create_style } from "./create_style.js";
4
+ export { register_selection_fieldset_controller_custom_element } from "./selection_fieldset_controller.js";
package/dist/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { define_custom_element } from "./custom_element.js";
2
+ export { Renderer } from "./renderer.js";
3
+ export type { TableOptions } from "./table_options.js";
4
+ export { create_style } from "./create_style.js";
5
+ export { register_selection_fieldset_controller_custom_element } from "./selection_fieldset_controller.js";
@@ -0,0 +1,16 @@
1
+ import m from "mithril";
2
+ import { AppState } from "./state_types.js";
3
+ import { Renderer } from "./renderer.js";
4
+ type Updaters = {
5
+ set_page_size: (page_size: number) => void;
6
+ set_page_number: (page_number: number) => void;
7
+ set_column_enabled: (column_key: string, enabled: boolean) => void;
8
+ sort_by_function: (sort_function_key: string) => void;
9
+ sort_by_column: (column_key: string, direction: "forward" | "reverse") => void;
10
+ set_column_width: (column_key: string, width: number) => void;
11
+ set_column_order: (keys: string[]) => void;
12
+ set_selected: (pk: string, selected: boolean) => void;
13
+ bulk_set_selected: (pks: Iterable<string>, selected: boolean) => void;
14
+ };
15
+ export declare function app_view(state: AppState<string>, renderer: Renderer, updaters: Updaters): (m.Vnode<any, any> | m.Vnode<any, any>[])[];
16
+ export {};
@@ -0,0 +1,484 @@
1
+ import m, { redraw, trust } from "mithril";
2
+ import { enable } from "@qnc/drag_sorter";
3
+ import { begin_header_drag } from "./column_resizing.js";
4
+ function classes(...values) {
5
+ return values.filter(Boolean).join(" ");
6
+ }
7
+ export function app_view(state, renderer, updaters) {
8
+ return [
9
+ m("style", state.row_width_style_string.get()),
10
+ top_control_bar({
11
+ current_page: state.page_number,
12
+ set_page: updaters.set_page_number,
13
+ total_pages: state.accessible_result_count !== null
14
+ ? Math.ceil(state.accessible_result_count / state.page_size)
15
+ : null,
16
+ page_size: state.page_size,
17
+ set_page_size: updaters.set_page_size,
18
+ result_count: state.result_count,
19
+ result_count_loading: state.fetching_result_count,
20
+ accessible_result_count: state.accessible_result_count,
21
+ extra_sort_functions: state.extra_sort_functions,
22
+ sort_by_function: updaters.sort_by_function,
23
+ current_sort: state.current_sort,
24
+ }),
25
+ column_controls(state.columns, updaters, renderer),
26
+ m("form", [
27
+ render_with_loader(state.fetching_page_data, [
28
+ table(state.columns, updaters, state.table_data, renderer, state.sort_state_interpreter, state.fill_last_page, state.page_size, state.table_key, state.include_selection_column, state.table_overflows_left, state.table_overflows_right, state.row_width_style_string, state.selected_pks),
29
+ state.include_selection_column &&
30
+ selection_controls(state, updaters),
31
+ ]),
32
+ ]),
33
+ ];
34
+ }
35
+ function table(columns, updaters, data, renderer, sort_state_interpreter, fill_last_page, page_size, table_key, include_selection_column, table_overflows_left, table_overflows_right, row_width_style_string, selected_pks) {
36
+ return m("div", {
37
+ class: classes(`qnc-data-table qnc-data-table-${CSS.escape(table_key)}`, table_overflows_left.get() && "qdt-overflows-left", table_overflows_right.get() && "qdt-overflows-right"),
38
+ role: "table",
39
+ // style: all_columns().map(c=>`${column_width_property_name(c.key)}: ${c.width}px`).join(';')
40
+ }, m("div", {
41
+ class: "qnc-data-table-head",
42
+ role: "rowgroup",
43
+ }, header_row({
44
+ columns,
45
+ updaters,
46
+ sort_state_interpreter,
47
+ table_key,
48
+ include_selection_column,
49
+ renderer,
50
+ table_overflows_left,
51
+ table_overflows_right,
52
+ row_width_style_string,
53
+ })), m("div", {
54
+ class: "qnc-data-table-body",
55
+ role: "rowgroup",
56
+ }, table_rows(columns, data, fill_last_page, page_size, table_key, include_selection_column, updaters, selected_pks)));
57
+ }
58
+ function HeaderComponent(initialVnode) {
59
+ let sticky_style_string = "";
60
+ function setup_detector(detector, watched_value) {
61
+ const table = detector.closest(".qnc-data-table");
62
+ if (!table)
63
+ throw new Error("could not find parent table element");
64
+ new IntersectionObserver(function (entries, observer) {
65
+ var _a;
66
+ watched_value.set(!((_a = entries[0]) === null || _a === void 0 ? void 0 : _a.isIntersecting));
67
+ }, {
68
+ root: table,
69
+ }).observe(detector);
70
+ }
71
+ // Note: layout tightlty coupled to recompute_sticky_style_string
72
+ function raw_header_row(columns, updaters, renderer, sort_state_interpreter, table_key, table_overflows_left, table_overflows_right, include_selection_column) {
73
+ return m("div", {
74
+ role: "row",
75
+ className: `qnc-data-table-row qnc-data-table-header-row qnc-data-table-row qnc-data-table-row-${CSS.escape(table_key)}`,
76
+ },
77
+ // "overflow-left detector"
78
+ m("div", {
79
+ role: "columnheader",
80
+ style: "padding: 0; width: 0; border: none;",
81
+ }, m("div", {
82
+ style: "width: 1px; height: 1px; background-color: transparent; pointerEvents: none;",
83
+ oncreate: (vnode) => {
84
+ setup_detector(vnode.dom, table_overflows_left);
85
+ },
86
+ })), include_selection_column && empty_checkbox_cell(table_key), columns.fixed_enabled_columns.map((c) => header_cell(renderer, c, sort_state_interpreter, updaters, true, table_key)), m("div", {
87
+ role: "columnheader",
88
+ class: `qdt-overflow-indicator-cell qdt-overflow-indicator-cell-left qnc-data-table-fixed-cell-${CSS.escape(table_key)}`,
89
+ }), columns.sortable_enabled_columns.map((c) => header_cell(renderer, c, sort_state_interpreter, updaters, false, table_key)), empty_expanding_cell("columnheader"), m("div", {
90
+ role: "columnheader",
91
+ class: `qdt-overflow-indicator-cell qdt-overflow-indicator-cell-right`,
92
+ }),
93
+ // "overflow-right detector"
94
+ m("div", {
95
+ role: "columnheader",
96
+ style: "padding: 0; width: 0; border: none; position: relative;",
97
+ }, m("div", {
98
+ style: "position: absolute; right: 0; width: 1px; height: 1px; background-color: transparent; pointerEvents: none;",
99
+ oncreate: (vnode) => {
100
+ setup_detector(vnode.dom, table_overflows_right);
101
+ },
102
+ })));
103
+ }
104
+ // Note: tightlty coupled to raw_header_row layout
105
+ function recompute_sticky_style_string(header_element, include_selection_column, columns, table_key) {
106
+ const old_value = sticky_style_string;
107
+ sticky_style_string = "";
108
+ const fixed_column_count = (include_selection_column ? 1 : 0) +
109
+ columns.fixed_enabled_columns.length +
110
+ 1; // the "sticky divider" column, where box-shadow is rendered
111
+ let previous_width = 0;
112
+ const start_index = 1; // skip over the "scroll detector" column
113
+ let i = start_index; // i will range over "element index" of all fixed column headers
114
+ const end_index = start_index +
115
+ fixed_column_count + // all fixed columns
116
+ 1; // left overflow indicator cell
117
+ while (i < end_index) {
118
+ const cell = header_element.children[i];
119
+ if (!cell)
120
+ break;
121
+ sticky_style_string += `.qnc-data-table-fixed-cell-${CSS.escape(table_key)}:nth-child(${i + 1}) {
122
+ position: sticky;
123
+ left: ${previous_width}px;
124
+ z-index: 2;
125
+ }`;
126
+ previous_width += cell.clientWidth;
127
+ i++;
128
+ }
129
+ return sticky_style_string != old_value;
130
+ }
131
+ /*
132
+ Rows need explicit min-width.
133
+ Otherwise, they are only as wide as table, and cells overflow.
134
+ This causes problems if:
135
+ 1. there are any background colours/borders on rows
136
+ 2. there are sticky columns, and row content is more than two table-widths wide (eventually, the sticky columns get pushed out view)
137
+
138
+ #1. can be solved by putting all colours/borders on cells, rather than rows, but issue #2 still requires fixed row widths.
139
+
140
+ 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.
141
+
142
+ 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).
143
+ */
144
+ function get_row_width_style(header, table_key) {
145
+ let row_width = 0;
146
+ let cell = header.firstElementChild;
147
+ while (cell) {
148
+ // skip the "empty expanding column"; otherwise, row width will never shrink, even when it should
149
+ if (!cell.classList.contains("qdt-empty-expanding-cell")) {
150
+ row_width += cell.clientWidth;
151
+ }
152
+ cell = cell.nextElementSibling;
153
+ }
154
+ return `.qnc-data-table-row-${CSS.escape(table_key)} {min-width: ${row_width}px;}`;
155
+ }
156
+ return {
157
+ oncreate(vnode) {
158
+ const { columns, include_selection_column, table_key, row_width_style_string, } = vnode.attrs;
159
+ row_width_style_string.set(get_row_width_style(vnode.dom, table_key));
160
+ if (recompute_sticky_style_string(vnode.dom, include_selection_column, columns, table_key))
161
+ redraw();
162
+ },
163
+ onupdate(vnode) {
164
+ const { columns, include_selection_column, table_key, row_width_style_string, } = vnode.attrs;
165
+ row_width_style_string.set(get_row_width_style(vnode.dom, table_key));
166
+ // Optimization opportunity: we _could_ hash (column order, column widths, include_selection_count)
167
+ // and return early whenever unchanged
168
+ if (recompute_sticky_style_string(vnode.dom, include_selection_column, columns, table_key))
169
+ redraw();
170
+ },
171
+ view(vnode) {
172
+ const { columns, updaters, renderer, sort_state_interpreter, table_key, table_overflows_left, table_overflows_right, include_selection_column, } = vnode.attrs;
173
+ return [
174
+ raw_header_row(columns, updaters, renderer, sort_state_interpreter, table_key, table_overflows_left, table_overflows_right, include_selection_column),
175
+ m("style", sticky_style_string),
176
+ ];
177
+ },
178
+ };
179
+ }
180
+ function header_row(attrs) {
181
+ return m(HeaderComponent, attrs);
182
+ }
183
+ function header_cell(renderer, column, sort_state_interpreter, updaters, fixed, table_key) {
184
+ const sort_direction = sort_state_interpreter(column.key);
185
+ return m("div", {
186
+ role: "columnheader",
187
+ style: {
188
+ width: column.width + "px",
189
+ },
190
+ class: classes("qdt-cell", "qdt-resizable-header", fixed && `qnc-data-table-fixed-cell-${CSS.escape(table_key)}`),
191
+ }, m("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)
192
+ renderer.render_header({
193
+ label: renderer.render_with_optional_help_text(column.display, column.help_text),
194
+ sortable: column.sortable,
195
+ sorted: sort_direction || "no",
196
+ set_sort: function (direction) {
197
+ updaters.sort_by_column(column.key, direction);
198
+ },
199
+ })), m("span", {
200
+ onmousedown: (e) => {
201
+ e.preventDefault(); // prevent text highlighting
202
+ begin_header_drag(e, (width) => {
203
+ updaters.set_column_width(column.key, width);
204
+ m.redraw();
205
+ }, column.width, () => {
206
+ m.redraw();
207
+ });
208
+ },
209
+ class: "qdt-column-resizer",
210
+ }));
211
+ }
212
+ function table_rows(columns, data, fill_last_page, page_size, table_key, include_selection_column, updaters, selected_pks) {
213
+ return [
214
+ data.map((row) => table_row(row, columns, table_key, include_selection_column
215
+ ? checkbox_cell(row[0], updaters, selected_pks, table_key)
216
+ : null)),
217
+ fill_last_page &&
218
+ Array.from(Array(Math.max(0, page_size - data.length))).map((x) => raw_table_row(columns.fixed_enabled_columns.map((c) => data_cell(c, null, true, table_key)), columns.sortable_enabled_columns.map((c) => data_cell(c, null, false, table_key)), table_key, null)),
219
+ ];
220
+ }
221
+ function raw_table_row(fixed_cells, sortable_cells, table_key, checkbox_cell) {
222
+ return m("div", {
223
+ role: "row",
224
+ className: `qnc-data-table-row qnc-data-table-body-row qnc-data-table-row qnc-data-table-row-${CSS.escape(table_key)}`,
225
+ },
226
+ // Empty "scroll detector" column
227
+ m("div", {
228
+ role: "cell",
229
+ style: "width: 0px; padding: 0; border: none;",
230
+ }), checkbox_cell,
231
+ // include_selection_column && checkbox_cell(pk, updaters, selected_pks),
232
+ fixed_cells, m("div", {
233
+ role: "cell",
234
+ class: `qdt-overflow-indicator-cell qdt-overflow-indicator-cell-left qnc-data-table-fixed-cell-${CSS.escape(table_key)}`,
235
+ }), sortable_cells, empty_expanding_cell(), m("div", {
236
+ role: "cell",
237
+ class: `qdt-overflow-indicator-cell qdt-overflow-indicator-cell-right`,
238
+ }),
239
+ // Empty "overflow detector" column
240
+ m("div", {
241
+ role: "cell",
242
+ style: "width: 0px; padding: 0; border: none;",
243
+ }));
244
+ }
245
+ // #coupled-cell-functions: these functions are tightly coupled. TODO: move to a separate module
246
+ function data_cell(column, content, fixed, table_key) {
247
+ return m("div", {
248
+ role: "cell",
249
+ style: {
250
+ width: column.width + "px",
251
+ },
252
+ class: classes("qdt-cell", fixed && `qnc-data-table-fixed-cell-${CSS.escape(table_key)}`),
253
+ }, content);
254
+ }
255
+ // Note: layout tightly coupled to HeaderRow
256
+ function table_row(row, columns, table_key, checkbox_cell) {
257
+ const [id, cell_map] = row;
258
+ return raw_table_row(columns.fixed_enabled_columns.map((c) => data_cell(c, trust(cell_map.get(c.key) || ""), true, table_key)), columns.sortable_enabled_columns.map((c) => data_cell(c, trust(cell_map.get(c.key) || ""), false, table_key)), table_key, checkbox_cell);
259
+ }
260
+ // #coupled-cell-functions: these functions are tightly coupled. TODO: move to a separate module
261
+ function empty_checkbox_cell(table_key) {
262
+ return m("div", {
263
+ role: "columnheader",
264
+ style: { width: "auto" }, // This is the only auto-width column; it works because every row has the same content
265
+ className: `qdt-cell qnc-data-table-fixed-cell-${CSS.escape(table_key)}`,
266
+ },
267
+ // Render an invisible checkbox, so this header cell takes up the same width as the rest of the checkbox cells
268
+ m("label", { className: "qdt-row-checkbox-label" }, m("input", {
269
+ type: "checkbox",
270
+ style: {
271
+ visibility: "hidden",
272
+ pointerEvents: "none",
273
+ },
274
+ tabIndex: -1,
275
+ })));
276
+ }
277
+ // #coupled-cell-functions: these functions are tightly coupled. TODO: move to a separate module
278
+ function checkbox_cell(pk, updaters, selected_pks, table_key) {
279
+ return m("div", {
280
+ role: "cell",
281
+ style: { width: "auto" }, // This is the only auto-width column; it works because every row has the same content
282
+ className: `qdt-cell qnc-data-table-fixed-cell-${CSS.escape(table_key)}`,
283
+ }, m("label", { className: "qdt-row-checkbox-label" }, m("input", {
284
+ type: "checkbox",
285
+ checked: selected_pks.has(pk),
286
+ onchange: (e) => {
287
+ updaters.set_selected(pk, !selected_pks.has(pk));
288
+ // TODO: handle shift+clicking, like django_data_tables did
289
+ },
290
+ })));
291
+ }
292
+ function empty_expanding_cell(role = "cell") {
293
+ return m("div", {
294
+ style: { flexGrow: "1", padding: "0", flexBasis: "0px" },
295
+ class: "qdt-empty-expanding-cell",
296
+ // Just in case users are using this in their selector for default cell background/border
297
+ role: role,
298
+ });
299
+ }
300
+ function extra_sort_function_widget(current_sort, extra_sort_functions, sort_by_function) {
301
+ // TODO: update checkbox-dropdown to work with radio buttons, publish on npm, document that it should always be included as a peer dependency
302
+ // use that, so we can make help text smaller, and not show in "summary"
303
+ const current_function_key = current_sort &&
304
+ current_sort.type == "function" &&
305
+ current_sort.function_key;
306
+ return m("label", "sort by: ", m("select", {
307
+ onchange: (e) => {
308
+ const key = e.target.value;
309
+ if (key)
310
+ sort_by_function(key);
311
+ },
312
+ }, current_sort == null && m("option", "-----"), current_sort &&
313
+ current_sort.type == "column" &&
314
+ m("option", "(selected column)"), extra_sort_functions.map((esf) => m("option", {
315
+ selected: current_function_key == esf.key,
316
+ value: esf.key,
317
+ }, esf.display, esf.help_text && ` (${esf.help_text})`))));
318
+ }
319
+ function top_control_bar(options) {
320
+ function space_horizontally(gap, items) {
321
+ return m("div", { style: { overflow: "hidden" } }, m("div", { style: { display: "flex", marginRight: "-" + gap } }, items.map((item) => m("div", { style: { marginRight: gap } }, item, " "))));
322
+ }
323
+ const { current_page, set_page, total_pages, page_size, set_page_size, result_count, result_count_loading, accessible_result_count, } = options;
324
+ return [
325
+ space_horizontally("2em", [
326
+ // Paginator
327
+ [
328
+ m("button", {
329
+ type: "button",
330
+ disabled: current_page <= 1,
331
+ onclick: (e) => set_page(current_page - 1),
332
+ }, "<"),
333
+ " Page ",
334
+ m("input", {
335
+ type: "number",
336
+ value: current_page,
337
+ max: total_pages,
338
+ style: { width: "3.5em" },
339
+ onchange: function (e) {
340
+ set_page(parseInt(e.target.value));
341
+ },
342
+ }),
343
+ " of ",
344
+ total_pages !== null && total_pages !== void 0 ? total_pages : "?",
345
+ " ",
346
+ m("button", {
347
+ type: "button",
348
+ disabled: current_page >= (total_pages !== null && total_pages !== void 0 ? total_pages : 1),
349
+ onclick: (e) => set_page(current_page + 1),
350
+ }, ">"),
351
+ ],
352
+ // Results per page
353
+ m("label", m("input", {
354
+ value: page_size,
355
+ type: "number",
356
+ style: { width: "3.5em" },
357
+ onchange: function (e) {
358
+ set_page_size(parseInt(e.target.value));
359
+ },
360
+ }), " results per page"),
361
+ // Result count
362
+ render_with_loader(result_count_loading, m("span", m("b", result_count), " result(s) total.")),
363
+ options.extra_sort_functions.length &&
364
+ extra_sort_function_widget(options.current_sort, options.extra_sort_functions, options.sort_by_function),
365
+ ]),
366
+ m("div", accessible_result_count !== null &&
367
+ result_count !== null &&
368
+ accessible_result_count < result_count &&
369
+ m("div", { style: { color: "red", fontWeight: "bold" } }, m("sup", "*"), "The result table is limited to the first ", accessible_result_count, " results.")),
370
+ ];
371
+ }
372
+ function column_controls(columns, updaters, renderer) {
373
+ return m("details", { className: "qdt-column-details" }, m("summary", "Columns"), m("ol", {
374
+ oncreate: (vnode) => enable(vnode.dom, {
375
+ onchange: (container) => {
376
+ updaters.set_column_order(Array.from(container.children).map(function (li) {
377
+ if (!(li instanceof HTMLElement))
378
+ throw new Error(`unexpected child: ${li}`);
379
+ const value = li.dataset.key;
380
+ if (typeof value == "undefined")
381
+ throw new Error("column li missing [data-key]");
382
+ return value;
383
+ }));
384
+ },
385
+ stationary_list_item_markers: true,
386
+ handle_selector: ".handle",
387
+ }),
388
+ }, columns.all_sortable_columns.map((c) => m("li", {
389
+ "data-key": c.key,
390
+ key: c.key,
391
+ className: "qnc-data-table-column-item",
392
+ }, m("label", m("input", {
393
+ type: "checkbox",
394
+ checked: c.enabled,
395
+ onclick: (e) => updaters.set_column_enabled(c.key, !c.enabled),
396
+ }), " ", c.display, " ",
397
+ // don't wrap c.display in help text, because it's part of the drag handle
398
+ c.help_text &&
399
+ renderer.render_with_help_text("", c.help_text), " ", m("span.handle", { style: { padding: "0 0.5em", cursor: "grab" } }, "↕︎"))))));
400
+ }
401
+ // I'd like to replace this with some kind of <qdt-loading-container> (make a new package on npm) which is user-styleable
402
+ // Alternatively (or as part of that work), we could make use of <qnc-spinner-uppercase>
403
+ // https://tracker.quadrant.net/issues/271/
404
+ function render_with_loader(loading, content) {
405
+ return m("div", {
406
+ style: {
407
+ "--qdt-loading-spinner-size": "1em",
408
+ minWidth: "var(--qdt-loading-spinner-size)",
409
+ minHeight: "var(--qdt-loading-spinner-size)",
410
+ position: "relative",
411
+ isolation: "isolate", // force new stacking context
412
+ },
413
+ },
414
+ // content wrapper
415
+ m("div", {
416
+ style: {
417
+ opacity: loading ? "50%" : "",
418
+ },
419
+ }, content),
420
+ // spinner
421
+ m("div", {
422
+ style: {
423
+ display: loading ? "block" : "none",
424
+ pointerEvents: "none",
425
+ zIndex: 2147483647,
426
+ position: "absolute",
427
+ top: "calc(50% - calc(var(--qdt-loading-spinner-size)/2))",
428
+ left: "calc(50% - calc(var(--qdt-loading-spinner-size)/2))",
429
+ borderRadius: "1000px",
430
+ border: "1px solid black",
431
+ borderWidth: "calc(var(--qdt-loading-spinner-size)/2)",
432
+ borderLeftColor: "white",
433
+ borderRightColor: "white",
434
+ opacity: "0.5",
435
+ animation: "qdt-loader-spin 1.2s linear infinite",
436
+ },
437
+ }));
438
+ }
439
+ function selection_controls(state, updaters) {
440
+ const page_ids = state.table_data.map(([pk, cells]) => pk);
441
+ const entire_page_selected = contains_all(state.selected_pks, new Set(page_ids));
442
+ const all_results_selected = contains_all(state.selected_pks, new Set(state.filtered_pks));
443
+ function clear_page() {
444
+ updaters.bulk_set_selected(page_ids, false);
445
+ }
446
+ function select_page() {
447
+ updaters.bulk_set_selected(page_ids, true);
448
+ }
449
+ function clear_table() {
450
+ updaters.bulk_set_selected(state.filtered_pks, false);
451
+ }
452
+ function select_table() {
453
+ updaters.bulk_set_selected(state.filtered_pks, true);
454
+ }
455
+ function clear_selection() {
456
+ updaters.bulk_set_selected(state.selected_pks, false);
457
+ }
458
+ const selection_operators = [
459
+ entire_page_selected
460
+ ? m("button", { onclick: clear_page, type: "button" }, "Deselect Current Page")
461
+ : m("button", { onclick: select_page, type: "button" }, "Select Current Page"),
462
+ all_results_selected
463
+ ? m("button", { onclick: clear_table, type: "button" }, "Deselect All Results")
464
+ : m("button", { onclick: select_table, type: "button" }, "Select All Results"),
465
+ m("button", {
466
+ onclick: clear_selection,
467
+ disabled: state.selected_pks.size == 0,
468
+ type: "button",
469
+ }, "Clear Selection"),
470
+ ];
471
+ return m("div.qdt-selection-controls", selection_operators);
472
+ }
473
+ const contains_all = Set.prototype.isSubsetOf
474
+ ? function (possible_superset, possible_subset) {
475
+ // @ts-ignore
476
+ return possible_subset.isSubsetOf(possible_superset);
477
+ }
478
+ : function (possible_superset, possible_subset) {
479
+ for (const value of possible_subset) {
480
+ if (!possible_superset.has(value))
481
+ return false;
482
+ }
483
+ return true;
484
+ };