@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,36 @@
1
+ /**
2
+ * Arguments for the by-column filter
3
+ */
4
+ export interface ByColumnFilterArgs {
5
+ column: string;
6
+ keyword?: string | null;
7
+ caseSensitive?: boolean;
8
+ }
9
+
10
+ /**
11
+ * Default filter function that filters rows by a column value containing a keyword
12
+ */
13
+ function ByColumnFilter(row: Record<string, unknown>, args: ByColumnFilterArgs): boolean {
14
+ const column = args.column;
15
+ const keyword = args.keyword == null ? '' : args.keyword.toString();
16
+
17
+ if (!keyword || !column) return true;
18
+
19
+ let actualVal = row[column];
20
+ if (actualVal == null) {
21
+ return false;
22
+ }
23
+
24
+ let actualValStr = actualVal.toString();
25
+ let keywordStr = keyword;
26
+
27
+ if (!args.caseSensitive) {
28
+ actualValStr = actualValStr.toLowerCase();
29
+ keywordStr = keywordStr.toLowerCase();
30
+ }
31
+
32
+ return actualValStr.indexOf(keywordStr) !== -1;
33
+ }
34
+
35
+ export default ByColumnFilter;
36
+
@@ -0,0 +1,325 @@
1
+ /**
2
+ * Cell preview functionality for DGTable
3
+ */
4
+
5
+ /* eslint-disable @typescript-eslint/ban-ts-comment */
6
+ // @ts-ignore - No type declarations available for this module
7
+ import { getElementWidth, getElementHeight, getElementOffset, setCssProps } from '@danielgindi/dom-utils/lib/Css.js';
8
+ import SelectionHelper from './SelectionHelper';
9
+ import { disableCssSelect } from './helpers';
10
+ import type { RowData } from './types';
11
+ import type { DGTableInterface } from './private_types';
12
+ import {
13
+ OriginalCellSymbol,
14
+ PreviewCellSymbol,
15
+ } from './private_types';
16
+ import { bindHeaderColumnEvents } from "./internal";
17
+
18
+ const nativeIndexOf = Array.prototype.indexOf;
19
+ const createElement = document.createElement.bind(document);
20
+
21
+ // Extended element types for cell preview
22
+ interface PreviewCellElement extends HTMLDivElement {
23
+ rowVIndex?: number;
24
+ rowIndex?: number;
25
+ columnName?: string;
26
+ [OriginalCellSymbol]?: HTMLElement;
27
+ }
28
+
29
+ interface CellElement extends HTMLElement {
30
+ [PreviewCellSymbol]?: PreviewCellElement;
31
+ }
32
+
33
+ interface RowElement extends HTMLElement {
34
+ vIndex?: number;
35
+ index?: number;
36
+ }
37
+
38
+ interface ElementOffset {
39
+ left: number;
40
+ top: number;
41
+ right?: number;
42
+ }
43
+
44
+ /**
45
+ * Handle cell mouse over event - show preview if content overflows
46
+ */
47
+ export function cellMouseOverEvent(table: DGTableInterface, el: CellElement): void {
48
+ const o = table._o, p = table._p;
49
+
50
+ const elInner = el.firstElementChild as HTMLElement;
51
+ if (!elInner) return;
52
+
53
+ if (!((elInner.scrollWidth - elInner.clientWidth > 1) ||
54
+ (elInner.scrollHeight - elInner.clientHeight > 1)))
55
+ return;
56
+
57
+ hideCellPreview(table);
58
+ p.abortCellPreview = false;
59
+
60
+ const rowEl = el.parentElement as RowElement;
61
+ if (!rowEl) return;
62
+
63
+ const previewCell = createElement('div') as PreviewCellElement;
64
+ previewCell.innerHTML = el.innerHTML;
65
+ previewCell.className = o.cellPreviewClassName;
66
+
67
+ const isHeaderCell = el.classList.contains(`${o.tableClassName}-header-cell`);
68
+ if (isHeaderCell) {
69
+ previewCell.classList.add('header');
70
+ if (el.classList.contains('sortable')) {
71
+ previewCell.classList.add('sortable');
72
+ }
73
+
74
+ previewCell.draggable = true;
75
+
76
+ bindHeaderColumnEvents(table, previewCell);
77
+ }
78
+
79
+ const elStyle = getComputedStyle(el);
80
+ const elInnerStyle = getComputedStyle(elInner);
81
+
82
+ const rtl = elStyle.float === 'right';
83
+ const prop = rtl ? 'right' : 'left';
84
+
85
+ const paddingL = parseFloat(elStyle.paddingLeft) || 0;
86
+ const paddingR = parseFloat(elStyle.paddingRight) || 0;
87
+ const paddingT = parseFloat(elStyle.paddingTop) || 0;
88
+ const paddingB = parseFloat(elStyle.paddingBottom) || 0;
89
+
90
+ let requiredWidth = elInner.scrollWidth + (el.clientWidth - elInner.offsetWidth);
91
+
92
+ const borderBox = elStyle.boxSizing === 'border-box';
93
+ if (borderBox) {
94
+ previewCell.style.boxSizing = 'border-box';
95
+ } else {
96
+ requiredWidth -= paddingL + paddingR;
97
+ previewCell.style.marginTop = (parseFloat(elStyle.borderTopWidth) || 0) + 'px';
98
+ }
99
+
100
+ if (!p.transparentBgColor1) {
101
+ // Detect browser's transparent spec
102
+ const tempDiv = document.createElement('div');
103
+ document.body.appendChild(tempDiv);
104
+ tempDiv.style.backgroundColor = 'transparent';
105
+ p.transparentBgColor1 = getComputedStyle(tempDiv).backgroundColor;
106
+ tempDiv.style.backgroundColor = 'rgba(0,0,0,0)';
107
+ p.transparentBgColor2 = getComputedStyle(tempDiv).backgroundColor;
108
+ tempDiv.remove();
109
+ }
110
+
111
+ const css: Record<string, string | number> = {
112
+ 'box-sizing': borderBox ? 'border-box' : 'content-box',
113
+ 'width': requiredWidth,
114
+ 'min-height': Math.max(getElementHeight(el), /%/.test(elStyle.minHeight) ? 0 : (parseFloat(elStyle.minHeight) || 0)) + 'px',
115
+ 'padding-left': paddingL,
116
+ 'padding-right': paddingR,
117
+ 'padding-top': paddingT,
118
+ 'padding-bottom': paddingB,
119
+ 'overflow': 'hidden',
120
+ 'position': 'absolute',
121
+ 'z-index': '-1',
122
+ [prop]: '0',
123
+ 'top': '0',
124
+ 'cursor': elStyle.cursor,
125
+ };
126
+
127
+ let bgColor = elStyle.backgroundColor;
128
+ if (bgColor === p.transparentBgColor1 || bgColor === p.transparentBgColor2) {
129
+ bgColor = getComputedStyle(rowEl).backgroundColor;
130
+ }
131
+ if (bgColor === p.transparentBgColor1 || bgColor === p.transparentBgColor2) {
132
+ bgColor = '#fff';
133
+ }
134
+ css['background-color'] = bgColor;
135
+
136
+ setCssProps(previewCell, css);
137
+ if (previewCell.firstChild) {
138
+ setCssProps(previewCell.firstChild as HTMLElement, {
139
+ 'direction': elInnerStyle.direction,
140
+ 'white-space': elInnerStyle.whiteSpace,
141
+ 'min-height': elInnerStyle.minHeight,
142
+ 'line-height': elInnerStyle.lineHeight,
143
+ 'font': elInnerStyle.font,
144
+ });
145
+ }
146
+
147
+ table.el.appendChild(previewCell);
148
+
149
+ if (isHeaderCell) {
150
+ disableCssSelect(previewCell);
151
+ }
152
+
153
+ previewCell.rowVIndex = rowEl.vIndex;
154
+ const rowIndex = previewCell.rowIndex = rowEl.index;
155
+ previewCell.columnName = p.visibleColumns[nativeIndexOf.call(rowEl.childNodes, el)]?.name;
156
+
157
+ try {
158
+ const selection = SelectionHelper.saveSelection(el);
159
+ if (selection)
160
+ SelectionHelper.restoreSelection(previewCell, selection);
161
+ } catch {
162
+ /* we're ok with this */
163
+ }
164
+
165
+ table.emit('cellpreview', {
166
+ el: previewCell.firstElementChild,
167
+ name: previewCell.columnName,
168
+ rowIndex: rowIndex ?? null,
169
+ rowData: rowIndex == null ? null : p.rows[rowIndex] as RowData,
170
+ cell: el,
171
+ cellEl: elInner,
172
+ });
173
+
174
+ if (p.abortCellPreview) {
175
+ previewCell.remove();
176
+ return;
177
+ }
178
+
179
+ if (rowIndex != null) {
180
+ previewCell.addEventListener('click', (event: MouseEvent) => {
181
+ table.emit('rowclick', {
182
+ event: event,
183
+ filteredRowIndex: rowEl.vIndex,
184
+ rowIndex: rowIndex,
185
+ rowEl: rowEl,
186
+ rowData: p.rows[rowIndex] as RowData,
187
+ });
188
+ });
189
+ }
190
+
191
+ const parent = table.el;
192
+ const scrollParent = parent === (window as unknown as HTMLElement) ? document : parent;
193
+
194
+ const parentStyle = getComputedStyle(parent);
195
+
196
+ const offset = getElementOffset(el) as ElementOffset;
197
+ const parentOffset = getElementOffset(parent) as ElementOffset;
198
+
199
+ // Handle RTL, go from the other side
200
+ if (rtl) {
201
+ const windowWidth = window.innerWidth;
202
+ offset.right = windowWidth - (offset.left + getElementWidth(el, true, true, true));
203
+ parentOffset.right = windowWidth - (parentOffset.left + getElementWidth(parent, true, true, true));
204
+ }
205
+
206
+ // If the parent has borders, then it would offset the offset...
207
+ offset.left -= parseFloat(parentStyle.borderLeftWidth) || 0;
208
+ if (prop === 'right' && offset.right !== undefined && parentOffset.right !== undefined)
209
+ offset.right -= parseFloat(parentStyle.borderRightWidth) || 0;
210
+ offset.top -= parseFloat(parentStyle.borderTopWidth) || 0;
211
+
212
+ // Handle border widths of the element being offset
213
+ const borderPropWidth = prop === 'left' ? 'borderLeftWidth' : 'borderRightWidth';
214
+ if (prop === 'left') {
215
+ offset.left += parseFloat(elStyle.borderLeftWidth) || 0;
216
+ } else if (offset.right !== undefined) {
217
+ offset.right += parseFloat(elStyle.borderRightWidth) || 0;
218
+ }
219
+ offset.top += parseFloat(elStyle.borderTopWidth) || parseFloat(elStyle.borderBottomWidth) || 0;
220
+
221
+ // Subtract offsets to get offset relative to parent
222
+ offset.left -= parentOffset.left;
223
+ if (prop === 'right' && offset.right !== undefined && parentOffset.right !== undefined)
224
+ offset.right -= parentOffset.right;
225
+ offset.top -= parentOffset.top;
226
+
227
+ // Constrain horizontally
228
+ const minHorz = 0;
229
+ const maxHorz = getElementWidth(parent, false, false, false) - getElementWidth(previewCell, true, true, true);
230
+ const horzOffset = prop === 'left' ? offset.left : (offset.right ?? 0);
231
+ const constrainedHorz = horzOffset < minHorz ? minHorz : (horzOffset > maxHorz ? maxHorz : horzOffset);
232
+ if (prop === 'left') {
233
+ offset.left = constrainedHorz;
234
+ } else {
235
+ offset.right = constrainedHorz;
236
+ }
237
+
238
+ // Constrain vertically
239
+ const totalHeight = getElementHeight(el, true, true, true);
240
+ const scrollTop = 'scrollTop' in scrollParent ? scrollParent.scrollTop : 0;
241
+ const maxTop = scrollTop + getElementHeight(parent, true) - totalHeight;
242
+ if (offset.top > maxTop) {
243
+ offset.top = Math.max(0, maxTop);
244
+ }
245
+
246
+ // Apply css to preview cell
247
+ const previewCss: Record<string, string | number> = {
248
+ 'top': offset.top + 'px',
249
+ 'z-index': 9999,
250
+ };
251
+ previewCss[prop] = (prop === 'left' ? offset.left : offset.right) + 'px';
252
+ setCssProps(previewCell, previewCss);
253
+
254
+ previewCell[OriginalCellSymbol] = el;
255
+ p.cellPreviewCell = previewCell;
256
+ el[PreviewCellSymbol] = previewCell;
257
+
258
+ p._bindCellHoverOut(el);
259
+ p._bindCellHoverOut(previewCell);
260
+
261
+ // Avoid interfering with wheel scrolling the table
262
+ previewCell.addEventListener('wheel', () => {
263
+ // Let the table naturally scroll with the wheel
264
+ hideCellPreview(table);
265
+ });
266
+ }
267
+
268
+ /**
269
+ * Handle cell mouse out event
270
+ */
271
+ export function cellMouseOutEvent(table: DGTableInterface, _el: HTMLElement): void {
272
+ hideCellPreview(table);
273
+ }
274
+
275
+ /**
276
+ * Hide the current cell preview
277
+ */
278
+ export function hideCellPreview(table: DGTableInterface): DGTableInterface {
279
+ const p = table._p;
280
+
281
+ if (p.cellPreviewCell) {
282
+ const previewCell = p.cellPreviewCell as PreviewCellElement;
283
+ const origCell = previewCell[OriginalCellSymbol] as CellElement;
284
+ let selection;
285
+
286
+ try {
287
+ selection = SelectionHelper.saveSelection(previewCell);
288
+ } catch {
289
+ /* we're ok with this */
290
+ }
291
+
292
+ p.cellPreviewCell.remove();
293
+ if (origCell) p._unbindCellHoverOut(origCell);
294
+ p._unbindCellHoverOut(previewCell);
295
+
296
+ try {
297
+ if (selection && origCell)
298
+ SelectionHelper.restoreSelection(origCell, selection);
299
+ } catch {
300
+ /* we're ok with this */
301
+ }
302
+
303
+ table.emit('cellpreviewdestroy', {
304
+ el: previewCell.firstChild,
305
+ name: previewCell.columnName,
306
+ rowIndex: previewCell.rowIndex ?? null,
307
+ rowData: previewCell.rowIndex == null ? null : p.rows[previewCell.rowIndex] as RowData,
308
+ cell: origCell,
309
+ cellEl: origCell?.firstChild,
310
+ });
311
+
312
+ if (origCell) {
313
+ delete origCell[PreviewCellSymbol];
314
+ }
315
+ delete previewCell[OriginalCellSymbol];
316
+
317
+ p.cellPreviewCell = null;
318
+ p.abortCellPreview = false;
319
+ } else {
320
+ p.abortCellPreview = true;
321
+ }
322
+
323
+ return table;
324
+ }
325
+
@@ -0,0 +1,131 @@
1
+ import {
2
+ InternalColumn,
3
+ } from './private_types';
4
+
5
+ /**
6
+ * A collection of columns that extends Array functionality
7
+ */
8
+ class ColumnCollection extends Array<InternalColumn> {
9
+ constructor() {
10
+ super();
11
+ }
12
+
13
+ /**
14
+ * Get the column by this name
15
+ */
16
+ get(column: string): InternalColumn | null {
17
+ for (let i = 0, len = this.length; i < len; i++) {
18
+ if (this[i].name === column) {
19
+ return this[i];
20
+ }
21
+ }
22
+ return null;
23
+ }
24
+
25
+ /**
26
+ * Get the index of the column by this name
27
+ */
28
+ indexOf(column: string | InternalColumn): number {
29
+ const columnName = typeof column === 'string' ? column : column.name;
30
+ for (let i = 0, len = this.length; i < len; i++) {
31
+ if (this[i].name === columnName) {
32
+ return i;
33
+ }
34
+ }
35
+ return -1;
36
+ }
37
+
38
+ /**
39
+ * Get the column by the specified order
40
+ */
41
+ getByOrder(order: number): InternalColumn | null {
42
+ for (let i = 0, len = this.length; i < len; i++) {
43
+ if (this[i].order === order) {
44
+ return this[i];
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+
50
+ /**
51
+ * Normalize order to be sequential starting from 0
52
+ */
53
+ normalizeOrder(): this {
54
+ const ordered: InternalColumn[] = [];
55
+ for (let i = 0; i < this.length; i++) {
56
+ ordered.push(this[i]);
57
+ }
58
+ ordered.sort((col1, col2) => col1.order < col2.order ? -1 : (col1.order > col2.order ? 1 : 0));
59
+ for (let i = 0; i < ordered.length; i++) {
60
+ ordered[i].order = i;
61
+ }
62
+ return this;
63
+ }
64
+
65
+ /**
66
+ * Get the array of columns, ordered by the order property
67
+ */
68
+ getColumns(): InternalColumn[] {
69
+ const cols: InternalColumn[] = [];
70
+ for (let i = 0; i < this.length; i++) {
71
+ cols.push(this[i]);
72
+ }
73
+ cols.sort((col1, col2) => col1.order < col2.order ? -1 : (col1.order > col2.order ? 1 : 0));
74
+ return cols;
75
+ }
76
+
77
+ /**
78
+ * Get the array of visible columns, ordered by the order property
79
+ */
80
+ getVisibleColumns(): InternalColumn[] {
81
+ const cols: InternalColumn[] = [];
82
+ for (let i = 0; i < this.length; i++) {
83
+ const column = this[i];
84
+ if (column.visible) {
85
+ cols.push(column);
86
+ }
87
+ }
88
+ cols.sort((col1, col2) => col1.order < col2.order ? -1 : (col1.order > col2.order ? 1 : 0));
89
+ return cols;
90
+ }
91
+
92
+ /**
93
+ * Get the maximum order currently in the array
94
+ */
95
+ getMaxOrder(): number {
96
+ let order = 0;
97
+ for (let i = 0; i < this.length; i++) {
98
+ const column = this[i];
99
+ if (column.order > order) {
100
+ order = column.order;
101
+ }
102
+ }
103
+ return order;
104
+ }
105
+
106
+ /**
107
+ * Move a column to a new spot in the collection
108
+ */
109
+ moveColumn(src: InternalColumn, dest: InternalColumn): this {
110
+ if (src && dest) {
111
+ const srcOrder = src.order;
112
+ const destOrder = dest.order;
113
+ if (srcOrder < destOrder) {
114
+ for (let i = srcOrder + 1; i <= destOrder; i++) {
115
+ const col = this.getByOrder(i);
116
+ if (col) col.order--;
117
+ }
118
+ } else {
119
+ for (let i = srcOrder - 1; i >= destOrder; i--) {
120
+ const col = this.getByOrder(i);
121
+ if (col) col.order++;
122
+ }
123
+ }
124
+ src.order = destOrder;
125
+ }
126
+ return this;
127
+ }
128
+ }
129
+
130
+ export default ColumnCollection;
131
+