@tipolox/litgrid-web 1.0.3 → 1.2.0
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/README.md +3 -0
- package/dist/components/DataGrid.d.ts +28 -1
- package/dist/components/DataGrid.d.ts.map +1 -1
- package/dist/components/DataGrid.js +272 -62
- package/dist/components/DataGrid.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +26 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -4
- package/src/DataGrid.ts +1 -0
- package/src/clipboard/serializeSelectedCells.ts +62 -0
- package/src/clipboard/serializeSelectedRows.ts +42 -0
- package/src/columnState/columnState.ts +102 -0
- package/src/components/DataGrid.ts +3317 -0
- package/src/index.ts +20 -0
- package/src/sizing/columnSizing.ts +25 -0
- package/src/sizing/constants.ts +3 -0
- package/src/sizing/rowSizing.ts +5 -0
- package/src/theme/darkTheme.ts +34 -0
- package/src/theme/lightTheme.ts +35 -0
- package/src/types.ts +138 -0
|
@@ -61,6 +61,7 @@ export class DataGrid extends LitElement {
|
|
|
61
61
|
this.scrollbarWidth = 0;
|
|
62
62
|
this.columnDefs = [];
|
|
63
63
|
this.sourceColumnDefs = [];
|
|
64
|
+
this._inferColumns = true;
|
|
64
65
|
this.columnWidths = new Map();
|
|
65
66
|
this.hiddenColumnKeys = new Set();
|
|
66
67
|
this._columnStateStorageKey = null;
|
|
@@ -73,6 +74,7 @@ export class DataGrid extends LitElement {
|
|
|
73
74
|
this.previousBodyCursor = '';
|
|
74
75
|
this.previousBodyUserSelect = '';
|
|
75
76
|
this.activeHeaderMenu = null;
|
|
77
|
+
this.headerMenuCapabilities = { sortable: true, filterable: true };
|
|
76
78
|
this.columnChooserOpen = false;
|
|
77
79
|
this.columnChooserPosition = null;
|
|
78
80
|
this.quickSearchVisible = false;
|
|
@@ -99,6 +101,7 @@ export class DataGrid extends LitElement {
|
|
|
99
101
|
this.shouldRestoreHeaderMenuFocus = false;
|
|
100
102
|
this.screenReaderMessage = '';
|
|
101
103
|
this.lastScreenReaderAnnouncement = '';
|
|
104
|
+
this.paginationFocusTarget = null;
|
|
102
105
|
// Use a larger cap (2^26) to avoid hitting browser element-height limits.
|
|
103
106
|
this.maxScrollableHeight = 67108864;
|
|
104
107
|
this.lastTotalSize = 0;
|
|
@@ -149,22 +152,7 @@ export class DataGrid extends LitElement {
|
|
|
149
152
|
this.requestUpdate();
|
|
150
153
|
};
|
|
151
154
|
this.stopColumnReorder = () => {
|
|
152
|
-
|
|
153
|
-
if (!state)
|
|
154
|
-
return;
|
|
155
|
-
this.columnReorderState = null;
|
|
156
|
-
window.removeEventListener('pointermove', this.handleColumnReorder);
|
|
157
|
-
window.removeEventListener('pointerup', this.stopColumnReorder);
|
|
158
|
-
window.removeEventListener('pointercancel', this.stopColumnReorder);
|
|
159
|
-
this.removeColumnDragPreview();
|
|
160
|
-
if (state.hasMoved)
|
|
161
|
-
this.stopBodyResize();
|
|
162
|
-
if (state.hasMoved && state.sourceIndex !== state.targetIndex) {
|
|
163
|
-
this.moveColumn(state.columnKey, state.targetIndex);
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
this.requestUpdate();
|
|
167
|
-
}
|
|
155
|
+
this.finishColumnReorder();
|
|
168
156
|
};
|
|
169
157
|
this.handleColumnResize = (event) => {
|
|
170
158
|
if (!this.columnResizeState) {
|
|
@@ -175,14 +163,7 @@ export class DataGrid extends LitElement {
|
|
|
175
163
|
this.requestUpdate();
|
|
176
164
|
};
|
|
177
165
|
this.stopColumnResize = () => {
|
|
178
|
-
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
this.columnResizeState = null;
|
|
182
|
-
this.commitColumnState('resize');
|
|
183
|
-
this.stopBodyResize();
|
|
184
|
-
window.removeEventListener('pointermove', this.handleColumnResize);
|
|
185
|
-
window.removeEventListener('pointerup', this.stopColumnResize);
|
|
166
|
+
this.finishColumnResize();
|
|
186
167
|
};
|
|
187
168
|
this.handleRowResize = (event) => {
|
|
188
169
|
if (!this.rowResizeState) {
|
|
@@ -243,6 +224,21 @@ export class DataGrid extends LitElement {
|
|
|
243
224
|
this.ariaLabel = 'Data grid';
|
|
244
225
|
this.ariaDescription = '';
|
|
245
226
|
this.screenReaderAnnouncements = true;
|
|
227
|
+
this.engine.onSelectionChange(detail => {
|
|
228
|
+
this.requestUpdate();
|
|
229
|
+
this.dispatchEvent(new CustomEvent('selection-change', {
|
|
230
|
+
detail, bubbles: true, composed: true
|
|
231
|
+
}));
|
|
232
|
+
});
|
|
233
|
+
this.engine.onQueryChange(detail => {
|
|
234
|
+
this.quickSearchDraft = detail.query.search;
|
|
235
|
+
this.clearBestFitWidths();
|
|
236
|
+
this.resetVerticalScroll();
|
|
237
|
+
this.requestUpdate();
|
|
238
|
+
this.dispatchEvent(new CustomEvent('query-change', {
|
|
239
|
+
detail, bubbles: true, composed: true
|
|
240
|
+
}));
|
|
241
|
+
});
|
|
246
242
|
}
|
|
247
243
|
static { this.styles = [lightThemeStyles, darkThemeStyles, css `
|
|
248
244
|
:host {
|
|
@@ -770,7 +766,16 @@ export class DataGrid extends LitElement {
|
|
|
770
766
|
firstUpdated() {
|
|
771
767
|
this.updateScrollbarWidth();
|
|
772
768
|
}
|
|
769
|
+
willUpdate() {
|
|
770
|
+
// Reconcile after schema and persisted visibility settle, before triggers disappear.
|
|
771
|
+
this.reconcileHeaderMenuCapabilities();
|
|
772
|
+
}
|
|
773
773
|
updated() {
|
|
774
|
+
const paginationTarget = this.paginationFocusTarget;
|
|
775
|
+
this.paginationFocusTarget = null;
|
|
776
|
+
if (paginationTarget?.isConnected && paginationTarget.disabled) {
|
|
777
|
+
this.renderRoot.querySelector('.pagination button:not([disabled]), .pagination select')?.focus();
|
|
778
|
+
}
|
|
774
779
|
this.updateScrollbarWidth();
|
|
775
780
|
if (this.shouldFocusViewport) {
|
|
776
781
|
this.shouldFocusViewport = false;
|
|
@@ -852,7 +857,7 @@ export class DataGrid extends LitElement {
|
|
|
852
857
|
}
|
|
853
858
|
}
|
|
854
859
|
getResolvedColumns() {
|
|
855
|
-
if (this.columnDefs.length > 0) {
|
|
860
|
+
if (this.columnDefs.length > 0 || !this._inferColumns) {
|
|
856
861
|
return this.columnDefs;
|
|
857
862
|
}
|
|
858
863
|
const firstRow = this.engine.getRows()[0];
|
|
@@ -1084,7 +1089,7 @@ export class DataGrid extends LitElement {
|
|
|
1084
1089
|
return null;
|
|
1085
1090
|
}
|
|
1086
1091
|
}
|
|
1087
|
-
restorePersistedColumnState() {
|
|
1092
|
+
restorePersistedColumnState(notify = true) {
|
|
1088
1093
|
if (!this._columnStateStorageKey || this.sourceColumnDefs.length === 0)
|
|
1089
1094
|
return;
|
|
1090
1095
|
const storage = this.getColumnStateStorage();
|
|
@@ -1100,7 +1105,8 @@ export class DataGrid extends LitElement {
|
|
|
1100
1105
|
this.hiddenColumnKeys = resolvedState.hiddenKeys;
|
|
1101
1106
|
this.activeCell = null;
|
|
1102
1107
|
this.requestUpdate();
|
|
1103
|
-
|
|
1108
|
+
if (notify)
|
|
1109
|
+
this.dispatchColumnStateChange('restore');
|
|
1104
1110
|
}
|
|
1105
1111
|
catch {
|
|
1106
1112
|
// Storage may be unavailable or blocked by browser privacy settings.
|
|
@@ -1162,6 +1168,7 @@ export class DataGrid extends LitElement {
|
|
|
1162
1168
|
});
|
|
1163
1169
|
}
|
|
1164
1170
|
const isOpening = this.activeHeaderMenu !== columnKey;
|
|
1171
|
+
this.headerMenuCapabilities = this.getColumnCapabilities(columnKey);
|
|
1165
1172
|
this.activeHeaderMenu = isOpening ? columnKey : null;
|
|
1166
1173
|
this.headerMenuTrigger = isOpening && event.currentTarget instanceof HTMLButtonElement
|
|
1167
1174
|
? event.currentTarget
|
|
@@ -1169,6 +1176,19 @@ export class DataGrid extends LitElement {
|
|
|
1169
1176
|
this.shouldFocusHeaderMenu = isOpening;
|
|
1170
1177
|
this.requestUpdate();
|
|
1171
1178
|
}
|
|
1179
|
+
getColumnCapabilities(columnKey) {
|
|
1180
|
+
const column = this.getResolvedColumns().find(column => column.key === columnKey);
|
|
1181
|
+
return { sortable: !!column && column.sortable !== false, filterable: !!column && column.filterable !== false };
|
|
1182
|
+
}
|
|
1183
|
+
reconcileHeaderMenuCapabilities() {
|
|
1184
|
+
if (this.activeHeaderMenu === null)
|
|
1185
|
+
return;
|
|
1186
|
+
const next = this.getColumnCapabilities(this.activeHeaderMenu);
|
|
1187
|
+
if (!this.isColumnVisible(this.activeHeaderMenu)
|
|
1188
|
+
|| next.sortable !== this.headerMenuCapabilities.sortable || next.filterable !== this.headerMenuCapabilities.filterable) {
|
|
1189
|
+
this.closeHeaderMenu(true);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1172
1192
|
closeHeaderMenu(restoreFocus = false) {
|
|
1173
1193
|
if (this.activeHeaderMenu === null && !this.columnChooserOpen)
|
|
1174
1194
|
return;
|
|
@@ -1300,6 +1320,10 @@ export class DataGrid extends LitElement {
|
|
|
1300
1320
|
}
|
|
1301
1321
|
announceRowSelection(rowIndex, intent = 'replace') {
|
|
1302
1322
|
const anchor = this.engine.getSelection().anchorRowIndex ?? rowIndex;
|
|
1323
|
+
if (intent === 'range' && this.engine.getConfig().selection.rowKey !== undefined) {
|
|
1324
|
+
this.announceScreenReader(`${this.getDisplayedSelectedRowCount()} displayed rows selected.`);
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1303
1327
|
this.announceScreenReader(intent === 'range' && anchor !== rowIndex
|
|
1304
1328
|
? `Rows ${Math.min(anchor, rowIndex) + 1} through ${Math.max(anchor, rowIndex) + 1} selected.`
|
|
1305
1329
|
: `Row ${rowIndex + 1} ${this.engine.isRowSelected(rowIndex) ? 'selected' : 'deselected'}.`);
|
|
@@ -1308,24 +1332,34 @@ export class DataGrid extends LitElement {
|
|
|
1308
1332
|
this.announceScreenReader(`${this.getColumnLabel(columnKey)}, row ${rowIndex + 1} ${this.engine.isCellSelected(rowIndex, columnKey) ? 'selected' : 'deselected'}.`);
|
|
1309
1333
|
}
|
|
1310
1334
|
announceVisibleRowCount(context) {
|
|
1335
|
+
if (this.engine.getConfig().queryMode === 'external') {
|
|
1336
|
+
this.announceScreenReader(`${context}. Server query requested.`);
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1311
1339
|
const rowCount = this.engine.getRowCount();
|
|
1312
1340
|
this.announceScreenReader(rowCount === 0 ? `${context}. No matching rows.` : `${context}. ${rowCount} ${rowCount === 1 ? 'row' : 'rows'} shown.`);
|
|
1313
1341
|
}
|
|
1314
1342
|
handleSortAscending(columnKey) {
|
|
1343
|
+
if (!this.getColumnCapabilities(columnKey).sortable)
|
|
1344
|
+
return;
|
|
1315
1345
|
this.engine.sortBy(columnKey, 'asc');
|
|
1316
|
-
this.announceScreenReader(`${this.getColumnLabel(columnKey)} sorted ascending.`);
|
|
1346
|
+
this.announceScreenReader(`${this.getColumnLabel(columnKey)} ${this.engine.getConfig().queryMode === 'external' ? 'ascending sort requested' : 'sorted ascending'}.`);
|
|
1317
1347
|
this.requestUpdate();
|
|
1318
1348
|
this.closeHeaderMenu(true);
|
|
1319
1349
|
}
|
|
1320
1350
|
handleSortDescending(columnKey) {
|
|
1351
|
+
if (!this.getColumnCapabilities(columnKey).sortable)
|
|
1352
|
+
return;
|
|
1321
1353
|
this.engine.sortBy(columnKey, 'desc');
|
|
1322
|
-
this.announceScreenReader(`${this.getColumnLabel(columnKey)} sorted descending.`);
|
|
1354
|
+
this.announceScreenReader(`${this.getColumnLabel(columnKey)} ${this.engine.getConfig().queryMode === 'external' ? 'descending sort requested' : 'sorted descending'}.`);
|
|
1323
1355
|
this.requestUpdate();
|
|
1324
1356
|
this.closeHeaderMenu(true);
|
|
1325
1357
|
}
|
|
1326
|
-
handleClearSort() {
|
|
1358
|
+
handleClearSort(columnKey) {
|
|
1359
|
+
if (!this.getColumnCapabilities(columnKey).sortable)
|
|
1360
|
+
return;
|
|
1327
1361
|
this.engine.clearSort();
|
|
1328
|
-
this.announceScreenReader('Sorting cleared.');
|
|
1362
|
+
this.announceScreenReader(this.engine.getConfig().queryMode === 'external' ? 'Clear sorting requested.' : 'Sorting cleared.');
|
|
1329
1363
|
this.requestUpdate();
|
|
1330
1364
|
this.closeHeaderMenu(true);
|
|
1331
1365
|
}
|
|
@@ -1498,7 +1532,8 @@ export class DataGrid extends LitElement {
|
|
|
1498
1532
|
this.clearQuickSearchDebounce();
|
|
1499
1533
|
const threshold = Math.max(0, Math.floor(this.quickSearchDebounceThreshold));
|
|
1500
1534
|
const debounceMs = Math.max(0, Math.floor(this.quickSearchDebounceMs));
|
|
1501
|
-
|
|
1535
|
+
const external = this.engine.getConfig().queryMode === 'external';
|
|
1536
|
+
if (debounceMs === 0 || (!external && this.sourceRowCount < threshold)) {
|
|
1502
1537
|
this.applyQuickSearch(query);
|
|
1503
1538
|
return;
|
|
1504
1539
|
}
|
|
@@ -1530,23 +1565,65 @@ export class DataGrid extends LitElement {
|
|
|
1530
1565
|
getFilters() {
|
|
1531
1566
|
return this.engine.getFilters();
|
|
1532
1567
|
}
|
|
1568
|
+
sortBy(columnKey, direction) {
|
|
1569
|
+
this.engine.sortBy(columnKey, direction);
|
|
1570
|
+
this.resetVerticalScroll();
|
|
1571
|
+
this.requestUpdate();
|
|
1572
|
+
}
|
|
1573
|
+
clearSort() {
|
|
1574
|
+
this.engine.clearSort();
|
|
1575
|
+
this.resetVerticalScroll();
|
|
1576
|
+
this.requestUpdate();
|
|
1577
|
+
}
|
|
1578
|
+
getQuery() { return this.engine.getQuery(); }
|
|
1579
|
+
refreshQuery() { this.engine.refreshQuery(); }
|
|
1580
|
+
get externalState() { return this.engine.getExternalState(); }
|
|
1581
|
+
set externalState(value) {
|
|
1582
|
+
if (value)
|
|
1583
|
+
this.setExternalState(value);
|
|
1584
|
+
}
|
|
1585
|
+
setExternalState(value) {
|
|
1586
|
+
if (!this.engine.setExternalState(value))
|
|
1587
|
+
return false;
|
|
1588
|
+
this.clearQuickSearchDebounce();
|
|
1589
|
+
this.quickSearchDraft = this.engine.getQuickSearch();
|
|
1590
|
+
this.filterDrafts = new Map(this.engine.getFilters().map(filter => [filter.columnKey, filter]));
|
|
1591
|
+
this.sourceRowCount = this.engine.getRowCount();
|
|
1592
|
+
this.clearBestFitWidths();
|
|
1593
|
+
this.requestUpdate();
|
|
1594
|
+
return true;
|
|
1595
|
+
}
|
|
1596
|
+
getPageLabel() {
|
|
1597
|
+
const pagination = this.engine.getPagination();
|
|
1598
|
+
if (this.engine.getConfig().queryMode === 'external' && pagination.totalKnown && pagination.pageIndex > 0 && pagination.pageIndex >= pagination.pageCount)
|
|
1599
|
+
return `Page ${pagination.pageIndex + 1} (out of range)`;
|
|
1600
|
+
return pagination.totalKnown === false ? `Page ${pagination.pageIndex + 1}`
|
|
1601
|
+
: `Page ${pagination.pageCount === 0 ? 0 : pagination.pageIndex + 1} of ${pagination.pageCount}`;
|
|
1602
|
+
}
|
|
1533
1603
|
setPage(pageIndex) {
|
|
1604
|
+
this.rememberPaginationFocus();
|
|
1534
1605
|
this.engine.setPage(pageIndex);
|
|
1535
1606
|
this.resetVerticalScroll();
|
|
1536
1607
|
const pagination = this.engine.getPagination();
|
|
1537
|
-
this.announceScreenReader(
|
|
1608
|
+
this.announceScreenReader(`${this.getPageLabel()}.`);
|
|
1538
1609
|
this.requestUpdate();
|
|
1539
1610
|
}
|
|
1540
1611
|
setPageSize(pageSize) {
|
|
1612
|
+
this.rememberPaginationFocus();
|
|
1541
1613
|
this.engine.setPageSize(pageSize);
|
|
1542
1614
|
this.resetVerticalScroll();
|
|
1543
1615
|
const pagination = this.engine.getPagination();
|
|
1544
|
-
this.announceScreenReader(`${pagination.pageSize} rows per page.
|
|
1616
|
+
this.announceScreenReader(`${pagination.pageSize} rows per page. ${this.getPageLabel()}.`);
|
|
1545
1617
|
this.requestUpdate();
|
|
1546
1618
|
}
|
|
1547
1619
|
getPagination() {
|
|
1548
1620
|
return this.engine.getPagination();
|
|
1549
1621
|
}
|
|
1622
|
+
rememberPaginationFocus() {
|
|
1623
|
+
const active = this.shadowRoot?.activeElement;
|
|
1624
|
+
if (active instanceof HTMLButtonElement && active.closest('.pagination'))
|
|
1625
|
+
this.paginationFocusTarget = active;
|
|
1626
|
+
}
|
|
1550
1627
|
getTotalRowCount() {
|
|
1551
1628
|
return this.engine.getTotalRowCount();
|
|
1552
1629
|
}
|
|
@@ -1565,13 +1642,18 @@ export class DataGrid extends LitElement {
|
|
|
1565
1642
|
this.announceRowSelection(rowIndex, effectiveIntent);
|
|
1566
1643
|
this.requestUpdate();
|
|
1567
1644
|
}
|
|
1645
|
+
announceAllRowsSelected() {
|
|
1646
|
+
const count = this.engine.getSelectableRowCount();
|
|
1647
|
+
const qualifier = count === this.engine.getRowCount() ? '' : 'selectable ';
|
|
1648
|
+
this.announceScreenReader(`All ${count} ${qualifier}displayed rows selected.`);
|
|
1649
|
+
}
|
|
1568
1650
|
selectAllRows() {
|
|
1569
|
-
if (this.engine.getConfig().selection.mode !== 'multi-row' || this.engine.
|
|
1651
|
+
if (this.engine.getConfig().selection.mode !== 'multi-row' || this.engine.getSelectableRowCount() === 0)
|
|
1570
1652
|
return;
|
|
1571
|
-
const unchanged = this.engine.getSelection().rowIndexes.size === this.engine.
|
|
1653
|
+
const unchanged = this.engine.getSelection().rowIndexes.size === this.engine.getSelectableRowCount();
|
|
1572
1654
|
this.engine.selectAllRows();
|
|
1573
1655
|
if (!unchanged)
|
|
1574
|
-
this.
|
|
1656
|
+
this.announceAllRowsSelected();
|
|
1575
1657
|
this.requestUpdate();
|
|
1576
1658
|
}
|
|
1577
1659
|
clearSelection() {
|
|
@@ -1585,6 +1667,21 @@ export class DataGrid extends LitElement {
|
|
|
1585
1667
|
getSelection() {
|
|
1586
1668
|
return this.engine.getSelection();
|
|
1587
1669
|
}
|
|
1670
|
+
getSelectionSnapshot() {
|
|
1671
|
+
return this.engine.getSelectionSnapshot();
|
|
1672
|
+
}
|
|
1673
|
+
selectRowByKey(key, intent) {
|
|
1674
|
+
const previous = this.engine.getSelectionSnapshot();
|
|
1675
|
+
const selected = this.engine.selectRowByKey(key, intent);
|
|
1676
|
+
const next = this.engine.getSelectionSnapshot();
|
|
1677
|
+
if (selected && next !== previous) {
|
|
1678
|
+
const index = next.rows.find(row => row.key === key)?.rowIndex
|
|
1679
|
+
?? previous.rows.find(row => row.key === key)?.rowIndex;
|
|
1680
|
+
if (index !== undefined)
|
|
1681
|
+
this.announceRowSelection(index, intent);
|
|
1682
|
+
}
|
|
1683
|
+
return selected;
|
|
1684
|
+
}
|
|
1588
1685
|
isRowSelected(rowIndex) {
|
|
1589
1686
|
return this.engine.isRowSelected(rowIndex);
|
|
1590
1687
|
}
|
|
@@ -1636,6 +1733,8 @@ export class DataGrid extends LitElement {
|
|
|
1636
1733
|
}
|
|
1637
1734
|
}
|
|
1638
1735
|
applyHeaderFilter(columnKey) {
|
|
1736
|
+
if (!this.getColumnCapabilities(columnKey).filterable)
|
|
1737
|
+
return;
|
|
1639
1738
|
const filter = this.getFilterDraft(columnKey);
|
|
1640
1739
|
this.setFilter({
|
|
1641
1740
|
...filter,
|
|
@@ -1644,6 +1743,8 @@ export class DataGrid extends LitElement {
|
|
|
1644
1743
|
this.closeHeaderMenu(true);
|
|
1645
1744
|
}
|
|
1646
1745
|
clearHeaderFilter(columnKey) {
|
|
1746
|
+
if (!this.getColumnCapabilities(columnKey).filterable)
|
|
1747
|
+
return;
|
|
1647
1748
|
this.clearFilter(columnKey);
|
|
1648
1749
|
this.closeHeaderMenu(true);
|
|
1649
1750
|
}
|
|
@@ -1743,6 +1844,24 @@ export class DataGrid extends LitElement {
|
|
|
1743
1844
|
this.columnDragPreview?.remove();
|
|
1744
1845
|
this.columnDragPreview = null;
|
|
1745
1846
|
}
|
|
1847
|
+
finishColumnReorder(commit = true) {
|
|
1848
|
+
const state = this.columnReorderState;
|
|
1849
|
+
if (!state)
|
|
1850
|
+
return;
|
|
1851
|
+
this.columnReorderState = null;
|
|
1852
|
+
window.removeEventListener('pointermove', this.handleColumnReorder);
|
|
1853
|
+
window.removeEventListener('pointerup', this.stopColumnReorder);
|
|
1854
|
+
window.removeEventListener('pointercancel', this.stopColumnReorder);
|
|
1855
|
+
this.removeColumnDragPreview();
|
|
1856
|
+
if (state.hasMoved)
|
|
1857
|
+
this.stopBodyResize();
|
|
1858
|
+
if (commit && state.hasMoved && state.sourceIndex !== state.targetIndex) {
|
|
1859
|
+
this.moveColumn(state.columnKey, state.targetIndex);
|
|
1860
|
+
}
|
|
1861
|
+
else {
|
|
1862
|
+
this.requestUpdate();
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1746
1865
|
getGridCellId(rowIndex, columnIndex) {
|
|
1747
1866
|
return `${this.gridId}-cell-${rowIndex}-${columnIndex}`;
|
|
1748
1867
|
}
|
|
@@ -1789,6 +1908,17 @@ export class DataGrid extends LitElement {
|
|
|
1789
1908
|
window.addEventListener('pointermove', this.handleColumnResize);
|
|
1790
1909
|
window.addEventListener('pointerup', this.stopColumnResize);
|
|
1791
1910
|
}
|
|
1911
|
+
finishColumnResize(commit = true) {
|
|
1912
|
+
if (!this.columnResizeState) {
|
|
1913
|
+
return;
|
|
1914
|
+
}
|
|
1915
|
+
this.columnResizeState = null;
|
|
1916
|
+
if (commit)
|
|
1917
|
+
this.commitColumnState('resize');
|
|
1918
|
+
this.stopBodyResize();
|
|
1919
|
+
window.removeEventListener('pointermove', this.handleColumnResize);
|
|
1920
|
+
window.removeEventListener('pointerup', this.stopColumnResize);
|
|
1921
|
+
}
|
|
1792
1922
|
startRowResize(event, rowIndex) {
|
|
1793
1923
|
event.preventDefault();
|
|
1794
1924
|
event.stopPropagation();
|
|
@@ -2244,6 +2374,8 @@ export class DataGrid extends LitElement {
|
|
|
2244
2374
|
`;
|
|
2245
2375
|
}
|
|
2246
2376
|
set data(value) {
|
|
2377
|
+
if (this.engine.getConfig().queryMode === 'external')
|
|
2378
|
+
return;
|
|
2247
2379
|
this.clearQuickSearchDebounce();
|
|
2248
2380
|
this.sourceRowCount = Array.isArray(value) ? value.length : 0;
|
|
2249
2381
|
this.engine.setData(value ?? []);
|
|
@@ -2256,22 +2388,15 @@ export class DataGrid extends LitElement {
|
|
|
2256
2388
|
this.selectRow(rowIndex, 'toggle');
|
|
2257
2389
|
}
|
|
2258
2390
|
getDisplayedSelectedRowCount() {
|
|
2259
|
-
|
|
2260
|
-
let selectedCount = 0;
|
|
2261
|
-
for (let rowIndex = 0; rowIndex < rowCount; rowIndex += 1) {
|
|
2262
|
-
if (this.engine.isRowSelected(rowIndex)) {
|
|
2263
|
-
selectedCount += 1;
|
|
2264
|
-
}
|
|
2265
|
-
}
|
|
2266
|
-
return selectedCount;
|
|
2391
|
+
return this.engine.getSelectionSnapshot().rowIndexes.length;
|
|
2267
2392
|
}
|
|
2268
2393
|
areAllDisplayedRowsSelected() {
|
|
2269
|
-
const rowCount = this.engine.
|
|
2394
|
+
const rowCount = this.engine.getSelectableRowCount();
|
|
2270
2395
|
return rowCount > 0 && this.getDisplayedSelectedRowCount() === rowCount;
|
|
2271
2396
|
}
|
|
2272
2397
|
hasPartiallySelectedDisplayedRows() {
|
|
2273
2398
|
const selectedCount = this.getDisplayedSelectedRowCount();
|
|
2274
|
-
return selectedCount > 0 && selectedCount < this.engine.
|
|
2399
|
+
return selectedCount > 0 && selectedCount < this.engine.getSelectableRowCount();
|
|
2275
2400
|
}
|
|
2276
2401
|
handleSelectAllRows() {
|
|
2277
2402
|
if (this.areAllDisplayedRowsSelected()) {
|
|
@@ -2280,7 +2405,80 @@ export class DataGrid extends LitElement {
|
|
|
2280
2405
|
}
|
|
2281
2406
|
else {
|
|
2282
2407
|
this.engine.selectAllRows();
|
|
2283
|
-
this.
|
|
2408
|
+
this.announceAllRowsSelected();
|
|
2409
|
+
}
|
|
2410
|
+
this.requestUpdate();
|
|
2411
|
+
}
|
|
2412
|
+
get inferColumns() {
|
|
2413
|
+
return this._inferColumns;
|
|
2414
|
+
}
|
|
2415
|
+
set inferColumns(value) {
|
|
2416
|
+
if (this._inferColumns === value)
|
|
2417
|
+
return;
|
|
2418
|
+
this._inferColumns = value;
|
|
2419
|
+
this.requestUpdate();
|
|
2420
|
+
}
|
|
2421
|
+
/** Reconcile an explicit schema, optionally switching its persistence namespace atomically. */
|
|
2422
|
+
updateColumnSchema(value, applyOrder = false, storageKey = this._columnStateStorageKey) {
|
|
2423
|
+
const columns = value.map(column => ({ ...column }));
|
|
2424
|
+
const nextByKey = new Map(columns.map(column => [column.key, column]));
|
|
2425
|
+
if (nextByKey.size !== columns.length)
|
|
2426
|
+
throw new Error('Duplicate column schema keys.');
|
|
2427
|
+
const previousByKey = new Map(this.sourceColumnDefs.map(column => [column.key, column]));
|
|
2428
|
+
const activeCell = this.activeCell;
|
|
2429
|
+
const activeColumnKey = activeCell
|
|
2430
|
+
? this.getVisibleColumns()[activeCell.columnIndex]?.key
|
|
2431
|
+
: undefined;
|
|
2432
|
+
const nextStorageKey = typeof storageKey === 'string' && storageKey.trim() ? storageKey : null;
|
|
2433
|
+
const storageChanged = nextStorageKey !== this._columnStateStorageKey;
|
|
2434
|
+
this.finishColumnResize(false);
|
|
2435
|
+
this.finishColumnReorder(false);
|
|
2436
|
+
if (storageChanged) {
|
|
2437
|
+
// Install the target schema and defaults before restoring its namespace.
|
|
2438
|
+
// Do not reconcile/persist the outgoing view, or carry its customization
|
|
2439
|
+
// into an unsaved target. A namespace switch itself performs no writes.
|
|
2440
|
+
this._columnStateStorageKey = nextStorageKey;
|
|
2441
|
+
this.sourceColumnDefs = columns;
|
|
2442
|
+
this.columnDefs = [...columns];
|
|
2443
|
+
this.columnWidths.clear();
|
|
2444
|
+
this.hiddenColumnKeys = new Set(columns.filter(column => column.hidden).map(column => column.key));
|
|
2445
|
+
this.restorePersistedColumnState(false);
|
|
2446
|
+
}
|
|
2447
|
+
else {
|
|
2448
|
+
for (const key of this.columnWidths.keys()) {
|
|
2449
|
+
if (!nextByKey.has(key))
|
|
2450
|
+
this.columnWidths.delete(key);
|
|
2451
|
+
}
|
|
2452
|
+
for (const key of this.hiddenColumnKeys) {
|
|
2453
|
+
if (!nextByKey.has(key))
|
|
2454
|
+
this.hiddenColumnKeys.delete(key);
|
|
2455
|
+
}
|
|
2456
|
+
for (const column of columns) {
|
|
2457
|
+
const previous = previousByKey.get(column.key);
|
|
2458
|
+
if (!previous || previous.width !== column.width)
|
|
2459
|
+
this.columnWidths.delete(column.key);
|
|
2460
|
+
if (!previous || previous.hidden !== column.hidden) {
|
|
2461
|
+
if (column.hidden)
|
|
2462
|
+
this.hiddenColumnKeys.add(column.key);
|
|
2463
|
+
else
|
|
2464
|
+
this.hiddenColumnKeys.delete(column.key);
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
// Surviving columns keep user order; newly registered columns follow them.
|
|
2468
|
+
// An explicit Order change reapplies the complete deterministic schema order.
|
|
2469
|
+
const retained = this.columnDefs.filter(column => nextByKey.has(column.key))
|
|
2470
|
+
.map(column => nextByKey.get(column.key));
|
|
2471
|
+
const retainedKeys = new Set(retained.map(column => column.key));
|
|
2472
|
+
this.columnDefs = applyOrder ? columns : [
|
|
2473
|
+
...retained, ...columns.filter(column => !retainedKeys.has(column.key))
|
|
2474
|
+
];
|
|
2475
|
+
this.sourceColumnDefs = columns;
|
|
2476
|
+
this.persistColumnState();
|
|
2477
|
+
}
|
|
2478
|
+
this.clearBestFitWidths();
|
|
2479
|
+
if (activeCell) {
|
|
2480
|
+
const columnIndex = this.getVisibleColumns().findIndex(column => column.key === activeColumnKey);
|
|
2481
|
+
this.activeCell = columnIndex < 0 ? null : { ...activeCell, columnIndex };
|
|
2284
2482
|
}
|
|
2285
2483
|
this.requestUpdate();
|
|
2286
2484
|
}
|
|
@@ -2333,6 +2531,12 @@ export class DataGrid extends LitElement {
|
|
|
2333
2531
|
render() {
|
|
2334
2532
|
const count = this.engine.getRowCount();
|
|
2335
2533
|
const pagination = this.engine.getPagination();
|
|
2534
|
+
const external = this.engine.getConfig().queryMode === 'external';
|
|
2535
|
+
const externalState = external ? this.engine.getExternalState() : null;
|
|
2536
|
+
const outOfRange = external && pagination.totalKnown && pagination.pageIndex > 0 && pagination.pageIndex >= pagination.pageCount;
|
|
2537
|
+
const externalStatus = externalState?.loading ? 'Loading results.' : externalState?.error
|
|
2538
|
+
? externalState.error : outOfRange ? 'Requested page is out of range. Choose a previous page.'
|
|
2539
|
+
: count === 0 ? (pagination.totalRows === 0 ? 'No results.' : 'No rows on this page.') : '';
|
|
2336
2540
|
const pageSizeOptions = [25, 50, 100].includes(pagination.pageSize)
|
|
2337
2541
|
? [25, 50, 100]
|
|
2338
2542
|
: [pagination.pageSize, 25, 50, 100].sort((left, right) => left - right);
|
|
@@ -2448,14 +2652,16 @@ export class DataGrid extends LitElement {
|
|
|
2448
2652
|
return html `
|
|
2449
2653
|
<div
|
|
2450
2654
|
class="shell"
|
|
2655
|
+
aria-busy=${external ? String(!!externalState?.loading) : nothing}
|
|
2451
2656
|
style=${`--virtual-grid-template: ${gridTemplate}; --total-column-width: ${totalColumnWidth}px; --total-row-width: ${totalRowWidth}px; --row-header-width: ${rowHeaderWidth}px; --header-grid-template: ${headerGridTemplate}; --scrollbar-width: ${this.scrollbarWidth}px;`}
|
|
2452
2657
|
@keydown=${this.handleShellKeyDown}
|
|
2453
2658
|
>
|
|
2454
2659
|
<p id=${`${this.gridId}-description`} class="screen-reader-only">${this.getScreenReaderDescription()}</p>
|
|
2660
|
+
${external && externalStatus ? html `<div class="external-status" role=${externalState?.error ? 'alert' : 'status'}>${externalStatus}</div>` : nothing}
|
|
2455
2661
|
${this.screenReaderAnnouncements
|
|
2456
2662
|
? html `<div class="screen-reader-only" role="status" aria-live="polite" aria-atomic="true">${this.screenReaderMessage}</div>`
|
|
2457
2663
|
: ''}
|
|
2458
|
-
${this.quickSearchVisible
|
|
2664
|
+
${this.quickSearchVisible || external
|
|
2459
2665
|
? html `
|
|
2460
2666
|
<div class="quick-search" role="search">
|
|
2461
2667
|
<input
|
|
@@ -2501,7 +2707,7 @@ export class DataGrid extends LitElement {
|
|
|
2501
2707
|
aria-label="Select all displayed rows"
|
|
2502
2708
|
.checked=${this.areAllDisplayedRowsSelected()}
|
|
2503
2709
|
.indeterminate=${this.hasPartiallySelectedDisplayedRows()}
|
|
2504
|
-
?disabled=${this.engine.
|
|
2710
|
+
?disabled=${this.engine.getSelectableRowCount() === 0}
|
|
2505
2711
|
@change=${() => this.handleSelectAllRows()}
|
|
2506
2712
|
/>`
|
|
2507
2713
|
: html `<span>#</span>`}
|
|
@@ -2565,19 +2771,23 @@ export class DataGrid extends LitElement {
|
|
|
2565
2771
|
${this.quickSearchVisible ? 'Hide Quick Search' : 'Show Quick Search'}
|
|
2566
2772
|
</button>
|
|
2567
2773
|
<hr class="header-menu-divider" />
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2774
|
+
${column.sortable === false ? '' : html `
|
|
2775
|
+
<button type="button" @click=${() => this.handleSortAscending(column.key)}>
|
|
2776
|
+
Sort Asc
|
|
2777
|
+
</button>
|
|
2778
|
+
<button type="button" @click=${() => this.handleSortDescending(column.key)}>
|
|
2779
|
+
Sort Desc
|
|
2780
|
+
</button>
|
|
2781
|
+
<button type="button" @click=${() => this.handleClearSort(column.key)}>
|
|
2782
|
+
Clear Sort
|
|
2783
|
+
</button>
|
|
2784
|
+
`}
|
|
2577
2785
|
<button type="button" @click=${(event) => this.toggleColumnChooser(event)}>
|
|
2578
2786
|
Columns
|
|
2579
2787
|
</button>
|
|
2580
2788
|
${(() => {
|
|
2789
|
+
if (column.filterable === false)
|
|
2790
|
+
return '';
|
|
2581
2791
|
const draft = this.getFilterDraft(column.key);
|
|
2582
2792
|
const valueFree = this.isValueFreeFilter(draft.operator);
|
|
2583
2793
|
return html `
|
|
@@ -2692,7 +2902,7 @@ export class DataGrid extends LitElement {
|
|
|
2692
2902
|
? html `
|
|
2693
2903
|
<div class="pagination" role="navigation" aria-label="Pagination">
|
|
2694
2904
|
<div class="pagination-summary">
|
|
2695
|
-
<span>${pagination.totalRows} rows</span>
|
|
2905
|
+
<span>${pagination.totalKnown === false ? 'Total unknown' : `${pagination.totalRows} rows`}</span>
|
|
2696
2906
|
<label>
|
|
2697
2907
|
Rows per page
|
|
2698
2908
|
<select
|
|
@@ -2708,7 +2918,7 @@ export class DataGrid extends LitElement {
|
|
|
2708
2918
|
</label>
|
|
2709
2919
|
</div>
|
|
2710
2920
|
<div class="pagination-navigation">
|
|
2711
|
-
<span
|
|
2921
|
+
<span>${this.getPageLabel()}</span>
|
|
2712
2922
|
<button
|
|
2713
2923
|
type="button"
|
|
2714
2924
|
?disabled=${pagination.pageIndex === 0}
|
|
@@ -2716,7 +2926,7 @@ export class DataGrid extends LitElement {
|
|
|
2716
2926
|
>Previous</button>
|
|
2717
2927
|
<button
|
|
2718
2928
|
type="button"
|
|
2719
|
-
?disabled=${pagination.pageCount === 0 || pagination.pageIndex >= pagination.pageCount - 1}
|
|
2929
|
+
?disabled=${pagination.totalKnown === false ? !pagination.hasMore : pagination.pageCount === 0 || pagination.pageIndex >= pagination.pageCount - 1}
|
|
2720
2930
|
@click=${() => this.setPage(pagination.pageIndex + 1)}
|
|
2721
2931
|
>Next</button>
|
|
2722
2932
|
</div>
|