@danielgindi/dgtable.js 2.0.7 → 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 +6909 -3929
  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 +6911 -3931
  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 -4346
  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 -3991
  66. package/src/row_collection.js +0 -183
  67. package/src/util.js +0 -17
@@ -0,0 +1,369 @@
1
+ /**
2
+ * Header events functionality for DGTable
3
+ */
4
+
5
+ import { find } from './util';
6
+ /* eslint-disable @typescript-eslint/ban-ts-comment */
7
+ // @ts-ignore - No type declarations available for this module
8
+ import { getElementWidth, getElementHeight, getElementOffset } from '@danielgindi/dom-utils/lib/Css.js';
9
+ import { isInputElementEvent } from './helpers';
10
+ import {
11
+ getColumnByResizePosition,
12
+ cancelColumnResize,
13
+ onMouseDownColumnHeader,
14
+ } from './column_resize';
15
+ import type { DGTableInterface } from './private_types';
16
+ import { RelatedTouchSymbol } from './private_types';
17
+
18
+ // Extended element types
19
+ interface HeaderCellElement extends HTMLElement {
20
+ columnName?: string;
21
+ }
22
+
23
+ interface DragData {
24
+ dragId: number;
25
+ column: string;
26
+ }
27
+
28
+ type PositionHost = {
29
+ pageX: number;
30
+ pageY: number;
31
+ clientX?: number;
32
+ identifier?: number;
33
+ };
34
+
35
+ type TouchOrMouseEvent = (MouseEvent | TouchEvent) & {
36
+ [RelatedTouchSymbol]?: PositionHost;
37
+ currentTarget: HTMLElement;
38
+ target: HTMLElement;
39
+ };
40
+
41
+ interface TableWithSort extends DGTableInterface {
42
+ sort(column?: string, descending?: boolean, add?: boolean): this;
43
+ render(): this;
44
+ moveColumn(src: string, dest: string): void;
45
+ }
46
+
47
+ /**
48
+ * Handle touch start on column header
49
+ */
50
+ export function onTouchStartColumnHeader(table: DGTableInterface, event: TouchEvent & { [RelatedTouchSymbol]?: PositionHost }): void {
51
+ const p = table._p;
52
+
53
+ if (p.currentTouchId) return;
54
+
55
+ const startTouch = event.changedTouches[0];
56
+ p.currentTouchId = startTouch.identifier;
57
+
58
+ const cellEl = event.currentTarget as HTMLElement;
59
+
60
+ const startPos = { x: startTouch.pageX, y: startTouch.pageY };
61
+ let currentPos = startPos;
62
+ const distanceTreshold = 9;
63
+
64
+ let tapAndHoldTimeout: ReturnType<typeof setTimeout>;
65
+
66
+ const unbind = function () {
67
+ p.currentTouchId = null;
68
+ p.eventsSink.remove(cellEl, '.colheader');
69
+ clearTimeout(tapAndHoldTimeout);
70
+ };
71
+
72
+ (event as any)[RelatedTouchSymbol] = event.changedTouches[0];
73
+ onMouseDownColumnHeader(table, event as unknown as MouseEvent & { [RelatedTouchSymbol]?: PositionHost });
74
+
75
+ tapAndHoldTimeout = setTimeout(() => {
76
+ unbind();
77
+
78
+ p.eventsSink
79
+ .add(cellEl, 'touchend.colheader', (event: Event) => {
80
+ if (!isInputElementEvent(event))
81
+ event.preventDefault();
82
+
83
+ p.eventsSink.remove(cellEl, '.colheader');
84
+ }, { once: true })
85
+ .add(cellEl, 'touchcancel.colheader', (_event: Event) => {
86
+ p.eventsSink.remove(cellEl, '.colheader');
87
+ }, { once: true });
88
+
89
+ const distanceTravelled = Math.sqrt(Math.pow(Math.abs(currentPos.x - startPos.x), 2) + Math.pow(Math.abs(currentPos.y - startPos.y), 2));
90
+
91
+ if (distanceTravelled < distanceTreshold) {
92
+ cancelColumnResize(table);
93
+ triggerColumnHeaderContextMenu(table, event);
94
+ }
95
+
96
+ }, 500);
97
+
98
+ p.eventsSink
99
+ .add(cellEl, 'touchend.colheader', (event: Event) => {
100
+ const touchEvent = event as TouchEvent & { [RelatedTouchSymbol]?: PositionHost };
101
+ const touch = find(Array.from(touchEvent.changedTouches), (t) => t.identifier === p.currentTouchId);
102
+ if (!touch) return;
103
+
104
+ unbind();
105
+
106
+ if (!isInputElementEvent(event))
107
+ event.preventDefault();
108
+
109
+ currentPos = { x: touch.pageX, y: touch.pageY };
110
+ const distanceTravelled = Math.sqrt(Math.pow(Math.abs(currentPos.x - startPos.x), 2) + Math.pow(Math.abs(currentPos.y - startPos.y), 2));
111
+
112
+ if (distanceTravelled < distanceTreshold || p.resizer) {
113
+ (touchEvent as any)[RelatedTouchSymbol] = touch;
114
+ onSortOnColumnHeaderEvent(table, touchEvent);
115
+ }
116
+
117
+ })
118
+ .add(cellEl, 'touchcancel.colheader', unbind)
119
+ .add(cellEl, 'touchmove.colheader', (event: Event) => {
120
+ const touchEvent = event as TouchEvent & { [RelatedTouchSymbol]?: PositionHost };
121
+ const touch = find(Array.from(touchEvent.changedTouches), (t) => t.identifier === p.currentTouchId);
122
+ if (!touch) return;
123
+
124
+ currentPos = { x: touch.pageX, y: touch.pageY };
125
+
126
+ if (p.resizer) {
127
+ event.preventDefault();
128
+
129
+ (touchEvent as any)[RelatedTouchSymbol] = touch;
130
+ onMouseMoveColumnHeader(table, touchEvent);
131
+ }
132
+ });
133
+ }
134
+
135
+ /**
136
+ * Handle mouse move on column header
137
+ */
138
+ export function onMouseMoveColumnHeader(table: DGTableInterface, event: Event): void {
139
+ const o = table._o;
140
+ const p = table._p;
141
+
142
+ if (!o.resizableColumns)
143
+ return;
144
+
145
+ const col = getColumnByResizePosition(table, event as MouseEvent);
146
+ const headerCell = (event.target as HTMLElement).closest(`div.${o.tableClassName}-header-cell,div.${o.cellPreviewClassName}`) as HTMLElement;
147
+ if (!headerCell) return;
148
+
149
+ if (!col || !p.columns.get(col)?.resizable) {
150
+ headerCell.style.cursor = '';
151
+ } else {
152
+ headerCell.style.cursor = 'e-resize';
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Handle mouse up on column header
158
+ */
159
+ export function onMouseUpColumnHeader(table: DGTableInterface, event: Event): void {
160
+ if ((event as MouseEvent).button !== 2)
161
+ return;
162
+
163
+ triggerColumnHeaderContextMenu(table, event);
164
+ }
165
+
166
+ /**
167
+ * Trigger context menu on column header
168
+ */
169
+ export function triggerColumnHeaderContextMenu(table: DGTableInterface, event: Event): void {
170
+ const o = table._o;
171
+
172
+ const touchEvent = event as TouchEvent;
173
+ const mouseEvent = event as MouseEvent;
174
+ const positionHost: PositionHost = (event as TouchOrMouseEvent)[RelatedTouchSymbol] ?? touchEvent.changedTouches?.[0] ?? { pageX: mouseEvent.pageX, pageY: mouseEvent.pageY };
175
+
176
+ const headerCell = (event.target as HTMLElement).closest(`div.${o.tableClassName}-header-cell,div.${o.cellPreviewClassName}`) as HeaderCellElement;
177
+ if (!headerCell) return;
178
+
179
+ const bounds = getElementOffset(headerCell) as { left: number; top: number; width?: number; height?: number };
180
+ bounds.width = getElementWidth(headerCell, true, true, true);
181
+ bounds.height = getElementHeight(headerCell, true, true, true);
182
+ table.emit('headercontextmenu', {
183
+ columnName: headerCell.columnName,
184
+ pageX: positionHost.pageX,
185
+ pageY: positionHost.pageY,
186
+ bounds: bounds,
187
+ });
188
+ }
189
+
190
+ /**
191
+ * Handle mouse leave on column header
192
+ */
193
+ export function onMouseLeaveColumnHeader(table: DGTableInterface, event: MouseEvent): void {
194
+ const o = table._o;
195
+ const headerCell = (event.target as HTMLElement).closest(`div.${o.tableClassName}-header-cell,div.${o.cellPreviewClassName}`) as HTMLElement;
196
+ if (headerCell) {
197
+ headerCell.style.cursor = '';
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Handle sort click on column header
203
+ */
204
+ export function onSortOnColumnHeaderEvent(table: DGTableInterface, event: Event): void {
205
+ if (isInputElementEvent(event))
206
+ return;
207
+
208
+ if (getColumnByResizePosition(table, event as MouseEvent))
209
+ return;
210
+
211
+ const o = table._o;
212
+ const p = table._p;
213
+
214
+ const headerCell = (event.target as HTMLElement).closest(`div.${o.tableClassName}-header-cell,div.${o.cellPreviewClassName}`) as HeaderCellElement;
215
+ if (!headerCell) return;
216
+
217
+ if (!o.sortableColumns)
218
+ return;
219
+
220
+ const column = p.columns.get(headerCell.columnName!);
221
+ const currentSort = p.rows.sortColumn;
222
+ if (column && column.sortable) {
223
+ let shouldAdd = true;
224
+
225
+ const lastSort = currentSort.length ? currentSort[currentSort.length - 1] : null;
226
+
227
+ if (lastSort && lastSort.column === column.name) {
228
+ if (!lastSort.descending || !o.allowCancelSort) {
229
+ lastSort.descending = !lastSort.descending;
230
+ } else {
231
+ shouldAdd = false;
232
+ currentSort.splice(currentSort.length - 1, 1);
233
+ }
234
+ }
235
+
236
+ const tableWithSort = table as unknown as TableWithSort;
237
+ if (shouldAdd) {
238
+ tableWithSort.sort(column.name, undefined, true).render();
239
+ } else {
240
+ tableWithSort.sort(); // just refresh current situation
241
+ }
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Handle drag start on column header
247
+ */
248
+ export function onStartDragColumnHeader(table: DGTableInterface, event: DragEvent): void {
249
+ const o = table._o;
250
+ const p = table._p;
251
+
252
+ if (o.movableColumns) {
253
+ const headerCell = (event.target as HTMLElement).closest(`div.${o.tableClassName}-header-cell,div.${o.cellPreviewClassName}`) as HeaderCellElement;
254
+ if (!headerCell) {
255
+ event.preventDefault();
256
+ return;
257
+ }
258
+
259
+ const column = p.columns.get(headerCell.columnName!);
260
+ if (column && column.movable) {
261
+ headerCell.style.opacity = '0.35';
262
+ p.dragId = Math.random() * 0x9999999;
263
+ event.dataTransfer?.setData('text', JSON.stringify({ dragId: p.dragId, column: column.name }));
264
+ } else {
265
+ event.preventDefault();
266
+ }
267
+ } else {
268
+ event.preventDefault();
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Handle drag end on column header
274
+ */
275
+ export function onDragEndColumnHeader(table: DGTableInterface, event: DragEvent): void {
276
+ const p = table._p;
277
+
278
+ if (!p.resizer) {
279
+ (event.target as HTMLElement).style.opacity = '';
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Handle drag enter on column header
285
+ */
286
+ export function onDragEnterColumnHeader(table: DGTableInterface, event: DragEvent): void {
287
+ const o = table._o;
288
+ const p = table._p;
289
+
290
+ if (o.movableColumns) {
291
+ let dataTransferred: DragData | null = null;
292
+ const dataStr = event.dataTransfer?.getData('text');
293
+ if (dataStr) {
294
+ try {
295
+ dataTransferred = JSON.parse(dataStr);
296
+ } catch {
297
+ dataTransferred = null;
298
+ }
299
+ }
300
+
301
+ const headerCell = (event.target as HTMLElement).closest(`div.${o.tableClassName}-header-cell,div.${o.cellPreviewClassName}`) as HeaderCellElement;
302
+ if (!headerCell) return;
303
+
304
+ if (!dataTransferred ||
305
+ (p.dragId === dataTransferred.dragId && headerCell.columnName !== dataTransferred.column)) {
306
+
307
+ const column = p.columns.get(headerCell.columnName!);
308
+ if (column && (column.movable || column !== p.visibleColumns[0])) {
309
+ headerCell.classList.add('drag-over');
310
+ }
311
+ }
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Handle drag over on column header
317
+ */
318
+ export function onDragOverColumnHeader(_table: DGTableInterface, event: DragEvent): void {
319
+ event.preventDefault();
320
+ }
321
+
322
+ /**
323
+ * Handle drag leave on column header
324
+ */
325
+ export function onDragLeaveColumnHeader(table: DGTableInterface, event: DragEvent): void {
326
+ const o = table._o;
327
+ const headerCell = (event.target as HTMLElement).closest(`div.${o.tableClassName}-header-cell,div.${o.cellPreviewClassName}`) as HTMLElement;
328
+ if (!headerCell) return;
329
+
330
+ const relatedTarget = event.relatedTarget as HTMLElement;
331
+ if (!relatedTarget?.contains(headerCell.firstChild)) {
332
+ headerCell.classList.remove('drag-over');
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Handle drop on column header
338
+ */
339
+ export function onDropColumnHeader(table: DGTableInterface, event: DragEvent): void {
340
+ event.preventDefault();
341
+
342
+ const o = table._o;
343
+ const p = table._p;
344
+
345
+ const dataStr = event.dataTransfer?.getData('text');
346
+ if (!dataStr) return;
347
+
348
+ let dataTransferred: DragData;
349
+ try {
350
+ dataTransferred = JSON.parse(dataStr);
351
+ } catch {
352
+ return;
353
+ }
354
+
355
+ const headerCell = (event.target as HTMLElement).closest(`div.${o.tableClassName}-header-cell,div.${o.cellPreviewClassName}`) as HeaderCellElement;
356
+ if (!headerCell) return;
357
+
358
+ if (o.movableColumns && dataTransferred.dragId === p.dragId) {
359
+ const srcColName = dataTransferred.column;
360
+ const destColName = headerCell.columnName!;
361
+ const srcCol = p.columns.get(srcColName);
362
+ const destCol = p.columns.get(destColName);
363
+ if (srcCol && destCol && srcCol.movable && (destCol.movable || destCol !== p.visibleColumns[0])) {
364
+ (table as unknown as TableWithSort).moveColumn(srcColName, destColName);
365
+ }
366
+ }
367
+ headerCell.classList.remove('drag-over');
368
+ }
369
+
package/src/helpers.ts ADDED
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Helper functions for DGTable
3
+ * These are extracted to keep the main class focused on public API
4
+ */
5
+
6
+ /* eslint-disable @typescript-eslint/ban-ts-comment */
7
+ // @ts-ignore - No type declarations available for this module
8
+ import { getElementWidth, setCssProps } from '@danielgindi/dom-utils/lib/Css.js';
9
+
10
+ import { ColumnWidthMode } from './constants';
11
+ import type { InternalColumn, DGTableInterface } from './private_types';
12
+
13
+ const createElement = document.createElement.bind(document);
14
+
15
+ /**
16
+ * BUGFIX: WebKit has a bug where it does not relayout
17
+ */
18
+ export function webkitRenderBugfix(el: HTMLElement): HTMLElement {
19
+ const oldDisplay = el.style.display;
20
+ el.style.display = 'none';
21
+ // No need to store this anywhere, the reference is enough
22
+ void el.offsetHeight;
23
+ el.style.display = oldDisplay;
24
+ return el;
25
+ }
26
+
27
+ /**
28
+ * Make element relative if not already positioned
29
+ */
30
+ export function relativizeElement(el: HTMLElement): void {
31
+ if (!['relative', 'absolute', 'fixed'].includes(getComputedStyle(el).position)) {
32
+ el.style.position = 'relative';
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Check if event target is an input element
38
+ */
39
+ export function isInputElementEvent(event: Event): boolean {
40
+ const target = event.target as HTMLElement;
41
+ return /^(?:INPUT|TEXTAREA|BUTTON|SELECT)$/.test(target.tagName);
42
+ }
43
+
44
+ /**
45
+ * Calculate horizontal padding of an element
46
+ */
47
+ export function horizontalPadding(el: Element): number {
48
+ const style = getComputedStyle(el);
49
+ return ((parseFloat(style.paddingLeft) || 0) +
50
+ (parseFloat(style.paddingRight) || 0));
51
+ }
52
+
53
+ /**
54
+ * Calculate horizontal border width of an element
55
+ */
56
+ export function horizontalBorderWidth(el: Element): number {
57
+ const style = getComputedStyle(el);
58
+ return ((parseFloat(style.borderLeftWidth) || 0) +
59
+ (parseFloat(style.borderRightWidth) || 0));
60
+ }
61
+
62
+ /**
63
+ * Disable CSS text selection on an element
64
+ */
65
+ export function disableCssSelect(el: HTMLElement): void {
66
+ const style = el.style as CSSStyleDeclaration & Record<string, string>;
67
+ style['-webkit-touch-callout'] = 'none';
68
+ style['-webkit-user-select'] = 'none';
69
+ style['-moz-user-select'] = 'none';
70
+ style['-ms-user-select'] = 'none';
71
+ style['-o-user-select'] = 'none';
72
+ style['user-select'] = 'none';
73
+ }
74
+
75
+ /**
76
+ * Get text width by measuring in a temporary element
77
+ */
78
+ export function getTextWidth(table: DGTableInterface, text: string): number {
79
+ const tableClassName = table._o.tableClassName;
80
+
81
+ const tableWrapper = createElement('div');
82
+ tableWrapper.className = table.el.className;
83
+ const header = createElement('div');
84
+ header.className = tableClassName + '-header';
85
+ const headerRow = createElement('div');
86
+ headerRow.className = tableClassName + '-header-row';
87
+ const cell = createElement('div');
88
+ cell.className = tableClassName + '-header-cell';
89
+ const cellContent = createElement('div');
90
+ cellContent.textContent = text;
91
+
92
+ cell.appendChild(cellContent);
93
+ headerRow.appendChild(cell);
94
+ header.appendChild(headerRow);
95
+ tableWrapper.appendChild(header);
96
+ setCssProps(tableWrapper, {
97
+ position: 'absolute',
98
+ top: '-9999px',
99
+ visibility: 'hidden',
100
+ });
101
+
102
+ document.body.appendChild(tableWrapper);
103
+
104
+ const width = getElementWidth(cell);
105
+
106
+ tableWrapper.remove();
107
+
108
+ return width;
109
+ }
110
+
111
+ /**
112
+ * Calculate width available for columns
113
+ */
114
+ export function calculateWidthAvailableForColumns(table: DGTableInterface): number {
115
+ const o = table._o, p = table._p;
116
+
117
+ // Changing display mode briefly, to prevent taking in account the parent's scrollbar width when we are the cause for it
118
+ let oldDisplay: string | undefined;
119
+ let lastScrollTop = 0;
120
+ let lastScrollLeft = 0;
121
+ if (p.table) {
122
+ lastScrollTop = p.table ? p.table.scrollTop : 0;
123
+ lastScrollLeft = p.table ? p.table.scrollLeft : 0;
124
+
125
+ if (o.virtualTable) {
126
+ oldDisplay = p.table.style.display;
127
+ p.table.style.display = 'none';
128
+ }
129
+ }
130
+
131
+ let detectedWidth = getElementWidth(table.el);
132
+
133
+ if (p.table) {
134
+ if (o.virtualTable && oldDisplay !== undefined) {
135
+ p.table.style.display = oldDisplay;
136
+ }
137
+
138
+ p.table.scrollTop = lastScrollTop;
139
+ p.table.scrollLeft = lastScrollLeft;
140
+ if (p.header) {
141
+ p.header.scrollLeft = lastScrollLeft;
142
+ }
143
+ }
144
+
145
+ const tableClassName = o.tableClassName;
146
+
147
+ const thisWrapper = createElement('div');
148
+ thisWrapper.className = table.el.className;
149
+ setCssProps(thisWrapper, {
150
+ 'z-index': '-1',
151
+ 'position': 'absolute',
152
+ left: '0',
153
+ top: '-9999px',
154
+ });
155
+ const header = createElement('div');
156
+ header.className = `${tableClassName}-header`;
157
+ thisWrapper.appendChild(header);
158
+ const headerRow = createElement('div') as HTMLDivElement & { index: null; vIndex: null };
159
+ headerRow.index = null;
160
+ headerRow.vIndex = null;
161
+ headerRow.className = `${tableClassName}-header-row`;
162
+ header.appendChild(headerRow);
163
+ for (let i = 0; i < p.visibleColumns.length; i++) {
164
+ const column = p.visibleColumns[i];
165
+ const cell = createElement('div') as HTMLDivElement & { columnName: string };
166
+ cell.className = `${tableClassName}-header-cell ${column.cellClasses || ''}`;
167
+ cell.columnName = column.name;
168
+ cell.appendChild(createElement('div'));
169
+ headerRow.appendChild(cell);
170
+ }
171
+ document.body.appendChild(thisWrapper);
172
+
173
+ detectedWidth -= horizontalBorderWidth(headerRow);
174
+
175
+ const cells = headerRow.querySelectorAll(`div.${tableClassName}-header-cell`) as NodeListOf<HTMLDivElement & { columnName: string }>;
176
+ for (const cell of cells) {
177
+ const cellStyle = getComputedStyle(cell);
178
+ const isBoxing = cellStyle.boxSizing === 'border-box';
179
+ if (!isBoxing) {
180
+ detectedWidth -=
181
+ (parseFloat(cellStyle.borderRightWidth) || 0) +
182
+ (parseFloat(cellStyle.borderLeftWidth) || 0) +
183
+ (horizontalPadding(cell)); // CELL's padding
184
+
185
+ const colName = cell.columnName;
186
+ const column = p.columns.get(colName);
187
+ if (column)
188
+ detectedWidth -= column.arrowProposedWidth || 0;
189
+ }
190
+ }
191
+
192
+ thisWrapper.remove();
193
+
194
+ return Math.max(0, detectedWidth);
195
+ }
196
+
197
+ /**
198
+ * Calculate the size required for the table body width
199
+ */
200
+ export function calculateTbodyWidth(table: DGTableInterface): number {
201
+ const p = table._p;
202
+ const o = table._o;
203
+
204
+ const tableClassName = o.tableClassName;
205
+ const rowClassName = tableClassName + '-row';
206
+ const cellClassName = tableClassName + '-cell';
207
+ const visibleColumns = p.visibleColumns;
208
+ const colCount = visibleColumns.length;
209
+
210
+ const row = createElement('div');
211
+ row.className = rowClassName;
212
+ row.style.float = 'left';
213
+
214
+ let sumActualWidth = 0;
215
+
216
+ for (let colIndex = 0; colIndex < colCount; colIndex++) {
217
+ const column = visibleColumns[colIndex];
218
+ const cell = createElement('div');
219
+ cell.className = cellClassName;
220
+ cell.style.width = (column.actualWidth ?? 0) + 'px';
221
+ if (column.cellClasses) cell.className += ' ' + column.cellClasses;
222
+ cell.appendChild(createElement('div'));
223
+ row.appendChild(cell);
224
+ sumActualWidth += column.actualWidth ?? 0;
225
+ }
226
+
227
+ const thisWrapper = createElement('div');
228
+ thisWrapper.className = table.el.className;
229
+ setCssProps(thisWrapper, {
230
+ 'z-index': '-1',
231
+ 'position': 'absolute',
232
+ 'left': '0',
233
+ 'top': '-9999px',
234
+ 'float': 'left',
235
+ 'width': '1px',
236
+ 'overflow': 'hidden',
237
+ });
238
+
239
+ const tableDiv = createElement('div');
240
+ tableDiv.className = tableClassName;
241
+ thisWrapper.appendChild(tableDiv);
242
+ const tableBodyDiv = createElement('div');
243
+ tableBodyDiv.className = tableClassName + '-body';
244
+ tableBodyDiv.style.width = (sumActualWidth + 10000) + 'px';
245
+ tableDiv.appendChild(tableBodyDiv);
246
+ tableBodyDiv.appendChild(row);
247
+
248
+ document.body.appendChild(thisWrapper);
249
+
250
+ const fractionTest = createElement('div');
251
+ setCssProps(fractionTest, {
252
+ border: '1.5px solid #000',
253
+ width: '0',
254
+ height: '0',
255
+ position: 'absolute',
256
+ left: '0',
257
+ top: '-9999px',
258
+ });
259
+ document.body.appendChild(fractionTest);
260
+ const fractionValue = parseFloat(getComputedStyle(fractionTest).borderWidth);
261
+ const hasFractions = Math.round(fractionValue) !== fractionValue;
262
+ fractionTest.remove();
263
+
264
+ let width = getElementWidth(row, true, true, true);
265
+ width -= p.scrollbarWidth || 0;
266
+
267
+ if (hasFractions) {
268
+ width++;
269
+ }
270
+
271
+ thisWrapper.remove();
272
+ return width;
273
+ }
274
+
275
+ /**
276
+ * Check if table is RTL
277
+ */
278
+ export function isTableRtl(table: DGTableInterface): boolean {
279
+ const p = table._p;
280
+ return p.table ? getComputedStyle(p.table).direction === 'rtl' : false;
281
+ }
282
+
283
+ /**
284
+ * Serialize column width to string
285
+ */
286
+ export function serializeColumnWidth(column: InternalColumn): string | number {
287
+ return column.widthMode === ColumnWidthMode.AUTO ? 'auto' :
288
+ column.widthMode === ColumnWidthMode.RELATIVE ? column.width * 100 + '%' :
289
+ column.width;
290
+ }
291
+