@apia/table2-controller 4.0.20
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/dist/index.d.ts +330 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +903 -0
- package/dist/index.js.map +1 -0
- package/package.json +38 -0
- package/readme.md +3 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,903 @@
|
|
|
1
|
+
import { makeObservable, action, observable, computed } from 'mobx';
|
|
2
|
+
import { EventEmitter, useMount, scrollParentIntoElement, disableChildrenFocus, getFocusSelector } from '@apia/util';
|
|
3
|
+
import { useRef } from 'react';
|
|
4
|
+
|
|
5
|
+
var __defProp$4 = Object.defineProperty;
|
|
6
|
+
var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
7
|
+
var __publicField$4 = (obj, key, value) => {
|
|
8
|
+
__defNormalProp$4(obj, key + "" , value);
|
|
9
|
+
return value;
|
|
10
|
+
};
|
|
11
|
+
const voidTable = {};
|
|
12
|
+
class Element {
|
|
13
|
+
constructor(state) {
|
|
14
|
+
this.state = state;
|
|
15
|
+
__publicField$4(this, "table", voidTable);
|
|
16
|
+
makeObservable(this, {
|
|
17
|
+
state: observable,
|
|
18
|
+
setState: action
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
getTable() {
|
|
22
|
+
return this.table;
|
|
23
|
+
}
|
|
24
|
+
setTable(table) {
|
|
25
|
+
this.table = table || voidTable;
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
getState(prop) {
|
|
29
|
+
return this.state[prop];
|
|
30
|
+
}
|
|
31
|
+
setState(prop, value) {
|
|
32
|
+
if (typeof prop === "function") {
|
|
33
|
+
this.state = prop(this.state);
|
|
34
|
+
} else {
|
|
35
|
+
this.state[prop] = value;
|
|
36
|
+
}
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
var __defProp$3 = Object.defineProperty;
|
|
42
|
+
var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
43
|
+
var __publicField$3 = (obj, key, value) => {
|
|
44
|
+
__defNormalProp$3(obj, key + "" , value);
|
|
45
|
+
return value;
|
|
46
|
+
};
|
|
47
|
+
class Cell extends Element {
|
|
48
|
+
constructor() {
|
|
49
|
+
super(...arguments);
|
|
50
|
+
__publicField$3(this, "parentRow", {});
|
|
51
|
+
}
|
|
52
|
+
getIndex() {
|
|
53
|
+
return this.getRow().getCellIndex(this);
|
|
54
|
+
}
|
|
55
|
+
getRow() {
|
|
56
|
+
return this.parentRow;
|
|
57
|
+
}
|
|
58
|
+
setRow(row) {
|
|
59
|
+
this.parentRow = row;
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
*
|
|
64
|
+
* @param doFocus - If true, the cell will be focused after being set as the focused cell.
|
|
65
|
+
* If false, the cell will contain the tabIndex = 0 but will not be focused.
|
|
66
|
+
*/
|
|
67
|
+
focus(doFocus = true) {
|
|
68
|
+
this.table.setFocusedCell(this, doFocus);
|
|
69
|
+
}
|
|
70
|
+
updateState(state) {
|
|
71
|
+
this.state = { ...this.state, ...state };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
var __defProp$2 = Object.defineProperty;
|
|
76
|
+
var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
77
|
+
var __publicField$2 = (obj, key, value) => {
|
|
78
|
+
__defNormalProp$2(obj, key + "" , value);
|
|
79
|
+
return value;
|
|
80
|
+
};
|
|
81
|
+
class Row extends Element {
|
|
82
|
+
constructor(id, properties) {
|
|
83
|
+
const { cells, isSelected, ...state } = properties || {};
|
|
84
|
+
super({ id, ...state, cells: cells || [] });
|
|
85
|
+
this.properties = properties;
|
|
86
|
+
__publicField$2(this, "container", {});
|
|
87
|
+
makeObservable(this, {
|
|
88
|
+
isSelected: computed,
|
|
89
|
+
index: computed
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
addCell(cell) {
|
|
93
|
+
this.state.cells.push(cell);
|
|
94
|
+
cell.setTable(this.table);
|
|
95
|
+
cell.setRow(this);
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
addCellAfter(anchor, newElement) {
|
|
99
|
+
const index = this.state.cells.indexOf(anchor) + 1;
|
|
100
|
+
if (index !== -1) {
|
|
101
|
+
newElement.setTable(this.table);
|
|
102
|
+
newElement.setRow(this);
|
|
103
|
+
this.state.cells.splice(index, 0, newElement);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
addCellBefore(anchor, newElement) {
|
|
107
|
+
const index = this.state.cells.indexOf(anchor);
|
|
108
|
+
if (index !== -1) {
|
|
109
|
+
newElement.setTable(this.table);
|
|
110
|
+
newElement.setRow(this);
|
|
111
|
+
this.state.cells.splice(index, 0, newElement);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
build(data) {
|
|
115
|
+
for (const cell of data.cells) {
|
|
116
|
+
const cellInstance = new Cell(cell);
|
|
117
|
+
this.addCell(cellInstance);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
get cells() {
|
|
121
|
+
return this.state.cells;
|
|
122
|
+
}
|
|
123
|
+
get isSelected() {
|
|
124
|
+
return this.table.isSelected(this);
|
|
125
|
+
}
|
|
126
|
+
getCellByColumnName(colName) {
|
|
127
|
+
return this.cells.find((c) => c.getState("colName") === colName);
|
|
128
|
+
}
|
|
129
|
+
getCellByIndex(index) {
|
|
130
|
+
return this.cells[index];
|
|
131
|
+
}
|
|
132
|
+
getCellIndex(cell) {
|
|
133
|
+
return this.cells.indexOf(cell);
|
|
134
|
+
}
|
|
135
|
+
getContainer() {
|
|
136
|
+
return this.container;
|
|
137
|
+
}
|
|
138
|
+
getFollowingRow() {
|
|
139
|
+
const containerRows = [...this.container.getState("rows").values()];
|
|
140
|
+
const index = containerRows.findIndex(
|
|
141
|
+
(r) => r.getState("id") === this.state.id
|
|
142
|
+
);
|
|
143
|
+
return containerRows[index + 1] || null;
|
|
144
|
+
}
|
|
145
|
+
get index() {
|
|
146
|
+
return this.getContainer().getRowIndex(this);
|
|
147
|
+
}
|
|
148
|
+
getPreviousRow() {
|
|
149
|
+
const containerRows = [...this.container.getState("rows").values()];
|
|
150
|
+
const index = containerRows.findIndex(
|
|
151
|
+
(r) => r.getState("id") === this.state.id
|
|
152
|
+
);
|
|
153
|
+
return containerRows[index - 1] || null;
|
|
154
|
+
}
|
|
155
|
+
insertAfter(row) {
|
|
156
|
+
this.getContainer()?.insertAfter(this, row);
|
|
157
|
+
}
|
|
158
|
+
insertBefore(row) {
|
|
159
|
+
this.getContainer()?.insertBefore(this, row);
|
|
160
|
+
}
|
|
161
|
+
remove() {
|
|
162
|
+
this.getContainer()?.removeRow(this);
|
|
163
|
+
}
|
|
164
|
+
scrollIntoView() {
|
|
165
|
+
this.table.scrollRowIntoView(this);
|
|
166
|
+
}
|
|
167
|
+
setContainer(container) {
|
|
168
|
+
this.container = container;
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
171
|
+
setTable(table) {
|
|
172
|
+
super.setTable(table);
|
|
173
|
+
this.state.cells.forEach((c) => c.setTable(table));
|
|
174
|
+
if (table && this.properties?.isSelected) {
|
|
175
|
+
table.toggleRowSelection(this, true);
|
|
176
|
+
}
|
|
177
|
+
return this;
|
|
178
|
+
}
|
|
179
|
+
swap(withRow) {
|
|
180
|
+
this.getContainer()?.swapRows(this, withRow);
|
|
181
|
+
}
|
|
182
|
+
toggleSelection(forceState) {
|
|
183
|
+
this.table.toggleRowSelection(this, forceState);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
class RowsGroup extends Element {
|
|
188
|
+
constructor(table, state) {
|
|
189
|
+
super({ ...state, rows: [] });
|
|
190
|
+
this.table = table;
|
|
191
|
+
makeObservable(this, { swapRows: action, rows: computed });
|
|
192
|
+
}
|
|
193
|
+
get rows() {
|
|
194
|
+
return this.state.rows;
|
|
195
|
+
}
|
|
196
|
+
addRow(row) {
|
|
197
|
+
this.state.rows.push(row);
|
|
198
|
+
row.setTable(this.table);
|
|
199
|
+
row.setContainer(this);
|
|
200
|
+
this.runAddRowPlugin(row);
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
addRows(rows) {
|
|
204
|
+
rows.forEach(this.addRow.bind(this));
|
|
205
|
+
return this;
|
|
206
|
+
}
|
|
207
|
+
clear() {
|
|
208
|
+
this.state.rows.forEach((row) => {
|
|
209
|
+
row.setContainer(null);
|
|
210
|
+
row.setTable(null);
|
|
211
|
+
});
|
|
212
|
+
return this.rows.splice(0, this.rows.length);
|
|
213
|
+
}
|
|
214
|
+
getRowById(id) {
|
|
215
|
+
return this.state.rows.find((c) => c.getState("id") === id);
|
|
216
|
+
}
|
|
217
|
+
getRowByIndex(index) {
|
|
218
|
+
return this.state.rows[index];
|
|
219
|
+
}
|
|
220
|
+
getRowIndex(row) {
|
|
221
|
+
return this.state.rows.indexOf(row);
|
|
222
|
+
}
|
|
223
|
+
insertAfter(anchor, newElement) {
|
|
224
|
+
if (anchor.getContainer() === this) {
|
|
225
|
+
const index = this.state.rows.indexOf(anchor);
|
|
226
|
+
this.state.rows.splice(index + 1, 0, newElement);
|
|
227
|
+
newElement.setTable(this.table);
|
|
228
|
+
newElement.setContainer(this);
|
|
229
|
+
this.runAddRowPlugin(newElement);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
insertBefore(anchor, newElement) {
|
|
233
|
+
if (anchor.getContainer() === this) {
|
|
234
|
+
const index = this.state.rows.indexOf(anchor);
|
|
235
|
+
this.state.rows.splice(index, 0, newElement);
|
|
236
|
+
newElement.setTable(this.table);
|
|
237
|
+
newElement.setContainer(this);
|
|
238
|
+
this.runAddRowPlugin(newElement);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
removeRow(row) {
|
|
242
|
+
if (row.getContainer() === this) {
|
|
243
|
+
row.setContainer(null);
|
|
244
|
+
this.state.rows = this.state.rows.filter((r) => r !== row);
|
|
245
|
+
this.table.removeRow(row);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
swapRows(a, b) {
|
|
249
|
+
if (a?.getContainer() === this && b?.getContainer() === this) {
|
|
250
|
+
const aIndex = this.state.rows.findIndex(
|
|
251
|
+
(r) => r.getState?.("id") === a.getState?.("id")
|
|
252
|
+
);
|
|
253
|
+
const bIndex = this.state.rows.findIndex(
|
|
254
|
+
(r) => r.getState?.("id") === b.getState?.("id")
|
|
255
|
+
);
|
|
256
|
+
if (aIndex !== -1 && bIndex !== -1) {
|
|
257
|
+
this.state.rows[aIndex] = b;
|
|
258
|
+
this.state.rows[bIndex] = a;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
runAddRowPlugin(newRow) {
|
|
263
|
+
const plugins = this.getTable().getApplicablePlugins("onAddRow");
|
|
264
|
+
plugins.forEach((c) => c(this, newRow));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
class TableBody extends RowsGroup {
|
|
269
|
+
constructor(table) {
|
|
270
|
+
super(table, { rows: [] });
|
|
271
|
+
}
|
|
272
|
+
build(data) {
|
|
273
|
+
for (const row of data.rows) {
|
|
274
|
+
const bodyRow = new Row(row.id);
|
|
275
|
+
this.addRow(bodyRow);
|
|
276
|
+
bodyRow.build(row);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
class TableHead extends RowsGroup {
|
|
282
|
+
constructor(table) {
|
|
283
|
+
super(table, { rows: [] });
|
|
284
|
+
}
|
|
285
|
+
build(data) {
|
|
286
|
+
this.state.rows = [];
|
|
287
|
+
const headerRow = new Row("header");
|
|
288
|
+
this.addRow(headerRow);
|
|
289
|
+
for (const column of data.columns) {
|
|
290
|
+
const headerCell = new Cell(column);
|
|
291
|
+
headerRow.addCell(headerCell);
|
|
292
|
+
}
|
|
293
|
+
if (data.filters.length) {
|
|
294
|
+
const filtersRow = new Row("filters");
|
|
295
|
+
filtersRow.setState((s) => ({
|
|
296
|
+
...s,
|
|
297
|
+
domProperties: { ...s.domProperties, className: "filtersRow" }
|
|
298
|
+
}));
|
|
299
|
+
let hasFilter = false;
|
|
300
|
+
for (const column of data.columns) {
|
|
301
|
+
const filter = data.filters.find(
|
|
302
|
+
(currentFilter) => currentFilter.getState("column") === column.colName
|
|
303
|
+
);
|
|
304
|
+
const headerCell = new Cell({
|
|
305
|
+
colName: column.colName,
|
|
306
|
+
Renderer: filter ? filter.Render : void 0
|
|
307
|
+
});
|
|
308
|
+
filtersRow.addCell(headerCell);
|
|
309
|
+
if (filter) {
|
|
310
|
+
hasFilter = true;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (hasFilter) {
|
|
314
|
+
this.addRow(filtersRow);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const defaultPlugins = () => [
|
|
321
|
+
new WaiAriaPlugin(),
|
|
322
|
+
new FocusControllerPlugin()
|
|
323
|
+
];
|
|
324
|
+
|
|
325
|
+
var __defProp$1 = Object.defineProperty;
|
|
326
|
+
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
327
|
+
var __publicField$1 = (obj, key, value) => {
|
|
328
|
+
__defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
329
|
+
return value;
|
|
330
|
+
};
|
|
331
|
+
class TableController extends EventEmitter {
|
|
332
|
+
constructor(props) {
|
|
333
|
+
super();
|
|
334
|
+
__publicField$1(this, "state", {
|
|
335
|
+
allowEdition: true,
|
|
336
|
+
allowResize: true,
|
|
337
|
+
allowSelection: true,
|
|
338
|
+
editionMode: false,
|
|
339
|
+
focusedCell: null,
|
|
340
|
+
hiddenColumns: /* @__PURE__ */ new Set(),
|
|
341
|
+
plugins: [],
|
|
342
|
+
isSelectionMultiple: true,
|
|
343
|
+
selection: /* @__PURE__ */ new Set()
|
|
344
|
+
});
|
|
345
|
+
__publicField$1(this, "body", new TableBody(this));
|
|
346
|
+
__publicField$1(this, "head", new TableHead(this));
|
|
347
|
+
Object.assign(this.state, props || {});
|
|
348
|
+
if (!props?.plugins) {
|
|
349
|
+
this.state.plugins.push(...defaultPlugins());
|
|
350
|
+
}
|
|
351
|
+
makeObservable(this, {
|
|
352
|
+
build: action,
|
|
353
|
+
clearSelection: action,
|
|
354
|
+
isEditionMode: computed,
|
|
355
|
+
setFocusedCell: action,
|
|
356
|
+
state: observable,
|
|
357
|
+
toggleRowSelection: action,
|
|
358
|
+
toggleEditionMode: action
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
setHiddenColumns(cols) {
|
|
362
|
+
this.state.hiddenColumns.forEach((c) => {
|
|
363
|
+
if (!cols.has(c)) {
|
|
364
|
+
this.state.hiddenColumns.delete(c);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
cols.forEach((c) => {
|
|
368
|
+
if (!this.state.hiddenColumns.has(c)) {
|
|
369
|
+
this.state.hiddenColumns.add(c);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Clears all data in the table, except for the events
|
|
375
|
+
* listeners and recreates the structure based on the passed data.
|
|
376
|
+
*/
|
|
377
|
+
build(data) {
|
|
378
|
+
this.head.build(data);
|
|
379
|
+
this.body.build(data);
|
|
380
|
+
this.restoreFocus();
|
|
381
|
+
}
|
|
382
|
+
getRowById(id) {
|
|
383
|
+
return this.head.getRowById(id) || this.body.getRowById(id);
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* EDITION MODE
|
|
387
|
+
*/
|
|
388
|
+
get isEditionMode() {
|
|
389
|
+
return this.state.editionMode;
|
|
390
|
+
}
|
|
391
|
+
toggleEditionMode(force) {
|
|
392
|
+
if (this.state.allowEdition) {
|
|
393
|
+
this.state.editionMode = force ?? !this.state.editionMode;
|
|
394
|
+
this.emit("editionMode", this.state.editionMode);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* SELECTION
|
|
399
|
+
*/
|
|
400
|
+
clearSelection() {
|
|
401
|
+
this.state.selection.clear();
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Returns the selected rows.
|
|
405
|
+
*/
|
|
406
|
+
getSelectedRows() {
|
|
407
|
+
return [...this.state.selection];
|
|
408
|
+
}
|
|
409
|
+
isSelected(row) {
|
|
410
|
+
return this.state.selection.has(row);
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Toggles the selection state of a given row.
|
|
414
|
+
*
|
|
415
|
+
* If the `isSelected` parameter is provided, it explicitly sets the selection
|
|
416
|
+
* state of the row to the given value (`true` for selected, `false` for deselected).
|
|
417
|
+
* If `isSelected` is not provided, the method will toggle the current selection state
|
|
418
|
+
* of the row (i.e., select it if it's currently not selected, or deselect it if it is).
|
|
419
|
+
*
|
|
420
|
+
* @param row - The row to toggle selection for.
|
|
421
|
+
* @param isSelected - Optional. A boolean indicating whether the row should be selected (`true`)
|
|
422
|
+
* or deselected (`false`). If not provided, the selection state will be toggled.
|
|
423
|
+
*/
|
|
424
|
+
toggleRowSelection(row, isSelected) {
|
|
425
|
+
if (this.state.allowSelection) {
|
|
426
|
+
isSelected = isSelected ?? !this.state.selection.has(row);
|
|
427
|
+
if (isSelected) {
|
|
428
|
+
if (row.getState("isSelectable") !== false) {
|
|
429
|
+
this.state.selection.add(row);
|
|
430
|
+
}
|
|
431
|
+
} else {
|
|
432
|
+
this.state.selection.delete(row);
|
|
433
|
+
}
|
|
434
|
+
this.emit("selectionChange", [...this.state.selection.values()]);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
restoreFocus() {
|
|
438
|
+
if (!this.state.focusedCell || this.state.focusedCell.getTable() !== this) {
|
|
439
|
+
this.state.focusedCell = this.body.getState("rows").values().next().value?.cells[0] || this.head.getState("rows").values().next().value?.cells[0] || null;
|
|
440
|
+
}
|
|
441
|
+
if (this.state.focusedCell) {
|
|
442
|
+
this.emit("cellFocus", { cell: this.state.focusedCell, doFocus: false });
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
removeRow(row) {
|
|
446
|
+
if (row.getTable() === this) {
|
|
447
|
+
row.setTable(null);
|
|
448
|
+
this.head.removeRow(row);
|
|
449
|
+
this.body.removeRow(row);
|
|
450
|
+
if (this.state.selection.has(row)) {
|
|
451
|
+
this.state.selection.delete(row);
|
|
452
|
+
this.emit("selectionChange", [...this.state.selection.values()]);
|
|
453
|
+
}
|
|
454
|
+
if (this.state.focusedCell?.getRow() === row) {
|
|
455
|
+
this.restoreFocus();
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* FOCUS
|
|
461
|
+
*/
|
|
462
|
+
/**
|
|
463
|
+
* @returns The currently focused cell in the table.
|
|
464
|
+
*/
|
|
465
|
+
getFocusedCell() {
|
|
466
|
+
return this.state.focusedCell;
|
|
467
|
+
}
|
|
468
|
+
setFocusedCell(cell, doFocus = true) {
|
|
469
|
+
this.state.focusedCell = cell;
|
|
470
|
+
this.emit("cellFocus", { cell, doFocus });
|
|
471
|
+
}
|
|
472
|
+
setSelection(indices) {
|
|
473
|
+
this.clearSelection();
|
|
474
|
+
indices.forEach((index) => {
|
|
475
|
+
const row = this.body.getRowByIndex(index);
|
|
476
|
+
if (row) {
|
|
477
|
+
this.toggleRowSelection(row, true);
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
scrollRowIntoView(row) {
|
|
482
|
+
this.emit("scrollIntoRow", row);
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* STATE
|
|
486
|
+
*/
|
|
487
|
+
/**
|
|
488
|
+
* Returns the current state of the `TableController` instance.
|
|
489
|
+
*
|
|
490
|
+
* @param prop - The property of the state to retrieve.
|
|
491
|
+
*/
|
|
492
|
+
getState(prop) {
|
|
493
|
+
return this.state[prop];
|
|
494
|
+
}
|
|
495
|
+
setState(prop, value) {
|
|
496
|
+
if (typeof prop === "function") {
|
|
497
|
+
this.state = prop(this.state);
|
|
498
|
+
} else {
|
|
499
|
+
this.state[prop] = value;
|
|
500
|
+
}
|
|
501
|
+
return this;
|
|
502
|
+
}
|
|
503
|
+
toggleColumnVisibility(columnName, isVisible) {
|
|
504
|
+
if (isVisible !== false && this.state.hiddenColumns.has(columnName)) {
|
|
505
|
+
this.state.hiddenColumns.delete(columnName);
|
|
506
|
+
} else {
|
|
507
|
+
this.state.hiddenColumns.add(columnName);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
isColumnVisible(columnName) {
|
|
511
|
+
return !this.state.hiddenColumns.has(columnName);
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* PLUGINS
|
|
515
|
+
*/
|
|
516
|
+
getPluginOfType(clazz) {
|
|
517
|
+
return this.state.plugins.find((plugin) => plugin instanceof clazz);
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Sets the plugins for the table.
|
|
521
|
+
*/
|
|
522
|
+
setPlugins(cb) {
|
|
523
|
+
this.state.plugins = cb(this.state.plugins);
|
|
524
|
+
}
|
|
525
|
+
getApplicablePlugins(method) {
|
|
526
|
+
return this.state.plugins.filter((c) => c[method] !== void 0).map((c) => c[method].bind(c, this));
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function getRow(controller, rowElement) {
|
|
531
|
+
const rowId = rowElement.dataset.rowid;
|
|
532
|
+
const row = controller.getRowById(rowId);
|
|
533
|
+
return row;
|
|
534
|
+
}
|
|
535
|
+
function selectBetweenRows(controller, rowA, rowB) {
|
|
536
|
+
const relativePosition = rowA.compareDocumentPosition(rowB);
|
|
537
|
+
let finished = false;
|
|
538
|
+
if (relativePosition & Node.DOCUMENT_POSITION_FOLLOWING) {
|
|
539
|
+
let current = rowA;
|
|
540
|
+
if (rowA.closest("thead") && !rowB.closest("thead")) {
|
|
541
|
+
current = rowA.closest("table")?.querySelector("tbody")?.firstElementChild;
|
|
542
|
+
}
|
|
543
|
+
while (!finished) {
|
|
544
|
+
if (current) {
|
|
545
|
+
getRow(controller, current)?.toggleSelection(true);
|
|
546
|
+
finished = current === rowB;
|
|
547
|
+
current = current.nextElementSibling;
|
|
548
|
+
} else {
|
|
549
|
+
finished = true;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
} else if (relativePosition & Node.DOCUMENT_POSITION_PRECEDING) {
|
|
553
|
+
let current = rowA;
|
|
554
|
+
while (!finished) {
|
|
555
|
+
if (current) {
|
|
556
|
+
getRow(controller, current)?.toggleSelection(true);
|
|
557
|
+
finished = current === rowB;
|
|
558
|
+
current = current.previousElementSibling;
|
|
559
|
+
} else {
|
|
560
|
+
finished = true;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
function useFocusByClick(controller) {
|
|
566
|
+
const lastRow = useRef(null);
|
|
567
|
+
return (ev) => {
|
|
568
|
+
const { target } = ev;
|
|
569
|
+
if (target instanceof HTMLElement) {
|
|
570
|
+
const cellElement = target.closest("td[data-colname],th[data-colname]");
|
|
571
|
+
const rowElement = target.closest("tr[data-rowid]");
|
|
572
|
+
if (rowElement instanceof HTMLTableRowElement) {
|
|
573
|
+
const row = getRow(controller, rowElement);
|
|
574
|
+
if (row) {
|
|
575
|
+
if (ev.shiftKey) {
|
|
576
|
+
if (lastRow.current) {
|
|
577
|
+
selectBetweenRows(controller, lastRow.current, rowElement);
|
|
578
|
+
}
|
|
579
|
+
} else if (ev.ctrlKey) {
|
|
580
|
+
row?.toggleSelection();
|
|
581
|
+
} else {
|
|
582
|
+
controller.clearSelection();
|
|
583
|
+
row?.toggleSelection(true);
|
|
584
|
+
}
|
|
585
|
+
lastRow.current = rowElement;
|
|
586
|
+
if (cellElement instanceof HTMLTableCellElement) {
|
|
587
|
+
const colName = cellElement.dataset.colname;
|
|
588
|
+
const cell = row?.getCellByColumnName(colName);
|
|
589
|
+
if (cell) {
|
|
590
|
+
if (target.closest("thead")) {
|
|
591
|
+
controller.emit("columnClick", cell);
|
|
592
|
+
}
|
|
593
|
+
cell.focus();
|
|
594
|
+
cellElement.focus();
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const pageSize = 6;
|
|
604
|
+
function handleArrowKey(controller, ev) {
|
|
605
|
+
const { target } = ev;
|
|
606
|
+
if (target instanceof HTMLElement) {
|
|
607
|
+
const cellElement = target.closest("td[data-colname],th[data-colname]");
|
|
608
|
+
const rowElement = target.closest("tr[data-rowid]");
|
|
609
|
+
if (rowElement instanceof HTMLTableRowElement) {
|
|
610
|
+
if (cellElement instanceof HTMLTableCellElement) {
|
|
611
|
+
const colName = cellElement.dataset.colname;
|
|
612
|
+
let nextFocusedRow = rowElement;
|
|
613
|
+
let nextFocusedCell = cellElement;
|
|
614
|
+
switch (ev.code) {
|
|
615
|
+
case "ArrowUp": {
|
|
616
|
+
const test = rowElement?.previousElementSibling;
|
|
617
|
+
if (test) {
|
|
618
|
+
nextFocusedRow = test;
|
|
619
|
+
} else if (nextFocusedRow.closest("tbody")) {
|
|
620
|
+
const thead = nextFocusedRow.closest("table").querySelector("thead");
|
|
621
|
+
nextFocusedRow = thead.lastElementChild;
|
|
622
|
+
}
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
625
|
+
case "ArrowDown": {
|
|
626
|
+
const test = rowElement?.nextElementSibling;
|
|
627
|
+
if (test) {
|
|
628
|
+
nextFocusedRow = test;
|
|
629
|
+
} else if (nextFocusedRow.closest("thead")) {
|
|
630
|
+
const tbody = nextFocusedRow.closest("table").querySelector("tbody");
|
|
631
|
+
nextFocusedRow = tbody.firstElementChild;
|
|
632
|
+
}
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
635
|
+
case "ArrowLeft":
|
|
636
|
+
nextFocusedCell = cellElement.previousElementSibling;
|
|
637
|
+
break;
|
|
638
|
+
case "ArrowRight":
|
|
639
|
+
nextFocusedCell = cellElement.nextElementSibling;
|
|
640
|
+
break;
|
|
641
|
+
case "PageUp": {
|
|
642
|
+
let i = 0;
|
|
643
|
+
do {
|
|
644
|
+
const test = nextFocusedRow.previousElementSibling;
|
|
645
|
+
if (!test && nextFocusedRow.closest("tbody")) {
|
|
646
|
+
const thead = nextFocusedRow.closest("table").querySelector("thead");
|
|
647
|
+
nextFocusedRow = thead.lastElementChild;
|
|
648
|
+
} else if (test) {
|
|
649
|
+
nextFocusedRow = test;
|
|
650
|
+
}
|
|
651
|
+
} while (nextFocusedRow instanceof HTMLTableRowElement && ++i < pageSize);
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
case "PageDown": {
|
|
655
|
+
let i = 0;
|
|
656
|
+
do {
|
|
657
|
+
const test = nextFocusedRow.nextElementSibling;
|
|
658
|
+
if (!test && nextFocusedRow.closest("thead")) {
|
|
659
|
+
const tbody = nextFocusedRow.closest("table").querySelector("tbody");
|
|
660
|
+
nextFocusedRow = tbody.firstElementChild;
|
|
661
|
+
} else if (test) {
|
|
662
|
+
nextFocusedRow = test;
|
|
663
|
+
}
|
|
664
|
+
} while (nextFocusedRow instanceof HTMLTableRowElement && ++i < pageSize);
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
case "Home": {
|
|
668
|
+
if (ev.ctrlKey) {
|
|
669
|
+
nextFocusedRow = rowElement.closest("table")?.querySelector("thead tr");
|
|
670
|
+
} else {
|
|
671
|
+
nextFocusedCell = rowElement.firstElementChild;
|
|
672
|
+
}
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
case "End":
|
|
676
|
+
if (ev.ctrlKey) {
|
|
677
|
+
nextFocusedRow = rowElement.closest("table").querySelector("tbody").lastElementChild;
|
|
678
|
+
} else {
|
|
679
|
+
nextFocusedCell = rowElement.lastChild;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
if (nextFocusedCell !== cellElement && nextFocusedCell) {
|
|
683
|
+
getRow(controller, rowElement).getCellByColumnName(nextFocusedCell.dataset.colname).focus();
|
|
684
|
+
} else if (nextFocusedRow !== rowElement && nextFocusedRow) {
|
|
685
|
+
if (ev.shiftKey) {
|
|
686
|
+
selectBetweenRows(controller, rowElement, nextFocusedRow);
|
|
687
|
+
} else {
|
|
688
|
+
controller.clearSelection();
|
|
689
|
+
getRow(controller, nextFocusedRow)?.toggleSelection(true);
|
|
690
|
+
}
|
|
691
|
+
const cell = getRow(controller, nextFocusedRow).getCellByColumnName(colName) || getRow(controller, nextFocusedRow).getCellByIndex(0);
|
|
692
|
+
cell?.focus();
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
function useFocusByKey(controller) {
|
|
699
|
+
return (ev) => {
|
|
700
|
+
if (controller.isEditionMode) {
|
|
701
|
+
if (["Enter", "Escape"].includes(ev.code)) {
|
|
702
|
+
ev.preventDefault();
|
|
703
|
+
}
|
|
704
|
+
switch (ev.code) {
|
|
705
|
+
case "Enter":
|
|
706
|
+
controller.emit("keyEnter", null);
|
|
707
|
+
break;
|
|
708
|
+
case "Escape":
|
|
709
|
+
controller.toggleEditionMode(false);
|
|
710
|
+
}
|
|
711
|
+
} else {
|
|
712
|
+
if ([
|
|
713
|
+
"Escape",
|
|
714
|
+
"ArrowUp",
|
|
715
|
+
"ArrowDown",
|
|
716
|
+
"ArrowLeft",
|
|
717
|
+
"ArrowRight",
|
|
718
|
+
"PageUp",
|
|
719
|
+
"PageDown",
|
|
720
|
+
"Home",
|
|
721
|
+
"End"
|
|
722
|
+
].includes(ev.code) || ev.code === "Enter" && controller.getState("allowEdition") || ev.code === "Space" && ev.ctrlKey) {
|
|
723
|
+
ev.preventDefault();
|
|
724
|
+
}
|
|
725
|
+
switch (ev.code) {
|
|
726
|
+
case "Enter":
|
|
727
|
+
controller.emit("keyEnter", null);
|
|
728
|
+
controller.toggleEditionMode();
|
|
729
|
+
break;
|
|
730
|
+
case "Space":
|
|
731
|
+
{
|
|
732
|
+
if (ev.ctrlKey) {
|
|
733
|
+
const row = ev.target?.closest("tr");
|
|
734
|
+
if (row instanceof HTMLElement) {
|
|
735
|
+
getRow(controller, row).toggleSelection();
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
break;
|
|
740
|
+
case "ArrowUp":
|
|
741
|
+
case "ArrowDown":
|
|
742
|
+
case "ArrowLeft":
|
|
743
|
+
case "ArrowRight":
|
|
744
|
+
case "Home":
|
|
745
|
+
case "End":
|
|
746
|
+
case "PageUp":
|
|
747
|
+
case "PageDown":
|
|
748
|
+
handleArrowKey(controller, ev);
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
var __defProp = Object.defineProperty;
|
|
756
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
757
|
+
var __publicField = (obj, key, value) => {
|
|
758
|
+
__defNormalProp(obj, key + "" , value);
|
|
759
|
+
return value;
|
|
760
|
+
};
|
|
761
|
+
function getCellElement(focusControllerRef, cell) {
|
|
762
|
+
return focusControllerRef.querySelector(
|
|
763
|
+
`tr[data-rowid="${cell.getRow().getState("id")}"] td[data-colname="${cell.getState("colName")}"], tr[data-rowid="${cell.getRow().getState("id")}"] th[data-colname="${cell.getState("colName")}"]`
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
function isVisible(el) {
|
|
767
|
+
if (!(el instanceof HTMLElement))
|
|
768
|
+
return false;
|
|
769
|
+
while (el) {
|
|
770
|
+
const style = getComputedStyle(el);
|
|
771
|
+
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
|
|
772
|
+
return false;
|
|
773
|
+
}
|
|
774
|
+
el = el.parentElement;
|
|
775
|
+
}
|
|
776
|
+
return true;
|
|
777
|
+
}
|
|
778
|
+
function scrollIntoRow(row) {
|
|
779
|
+
if (row) {
|
|
780
|
+
scrollParentIntoElement(row, 150, 0, 0);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
function restoreFocus(controller, tableElement, doFocus) {
|
|
784
|
+
const cell = controller.getFocusedCell();
|
|
785
|
+
if (cell) {
|
|
786
|
+
const cellElement = getCellElement(tableElement, cell);
|
|
787
|
+
if (cellElement) {
|
|
788
|
+
disableChildrenFocus(tableElement);
|
|
789
|
+
const widgets = cellElement.querySelectorAll(getFocusSelector());
|
|
790
|
+
const widget = [...widgets].find((c) => isVisible(c));
|
|
791
|
+
const focusableElement = widget || cellElement;
|
|
792
|
+
focusableElement.setAttribute("tabindex", "0");
|
|
793
|
+
restoreEditionMode(tableElement, cell.getTable().isEditionMode);
|
|
794
|
+
if (doFocus) {
|
|
795
|
+
focusableElement.focus();
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
function restoreEditionMode(focusController, editionMode) {
|
|
801
|
+
const focusable = focusController?.querySelector(
|
|
802
|
+
'[tabindex="0"]'
|
|
803
|
+
);
|
|
804
|
+
const cell = focusable?.closest("td, th");
|
|
805
|
+
if (cell instanceof HTMLElement) {
|
|
806
|
+
const widgets = cell.querySelectorAll(getFocusSelector());
|
|
807
|
+
widgets.forEach((widget) => {
|
|
808
|
+
widget.setAttribute("tabindex", editionMode ? "0" : "-1");
|
|
809
|
+
});
|
|
810
|
+
const firstFocusable = [...widgets].find((c) => isVisible(c)) || cell;
|
|
811
|
+
firstFocusable.setAttribute("tabindex", "0");
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
class FocusControllerPlugin {
|
|
815
|
+
constructor() {
|
|
816
|
+
__publicField(this, "rootElement", null);
|
|
817
|
+
}
|
|
818
|
+
scrollIntoView(rowId) {
|
|
819
|
+
const row = this.rootElement?.querySelector(
|
|
820
|
+
`tr[data-rowid="${rowId}"]`
|
|
821
|
+
);
|
|
822
|
+
if (row) {
|
|
823
|
+
scrollIntoRow(row);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
useElementDomProps(controller, element, _context) {
|
|
827
|
+
if (element instanceof TableController) {
|
|
828
|
+
const disconnect = useRef(() => {
|
|
829
|
+
});
|
|
830
|
+
useMount(() => {
|
|
831
|
+
const uns1 = controller.on("cellFocus", (ev) => {
|
|
832
|
+
if (this.rootElement) {
|
|
833
|
+
restoreFocus(controller, this.rootElement, ev.doFocus);
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
const uns2 = controller.on("editionMode", (editionMode) => {
|
|
837
|
+
if (this.rootElement) {
|
|
838
|
+
restoreEditionMode(this.rootElement, editionMode);
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
const uns3 = controller.on("scrollIntoRow", (row) => {
|
|
842
|
+
this.scrollIntoView(row.getState("id"));
|
|
843
|
+
});
|
|
844
|
+
return () => {
|
|
845
|
+
uns1();
|
|
846
|
+
uns2();
|
|
847
|
+
uns3();
|
|
848
|
+
};
|
|
849
|
+
});
|
|
850
|
+
return {
|
|
851
|
+
className: "table__focus_controller",
|
|
852
|
+
onClick: useFocusByClick(controller),
|
|
853
|
+
onKeyDown: useFocusByKey(controller),
|
|
854
|
+
onBlur: (ev) => {
|
|
855
|
+
if (ev.relatedTarget instanceof HTMLElement && ev.relatedTarget.closest(".table__focus_controller") !== this.rootElement) {
|
|
856
|
+
controller.toggleEditionMode(false);
|
|
857
|
+
}
|
|
858
|
+
},
|
|
859
|
+
ref: (el) => {
|
|
860
|
+
this.rootElement = el;
|
|
861
|
+
if (el) {
|
|
862
|
+
const observer = new MutationObserver(() => {
|
|
863
|
+
restoreFocus(controller, this.rootElement, false);
|
|
864
|
+
});
|
|
865
|
+
disconnect.current?.();
|
|
866
|
+
observer.observe(el, { subtree: true, childList: true });
|
|
867
|
+
disconnect.current = () => {
|
|
868
|
+
observer.disconnect();
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
return {};
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
class WaiAriaPlugin {
|
|
879
|
+
useElementDomProps(controller, element, context) {
|
|
880
|
+
if (element instanceof Row) {
|
|
881
|
+
const base = element.getContainer() === controller.head ? 0 : controller.head.rows.length;
|
|
882
|
+
return {
|
|
883
|
+
"aria-selected": element.isSelected,
|
|
884
|
+
"aria-rowindex": context + base + 1
|
|
885
|
+
};
|
|
886
|
+
} else if (element instanceof Cell) {
|
|
887
|
+
return {
|
|
888
|
+
"aria-colindex": context + 1
|
|
889
|
+
};
|
|
890
|
+
} else if (element instanceof TableController) {
|
|
891
|
+
return {
|
|
892
|
+
role: "treegrid",
|
|
893
|
+
"aria-multiselectable": controller.getState("allowSelection") && controller.getState("isSelectionMultiple") ? true : void 0,
|
|
894
|
+
"aria-colcount": controller.head.rows[0]?.cells.length || 0,
|
|
895
|
+
"aria-rowcount": controller.head.rows.length + controller.body.rows.length
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
return {};
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
export { Cell, Element, FocusControllerPlugin, Row, RowsGroup, TableBody, TableController, TableHead, WaiAriaPlugin, defaultPlugins };
|
|
903
|
+
//# sourceMappingURL=index.js.map
|