canvas-datagrid 0.3.19 → 0.3.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/draw.js CHANGED
@@ -164,6 +164,34 @@ export default function (self) {
164
164
  self.ctx.fill();
165
165
  return ml + aw + mr;
166
166
  }
167
+ function drawFilterButtonArrow(x, y) {
168
+ var mt =
169
+ ((self.style.filterButtonHeight - self.style.filterButtonArrowHeight) /
170
+ 2) *
171
+ self.scale,
172
+ ml =
173
+ ((self.style.filterButtonWidth - self.style.filterButtonArrowWidth) /
174
+ 2) *
175
+ self.scale,
176
+ aw = self.style.filterButtonArrowWidth * self.scale,
177
+ ah = self.style.filterButtonArrowHeight * self.scale;
178
+ x += self.canvasOffsetLeft;
179
+ y += self.canvasOffsetTop;
180
+ self.ctx.fillStyle = self.style.filterButtonArrowColor;
181
+ self.ctx.strokeStyle = self.style.filterButtonArrowBorderColor;
182
+ self.ctx.beginPath();
183
+ x = x + ml;
184
+ y = y + mt;
185
+
186
+ self.ctx.moveTo(x, y);
187
+ self.ctx.lineTo(x + aw, y);
188
+ self.ctx.lineTo(x + aw * 0.5, y + ah);
189
+ self.ctx.moveTo(x, y);
190
+
191
+ self.ctx.stroke();
192
+ self.ctx.fill();
193
+ return ml + aw;
194
+ }
167
195
  function radiusRect(x, y, w, h, radius) {
168
196
  x += self.canvasOffsetLeft;
169
197
  y += self.canvasOffsetTop;
@@ -816,6 +844,9 @@ export default function (self) {
816
844
  isCorner = /cornerCell/.test(cellStyle),
817
845
  isRowHeader = 'rowHeaderCell' === cellStyle,
818
846
  isColumnHeader = 'columnHeaderCell' === cellStyle,
847
+ isFilterable =
848
+ self.filterable.rows.includes(rowIndex) &&
849
+ self.filterable.columns.includes(headerIndex),
819
850
  wrap = self.style.cellWhiteSpace !== 'nowrap',
820
851
  selected =
821
852
  self.selections[rowOrderIndex] &&
@@ -824,6 +855,9 @@ export default function (self) {
824
855
  self.hovers.rowIndex === rowOrderIndex &&
825
856
  (self.attributes.hoverMode === 'row' ||
826
857
  self.hovers.columnIndex === columnOrderIndex),
858
+ openedFilter =
859
+ self.selectedFilterButton.rowIndex == rowIndex &&
860
+ self.selectedFilterButton.columnIndex == headerIndex,
827
861
  active =
828
862
  self.activeCell.rowIndex === rowOrderIndex &&
829
863
  self.activeCell.columnIndex === columnOrderIndex,
@@ -915,6 +949,8 @@ export default function (self) {
915
949
  isColumnHeader: isColumnHeader,
916
950
  isColumnHeaderCellCap: isColumnHeaderCellCap,
917
951
  isRowHeader: isRowHeader,
952
+ isFilterable: isFilterable,
953
+ openedFilter: openedFilter,
918
954
  rowOpen: rowOpen,
919
955
  header: header,
920
956
 
@@ -1114,6 +1150,10 @@ export default function (self) {
1114
1150
  }
1115
1151
  self.ctx.restore();
1116
1152
 
1153
+ if (isFilterable) {
1154
+ drawFilterButton(cell);
1155
+ }
1156
+
1117
1157
  // Gaps may occur in row numbers between consecutively rendered rows
1118
1158
  // when we are filtering. We draw attention to this by drawing a thick
1119
1159
  // border overlapping the two consecutive row headers. If sorting, visible
@@ -1154,6 +1194,34 @@ export default function (self) {
1154
1194
  return cell.width;
1155
1195
  };
1156
1196
  }
1197
+ function drawFilterButton(cell, ev) {
1198
+ var posX = cell.x + cell.width - self.style.filterButtonWidth - 1;
1199
+ var posY = cell.y + cell.height - self.style.filterButtonHeight - 2;
1200
+ if (self.dispatchEvent('beforerenderfilterbutton', ev)) {
1201
+ return;
1202
+ }
1203
+ self.ctx.save();
1204
+ self.ctx.strokeStyle = self.style.filterButtonBorderColor;
1205
+ self.ctx.fillStyle = self.style.filterButtonBackgroundColor;
1206
+ if (cell.openedFilter) {
1207
+ self.ctx.fillStyle = self.style.filterButtonActiveBackgroundColor;
1208
+ } else if (cell.hovered && self.hovers.onFilterButton) {
1209
+ self.ctx.fillStyle = self.style.filterButtonHoverBackgroundColor;
1210
+ }
1211
+ radiusRect(
1212
+ posX,
1213
+ posY,
1214
+ self.style.filterButtonWidth,
1215
+ self.style.filterButtonHeight,
1216
+ self.style.filterButtonBorderRadius,
1217
+ );
1218
+ self.ctx.stroke();
1219
+ self.ctx.fill();
1220
+ drawFilterButtonArrow(posX, posY);
1221
+ self.ctx.clip();
1222
+ self.dispatchEvent('afterrenderfilterbutton', ev);
1223
+ self.ctx.restore();
1224
+ }
1157
1225
  function drawRowHeader(rowData, rowIndex, rowOrderIndex) {
1158
1226
  if (self.attributes.showRowHeaders) {
1159
1227
  x = 0;
@@ -468,7 +468,26 @@ export default function (self) {
468
468
  self.hovers = {
469
469
  rowIndex: cell.rowIndex,
470
470
  columnIndex: cell.columnIndex,
471
+ onFilterButton: false,
471
472
  };
473
+ if (
474
+ cell.isFilterable &&
475
+ x >
476
+ cell.x +
477
+ cell.width +
478
+ self.canvasOffsetLeft -
479
+ self.style.filterButtonWidth &&
480
+ x < cell.x + cell.width + self.canvasOffsetLeft &&
481
+ y >
482
+ cell.y +
483
+ cell.height +
484
+ self.canvasOffsetTop -
485
+ self.style.filterButtonHeight &&
486
+ y < cell.y + cell.height + self.canvasOffsetTop
487
+ ) {
488
+ self.hovers.onFilterButton = true;
489
+ self.draw();
490
+ }
472
491
  }
473
492
  if (self.selecting || self.reorderObject) {
474
493
  delta = {
@@ -512,30 +531,57 @@ export default function (self) {
512
531
  ) {
513
532
  self.selectRow(cell.rowIndex, ctrl, null, true);
514
533
  } else if (self.attributes.selectionMode !== 'row') {
515
- if (!self.dragAddToSelection && cell.rowIndex !== undefined) {
516
- if (
517
- self.selections[cell.rowIndex] &&
518
- self.selections[cell.rowIndex].indexOf(cell.columnIndex) !== -1
519
- ) {
520
- self.selections[cell.rowIndex].splice(
521
- self.selections[cell.rowIndex].indexOf(cell.columnIndex),
522
- 1,
523
- );
534
+ if (cell.hovered && self.hovers.onFilterButton) {
535
+ if (cell.openedFilter) {
536
+ cell.openedFilter = false;
537
+ self.selectedFilterButton = {
538
+ columnIndex: -1,
539
+ rowIndex: -1,
540
+ };
541
+ } else {
542
+ self.selectedFilterButton.rowIndex = cell.rowIndex;
543
+ self.selectedFilterButton.columnIndex = cell.columnIndex;
544
+ self.contextmenuEvent(e, {
545
+ x: cell.x + cell.width - self.style.filterButtonWidth,
546
+ y: cell.y + cell.height,
547
+ rect: { left: 0, top: 0 },
548
+ });
524
549
  }
550
+ self.draw();
551
+ return;
525
552
  } else {
526
- self.selections[cell.rowIndex] =
527
- self.selections[cell.rowIndex] || [];
528
- if (
529
- self.selections[cell.rowIndex].indexOf(cell.columnIndex) === -1
530
- ) {
531
- self.selections[cell.rowIndex].push(cell.columnIndex);
532
- const event = {
533
- selections: self.selections,
534
- selectedData: self.getSelectedData(),
535
- selectedCells: self.getSelectedCells(),
536
- selectionBounds: self.getSelectionBounds(),
537
- };
538
- self.dispatchEvent('selectionchanged', event);
553
+ self.selectedFilterButton = {
554
+ columnIndex: -1,
555
+ rowIndex: -1,
556
+ };
557
+ if (self.hovers.onFilterButton) return;
558
+ if (!self.dragAddToSelection && cell.rowIndex !== undefined) {
559
+ if (
560
+ self.selections[cell.rowIndex] &&
561
+ self.selections[cell.rowIndex].indexOf(cell.columnIndex) !==
562
+ -1
563
+ ) {
564
+ self.selections[cell.rowIndex].splice(
565
+ self.selections[cell.rowIndex].indexOf(cell.columnIndex),
566
+ 1,
567
+ );
568
+ }
569
+ } else {
570
+ self.selections[cell.rowIndex] =
571
+ self.selections[cell.rowIndex] || [];
572
+ if (
573
+ self.selections[cell.rowIndex].indexOf(cell.columnIndex) ===
574
+ -1
575
+ ) {
576
+ self.selections[cell.rowIndex].push(cell.columnIndex);
577
+ var event = {
578
+ selections: self.selections,
579
+ selectedData: self.getSelectedData(),
580
+ selectedCells: self.getSelectedCells(),
581
+ selectionBounds: self.getSelectionBounds(),
582
+ };
583
+ self.dispatchEvent('selectionchanged', event);
584
+ }
539
585
  }
540
586
  }
541
587
  }
@@ -548,13 +594,18 @@ export default function (self) {
548
594
  dragBounds.right !== self.selectionBounds.right) &&
549
595
  !ctrl
550
596
  ) {
551
- self.selections = [];
597
+ if (!(cell.hovered && self.hovers.onFilterButton)) {
598
+ self.selections = [];
599
+ }
600
+ if (dragBounds.top === -1) {
601
+ dragBounds.top = 0;
602
+ }
552
603
  sBounds = dragBounds;
553
604
  if (self.attributes.selectionMode === 'row') {
554
605
  for (i = sBounds.top; i <= sBounds.bottom; i += 1) {
555
606
  self.selectRow(i, true, null, true);
556
607
  }
557
- } else if (dragBounds.top !== -1) {
608
+ } else {
558
609
  self.selectArea(sBounds, true);
559
610
  if (sBounds.left == -1 && sBounds.top !== sBounds.bottom)
560
611
  self.isMultiRowsSelected = true;
@@ -632,7 +683,9 @@ export default function (self) {
632
683
  -1 &&
633
684
  !ctrl
634
685
  ) {
635
- self.setActiveCell(i.columnIndex, i.rowIndex);
686
+ if (!self.hovers.onFilterButton) {
687
+ self.setActiveCell(i.columnIndex, i.rowIndex);
688
+ }
636
689
  }
637
690
  if (self.currentCell.context === 'cell') {
638
691
  if (self.currentCell.style === 'cornerCell') {
@@ -744,7 +797,26 @@ export default function (self) {
744
797
  }
745
798
  self.ellipsisCache = {};
746
799
  };
747
- self.stopDragResize = function () {
800
+ self.stopDragResize = function (event) {
801
+ const pos = self.getLayerPos(event);
802
+
803
+ if (self.dragMode === 'ew-resize') {
804
+ const hasMoved = !!(pos.x - self.dragStart.x);
805
+ // Check that dragItem is selected or part of selection.
806
+ const dragItemIsSelected = self.isColumnSelected(
807
+ self.dragItem.columnIndex,
808
+ );
809
+
810
+ if (hasMoved && dragItemIsSelected) {
811
+ const width = Math.max(
812
+ self.resizingStartingWidth + pos.x - self.dragStart.x,
813
+ self.style.minColumnWidth,
814
+ );
815
+ // If the column is selected, resize it to width plus any other selected columns.
816
+ self.fitSelectedColumns(width);
817
+ }
818
+ }
819
+
748
820
  self.resize();
749
821
  document.body.removeEventListener(
750
822
  'mousemove',
@@ -1173,8 +1245,12 @@ export default function (self) {
1173
1245
  !freeze &&
1174
1246
  !resize
1175
1247
  ) {
1176
- if (!(self.dragMode == 'row-reorder' && self.isMultiRowsSelected))
1248
+ if (
1249
+ !(self.dragMode == 'row-reorder' && self.isMultiRowsSelected) &&
1250
+ !(self.currentCell.hovered && self.hovers.onFilterButton)
1251
+ ) {
1177
1252
  self.selections = [];
1253
+ }
1178
1254
  }
1179
1255
  if (self.dragStartObject.isGrid) {
1180
1256
  return;
@@ -1371,6 +1447,8 @@ export default function (self) {
1371
1447
  self.selectNone();
1372
1448
  } else if (ctrl && e.key === 'a') {
1373
1449
  self.selectAll();
1450
+ } else if (['Backspace', 'Delete'].includes(e.key)) {
1451
+ self.deleteSelectedData();
1374
1452
  } else if (e.key === 'ArrowDown') {
1375
1453
  y += 1;
1376
1454
  } else if (e.key === 'ArrowUp') {
@@ -1550,13 +1628,26 @@ export default function (self) {
1550
1628
  self.currentCell.context === 'ew-resize' &&
1551
1629
  self.currentCell.style === 'columnHeaderCell'
1552
1630
  ) {
1553
- self.fitColumnToValues(self.currentCell.header.name);
1631
+ // Check that double-clicked cell is selected or part of selection.
1632
+ const currentCellIsSelected = self.isColumnSelected(
1633
+ self.currentCell.columnIndex,
1634
+ );
1635
+
1636
+ if (currentCellIsSelected) {
1637
+ // There might be more
1638
+ self.fitSelectedColumns();
1639
+ } else {
1640
+ self.fitColumnToValues(self.currentCell.header.name);
1641
+ }
1554
1642
  } else if (
1555
1643
  self.currentCell.context === 'ew-resize' &&
1556
1644
  self.currentCell.style === 'cornerCell'
1557
1645
  ) {
1558
1646
  self.autosize();
1559
- } else if (['cell', 'activeCell'].indexOf(self.currentCell.style) !== -1) {
1647
+ } else if (
1648
+ ['cell', 'activeCell'].includes(self.currentCell.style) &&
1649
+ !self.hovers.onFilterButton
1650
+ ) {
1560
1651
  self.beginEditAt(self.currentCell.columnIndex, self.currentCell.rowIndex);
1561
1652
  }
1562
1653
  };
@@ -1780,104 +1871,38 @@ export default function (self) {
1780
1871
  });
1781
1872
  };
1782
1873
  self.cut = function (e) {
1783
- self.copy(e);
1784
- const schema = self.getSchema();
1785
-
1786
- var affectedCells = [];
1787
- for (const [rowIndex, row] of self.selections.entries()) {
1788
- if (!row) continue;
1874
+ if (self.dispatchEvent('cut', { NativeEvent: e })) {
1875
+ return;
1876
+ }
1789
1877
 
1790
- const boundRowIndex = self.getBoundRowIndexFromViewRowIndex(rowIndex);
1878
+ // Expecting instance of `ClipboardEvent` with `clipboardData` attribute
1879
+ if (!self.hasFocus || !e.clipboardData) {
1880
+ return;
1881
+ }
1791
1882
 
1792
- for (const columnIndex of row) {
1793
- const boundColumnIndex = self.getBoundColumnIndexFromViewColumnIndex(
1794
- columnIndex,
1795
- );
1796
- const colName = schema[boundColumnIndex].name;
1883
+ self.copySelectedCellsToClipboard(e.clipboardData);
1797
1884
 
1798
- self.viewData[rowIndex][colName] = '';
1885
+ const affectedCells = self.clearSelectedCells();
1799
1886
 
1800
- affectedCells.push([
1801
- rowIndex,
1802
- columnIndex,
1803
- boundRowIndex,
1804
- boundColumnIndex,
1805
- ]);
1806
- }
1807
- }
1887
+ self.dispatchEvent('aftercut', {
1888
+ cells: affectedCells,
1889
+ });
1808
1890
 
1809
- if (self.dispatchEvent('cut', { NativeEvent: e, cells: affectedCells })) {
1810
- return;
1811
- }
1891
+ requestAnimationFrame(() => self.draw());
1812
1892
  };
1813
1893
  self.copy = function (e) {
1814
1894
  if (self.dispatchEvent('copy', { NativeEvent: e })) {
1815
1895
  return;
1816
1896
  }
1897
+
1898
+ // Expecting instance of `ClipboardEvent` with `clipboardData` attribute
1817
1899
  if (!self.hasFocus || !e.clipboardData) {
1818
1900
  return;
1819
1901
  }
1820
- var t = '',
1821
- d = '',
1822
- textRows = [],
1823
- sData = self.getSelectedData(),
1824
- s = self.getSchema(),
1825
- sSorted = [],
1826
- firstRowKeys,
1827
- isNeat = true; // Selected like [[0, 1], [0, 1]] of [[0, 3]] is neat; Selected like [[0, 1], [1, 2]] is untidy
1828
- function htmlSafe(v) {
1829
- if (typeof v === 'number') return v;
1830
- return v.replace(/</g, '&lt;').replace(/>/g, '&gt;');
1831
- }
1832
- s.forEach(function (column, columnIndex) {
1833
- sSorted.push(s[self.orders.columns[columnIndex]]);
1834
- });
1835
- if (sData.length > 0) {
1836
- sData.forEach((row) => {
1837
- if (!row) return;
1838
- var rowKeys = Object.keys(row);
1839
- var textRow = [];
1840
- if (!firstRowKeys) firstRowKeys = Object.keys(row);
1841
- if (isNeat && rowKeys.length !== firstRowKeys.length) isNeat = false;
1842
- sSorted.forEach(function (column, columnIndex) {
1843
- if (rowKeys.indexOf(String(column.name)) < 0) {
1844
- if (firstRowKeys.indexOf(String(column.name)) < 0) {
1845
- return;
1846
- } else if (isNeat) {
1847
- isNeat = false;
1848
- }
1849
- }
1850
- textRow.push(row[column.name] || '');
1851
- });
1852
- textRows.push(textRow);
1853
- });
1854
- if (isNeat) {
1855
- t = textRows.map((row) => row.join('\t')).join('\n');
1856
- /**
1857
- * The html content copied by Excel has not header
1858
- */
1859
- d += '<table>';
1860
- d += textRows
1861
- .map(
1862
- (row) =>
1863
- '<tr>' +
1864
- row.map((value) => '<td>' + htmlSafe(value) + '</td>').join('') +
1865
- '</tr>',
1866
- )
1867
- .join('');
1868
- d += '</table>';
1869
- } else {
1870
- t = textRows.map((row) => row.join('')).join('');
1871
- d = t;
1872
- }
1873
- if (t) {
1874
- e.clipboardData.setData('text/html', d);
1875
- e.clipboardData.setData('text/plain', t);
1876
- e.clipboardData.setData('text/csv', t);
1877
- e.clipboardData.setData('application/json', JSON.stringify(sData));
1878
- }
1879
- e.preventDefault();
1880
- }
1902
+
1903
+ self.copySelectedCellsToClipboard(e.clipboardData);
1904
+
1905
+ e.preventDefault();
1881
1906
  };
1882
1907
  return;
1883
1908
  }
@@ -86,11 +86,49 @@ const parseData = function (data, mimeType) {
86
86
  return parseText(data);
87
87
  };
88
88
 
89
+ const htmlSafe = (value) => {
90
+ if (typeof value === 'number') return value;
91
+ return value.replace(/</g, '&lt;').replace(/>/g, '&gt;');
92
+ };
93
+
94
+ const createTextString = (selectedData, isNeat) => {
95
+ // Selected like [[0, 1], [0, 1]] of [[0, 3]] is neat; Selected like [[0, 1], [1, 2]] is untidy.
96
+ // If not isNeat we just return a simple string of concatenated values.
97
+ if (!isNeat)
98
+ return selectedData.map((row) => Object.values(row).join('')).join('');
99
+
100
+ // If isNeat, we can create tab separated mutti-line text.
101
+ return selectedData.map((row) => Object.values(row).join('\t')).join('\n');
102
+ };
103
+
104
+ const createHTMLString = (selectedData, isNeat) => {
105
+ if (!isNeat) return createTextString(selectedData, isNeat);
106
+
107
+ // If isNeat, we can create a HTML table with the selected data.
108
+ let htmlString = '<table>';
109
+ htmlString += selectedData
110
+ .map(
111
+ (row) =>
112
+ '<tr>' +
113
+ Object.values(row)
114
+ .map((value) => '<td>' + htmlSafe(value) + '</td>')
115
+ .join('') +
116
+ '</tr>',
117
+ )
118
+ .join('');
119
+ htmlString += '</table>';
120
+
121
+ return htmlString;
122
+ };
123
+
89
124
  export {
125
+ createTextString,
126
+ createHTMLString,
90
127
  isSupportedHtml,
91
- sanitizeElementData,
128
+ htmlSafe,
92
129
  parseData,
93
130
  parseHtmlTable,
94
131
  parseHtmlText,
95
132
  parseText,
133
+ sanitizeElementData,
96
134
  };
package/lib/intf.js CHANGED
@@ -2,6 +2,8 @@
2
2
  /*globals HTMLElement: false, Reflect: false, define: true, MutationObserver: false, requestAnimationFrame: false, performance: false, btoa: false*/
3
3
  'use strict';
4
4
 
5
+ import { createHTMLString, createTextString } from './events/util';
6
+
5
7
  export default function (self, ctor) {
6
8
  self.scale = 1;
7
9
  self.orders = {
@@ -61,6 +63,14 @@ export default function (self, ctor) {
61
63
  trees: {},
62
64
  };
63
65
  self.selections = [];
66
+ self.filterable = {
67
+ rows: [],
68
+ columns: [],
69
+ };
70
+ self.selectedFilterButton = {
71
+ columnIndex: -1,
72
+ rowIndex: -1,
73
+ };
64
74
  self.hovers = {};
65
75
  self.attributes = {};
66
76
  self.style = {};
@@ -88,6 +98,7 @@ export default function (self, ctor) {
88
98
  self.eventNames = [
89
99
  'afterdraw',
90
100
  'afterrendercell',
101
+ 'afterrenderfilterbutton',
91
102
  'attributechanged',
92
103
  'beforebeginedit',
93
104
  'beforecreatecellgrid',
@@ -95,6 +106,7 @@ export default function (self, ctor) {
95
106
  'beforeendedit',
96
107
  'beforerendercell',
97
108
  'beforerendercellgrid',
109
+ 'beforerenderfilterbutton',
98
110
  'beginedit',
99
111
  'cellmouseout',
100
112
  'cellmouseover',
@@ -232,6 +244,89 @@ export default function (self, ctor) {
232
244
 
233
245
  return selectedCells;
234
246
  };
247
+ self.copySelectedCellsToClipboard = function (clipboardData) {
248
+ const selectedData = [];
249
+ const schema = self.getSchema();
250
+ let firstRowAsString;
251
+ let isNeat = true; // Selected like [[0, 1], [0, 1]] of [[0, 3]] is neat; Selected like [[0, 1], [1, 2]] is untidy
252
+
253
+ for (const [rowIndex, row] of self.selections.entries()) {
254
+ // If no cells are selected for a particular rowIndex the selections array will contain an empty element for that rowIndex.
255
+ if (!row) continue;
256
+
257
+ // Convert to string for easy comparison to the first row.
258
+ const rowAsString = row.join(',').toString();
259
+ const rowDict = {};
260
+
261
+ if (!firstRowAsString) firstRowAsString = row.join(',').toString();
262
+ if (isNeat && rowAsString !== firstRowAsString) isNeat = false;
263
+
264
+ const boundRowIndex = self.getBoundRowIndexFromViewRowIndex(rowIndex);
265
+
266
+ for (let columnIndex of row) {
267
+ // If the whole row is selected the columnIndex for the rowHeader is -1.
268
+ if (columnIndex < 0) continue;
269
+
270
+ const boundColumnIndex = self.getBoundColumnIndexFromViewColumnIndex(
271
+ columnIndex,
272
+ );
273
+ const columnName = schema[boundColumnIndex].name;
274
+
275
+ const value = self.originalData[boundRowIndex][columnName];
276
+
277
+ rowDict[columnName] = value;
278
+ }
279
+ selectedData.push(rowDict);
280
+ }
281
+
282
+ if (selectedData.length > 0) {
283
+ const textString = createTextString(selectedData, isNeat);
284
+ const htmlString = createHTMLString(selectedData, isNeat);
285
+
286
+ const copiedData = {
287
+ 'text/plain': textString,
288
+ 'text/html': htmlString,
289
+ 'text/csv': textString,
290
+ 'application/json': JSON.stringify(selectedData),
291
+ };
292
+
293
+ for (const [mimeType, data] of Object.entries(copiedData)) {
294
+ clipboardData.setData(mimeType, data);
295
+ }
296
+ }
297
+ };
298
+ self.clearSelectedCells = function () {
299
+ const schema = self.getSchema();
300
+ const affectedCells = [];
301
+
302
+ for (const [rowIndex, row] of self.selections.entries()) {
303
+ // If no cells are selected for a particular rowIndex the selections array will contain an empty element for that rowIndex.
304
+ if (!row) continue;
305
+
306
+ const boundRowIndex = self.getBoundRowIndexFromViewRowIndex(rowIndex);
307
+
308
+ for (let columnIndex of row) {
309
+ // If the whole row is selected the columnIndex for the rowHeader is -1.
310
+ if (columnIndex < 0) continue;
311
+
312
+ const boundColumnIndex = self.getBoundColumnIndexFromViewColumnIndex(
313
+ columnIndex,
314
+ );
315
+ const columnName = schema[boundColumnIndex].name;
316
+
317
+ self.viewData[boundRowIndex][columnName] = '';
318
+
319
+ affectedCells.push([
320
+ rowIndex,
321
+ columnIndex,
322
+ boundRowIndex,
323
+ boundColumnIndex,
324
+ ]);
325
+ }
326
+ }
327
+
328
+ return affectedCells;
329
+ };
235
330
  self.getBoundRowIndexFromViewRowIndex = function (viewRowIndex) {
236
331
  if (self.boundRowIndexMap && self.boundRowIndexMap.has(viewRowIndex)) {
237
332
  return self.boundRowIndexMap.get(viewRowIndex);
@@ -301,6 +396,27 @@ export default function (self, ctor) {
301
396
  self.createRowOrders = function () {
302
397
  self.orders.rows = fillArray(0, self.originalData.length - 1);
303
398
  };
399
+ self.fitSelectedColumns = function (width) {
400
+ const selectedColumns = self.selections[0];
401
+ const schema = self.getSchema();
402
+
403
+ for (const selectedColumn of selectedColumns) {
404
+ // Make sure the column is not the row header and that the whole column has in fact been selected.
405
+ if (selectedColumn >= 0 && self.isColumnSelected(selectedColumn)) {
406
+ if (isNaN(width)) {
407
+ const column = schema[self.orders.columns[selectedColumn]];
408
+ self.fitColumnToValues(column.name);
409
+ } else {
410
+ self.sizes.columns[selectedColumn] = width;
411
+ self.dispatchEvent('resizecolumn', {
412
+ x: width,
413
+ y: self.resizingStartingHeight,
414
+ draggingItem: self.currentCell,
415
+ });
416
+ }
417
+ }
418
+ }
419
+ };
304
420
  self.getVisibleSchema = function () {
305
421
  return self.getSchema().filter(function (col) {
306
422
  return !col.hidden;
@@ -547,6 +663,7 @@ export default function (self, ctor) {
547
663
  self.controlInput.removeEventListener('keypress', self.keypress, false);
548
664
  self.controlInput.removeEventListener('keyup', self.keyup, false);
549
665
  self.controlInput.removeEventListener('keydown', self.keydown, false);
666
+ document.body.removeEventListener('mousemove', self.mousemove);
550
667
  window.removeEventListener('resize', self.resize);
551
668
  if (self.observer && self.observer.disconnect) {
552
669
  self.observer.disconnect();
@@ -724,6 +841,7 @@ export default function (self, ctor) {
724
841
  self.pointerLockPosition = { x: 0, y: 0 };
725
842
  Object.keys(self.style).forEach(self.parseStyleValue);
726
843
  self.intf.moveSelection = self.moveSelection;
844
+ self.intf.deleteSelectedData = self.deleteSelectedData;
727
845
  self.intf.moveTo = self.moveTo;
728
846
  self.intf.addEventListener = self.addEventListener;
729
847
  self.intf.removeEventListener = self.removeEventListener;
@@ -755,6 +873,7 @@ export default function (self, ctor) {
755
873
  self.intf.clearChangeLog = self.clearChangeLog;
756
874
  self.intf.gotoCell = self.gotoCell;
757
875
  self.intf.gotoRow = self.gotoRow;
876
+ self.intf.addButton = self.addButton;
758
877
  self.intf.getHeaderByName = self.getHeaderByName;
759
878
  self.intf.findColumnScrollLeft = self.findColumnScrollLeft;
760
879
  self.intf.findRowScrollTop = self.findRowScrollTop;
@@ -1534,6 +1653,9 @@ export default function (self, ctor) {
1534
1653
  if (!preventScrollEvent) {
1535
1654
  self.scroll();
1536
1655
  }
1656
+ if (self.button) {
1657
+ self.moveButtonPos();
1658
+ }
1537
1659
  }
1538
1660
  function setScrollLeft(value, preventScrollEvent) {
1539
1661
  if (isNaN(value)) {
@@ -1552,6 +1674,9 @@ export default function (self, ctor) {
1552
1674
  if (!preventScrollEvent) {
1553
1675
  self.scroll();
1554
1676
  }
1677
+ if (self.button) {
1678
+ self.moveButtonPos();
1679
+ }
1555
1680
  }
1556
1681
  self.scrollBox.toString = function () {
1557
1682
  return (