@danielgindi/dgtable.js 2.0.6 → 2.0.8

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 (67) hide show
  1. package/README.md +547 -282
  2. package/dist/SelectionHelper.d.ts +24 -0
  3. package/dist/SelectionHelper.d.ts.map +1 -0
  4. package/dist/by_column_filter.d.ts +14 -0
  5. package/dist/by_column_filter.d.ts.map +1 -0
  6. package/dist/cell_preview.d.ts +28 -0
  7. package/dist/cell_preview.d.ts.map +1 -0
  8. package/dist/column_collection.d.ts +41 -0
  9. package/dist/column_collection.d.ts.map +1 -0
  10. package/dist/column_resize.d.ts +25 -0
  11. package/dist/column_resize.d.ts.map +1 -0
  12. package/dist/constants.d.ts +19 -0
  13. package/dist/constants.d.ts.map +1 -0
  14. package/dist/header_events.d.ts +63 -0
  15. package/dist/header_events.d.ts.map +1 -0
  16. package/dist/helpers.d.ts +50 -0
  17. package/dist/helpers.d.ts.map +1 -0
  18. package/dist/index.d.ts +166 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/internal.d.ts +56 -0
  21. package/dist/internal.d.ts.map +1 -0
  22. package/dist/lib.cjs.js +6906 -3933
  23. package/dist/lib.cjs.js.map +1 -1
  24. package/dist/lib.cjs.min.js +2 -2
  25. package/dist/lib.cjs.min.js.map +1 -1
  26. package/dist/lib.es6.js +6908 -3935
  27. package/dist/lib.es6.js.map +1 -1
  28. package/dist/lib.es6.min.js +2 -2
  29. package/dist/lib.es6.min.js.map +1 -1
  30. package/dist/lib.umd.js +9251 -4353
  31. package/dist/lib.umd.js.map +1 -1
  32. package/dist/lib.umd.min.js +2 -2
  33. package/dist/lib.umd.min.js.map +1 -1
  34. package/dist/private_types.d.ts +145 -0
  35. package/dist/private_types.d.ts.map +1 -0
  36. package/dist/rendering.d.ts +57 -0
  37. package/dist/rendering.d.ts.map +1 -0
  38. package/dist/row_collection.d.ts +38 -0
  39. package/dist/row_collection.d.ts.map +1 -0
  40. package/dist/types.d.ts +221 -0
  41. package/dist/types.d.ts.map +1 -0
  42. package/dist/util.d.ts +9 -0
  43. package/dist/util.d.ts.map +1 -0
  44. package/eslint.config.mjs +1 -0
  45. package/package.json +17 -12
  46. package/src/SelectionHelper.ts +90 -0
  47. package/src/by_column_filter.ts +36 -0
  48. package/src/cell_preview.ts +325 -0
  49. package/src/column_collection.ts +131 -0
  50. package/src/column_resize.ts +363 -0
  51. package/src/constants.ts +22 -0
  52. package/src/header_events.ts +369 -0
  53. package/src/helpers.ts +291 -0
  54. package/src/index.ts +1628 -0
  55. package/src/internal.ts +263 -0
  56. package/src/private_types.ts +156 -0
  57. package/src/rendering.ts +771 -0
  58. package/src/row_collection.ts +197 -0
  59. package/src/types.ts +265 -0
  60. package/src/util.ts +27 -0
  61. package/tsconfig.json +38 -0
  62. package/src/SelectionHelper.js +0 -65
  63. package/src/by_column_filter.js +0 -25
  64. package/src/column_collection.js +0 -153
  65. package/src/index.js +0 -3999
  66. package/src/row_collection.js +0 -183
  67. package/src/util.js +0 -17
