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/CHANGELOG.md +31 -0
- package/dist/canvas-datagrid.debug.js +940 -171
- package/dist/canvas-datagrid.debug.map +1 -1
- package/dist/canvas-datagrid.js +1 -1
- package/dist/canvas-datagrid.module.js +1 -1
- package/dist/canvas-datagrid.module.js.map +1 -1
- package/dist/types.d.ts +14 -0
- package/lib/button.js +326 -0
- package/lib/contextMenu.js +47 -8
- package/lib/defaults.js +27 -0
- package/lib/docs.js +9 -1
- package/lib/dom.js +90 -0
- package/lib/draw.js +68 -0
- package/lib/events/index.js +139 -114
- package/lib/events/util.js +39 -1
- package/lib/intf.js +125 -0
- package/lib/main.js +2 -0
- package/lib/publicMethods.js +74 -3
- package/package.json +1 -1
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;
|
package/lib/events/index.js
CHANGED
|
@@ -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 (
|
|
516
|
-
if (
|
|
517
|
-
|
|
518
|
-
self.
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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.
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
)
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
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.
|
|
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
|
|
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.
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
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.
|
|
1784
|
-
|
|
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
|
-
|
|
1878
|
+
// Expecting instance of `ClipboardEvent` with `clipboardData` attribute
|
|
1879
|
+
if (!self.hasFocus || !e.clipboardData) {
|
|
1880
|
+
return;
|
|
1881
|
+
}
|
|
1791
1882
|
|
|
1792
|
-
|
|
1793
|
-
const boundColumnIndex = self.getBoundColumnIndexFromViewColumnIndex(
|
|
1794
|
-
columnIndex,
|
|
1795
|
-
);
|
|
1796
|
-
const colName = schema[boundColumnIndex].name;
|
|
1883
|
+
self.copySelectedCellsToClipboard(e.clipboardData);
|
|
1797
1884
|
|
|
1798
|
-
|
|
1885
|
+
const affectedCells = self.clearSelectedCells();
|
|
1799
1886
|
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
boundRowIndex,
|
|
1804
|
-
boundColumnIndex,
|
|
1805
|
-
]);
|
|
1806
|
-
}
|
|
1807
|
-
}
|
|
1887
|
+
self.dispatchEvent('aftercut', {
|
|
1888
|
+
cells: affectedCells,
|
|
1889
|
+
});
|
|
1808
1890
|
|
|
1809
|
-
|
|
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
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
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, '<').replace(/>/g, '>');
|
|
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
|
}
|
package/lib/events/util.js
CHANGED
|
@@ -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, '<').replace(/>/g, '>');
|
|
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
|
-
|
|
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 (
|