canvas-datagrid 0.3.21 → 0.3.25

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/dom.js CHANGED
@@ -143,11 +143,16 @@ export default function (self) {
143
143
  s = e,
144
144
  h,
145
145
  w;
146
+ let calculatedTree = false;
146
147
  while (e.offsetParent && e.nodeName !== 'CANVAS-DATAGRID') {
147
- x += e.offsetLeft;
148
- y += e.offsetTop;
149
- h = e.offsetHeight;
150
- w = e.offsetWidth;
148
+ const isTree = e.nodeType === 'canvas-datagrid-tree';
149
+ if (!isTree || !calculatedTree) {
150
+ x += e.offsetLeft;
151
+ y += e.offsetTop;
152
+ h = e.offsetHeight;
153
+ w = e.offsetWidth;
154
+ }
155
+ if (isTree) calculatedTree = true;
151
156
  e = e.offsetParent;
152
157
  }
153
158
  if (ignoreScrollOffset) {
@@ -797,7 +797,26 @@ export default function (self) {
797
797
  }
798
798
  self.ellipsisCache = {};
799
799
  };
800
- 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
+
801
820
  self.resize();
802
821
  document.body.removeEventListener(
803
822
  'mousemove',
@@ -956,6 +975,7 @@ export default function (self) {
956
975
  -1,
957
976
  self.activeCell.rowIndex,
958
977
  );
978
+ if (!self.reorderObject) return;
959
979
  self.reorderTarget = self.currentCell;
960
980
  self.reorderObject.dragOffset = {
961
981
  x: x,
@@ -1609,7 +1629,17 @@ export default function (self) {
1609
1629
  self.currentCell.context === 'ew-resize' &&
1610
1630
  self.currentCell.style === 'columnHeaderCell'
1611
1631
  ) {
1612
- self.fitColumnToValues(self.currentCell.header.name);
1632
+ // Check that double-clicked cell is selected or part of selection.
1633
+ const currentCellIsSelected = self.isColumnSelected(
1634
+ self.currentCell.columnIndex,
1635
+ );
1636
+
1637
+ if (currentCellIsSelected) {
1638
+ // There might be more
1639
+ self.fitSelectedColumns();
1640
+ } else {
1641
+ self.fitColumnToValues(self.currentCell.header.name);
1642
+ }
1613
1643
  } else if (
1614
1644
  self.currentCell.context === 'ew-resize' &&
1615
1645
  self.currentCell.style === 'cornerCell'
@@ -1841,107 +1871,40 @@ export default function (self) {
1841
1871
  self.draw();
1842
1872
  });
1843
1873
  };
1844
- self.cut = function (e) {
1845
- self.copy(e);
1846
- const schema = self.getSchema();
1847
-
1848
- var affectedCells = [];
1849
- for (const [rowIndex, row] of self.selections.entries()) {
1850
- if (!row) continue;
1851
-
1852
- const boundRowIndex = self.getBoundRowIndexFromViewRowIndex(rowIndex);
1853
-
1854
- for (const columnIndex of row) {
1855
- const boundColumnIndex = self.getBoundColumnIndexFromViewColumnIndex(
1856
- columnIndex,
1857
- );
1858
- const colName = schema[boundColumnIndex].name;
1859
-
1860
- self.viewData[rowIndex][colName] = '';
1861
-
1862
- affectedCells.push([
1863
- rowIndex,
1864
- columnIndex,
1865
- boundRowIndex,
1866
- boundColumnIndex,
1867
- ]);
1868
- }
1874
+ self.cut = function (event) {
1875
+ if (self.dispatchEvent('cut', { NativeEvent: event })) {
1876
+ return;
1869
1877
  }
1870
1878
 
1871
- if (self.dispatchEvent('cut', { NativeEvent: e, cells: affectedCells })) {
1879
+ // Expecting instance of `ClipboardEvent` with `clipboardData` attribute
1880
+ if (!self.hasFocus || !event.clipboardData) {
1872
1881
  return;
1873
1882
  }
1874
1883
 
1884
+ self.copySelectedCellsToClipboard(event.clipboardData);
1885
+
1886
+ const affectedCells = self.clearSelectedCells();
1887
+
1888
+ self.dispatchEvent('aftercut', {
1889
+ cells: affectedCells,
1890
+ });
1891
+
1875
1892
  requestAnimationFrame(() => self.draw());
1893
+ event.preventDefault();
1876
1894
  };
1877
- self.copy = function (e) {
1878
- if (self.dispatchEvent('copy', { NativeEvent: e })) {
1895
+ self.copy = function (event) {
1896
+ if (self.dispatchEvent('copy', { NativeEvent: event })) {
1879
1897
  return;
1880
1898
  }
1881
- if (!self.hasFocus || !e.clipboardData) {
1899
+
1900
+ // Expecting instance of `ClipboardEvent` with `clipboardData` attribute
1901
+ if (!self.hasFocus || !event.clipboardData) {
1882
1902
  return;
1883
1903
  }
1884
- var t = '',
1885
- d = '',
1886
- textRows = [],
1887
- sData = self.getSelectedData(),
1888
- s = self.getSchema(),
1889
- sSorted = [],
1890
- firstRowKeys,
1891
- isNeat = true; // Selected like [[0, 1], [0, 1]] of [[0, 3]] is neat; Selected like [[0, 1], [1, 2]] is untidy
1892
- function htmlSafe(v) {
1893
- if (typeof v === 'number') return v;
1894
- return v.replace(/</g, '&lt;').replace(/>/g, '&gt;');
1895
- }
1896
- s.forEach(function (column, columnIndex) {
1897
- sSorted.push(s[self.orders.columns[columnIndex]]);
1898
- });
1899
- if (sData.length > 0) {
1900
- sData.forEach((row) => {
1901
- if (!row) return;
1902
- var rowKeys = Object.keys(row);
1903
- var textRow = [];
1904
- if (!firstRowKeys) firstRowKeys = Object.keys(row);
1905
- if (isNeat && rowKeys.length !== firstRowKeys.length) isNeat = false;
1906
- sSorted.forEach(function (column, columnIndex) {
1907
- if (rowKeys.indexOf(String(column.name)) < 0) {
1908
- if (firstRowKeys.indexOf(String(column.name)) < 0) {
1909
- return;
1910
- } else if (isNeat) {
1911
- isNeat = false;
1912
- }
1913
- }
1914
- textRow.push(row[column.name] || '');
1915
- });
1916
- textRows.push(textRow);
1917
- });
1918
- if (isNeat) {
1919
- t = textRows.map((row) => row.join('\t')).join('\n');
1920
- /**
1921
- * The html content copied by Excel has not header
1922
- */
1923
- d += '<table>';
1924
- d += textRows
1925
- .map(
1926
- (row) =>
1927
- '<tr>' +
1928
- row.map((value) => '<td>' + htmlSafe(value) + '</td>').join('') +
1929
- '</tr>',
1930
- )
1931
- .join('');
1932
- d += '</table>';
1933
- } else {
1934
- t = textRows.map((row) => row.join('')).join('');
1935
- d = t;
1936
- }
1937
- if (t) {
1938
- e.clipboardData.setData('text/html', d);
1939
- e.clipboardData.setData('text/plain', t);
1940
- e.clipboardData.setData('text/csv', t);
1941
- e.clipboardData.setData('application/json', JSON.stringify(sData));
1942
- }
1943
- e.preventDefault();
1944
- }
1904
+
1905
+ self.copySelectedCellsToClipboard(event.clipboardData);
1906
+
1907
+ event.preventDefault();
1945
1908
  };
1946
1909
  return;
1947
1910
  }
@@ -86,11 +86,50 @@ const parseData = function (data, mimeType) {
86
86
  return parseText(data);
87
87
  };
88
88
 
89
+ const htmlSafe = function (value) {
90
+ if (typeof value !== 'string') return value;
91
+
92
+ return value.replace(/</g, '&lt;').replace(/>/g, '&gt;');
93
+ };
94
+
95
+ const createTextString = function (selectedData, isNeat) {
96
+ // Selected like [[0, 1], [0, 1]] of [[0, 3]] is neat; Selected like [[0, 1], [1, 2]] is untidy.
97
+ // If not isNeat we just return a simple string of concatenated values.
98
+ if (!isNeat)
99
+ return selectedData.map((row) => Object.values(row).join('')).join('');
100
+
101
+ // If isNeat, we can create tab separated mutti-line text.
102
+ return selectedData.map((row) => Object.values(row).join('\t')).join('\n');
103
+ };
104
+
105
+ const createHTMLString = function (selectedData, isNeat) {
106
+ if (!isNeat) return createTextString(selectedData, isNeat);
107
+
108
+ // If isNeat, we can create a HTML table with the selected data.
109
+ let htmlString = '<table>';
110
+ htmlString += selectedData
111
+ .map(
112
+ (row) =>
113
+ '<tr>' +
114
+ Object.values(row)
115
+ .map((value) => '<td>' + htmlSafe(value) + '</td>')
116
+ .join('') +
117
+ '</tr>',
118
+ )
119
+ .join('');
120
+ htmlString += '</table>';
121
+
122
+ return htmlString;
123
+ };
124
+
89
125
  export {
126
+ createTextString,
127
+ createHTMLString,
90
128
  isSupportedHtml,
91
- sanitizeElementData,
129
+ htmlSafe,
92
130
  parseData,
93
131
  parseHtmlTable,
94
132
  parseHtmlText,
95
133
  parseText,
134
+ sanitizeElementData,
96
135
  };
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 = {
@@ -242,6 +244,57 @@ export default function (self, ctor) {
242
244
 
243
245
  return selectedCells;
244
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
+ };
245
298
  self.clearSelectedCells = function () {
246
299
  const schema = self.getSchema();
247
300
  const affectedCells = [];
@@ -261,7 +314,7 @@ export default function (self, ctor) {
261
314
  );
262
315
  const columnName = schema[boundColumnIndex].name;
263
316
 
264
- self.viewData[boundRowIndex][columnName] = null;
317
+ self.viewData[boundRowIndex][columnName] = '';
265
318
 
266
319
  affectedCells.push([
267
320
  rowIndex,
@@ -343,6 +396,27 @@ export default function (self, ctor) {
343
396
  self.createRowOrders = function () {
344
397
  self.orders.rows = fillArray(0, self.originalData.length - 1);
345
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
+ };
346
420
  self.getVisibleSchema = function () {
347
421
  return self.getSchema().filter(function (col) {
348
422
  return !col.hidden;
@@ -342,18 +342,25 @@ export default function (self) {
342
342
  * @param {number} [offsetY=0] Percentage offset the cell should be from the top edge (not including headers). The default is 0, meaning the cell will appear at the top edge. Valid values are 0 through 1. 1 = Bottom, 0 = Top, 0.5 = Center.
343
343
  */
344
344
  self.scrollIntoView = function (x, y, offsetX, offsetY) {
345
- if (
346
- self.visibleCells.filter(function (cell) {
347
- return (
348
- (cell.rowIndex === y || y === undefined) &&
349
- (cell.columnIndex === x || x === undefined) &&
350
- cell.x > 0 &&
351
- cell.y > 0 &&
352
- cell.x + cell.width < self.width &&
353
- cell.y + cell.height < self.height
354
- );
355
- }).length === 0
356
- ) {
345
+ const matched = self.visibleCells.filter(function (cell) {
346
+ return (
347
+ (cell.rowIndex === y || y === undefined) &&
348
+ (cell.columnIndex === x || x === undefined) &&
349
+ cell.x > 0 &&
350
+ cell.y > 0 &&
351
+ cell.x + cell.width < self.width &&
352
+ cell.y + cell.height < self.height
353
+ );
354
+ });
355
+ if (matched.length === 1 && x !== undefined && y !== undefined) {
356
+ // goto specific cell and its part be hidden by header
357
+ if (
358
+ matched[0].x < self.getRowHeaderCellWidth() ||
359
+ matched[0].y < self.getColumnHeaderCellHeight()
360
+ )
361
+ matched.length = 0;
362
+ }
363
+ if (matched.length === 0) {
357
364
  self.gotoCell(x, y, offsetX, offsetY);
358
365
  }
359
366
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canvas-datagrid",
3
- "version": "0.3.21",
3
+ "version": "0.3.25",
4
4
  "description": "Canvas based data grid web component. Capable of displaying millions of contiguous hierarchical rows and columns without paging or loading, on a single canvas element.",
5
5
  "main": "./dist/canvas-datagrid.js",
6
6
  "module": "./dist/canvas-datagrid.module.js",