package/src/index.ts ADDED
@@ -0,0 +1,1628 @@
1
+ /* eslint-env browser */
2
+ /* eslint-disable @typescript-eslint/ban-ts-comment */
3
+
4
+ import { htmlEncode } from './util';
5
+ import RowCollection from './row_collection';
6
+ import ColumnCollection from './column_collection';
7
+ // @ts-ignore - No type declarations available for this module
8
+ import { getScrollHorz, setScrollHorz } from '@danielgindi/dom-utils/lib/ScrollHelper.js';
9
+ // @ts-ignore - No type declarations available for this module
10
+ import { getElementHeight } from '@danielgindi/dom-utils/lib/Css.js';
11
+ // @ts-ignore - No type declarations available for this module
12
+ import { scopedSelectorAll } from '@danielgindi/dom-utils/lib/DomCompat.js';
13
+ import ByColumnFilter from './by_column_filter';
14
+ // @ts-ignore - No type declarations available for this module
15
+ import DomEventsSink from '@danielgindi/dom-utils/lib/DomEventsSink.js';
16
+ import mitt from 'mitt';
17
+
18
+ // Constants
19
+ import {
20
+ ColumnWidthMode,
21
+ Width,
22
+ } from './constants';
23
+
24
+ // Helpers
25
+ import {
26
+ getTextWidth,
27
+ calculateWidthAvailableForColumns,
28
+ calculateTbodyWidth,
29
+ serializeColumnWidth,
30
+ } from './helpers';
31
+
32
+ // Cell Preview
33
+ import {
34
+ cellMouseOverEvent,
35
+ cellMouseOutEvent,
36
+ hideCellPreview,
37
+ } from './cell_preview';
38
+
39
+ // Column Resize
40
+ import {
41
+ cancelColumnResize,
42
+ } from './column_resize';
43
+
44
+ // Header Events
45
+ import {
46
+ onDragEndColumnHeader,
47
+ } from './header_events';
48
+
49
+ // Rendering
50
+ import {
51
+ renderSkeletonBase,
52
+ renderSkeletonBody,
53
+ renderSkeletonHeaderCells,
54
+ destroyHeaderCells,
55
+ updateVirtualHeight,
56
+ updateLastCellWidthFromScrollbar,
57
+ updateTableWidth,
58
+ resizeColumnElements,
59
+ clearSortArrows,
60
+ showSortArrow,
61
+ } from './rendering';
62
+
63
+ // Internal helpers (not exposed on class)
64
+ import {
65
+ setupHovers,
66
+ parseColumnWidth,
67
+ initColumnFromData,
68
+ ensureVisibleColumns,
69
+ refilter,
70
+ getHtmlForCell,
71
+ } from './internal';
72
+
73
+ // Types
74
+ import {
75
+ DGTableOptions,
76
+ RowData,
77
+ ColumnSortOptions,
78
+ FilterFunction,
79
+ ColumnOptions,
80
+ CellFormatter,
81
+ HeaderCellFormatter,
82
+ OnComparatorRequired,
83
+ CustomSortingProvider,
84
+ SerializedColumn,
85
+ SerializedColumnSort,
86
+ DGTableEventMap,
87
+ } from './types';
88
+
89
+ // Private types
90
+ import type {
91
+ DGTableInternalOptions,
92
+ DGTablePrivateState,
93
+ } from './private_types';
94
+ import {
95
+ IsSafeSymbol,
96
+ } from './private_types';
97
+
98
+
99
+ const hasOwnProperty = Object.prototype.hasOwnProperty;
100
+
101
+ // noinspection JSUnusedGlobalSymbols
102
+ class DGTable {
103
+ // Static properties
104
+ static VERSION = '@@VERSION';
105
+ static Width = Width;
106
+
107
+ // Instance properties
108
+ VERSION: string;
109
+ el!: HTMLElement;
110
+ _o!: DGTableInternalOptions;
111
+ _p!: DGTablePrivateState;
112
+ __removed?: boolean;
113
+
114
+ /**
115
+ * @param options - initialization options
116
+ */
117
+ constructor(options?: DGTableOptions) {
118
+ this.VERSION = DGTable.VERSION;
119
+
120
+ const o = this._o = {} as DGTableInternalOptions;
121
+ const p = this._p = {
122
+ eventsSink: new DomEventsSink(),
123
+ mitt: mitt(),
124
+ tableSkeletonNeedsRendering: true,
125
+ } as DGTablePrivateState;
126
+
127
+ this.el = (options.el && options.el instanceof HTMLElement) ? options.el : document.createElement('div');
128
+
129
+ if (this.el !== options.el) {
130
+ this.el.classList.add(options.className || 'dgtable-wrapper');
131
+ }
132
+
133
+ p.eventsSink.add(this.el, 'dragend.colresize', (e: Event) => onDragEndColumnHeader(this, e as DragEvent));
134
+
135
+ // Initialize options with defaults
136
+ o.virtualTable = options.virtualTable === undefined ? true : !!options.virtualTable;
137
+ o.estimatedRowHeight = options.estimatedRowHeight || undefined;
138
+ o.rowsBufferSize = options.rowsBufferSize || 3;
139
+ o.minColumnWidth = Math.max(options.minColumnWidth || 35, 0);
140
+ o.resizeAreaWidth = options.resizeAreaWidth || 8;
141
+ o.resizableColumns = options.resizableColumns === undefined ? true : !!options.resizableColumns;
142
+ o.movableColumns = options.movableColumns === undefined ? true : !!options.movableColumns;
143
+ o.sortableColumns = options.sortableColumns === undefined ? 1 : (parseInt(String(options.sortableColumns), 10) || 1);
144
+ o.adjustColumnWidthForSortArrow = options.adjustColumnWidthForSortArrow === undefined ? true : !!options.adjustColumnWidthForSortArrow;
145
+ o.convertColumnWidthsToRelative = options.convertColumnWidthsToRelative === undefined ? false : !!options.convertColumnWidthsToRelative;
146
+ o.autoFillTableWidth = options.autoFillTableWidth === undefined ? false : !!options.autoFillTableWidth;
147
+ o.allowCancelSort = options.allowCancelSort === undefined ? true : !!options.allowCancelSort;
148
+ o.cellClasses = options.cellClasses === undefined ? '' : options.cellClasses;
149
+ o.resizerClassName = options.resizerClassName === undefined ? 'dgtable-resize' : options.resizerClassName;
150
+ o.tableClassName = options.tableClassName === undefined ? 'dgtable' : options.tableClassName;
151
+ o.allowCellPreview = options.allowCellPreview === undefined ? true : options.allowCellPreview;
152
+ o.allowHeaderCellPreview = options.allowHeaderCellPreview === undefined ? true : options.allowHeaderCellPreview;
153
+ o.cellPreviewClassName = options.cellPreviewClassName === undefined ? 'dgtable-cell-preview' : options.cellPreviewClassName;
154
+ o.cellPreviewAutoBackground = options.cellPreviewAutoBackground === undefined ? true : options.cellPreviewAutoBackground;
155
+ o.onComparatorRequired = options.onComparatorRequired === undefined ? null : options.onComparatorRequired;
156
+ o.customSortingProvider = options.customSortingProvider === undefined ? null : options.customSortingProvider;
157
+ o.width = options.width === undefined ? Width.NONE : options.width;
158
+ o.relativeWidthGrowsToFillWidth = options.relativeWidthGrowsToFillWidth === undefined ? true : !!options.relativeWidthGrowsToFillWidth;
159
+ o.relativeWidthShrinksToFillWidth = options.relativeWidthShrinksToFillWidth === undefined ? false : !!options.relativeWidthShrinksToFillWidth;
160
+
161
+ this.setCellFormatter(options.cellFormatter);
162
+ this.setHeaderCellFormatter(options.headerCellFormatter);
163
+ this.setFilter(options.filter);
164
+
165
+ o.height = options.height;
166
+
167
+ // Prepare columns
168
+ this.setColumns(options.columns || [], false);
169
+
170
+ // Set sorting columns
171
+ let sortColumns = [];
172
+
173
+ if (options.sortColumn) {
174
+
175
+ let tmpSortColumns: (string | ColumnSortOptions)[] = Array.isArray(options.sortColumn)
176
+ ? options.sortColumn
177
+ : [options.sortColumn];
178
+
179
+ for (let i = 0, len = tmpSortColumns.length; i < len; i++) {
180
+ let sortColumn = tmpSortColumns[i];
181
+ if (typeof sortColumn === 'string') {
182
+ sortColumn = { column: sortColumn, descending: false };
183
+ }
184
+ let col = p.columns.get(sortColumn.column);
185
+ if (!col) continue;
186
+
187
+ sortColumns.push({
188
+ column: sortColumn.column,
189
+ comparePath: col.comparePath || col.dataPath,
190
+ descending: sortColumn.descending ?? false,
191
+ });
192
+ }
193
+ }
194
+
195
+ p.rows = new RowCollection({ sortColumn: sortColumns });
196
+ p.rows.onComparatorRequired = (column, descending, defaultComparator) => {
197
+ if (o.onComparatorRequired) {
198
+ return o.onComparatorRequired(column, descending, defaultComparator);
199
+ }
200
+ };
201
+ p.rows.customSortingProvider = (data, sort) => {
202
+ if (o.customSortingProvider) {
203
+ return o.customSortingProvider(data, sort);
204
+ } else {
205
+ return sort(data);
206
+ }
207
+ };
208
+
209
+ p.filteredRows = null;
210
+
211
+ p.scrollbarWidth = 0;
212
+ p._lastVirtualScrollHeight = 0;
213
+
214
+ setupHovers(this);
215
+ }
216
+
217
+ // =========================================================================
218
+ // PUBLIC API - Events
219
+ // =========================================================================
220
+
221
+ /**
222
+ * Register an event handler.
223
+ * Built-in events have typed handlers. Custom events use `unknown` data type.
224
+ */
225
+ on<K extends keyof DGTableEventMap>(event: K, handler: (value: DGTableEventMap[K]) => void): this;
226
+ on<T = unknown>(event: string & {}, handler: (value: T) => void): this;
227
+ on(event: string, handler: (value: unknown) => void) {
228
+ this._p.mitt.on(event, handler);
229
+ return this;
230
+ }
231
+
232
+ /**
233
+ * Register a one-time event handler.
234
+ * Built-in events have typed handlers. Custom events use `unknown` data type.
235
+ */
236
+ once<K extends keyof DGTableEventMap>(event: K, handler: (value: DGTableEventMap[K]) => void): this;
237
+ once<T = unknown>(event: string & {}, handler: (value: T) => void): this;
238
+ once(event: string, handler: (value: unknown) => void) {
239
+ const wrapped = (value: unknown) => {
240
+ this._p.mitt.off(event, wrapped);
241
+ handler(value);
242
+ };
243
+ this._p.mitt.on(event, wrapped);
244
+ return this;
245
+ }
246
+
247
+ /**
248
+ * Remove a handler for an event, all handlers for an event, or all handlers completely.
249
+ * Built-in events have typed handlers. Custom events use `unknown` data type.
250
+ */
251
+ off<K extends keyof DGTableEventMap>(event?: K, handler?: (value: DGTableEventMap[K]) => void): this;
252
+ off<T = unknown>(event?: string & {}, handler?: (value: T) => void): this;
253
+ off(event?: string, handler?: (value: unknown) => void) {
254
+ if (!event && !handler) {
255
+ this._p.mitt.all.clear();
256
+ } else {
257
+ this._p.mitt.off(event, handler);
258
+ }
259
+ return this;
260
+ }
261
+
262
+ /**
263
+ * Emit an event.
264
+ * Built-in events have typed data. Custom events accept any data type.
265
+ */
266
+ emit<K extends keyof DGTableEventMap>(event: K, value?: DGTableEventMap[K]): this;
267
+ emit<T = unknown>(event: string & {}, value?: T): this;
268
+ emit(event: string, value?: unknown) {
269
+ this._p.mitt.emit(event, value);
270
+ return this;
271
+ }
272
+
273
+ // =========================================================================
274
+ // PUBLIC API - Lifecycle
275
+ // =========================================================================
276
+
277
+ /**
278
+ * Destroy, releasing all memory, events and DOM elements
279
+ */
280
+ destroy() {
281
+ const p = this._p;
282
+ const el = this.el;
283
+
284
+ if (this.__removed || !p) {
285
+ return this;
286
+ }
287
+
288
+ if (p.resizer) {
289
+ p.resizer.remove();
290
+ p.resizer = null;
291
+ }
292
+
293
+ p.virtualListHelper?.destroy();
294
+ p.virtualListHelper = null;
295
+
296
+ destroyHeaderCells(this);
297
+
298
+ p.table?.remove();
299
+ p.tbody?.remove();
300
+
301
+ if (p.workerListeners) {
302
+ for (let j = 0; j < p.workerListeners.length; j++) {
303
+ let worker = p.workerListeners[j];
304
+ worker.worker.removeEventListener('message', worker.listener, false);
305
+ }
306
+ p.workerListeners.length = 0;
307
+ }
308
+
309
+ p.rows.length = p.columns.length = 0;
310
+
311
+ if (p._deferredRender) {
312
+ clearTimeout(p._deferredRender);
313
+ }
314
+
315
+ // Cleanup
316
+ for (let prop in this) {
317
+ if (hasOwnProperty.call(this, prop)) {
318
+ this[prop] = null;
319
+ }
320
+ }
321
+
322
+ this.__removed = true;
323
+
324
+ if (el) {
325
+ el.remove();
326
+ }
327
+
328
+ return this;
329
+ }
330
+
331
+ // Backwards compatibility
332
+ close() {
333
+ this.destroy();
334
+ }
335
+
336
+ // Backwards compatibility
337
+ remove() {
338
+ this.destroy();
339
+ }
340
+
341
+ // =========================================================================
342
+ // PUBLIC API - Rendering
343
+ // =========================================================================
344
+
345
+ /** Render the table */
346
+ render() {
347
+ const o = this._o, p = this._p;
348
+
349
+ if (!this.el.offsetParent) {
350
+ if (!p._deferredRender) {
351
+ p._deferredRender = setTimeout(() => {
352
+ p._deferredRender = null;
353
+ if (!this.__removed && this.el.offsetParent) {
354
+ this.render();
355
+ }
356
+ });
357
+ }
358
+
359
+ return this;
360
+ }
361
+
362
+ if (p.tableSkeletonNeedsRendering === true) {
363
+ p.tableSkeletonNeedsRendering = false;
364
+
365
+ if (o.width === Width.AUTO) {
366
+ clearSortArrows(this);
367
+ }
368
+
369
+ let lastScrollTop = p.table && p.table.parentNode ? p.table.scrollTop : NaN,
370
+ lastScrollHorz = p.table && p.table.parentNode ? getScrollHorz(p.table) : NaN;
371
+
372
+ renderSkeletonBase(this);
373
+ renderSkeletonBody(this);
374
+ this.tableWidthChanged(true, false);
375
+ renderSkeletonHeaderCells(this);
376
+
377
+ p.virtualListHelper.setCount((p.filteredRows ?? p.rows).length);
378
+
379
+ updateVirtualHeight(this);
380
+ updateLastCellWidthFromScrollbar(this, true);
381
+ updateTableWidth(this, true);
382
+
383
+ // Show sort arrows
384
+ for (let i = 0; i < p.rows.sortColumn.length; i++) {
385
+ showSortArrow(this, p.rows.sortColumn[i].column, p.rows.sortColumn[i].descending);
386
+ }
387
+ if (o.adjustColumnWidthForSortArrow && p.rows.sortColumn.length) {
388
+ this.tableWidthChanged(true);
389
+ } else if (!o.virtualTable) {
390
+ this.tableWidthChanged();
391
+ }
392
+
393
+ if (!isNaN(lastScrollTop))
394
+ p.table.scrollTop = lastScrollTop;
395
+
396
+ if (!isNaN(lastScrollHorz)) {
397
+ setScrollHorz(p.table, lastScrollHorz);
398
+ setScrollHorz(p.header, lastScrollHorz);
399
+ }
400
+
401
+ this.emit('renderskeleton');
402
+ }
403
+
404
+ p.virtualListHelper.render();
405
+
406
+ this.emit('render');
407
+ return this;
408
+ }
409
+
410
+ /** Forces a full render of the table */
411
+ clearAndRender(render?: boolean) {
412
+ let p = this._p;
413
+
414
+ p.tableSkeletonNeedsRendering = true;
415
+ p.notifyRendererOfColumnsConfig?.();
416
+
417
+ if (render === undefined || render) {
418
+ this.render();
419
+ }
420
+
421
+ return this;
422
+ }
423
+
424
+ // =========================================================================
425
+ // PUBLIC API - Columns
426
+ // =========================================================================
427
+
428
+ /** Sets the columns of the table */
429
+ setColumns(columns?: ColumnOptions[] | null, render?: boolean) {
430
+ const p = this._p;
431
+
432
+ columns = columns || [];
433
+
434
+ let normalizedCols = new ColumnCollection();
435
+ for (let i = 0, order = 0; i < columns.length; i++) {
436
+
437
+ let columnData = columns[i];
438
+ let normalizedColumn = initColumnFromData(this._o, columnData);
439
+
440
+ if (columnData.order !== undefined) {
441
+ if (columnData.order > order) {
442
+ order = columnData.order + 1;
443
+ }
444
+ normalizedColumn.order = columnData.order;
445
+ } else {
446
+ normalizedColumn.order = order++;
447
+ }
448
+
449
+ normalizedCols.push(normalizedColumn);
450
+ }
451
+ normalizedCols.normalizeOrder();
452
+
453
+ p.columns = normalizedCols;
454
+ p.visibleColumns = normalizedCols.getVisibleColumns();
455
+
456
+ ensureVisibleColumns(this);
457
+ this.clearAndRender(render);
458
+
459
+ return this;
460
+ }
461
+
462
+ /** Add a column to the table */
463
+ addColumn(columnData: ColumnOptions, before?: string | number, render?: boolean) {
464
+ const p = this._p;
465
+ let columns = p.columns;
466
+
467
+ if (columnData && !columns.get(columnData.name)) {
468
+ let beforeColumn = null;
469
+ if (before !== undefined) {
470
+ beforeColumn = typeof before === 'string'
471
+ ? columns.get(before)
472
+ : columns.getByOrder(before);
473
+ }
474
+
475
+ let column = initColumnFromData(this._o, columnData);
476
+ column.order = beforeColumn ? beforeColumn.order : (columns.getMaxOrder() + 1);
477
+
478
+ for (let i = columns.getMaxOrder(), to = column.order; i >= to; i--) {
479
+ let col = columns.getByOrder(i);
480
+ if (col) {
481
+ col.order++;
482
+ }
483
+ }
484
+
485
+ columns.push(column);
486
+ columns.normalizeOrder();
487
+
488
+ p.visibleColumns = columns.getVisibleColumns();
489
+ ensureVisibleColumns(this);
490
+ this.clearAndRender(render);
491
+
492
+ this.emit('addcolumn', column.name);
493
+ }
494
+ return this;
495
+ }
496
+
497
+ /** Remove a column from the table */
498
+ removeColumn(column: string, render?: boolean) {
499
+ const p = this._p;
500
+ let columns = p.columns;
501
+
502
+ let colIdx = columns.indexOf(column);
503
+ if (colIdx > -1) {
504
+ columns.splice(colIdx, 1);
505
+ columns.normalizeOrder();
506
+
507
+ p.visibleColumns = columns.getVisibleColumns();
508
+ ensureVisibleColumns(this);
509
+ this.clearAndRender(render);
510
+
511
+ this.emit('removecolumn', column);
512
+ }
513
+ return this;
514
+ }
515
+
516
+ /** Set a new label to a column */
517
+ setColumnLabel(column: string, label: string) {
518
+ const p = this._p;
519
+
520
+ let col = p.columns.get(column);
521
+ if (col) {
522
+ col.label = label === undefined ? col.name : label;
523
+
524
+ if (col.element) {
525
+ for (let i = 0; i < col.element.firstChild.childNodes.length; i++) {
526
+ let node = col.element.firstChild.childNodes[i];
527
+ if (node.nodeType === 3) {
528
+ node.textContent = col.label;
529
+ break;
530
+ }
531
+ }
532
+ }
533
+ }
534
+ return this;
535
+ }
536
+
537
+ /** Move a column to a new position */
538
+ moveColumn(src: string | number, dest: string | number, visibleOnly = true) {
539
+ const o = this._o, p = this._p;
540
+
541
+ let columns = p.columns,
542
+ col, destCol;
543
+
544
+ let columnsArray = visibleOnly ? p.visibleColumns : columns.getColumns();
545
+
546
+ if (typeof src === 'string') {
547
+ col = columns.get(src);
548
+ } else if (typeof src === 'number') {
549
+ col = columnsArray[src];
550
+ }
551
+ if (typeof dest === 'string') {
552
+ destCol = columns.get(dest);
553
+ } else if (typeof dest === 'number') {
554
+ destCol = columnsArray[dest];
555
+ }
556
+
557
+ if (col && destCol && src !== dest) {
558
+ let srcOrder = col.order, destOrder = destCol.order;
559
+
560
+ let visibleColumns = columns.moveColumn(col, destCol).getVisibleColumns();
561
+
562
+ if (p.visibleColumns.length !== visibleColumns.length ||
563
+ p.visibleColumns.some((x, i) => x !== visibleColumns[i])) {
564
+
565
+ p.visibleColumns = visibleColumns;
566
+ ensureVisibleColumns(this);
567
+
568
+ if (o.virtualTable) {
569
+ this.clearAndRender();
570
+ } else {
571
+ const headerCells = scopedSelectorAll(p.headerRow, `>div.${o.tableClassName}-header-cell`);
572
+ let beforePos = srcOrder < destOrder ? destOrder + 1 : destOrder,
573
+ fromPos = srcOrder;
574
+ headerCells[0].parentNode.insertBefore(headerCells[fromPos], headerCells[beforePos]);
575
+
576
+ let srcCol = p.visibleColumns[srcOrder];
577
+ let srcWidth = ((srcCol.actualWidthConsideringScrollbarWidth || srcCol.actualWidth) ?? 0) + 'px';
578
+ let destCol = p.visibleColumns[destOrder];
579
+ let destWidth = ((destCol.actualWidthConsideringScrollbarWidth || destCol.actualWidth) ?? 0) + 'px';
580
+
581
+ let tbodyChildren = p.tbody.childNodes;
582
+ for (let i = 0, count = tbodyChildren.length; i < count; i++) {
583
+ let row = tbodyChildren[i] as HTMLElement;
584
+ if (row.nodeType !== 1) continue;
585
+ row.insertBefore(row.childNodes[fromPos], row.childNodes[beforePos]);
586
+ ((row.childNodes[destOrder] as HTMLElement).firstChild as HTMLElement).style.width = destWidth;
587
+ ((row.childNodes[srcOrder] as HTMLElement).firstChild as HTMLElement).style.width = srcWidth;
588
+ }
589
+ }
590
+ }
591
+
592
+ this.emit('movecolumn', { name: col.name, src: srcOrder, dest: destOrder });
593
+ }
594
+ return this;
595
+ }
596
+
597
+ /** Show or hide a column */
598
+ setColumnVisible(column: string, visible: boolean) {
599
+ const p = this._p;
600
+
601
+ let col = p.columns.get(column);
602
+
603
+ visible = !!visible;
604
+
605
+ if (col && !!col.visible !== visible) {
606
+ col.visible = visible;
607
+ p.visibleColumns = p.columns.getVisibleColumns();
608
+ this.emit(visible ? 'showcolumn' : 'hidecolumn', column);
609
+ ensureVisibleColumns(this);
610
+ this.clearAndRender();
611
+ }
612
+ return this;
613
+ }
614
+
615
+ /** Get the visibility mode of a column */
616
+ isColumnVisible(column: string): boolean {
617
+ const p = this._p;
618
+ let col = p.columns.get(column);
619
+ if (col) {
620
+ return col.visible;
621
+ }
622
+ return false;
623
+ }
624
+
625
+ /** Globally set the minimum column width */
626
+ setMinColumnWidth(minColumnWidth: number) {
627
+ let o = this._o;
628
+ minColumnWidth = Math.max(minColumnWidth, 0);
629
+ if (o.minColumnWidth !== minColumnWidth) {
630
+ o.minColumnWidth = minColumnWidth;
631
+ this.tableWidthChanged(true);
632
+ }
633
+ return this;
634
+ }
635
+
636
+ /** Get the current minimum column width */
637
+ getMinColumnWidth(): number {
638
+ return this._o.minColumnWidth;
639
+ }
640
+
641
+ /** Set a new width to a column */
642
+ setColumnWidth(column: string, width: number | string) {
643
+ const p = this._p;
644
+
645
+ let col = p.columns.get(column);
646
+
647
+ let parsedWidth = parseColumnWidth(width, col.ignoreMin ? 0 : this._o.minColumnWidth);
648
+
649
+ if (col) {
650
+ let oldWidth = serializeColumnWidth(col);
651
+
652
+ col.width = parsedWidth.width;
653
+ col.widthMode = parsedWidth.mode;
654
+
655
+ let newWidth = serializeColumnWidth(col);
656
+
657
+ if (oldWidth !== newWidth) {
658
+ this.tableWidthChanged(true);
659
+ }
660
+
661
+ this.emit('columnwidth', { name: col.name, width: newWidth, oldWidth: oldWidth });
662
+ }
663
+ return this;
664
+ }
665
+
666
+ /** Get the serialized width of the specified column */
667
+ getColumnWidth(column: string): string | number | null {
668
+ const p = this._p;
669
+
670
+ let col = p.columns.get(column);
671
+ if (col) {
672
+ return serializeColumnWidth(col);
673
+ }
674
+ return null;
675
+ }
676
+
677
+ /** Get configuration for a specific column */
678
+ getColumnConfig(column: string): SerializedColumn | null {
679
+ const p = this._p;
680
+ let col = p.columns.get(column);
681
+ if (col) {
682
+ return {
683
+ 'order': col.order,
684
+ 'width': serializeColumnWidth(col),
685
+ 'visible': col.visible,
686
+ 'label': col.label,
687
+ };
688
+ }
689
+ return null;
690
+ }
691
+
692
+ /** Returns a config object for all columns */
693
+ getColumnsConfig() {
694
+ const p = this._p;
695
+
696
+ let config: Record<string, ReturnType<typeof this.getColumnConfig>> = {};
697
+ for (let i = 0; i < p.columns.length; i++) {
698
+ config[p.columns[i].name] = this.getColumnConfig(p.columns[i].name);
699
+ }
700
+ return config;
701
+ }
702
+
703
+ // =========================================================================
704
+ // PUBLIC API - Sorting
705
+ // =========================================================================
706
+
707
+ /** Set the limit on concurrent columns sorted */
708
+ setSortableColumns(sortableColumns: number) {
709
+ const p = this._p, o = this._o;
710
+ if (o.sortableColumns !== sortableColumns) {
711
+ o.sortableColumns = sortableColumns;
712
+ if (p.table) {
713
+ const headerCells = scopedSelectorAll(p.headerRow, `>div.${o.tableClassName}-header-cell`);
714
+ for (let i = 0, len = headerCells.length; i < len; i++) {
715
+ const cell = headerCells[i];
716
+ cell.classList[(o.sortableColumns > 0 && p.visibleColumns[i].sortable) ? 'add' : 'remove']('sortable');
717
+ }
718
+ }
719
+ }
720
+ return this;
721
+ }
722
+
723
+ /** Get the limit on concurrent columns sorted */
724
+ getSortableColumns(): number {
725
+ return this._o.sortableColumns;
726
+ }
727
+
728
+ /** Set whether columns are movable */
729
+ setMovableColumns(movableColumns?: boolean) {
730
+ let o = this._o;
731
+ movableColumns = movableColumns === undefined ? true : !!movableColumns;
732
+ if (o.movableColumns !== movableColumns) {
733
+ o.movableColumns = movableColumns;
734
+ }
735
+ return this;
736
+ }
737
+
738
+ /** Get whether columns are movable */
739
+ getMovableColumns(): boolean {
740
+ return this._o.movableColumns;
741
+ }
742
+
743
+ /** Set whether columns are resizable */
744
+ setResizableColumns(resizableColumns?: boolean) {
745
+ let o = this._o;
746
+ resizableColumns = resizableColumns === undefined ? true : !!resizableColumns;
747
+ if (o.resizableColumns !== resizableColumns) {
748
+ o.resizableColumns = resizableColumns;
749
+ }
750
+ return this;
751
+ }
752
+
753
+ /** Get whether columns are resizable */
754
+ getResizableColumns(): boolean {
755
+ return this._o.resizableColumns;
756
+ }
757
+
758
+ /** Sets a function that supplies comparators dynamically */
759
+ setOnComparatorRequired(comparatorProvider: OnComparatorRequired | null) {
760
+ let o = this._o;
761
+ if (o.onComparatorRequired !== comparatorProvider) {
762
+ o.onComparatorRequired = comparatorProvider;
763
+ }
764
+ return this;
765
+ }
766
+
767
+ /** Sets custom sorting function for a data set */
768
+ setCustomSortingProvider(customSortingProvider: CustomSortingProvider | null) {
769
+ let o = this._o;
770
+ if (o.customSortingProvider !== customSortingProvider) {
771
+ o.customSortingProvider = customSortingProvider;
772
+ }
773
+ return this;
774
+ }
775
+
776
+ /** Sort the table by column */
777
+ sort(column?: string, descending?: boolean, add?: boolean) {
778
+ const o = this._o, p = this._p;
779
+
780
+ let columns = p.columns,
781
+ col = columns.get(column);
782
+
783
+ let currentSort = p.rows.sortColumn;
784
+
785
+ if (col) {
786
+ if (add) {
787
+ for (let i = 0; i < currentSort.length; i++) {
788
+ if (currentSort[i].column === col.name) {
789
+ if (i < currentSort.length - 1) {
790
+ currentSort.length = 0;
791
+ } else {
792
+ descending = currentSort[currentSort.length - 1].descending;
793
+ currentSort.splice(currentSort.length - 1, 1);
794
+ }
795
+ break;
796
+ }
797
+ }
798
+ if ((o.sortableColumns > 0 && currentSort.length >= o.sortableColumns) || currentSort.length >= p.visibleColumns.length) {
799
+ currentSort.length = 0;
800
+ }
801
+
802
+ } else {
803
+ currentSort.length = 0;
804
+ }
805
+
806
+ descending = descending === undefined ? false : descending;
807
+
808
+ currentSort.push({
809
+ column: col.name,
810
+ comparePath: col.comparePath || col.dataPath,
811
+ descending: !!descending,
812
+ });
813
+ } else {
814
+ currentSort.length = 0;
815
+ }
816
+
817
+ clearSortArrows(this);
818
+
819
+ for (let i = 0; i < currentSort.length; i++) {
820
+ showSortArrow(this, currentSort[i].column, currentSort[i].descending);
821
+ }
822
+
823
+ if (o.adjustColumnWidthForSortArrow && !p.tableSkeletonNeedsRendering) {
824
+ this.tableWidthChanged(true);
825
+ }
826
+
827
+ p.rows.sortColumn = currentSort;
828
+
829
+ if (currentSort.length) {
830
+ p.rows.sort();
831
+ if (p.filteredRows) {
832
+ p.filteredRows.sort();
833
+ }
834
+ }
835
+
836
+ if (p.virtualListHelper)
837
+ p.virtualListHelper.invalidate().render();
838
+
839
+ let sorts = [];
840
+ for (let i = 0; i < currentSort.length; i++) {
841
+ sorts.push({ 'column': currentSort[i].column, 'descending': currentSort[i].descending });
842
+ }
843
+ this.emit('sort', { sorts: sorts });
844
+
845
+ return this;
846
+ }
847
+
848
+ /** Re-sort the table using current sort specifiers */
849
+ resort() {
850
+ const p = this._p;
851
+ let columns = p.columns;
852
+
853
+ let currentSort = p.rows.sortColumn;
854
+ if (currentSort.length) {
855
+
856
+ for (let i = 0; i < currentSort.length; i++) {
857
+ if (!columns.get(currentSort[i].column)) {
858
+ currentSort.splice(i--, 1);
859
+ }
860
+ }
861
+
862
+ p.rows.sortColumn = currentSort;
863
+ if (currentSort.length) {
864
+ p.rows.sort();
865
+ if (p.filteredRows) {
866
+ p.filteredRows.sort();
867
+ }
868
+ }
869
+
870
+ let sorts = [];
871
+ for (let i = 0; i < currentSort.length; i++) {
872
+ sorts.push({ 'column': currentSort[i].column, 'descending': currentSort[i].descending });
873
+ }
874
+ this.emit('sort', { sorts: sorts, resort: true });
875
+ }
876
+
877
+ return this;
878
+ }
879
+
880
+ /** Returns an array of the currently sorted columns */
881
+ getSortedColumns(): SerializedColumnSort[] {
882
+ const p = this._p;
883
+
884
+ let sorted = [];
885
+ for (let i = 0; i < p.rows.sortColumn.length; i++) {
886
+ let sort = p.rows.sortColumn[i];
887
+ sorted.push({ column: sort.column, descending: sort.descending });
888
+ }
889
+ return sorted;
890
+ }
891
+
892
+ // =========================================================================
893
+ // PUBLIC API - Formatters & Filters
894
+ // =========================================================================
895
+
896
+ /** Sets a new cell formatter */
897
+ setCellFormatter(formatter?: CellFormatter | null) {
898
+ if (!formatter) {
899
+ const defaultFormatter = (val: unknown) => (typeof val === 'string') ? htmlEncode(val) : val;
900
+ (defaultFormatter as unknown as Record<symbol, boolean>)[IsSafeSymbol] = true;
901
+ formatter = defaultFormatter as unknown as CellFormatter;
902
+ }
903
+
904
+ this._o.cellFormatter = formatter;
905
+
906
+ return this;
907
+ }
908
+
909
+ /** Sets a new header cell formatter */
910
+ setHeaderCellFormatter(formatter?: HeaderCellFormatter | null) {
911
+ this._o.headerCellFormatter = formatter || function (val: string) {
912
+ return (typeof val === 'string') ? htmlEncode(val) : val;
913
+ };
914
+
915
+ return this;
916
+ }
917
+
918
+ /** Set the filter function */
919
+ setFilter(filterFunc?: FilterFunction | null) {
920
+ this._o.filter = filterFunc;
921
+ return this;
922
+ }
923
+
924
+ /** Filter the table rows */
925
+ filter(args?: unknown) {
926
+ const p = this._p;
927
+
928
+ let filterFunc = (this._o.filter || ByColumnFilter) as FilterFunction;
929
+
930
+ // Deprecated use of older by-column filter
931
+ if (typeof arguments[0] === 'string' && typeof arguments[1] === 'string') {
932
+ args = {
933
+ column: arguments[0],
934
+ keyword: arguments[1],
935
+ caseSensitive: arguments[2],
936
+ };
937
+ }
938
+
939
+ let hadFilter = !!p.filteredRows;
940
+ if (p.filteredRows) {
941
+ p.filteredRows = null;
942
+ }
943
+
944
+ p.filterArgs = args == null ? null : ((typeof args === 'object' && !Array.isArray(args)) ? Object.assign({}, args) : args);
945
+
946
+ if (p.filterArgs !== null) {
947
+ p.filteredRows = p.rows.filteredCollection(filterFunc, p.filterArgs);
948
+
949
+ if (hadFilter || p.filteredRows) {
950
+ this.clearAndRender();
951
+ this.emit('filter', args);
952
+ }
953
+ }
954
+ else {
955
+ p.filterArgs = null;
956
+ p.filteredRows = null;
957
+ this.clearAndRender();
958
+ this.emit('filterclear', {});
959
+ }
960
+
961
+ return this;
962
+ }
963
+
964
+ /** Clear the current filter */
965
+ clearFilter() {
966
+ const p = this._p;
967
+
968
+ if (p.filteredRows) {
969
+ p.filterArgs = null;
970
+ p.filteredRows = null;
971
+ this.clearAndRender();
972
+ this.emit('filterclear', {});
973
+ }
974
+
975
+ return this;
976
+ }
977
+
978
+ // =========================================================================
979
+ // PUBLIC API - Row Operations
980
+ // =========================================================================
981
+
982
+ /** Returns the HTML string for a specific cell by row index */
983
+ getHtmlForRowCell(rowIndex: number, columnName: string): string | null {
984
+ const p = this._p;
985
+
986
+ if (rowIndex < 0 || rowIndex > p.rows.length - 1) return null;
987
+ let column = p.columns.get(columnName);
988
+ if (!column) return null;
989
+ let rowData = p.rows[rowIndex];
990
+
991
+ return getHtmlForCell(this._o, rowData, column);
992
+ }
993
+
994
+ /** Returns the HTML string for a specific cell by row data */
995
+ getHtmlForRowDataCell(rowData: RowData, columnName: string): string | null {
996
+ const p = this._p;
997
+
998
+ let column = p.columns.get(columnName);
999
+ if (!column) return null;
1000
+
1001
+ return getHtmlForCell(this._o, rowData, column);
1002
+ }
1003
+
1004
+ /** Returns the y position of a row by index */
1005
+ getRowYPos(rowIndex: number): number | null {
1006
+ const p = this._p;
1007
+
1008
+ return p.virtualListHelper.getItemPosition(rowIndex) || null;
1009
+ }
1010
+
1011
+ /** Returns the row data for a specific row */
1012
+ getDataForRow(row: number): RowData | null {
1013
+ const p = this._p;
1014
+
1015
+ if (row < 0 || row > p.rows.length - 1) return null;
1016
+ return p.rows[row];
1017
+ }
1018
+
1019
+ /** Gets the number of rows */
1020
+ getRowCount(): number {
1021
+ const p = this._p;
1022
+ return p.rows ? p.rows.length : 0;
1023
+ }
1024
+
1025
+ /** Returns the actual row index for specific row data */
1026
+ getIndexForRow(rowData: RowData): number {
1027
+ const p = this._p;
1028
+ return p.rows.indexOf(rowData);
1029
+ }
1030
+
1031
+ /** Gets the number of filtered rows */
1032
+ getFilteredRowCount(): number {
1033
+ const p = this._p;
1034
+ return (p.filteredRows || p.rows).length;
1035
+ }
1036
+
1037
+ /** Returns the filtered row index for specific row data */
1038
+ getIndexForFilteredRow(rowData: RowData): number {
1039
+ const p = this._p;
1040
+ return (p.filteredRows || p.rows).indexOf(rowData);
1041
+ }
1042
+
1043
+ /** Returns the row data for a specific filtered row */
1044
+ getDataForFilteredRow(row: number): RowData | null {
1045
+ const p = this._p;
1046
+ if (row < 0 || row > (p.filteredRows || p.rows).length - 1) return null;
1047
+ return (p.filteredRows || p.rows)[row];
1048
+ }
1049
+
1050
+ /** Returns DOM element of the header row */
1051
+ getHeaderRowElement(): HTMLElement | undefined {
1052
+ return this._p.headerRow;
1053
+ }
1054
+
1055
+ /** Add rows to the table */
1056
+ addRows(data: RowData | RowData[], at?: number | boolean, resort?: boolean, render?: boolean) {
1057
+ let p = this._p;
1058
+
1059
+ if (typeof at === 'boolean') {
1060
+ render = resort;
1061
+ resort = at;
1062
+ at = -1;
1063
+ }
1064
+
1065
+ if (typeof at !== 'number')
1066
+ at = -1;
1067
+
1068
+ if (at < 0 || at > p.rows.length)
1069
+ at = p.rows.length;
1070
+
1071
+ render = (render === undefined) ? true : !!render;
1072
+
1073
+ const dataArray = Array.isArray(data) ? data : [data];
1074
+ const dataCount = dataArray.length;
1075
+
1076
+ if (data) {
1077
+ p.rows.add(data, at);
1078
+
1079
+ if (p.filteredRows || (resort && p.rows.sortColumn.length)) {
1080
+
1081
+ if (resort && p.rows.sortColumn.length) {
1082
+ this.resort();
1083
+ } else {
1084
+ refilter(this);
1085
+ }
1086
+
1087
+ p.tableSkeletonNeedsRendering = true;
1088
+
1089
+ if (render) {
1090
+ // Render the skeleton with all rows from scratch
1091
+ this.render();
1092
+ }
1093
+
1094
+ } else if (render) {
1095
+ p.virtualListHelper.addItemsAt(dataCount, at);
1096
+
1097
+ if (this._o.virtualTable) {
1098
+ updateVirtualHeight(this);
1099
+ updateLastCellWidthFromScrollbar(this);
1100
+ this.render();
1101
+ updateTableWidth(this, false);
1102
+
1103
+ } else if (p.tbody) {
1104
+ this.render();
1105
+ updateLastCellWidthFromScrollbar(this);
1106
+ updateTableWidth(this, true);
1107
+ }
1108
+ }
1109
+
1110
+ this.emit('addrows', { count: dataCount, clear: false });
1111
+ }
1112
+ return this;
1113
+ }
1114
+
1115
+ /** Removes rows from the table */
1116
+ removeRows(rowIndex: number, count: number, render?: boolean) {
1117
+ let p = this._p;
1118
+
1119
+ if (typeof count !== 'number' || count <= 0) return this;
1120
+
1121
+ if (rowIndex < 0 || rowIndex > p.rows.length - 1) return this;
1122
+
1123
+ p.rows.splice(rowIndex, count);
1124
+ render = (render === undefined) ? true : !!render;
1125
+
1126
+ if (p.filteredRows) {
1127
+ refilter(this);
1128
+
1129
+ p.tableSkeletonNeedsRendering = true;
1130
+
1131
+ if (render) {
1132
+ // Render the skeleton with all rows from scratch
1133
+ this.render();
1134
+ }
1135
+
1136
+ } else if (render) {
1137
+ p.virtualListHelper.removeItemsAt(count, rowIndex);
1138
+
1139
+ if (this._o.virtualTable) {
1140
+ updateVirtualHeight(this);
1141
+ updateLastCellWidthFromScrollbar(this);
1142
+ this.render();
1143
+ updateTableWidth(this, false);
1144
+ } else {
1145
+ this.render();
1146
+ updateLastCellWidthFromScrollbar(this);
1147
+ updateTableWidth(this, true);
1148
+ }
1149
+ }
1150
+
1151
+ return this;
1152
+ }
1153
+
1154
+ /** Removes a single row from the table */
1155
+ removeRow(rowIndex: number, render?: boolean) {
1156
+ return this.removeRows(rowIndex, 1, render);
1157
+ }
1158
+
1159
+ /** Refreshes the row specified */
1160
+ refreshRow(rowIndex: number, render = true) {
1161
+ let p = this._p;
1162
+
1163
+ if (rowIndex < 0 || rowIndex > p.rows.length - 1)
1164
+ return this;
1165
+
1166
+ // Find out if the row is in the rendered dataset
1167
+ let filteredRowIndex = -1;
1168
+ if (p.filteredRows && (filteredRowIndex = p.filteredRows.indexOf(p.rows[rowIndex])) === -1)
1169
+ return this;
1170
+
1171
+ if (filteredRowIndex === -1) {
1172
+ filteredRowIndex = rowIndex;
1173
+ }
1174
+
1175
+ p.virtualListHelper.refreshItemAt(filteredRowIndex);
1176
+
1177
+ if (render)
1178
+ p.virtualListHelper.render();
1179
+
1180
+ return this;
1181
+ }
1182
+
1183
+ /** Get the DOM element for the specified row, if it exists */
1184
+ getRowElement(rowIndex: number): HTMLElement | null {
1185
+ let p = this._p;
1186
+
1187
+ if (rowIndex < 0 || rowIndex > p.rows.length - 1)
1188
+ return null;
1189
+
1190
+ // Find out if the row is in the rendered dataset
1191
+ let filteredRowIndex = -1;
1192
+ if (p.filteredRows && (filteredRowIndex = p.filteredRows.indexOf(p.rows[rowIndex])) === -1)
1193
+ return null;
1194
+
1195
+ if (filteredRowIndex === -1) {
1196
+ filteredRowIndex = rowIndex;
1197
+ }
1198
+
1199
+ return p.virtualListHelper.getItemElementAt(filteredRowIndex) || null;
1200
+ }
1201
+
1202
+ /** Refreshes all virtual rows */
1203
+ refreshAllVirtualRows() {
1204
+ const p = this._p;
1205
+ p.virtualListHelper.invalidate().render();
1206
+ return this;
1207
+ }
1208
+
1209
+ /** Replace the whole dataset */
1210
+ setRows(data: RowData[], resort?: boolean) {
1211
+ let p = this._p;
1212
+
1213
+ p.rows.reset(data);
1214
+
1215
+ if (resort && p.rows.sortColumn.length) {
1216
+ this.resort();
1217
+ } else {
1218
+ refilter(this);
1219
+ }
1220
+
1221
+ this.clearAndRender().emit('addrows', { count: data.length, clear: true });
1222
+
1223
+ return this;
1224
+ }
1225
+
1226
+ // =========================================================================
1227
+ // PUBLIC API - Size Changes
1228
+ // =========================================================================
1229
+
1230
+ /** Notify the table that its width has changed */
1231
+ tableWidthChanged(forceUpdate?: boolean, renderColumns?: boolean) {
1232
+ let o = this._o,
1233
+ p = this._p,
1234
+ detectedWidth = calculateWidthAvailableForColumns(this),
1235
+ sizeLeft = detectedWidth,
1236
+ relatives = 0;
1237
+
1238
+ if (!p.table) return this;
1239
+
1240
+ renderColumns = renderColumns === undefined || renderColumns;
1241
+
1242
+ let tableWidthBeforeCalculations = 0;
1243
+
1244
+ if (!p.tbody) {
1245
+ renderColumns = false;
1246
+ }
1247
+
1248
+ if (renderColumns) {
1249
+ tableWidthBeforeCalculations = parseFloat(p.tbody.style.minWidth) || 0;
1250
+ }
1251
+
1252
+ if (sizeLeft !== p.lastDetectedWidth || forceUpdate) {
1253
+ p.lastDetectedWidth = detectedWidth;
1254
+
1255
+ let absWidthTotal = 0, changedColumnIndexes = [], totalRelativePercentage = 0;
1256
+
1257
+ for (let i = 0; i < p.columns.length; i++) {
1258
+ p.columns[i].actualWidthConsideringScrollbarWidth = null;
1259
+ }
1260
+
1261
+ for (let i = 0; i < p.visibleColumns.length; i++) {
1262
+ let col = p.visibleColumns[i];
1263
+ if (col.widthMode === ColumnWidthMode.ABSOLUTE) {
1264
+ let width = col.width;
1265
+ width += col.arrowProposedWidth || 0;
1266
+ if (!col.ignoreMin && width < o.minColumnWidth) {
1267
+ width = o.minColumnWidth;
1268
+ }
1269
+ sizeLeft -= width;
1270
+ absWidthTotal += width;
1271
+
1272
+ // Update actualWidth
1273
+ if (width !== col.actualWidth) {
1274
+ col.actualWidth = width;
1275
+ changedColumnIndexes.push(i);
1276
+ }
1277
+ } else if (col.widthMode === ColumnWidthMode.AUTO) {
1278
+ let width = getTextWidth(this, col.label) + 20;
1279
+ width += col.arrowProposedWidth || 0;
1280
+ if (!col.ignoreMin && width < o.minColumnWidth) {
1281
+ width = o.minColumnWidth;
1282
+ }
1283
+ sizeLeft -= width;
1284
+ absWidthTotal += width;
1285
+
1286
+ // Update actualWidth
1287
+ if (width !== col.actualWidth) {
1288
+ col.actualWidth = width;
1289
+ if (!o.convertColumnWidthsToRelative) {
1290
+ changedColumnIndexes.push(i);
1291
+ }
1292
+ }
1293
+ } else if (col.widthMode === ColumnWidthMode.RELATIVE) {
1294
+ totalRelativePercentage += col.width;
1295
+ relatives++;
1296
+ }
1297
+ }
1298
+
1299
+ // Normalize relative sizes if needed
1300
+ if (o.convertColumnWidthsToRelative) {
1301
+ for (let i = 0; i < p.visibleColumns.length; i++) {
1302
+ let col = p.visibleColumns[i];
1303
+ if (col.widthMode === ColumnWidthMode.AUTO) {
1304
+ col.widthMode = ColumnWidthMode.RELATIVE;
1305
+ sizeLeft += col.actualWidth;
1306
+ col.width = col.actualWidth / absWidthTotal;
1307
+ totalRelativePercentage += col.width;
1308
+ relatives++;
1309
+ }
1310
+ }
1311
+ }
1312
+
1313
+ // Normalize relative sizes if needed
1314
+ if (relatives && ((totalRelativePercentage < 1 && o.relativeWidthGrowsToFillWidth) ||
1315
+ (totalRelativePercentage > 1 && o.relativeWidthShrinksToFillWidth))) {
1316
+ for (let i = 0; i < p.visibleColumns.length; i++) {
1317
+ let col = p.visibleColumns[i];
1318
+ if (col.widthMode === ColumnWidthMode.RELATIVE) {
1319
+ col.width /= totalRelativePercentage;
1320
+ }
1321
+ }
1322
+ }
1323
+
1324
+ let sizeLeftForRelative = Math.max(0, sizeLeft); // Use this as the space to take the relative widths out of
1325
+ if (sizeLeftForRelative === 0) {
1326
+ sizeLeftForRelative = p.table.clientWidth;
1327
+ }
1328
+
1329
+ let minColumnWidthRelative = (o.minColumnWidth / sizeLeftForRelative);
1330
+ if (isNaN(minColumnWidthRelative)) {
1331
+ minColumnWidthRelative = 0;
1332
+ }
1333
+ if (minColumnWidthRelative > 0) {
1334
+ let extraRelative = 0, delta;
1335
+
1336
+ // First pass - make sure they are all constrained to the minimum width
1337
+ for (let i = 0; i < p.visibleColumns.length; i++) {
1338
+ let col = p.visibleColumns[i];
1339
+ if (col.widthMode === ColumnWidthMode.RELATIVE) {
1340
+ if (!col.ignoreMin && col.width < minColumnWidthRelative) {
1341
+ extraRelative += minColumnWidthRelative - col.width;
1342
+ col.width = minColumnWidthRelative;
1343
+ }
1344
+ }
1345
+ }
1346
+
1347
+ // Second pass - try to take the extra width out of the other columns to compensate
1348
+ for (let i = 0; i < p.visibleColumns.length; i++) {
1349
+ let col = p.visibleColumns[i];
1350
+ if (col.widthMode === ColumnWidthMode.RELATIVE) {
1351
+ if (!col.ignoreMin && col.width > minColumnWidthRelative) {
1352
+ if (extraRelative > 0) {
1353
+ delta = Math.min(extraRelative, col.width - minColumnWidthRelative);
1354
+ col.width -= delta;
1355
+ extraRelative -= delta;
1356
+ }
1357
+ }
1358
+ }
1359
+ }
1360
+ }
1361
+
1362
+ // Try to fill width
1363
+ if (o.autoFillTableWidth && sizeLeft > 0) {
1364
+ let nonResizableTotal = 0;
1365
+ let sizeLeftToFill = sizeLeft;
1366
+
1367
+ for (let i = 0; i < p.visibleColumns.length; i++) {
1368
+ let col = p.visibleColumns[i];
1369
+ if (!col.resizable && col.widthMode === ColumnWidthMode.ABSOLUTE)
1370
+ nonResizableTotal += col.width;
1371
+
1372
+ if (col.widthMode === ColumnWidthMode.RELATIVE)
1373
+ sizeLeftToFill -= Math.round(sizeLeftForRelative * col.width);
1374
+ }
1375
+
1376
+ let conv = ((detectedWidth - nonResizableTotal) / (detectedWidth - sizeLeftToFill - nonResizableTotal)) || NaN;
1377
+ for (let i = 0; i < p.visibleColumns.length && sizeLeftToFill > 0; i++) {
1378
+ let col = p.visibleColumns[i];
1379
+ if (!col.resizable && col.widthMode === ColumnWidthMode.ABSOLUTE)
1380
+ continue;
1381
+
1382
+ if (col.widthMode === ColumnWidthMode.RELATIVE) {
1383
+ col.width *= conv;
1384
+ } else {
1385
+ let width = col.actualWidth * conv;
1386
+ if (col.actualWidth !== width) {
1387
+ col.actualWidth = width;
1388
+ if (changedColumnIndexes.indexOf(i) === -1)
1389
+ changedColumnIndexes.push(i);
1390
+ }
1391
+ }
1392
+ }
1393
+ }
1394
+
1395
+ // Materialize relative sizes
1396
+ for (let i = 0; i < p.visibleColumns.length; i++) {
1397
+ let col = p.visibleColumns[i];
1398
+ if (col.widthMode === ColumnWidthMode.RELATIVE) {
1399
+ let width = Math.round(sizeLeftForRelative * col.width);
1400
+ sizeLeft -= width;
1401
+ relatives--;
1402
+
1403
+ // Take care of rounding errors
1404
+ if (relatives === 0 && sizeLeft === 1) {
1405
+ width++;
1406
+ sizeLeft--;
1407
+ }
1408
+ if (sizeLeft === -1) {
1409
+ width--;
1410
+ sizeLeft++;
1411
+ }
1412
+
1413
+ // Update actualWidth
1414
+ if (width !== col.actualWidth) {
1415
+ col.actualWidth = width;
1416
+ changedColumnIndexes.push(i);
1417
+ }
1418
+ }
1419
+ }
1420
+
1421
+ if (p.visibleColumns.length) {
1422
+ // (There should always be at least 1 column visible, but just in case)
1423
+ p.visibleColumns[p.visibleColumns.length - 1].actualWidthConsideringScrollbarWidth =
1424
+ p.visibleColumns[p.visibleColumns.length - 1].actualWidth - (p.scrollbarWidth || 0);
1425
+ }
1426
+
1427
+ p.notifyRendererOfColumnsConfig?.();
1428
+
1429
+ if (renderColumns) {
1430
+ let tableWidth = calculateTbodyWidth(this);
1431
+
1432
+ if (tableWidthBeforeCalculations < tableWidth) {
1433
+ updateTableWidth(this, false);
1434
+ }
1435
+
1436
+ for (let i = 0; i < changedColumnIndexes.length; i++) {
1437
+ resizeColumnElements(this, changedColumnIndexes[i]);
1438
+ }
1439
+
1440
+ if (tableWidthBeforeCalculations > tableWidth) {
1441
+ updateTableWidth(this, false);
1442
+ }
1443
+ }
1444
+ }
1445
+
1446
+ return this;
1447
+ }
1448
+
1449
+ /** Notify the table that its height has changed */
1450
+ tableHeightChanged() {
1451
+ let o = this._o,
1452
+ p = this._p;
1453
+
1454
+ if (!p.table) {
1455
+ return this;
1456
+ }
1457
+
1458
+ const tableStyle = getComputedStyle(p.table);
1459
+
1460
+ let height = getElementHeight(this.el, true)
1461
+ - (parseFloat(tableStyle.borderTopWidth) || 0)
1462
+ - (parseFloat(tableStyle.borderBottomWidth) || 0);
1463
+
1464
+ if (height !== o.height) {
1465
+
1466
+ o.height = height;
1467
+
1468
+ if (p.tbody) {
1469
+ p.tbody.style.height = Math.max(o.height - getElementHeight(p.header, true, true, true), 1) + 'px';
1470
+ }
1471
+
1472
+ if (o.virtualTable) {
1473
+ this.clearAndRender();
1474
+ }
1475
+ }
1476
+
1477
+ return this;
1478
+ }
1479
+
1480
+ // =========================================================================
1481
+ // PUBLIC API - Cell Preview
1482
+ // =========================================================================
1483
+
1484
+ /** Hides the current cell preview */
1485
+ hideCellPreview() {
1486
+ hideCellPreview(this);
1487
+ return this;
1488
+ }
1489
+
1490
+ /** A synonym for hideCellPreview() */
1491
+ abortCellPreview() {
1492
+ this.hideCellPreview();
1493
+ return this;
1494
+ }
1495
+
1496
+ /** Cancel a resize in progress */
1497
+ cancelColumnResize() {
1498
+ cancelColumnResize(this);
1499
+ return this;
1500
+ }
1501
+
1502
+ // =========================================================================
1503
+ // PUBLIC API - Web Workers
1504
+ // =========================================================================
1505
+
1506
+ /** Creates a URL representing the data in the specified element (for Web Workers) */
1507
+ getUrlForElementContent(id: string): string | null {
1508
+ let blob,
1509
+ el = document.getElementById(id);
1510
+ if (el) {
1511
+ let data = el.textContent;
1512
+ if (typeof Blob === 'function') {
1513
+ blob = new Blob([data || '']);
1514
+ } else {
1515
+ // Legacy BlobBuilder support (deprecated)
1516
+ const win = window as unknown as Record<string, unknown>;
1517
+ const BlobBuilder = win.BlobBuilder || win.WebKitBlobBuilder || win.MozBlobBuilder || win.MSBlobBuilder;
1518
+ if (!BlobBuilder) {
1519
+ return null;
1520
+ }
1521
+ const builder = new (BlobBuilder as new () => { append(data: string): void; getBlob(): Blob })();
1522
+ builder.append(data || '');
1523
+ blob = builder.getBlob();
1524
+ }
1525
+ return (window.URL || (window as unknown as { webkitURL: typeof URL }).webkitURL).createObjectURL(blob);
1526
+ }
1527
+ return null;
1528
+ }
1529
+
1530
+ /** Check if Web Workers are supported */
1531
+ isWorkerSupported(): boolean {
1532
+ return window['Worker'] instanceof Function;
1533
+ }
1534
+
1535
+ /** Creates a Web Worker for updating the table */
1536
+ createWebWorker(url: string, start?: boolean, resort?: boolean): Worker | null {
1537
+ if (this.isWorkerSupported()) {
1538
+ let p = this._p;
1539
+
1540
+ let worker = new Worker(url);
1541
+ let listener = (evt: MessageEvent) => {
1542
+ if (evt.data.append) {
1543
+ this.addRows(evt.data.rows, resort);
1544
+ } else {
1545
+ this.setRows(evt.data.rows, resort);
1546
+ }
1547
+ };
1548
+ worker.addEventListener('message', listener, false);
1549
+ if (!p.workerListeners) {
1550
+ p.workerListeners = [];
1551
+ }
1552
+ p.workerListeners.push({ worker: worker, listener: listener });
1553
+ if (start || start === undefined) {
1554
+ worker.postMessage(null);
1555
+ }
1556
+ return worker;
1557
+ }
1558
+ return null;
1559
+ }
1560
+
1561
+ /** Unbinds a Web Worker from the table, stopping updates */
1562
+ unbindWebWorker(worker: Worker) {
1563
+ let p = this._p;
1564
+
1565
+ if (p.workerListeners) {
1566
+ for (let j = 0; j < p.workerListeners.length; j++) {
1567
+ if (p.workerListeners[j].worker === worker) {
1568
+ worker.removeEventListener('message', p.workerListeners[j].listener, false);
1569
+ p.workerListeners.splice(j, 1);
1570
+ j--;
1571
+ }
1572
+ }
1573
+ }
1574
+
1575
+ return this;
1576
+ }
1577
+ }
1578
+
1579
+ export default DGTable;
1580
+
1581
+ // Re-export types for TypeScript users
1582
+ export type {
1583
+ // Configuration types
1584
+ DGTableOptions,
1585
+ ColumnOptions,
1586
+ ColumnSortOptions,
1587
+ SerializedColumn,
1588
+ SerializedColumnSort,
1589
+ RowData,
1590
+ // Function types
1591
+ CellFormatter,
1592
+ HeaderCellFormatter,
1593
+ FilterFunction,
1594
+ ComparatorFunction,
1595
+ OnComparatorRequired,
1596
+ CustomSortingProvider,
1597
+ // Event types
1598
+ RowCreateEvent,
1599
+ RowClickEvent,
1600
+ CellPreviewEvent,
1601
+ CellPreviewDestroyEvent,
1602
+ HeaderContextMenuEvent,
1603
+ MoveColumnEvent,
1604
+ ColumnWidthEvent,
1605
+ AddRowsEvent,
1606
+ SortEvent,
1607
+ // Event map for typed handlers
1608
+ DGTableEventMap,
1609
+ } from './types';
1610
+
1611
+
1612
+
1613
+
1614
+
1615
+
1616
+
1617
+
1618
+
1619
+
1620
+
1621
+
1622
+
1623
+
1624
+
1625
+
1626
+
1627
+
1628
+