@tipolox/litgrid-web 1.1.0 → 1.2.1
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 +2 -0
- package/dist/components/DataGrid.d.ts +21 -1
- package/dist/components/DataGrid.d.ts.map +1 -1
- package/dist/components/DataGrid.js +224 -35
- 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 +22 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/components/DataGrid.ts +227 -37
- package/src/index.ts +4 -0
- package/src/types.ts +30 -0
|
@@ -7,6 +7,11 @@ import {
|
|
|
7
7
|
type GridConfig,
|
|
8
8
|
type GridFilter,
|
|
9
9
|
type GridSelection,
|
|
10
|
+
type GridRowKey,
|
|
11
|
+
type SelectionChangeDetail,
|
|
12
|
+
type GridExternalState,
|
|
13
|
+
type GridQueryChangeDetail,
|
|
14
|
+
type SortDirection,
|
|
10
15
|
type SelectionIntent
|
|
11
16
|
} from '@tipolox/litgrid-core'
|
|
12
17
|
import {
|
|
@@ -118,6 +123,7 @@ export class DataGrid extends LitElement {
|
|
|
118
123
|
private previousBodyCursor = ''
|
|
119
124
|
private previousBodyUserSelect = ''
|
|
120
125
|
private activeHeaderMenu: string | null = null
|
|
126
|
+
private headerMenuCapabilities = { sortable: true, filterable: true }
|
|
121
127
|
private columnChooserOpen = false
|
|
122
128
|
private columnChooserPosition: { left: number; top: number } | null = null
|
|
123
129
|
private quickSearchVisible = false
|
|
@@ -145,6 +151,7 @@ export class DataGrid extends LitElement {
|
|
|
145
151
|
private shouldRestoreHeaderMenuFocus = false
|
|
146
152
|
private screenReaderMessage = ''
|
|
147
153
|
private lastScreenReaderAnnouncement = ''
|
|
154
|
+
private paginationFocusTarget: HTMLButtonElement | null = null
|
|
148
155
|
|
|
149
156
|
// Use a larger cap (2^26) to avoid hitting browser element-height limits.
|
|
150
157
|
|
|
@@ -171,6 +178,21 @@ export class DataGrid extends LitElement {
|
|
|
171
178
|
this.ariaLabel = 'Data grid'
|
|
172
179
|
this.ariaDescription = ''
|
|
173
180
|
this.screenReaderAnnouncements = true
|
|
181
|
+
this.engine.onSelectionChange(detail => {
|
|
182
|
+
this.requestUpdate()
|
|
183
|
+
this.dispatchEvent(new CustomEvent<SelectionChangeDetail>('selection-change', {
|
|
184
|
+
detail, bubbles: true, composed: true
|
|
185
|
+
}))
|
|
186
|
+
})
|
|
187
|
+
this.engine.onQueryChange(detail => {
|
|
188
|
+
this.quickSearchDraft = detail.query.search
|
|
189
|
+
this.clearBestFitWidths()
|
|
190
|
+
this.resetVerticalScroll()
|
|
191
|
+
this.requestUpdate()
|
|
192
|
+
this.dispatchEvent(new CustomEvent<GridQueryChangeDetail>('query-change', {
|
|
193
|
+
detail, bubbles: true, composed: true
|
|
194
|
+
}))
|
|
195
|
+
})
|
|
174
196
|
}
|
|
175
197
|
|
|
176
198
|
static styles = [lightThemeStyles, darkThemeStyles, css`
|
|
@@ -191,11 +213,68 @@ export class DataGrid extends LitElement {
|
|
|
191
213
|
overflow: hidden;
|
|
192
214
|
}
|
|
193
215
|
|
|
216
|
+
.viewport-frame {
|
|
217
|
+
position: relative;
|
|
218
|
+
min-width: 0;
|
|
219
|
+
}
|
|
220
|
+
|
|
194
221
|
.viewport {
|
|
195
222
|
overflow: auto;
|
|
196
223
|
height: var(--grid-height, 320px);
|
|
197
224
|
}
|
|
198
225
|
|
|
226
|
+
/* The transient external status covers only the body and never enters the
|
|
227
|
+
normal layout flow. Header/search/footer positions remain unchanged. */
|
|
228
|
+
.external-status {
|
|
229
|
+
position: absolute;
|
|
230
|
+
inset: 0;
|
|
231
|
+
z-index: 2;
|
|
232
|
+
box-sizing: border-box;
|
|
233
|
+
display: flex;
|
|
234
|
+
align-items: center;
|
|
235
|
+
justify-content: center;
|
|
236
|
+
padding: 16px;
|
|
237
|
+
background: var(--litgrid-color-surface);
|
|
238
|
+
color: var(--litgrid-color-text-strong);
|
|
239
|
+
text-align: center;
|
|
240
|
+
pointer-events: none;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.external-status-label {
|
|
244
|
+
display: inline-flex;
|
|
245
|
+
align-items: center;
|
|
246
|
+
justify-content: center;
|
|
247
|
+
gap: 10px;
|
|
248
|
+
max-width: 100%;
|
|
249
|
+
border: 1px solid var(--litgrid-color-border);
|
|
250
|
+
border-radius: 8px;
|
|
251
|
+
padding: 10px 14px;
|
|
252
|
+
background: var(--litgrid-color-surface-subtle);
|
|
253
|
+
font-weight: 600;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.external-status.loading .external-status-label::before {
|
|
257
|
+
content: '';
|
|
258
|
+
box-sizing: border-box;
|
|
259
|
+
flex: none;
|
|
260
|
+
width: 16px;
|
|
261
|
+
height: 16px;
|
|
262
|
+
border: 2px solid var(--litgrid-color-border-strong);
|
|
263
|
+
border-top-color: var(--litgrid-color-accent);
|
|
264
|
+
border-radius: 50%;
|
|
265
|
+
animation: litgrid-external-loading-spin 0.8s linear infinite;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
@keyframes litgrid-external-loading-spin {
|
|
269
|
+
to { transform: rotate(360deg); }
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
@media (prefers-reduced-motion: reduce) {
|
|
273
|
+
.external-status.loading .external-status-label::before {
|
|
274
|
+
animation: none;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
199
278
|
.screen-reader-only {
|
|
200
279
|
position: absolute;
|
|
201
280
|
width: 1px;
|
|
@@ -701,7 +780,17 @@ export class DataGrid extends LitElement {
|
|
|
701
780
|
this.updateScrollbarWidth()
|
|
702
781
|
}
|
|
703
782
|
|
|
783
|
+
willUpdate() {
|
|
784
|
+
// Reconcile after schema and persisted visibility settle, before triggers disappear.
|
|
785
|
+
this.reconcileHeaderMenuCapabilities()
|
|
786
|
+
}
|
|
787
|
+
|
|
704
788
|
updated() {
|
|
789
|
+
const paginationTarget = this.paginationFocusTarget
|
|
790
|
+
this.paginationFocusTarget = null
|
|
791
|
+
if (paginationTarget?.isConnected && paginationTarget.disabled) {
|
|
792
|
+
this.renderRoot.querySelector<HTMLElement>('.pagination button:not([disabled]), .pagination select')?.focus()
|
|
793
|
+
}
|
|
705
794
|
this.updateScrollbarWidth()
|
|
706
795
|
|
|
707
796
|
if (this.shouldFocusViewport) {
|
|
@@ -1162,6 +1251,7 @@ export class DataGrid extends LitElement {
|
|
|
1162
1251
|
})
|
|
1163
1252
|
}
|
|
1164
1253
|
const isOpening = this.activeHeaderMenu !== columnKey
|
|
1254
|
+
this.headerMenuCapabilities = this.getColumnCapabilities(columnKey)
|
|
1165
1255
|
|
|
1166
1256
|
this.activeHeaderMenu = isOpening ? columnKey : null
|
|
1167
1257
|
this.headerMenuTrigger = isOpening && event.currentTarget instanceof HTMLButtonElement
|
|
@@ -1171,6 +1261,20 @@ export class DataGrid extends LitElement {
|
|
|
1171
1261
|
this.requestUpdate()
|
|
1172
1262
|
}
|
|
1173
1263
|
|
|
1264
|
+
private getColumnCapabilities(columnKey: string) {
|
|
1265
|
+
const column = this.getResolvedColumns().find(column => column.key === columnKey)
|
|
1266
|
+
return { sortable: !!column && column.sortable !== false, filterable: !!column && column.filterable !== false }
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
private reconcileHeaderMenuCapabilities() {
|
|
1270
|
+
if (this.activeHeaderMenu === null) return
|
|
1271
|
+
const next = this.getColumnCapabilities(this.activeHeaderMenu)
|
|
1272
|
+
if (!this.isColumnVisible(this.activeHeaderMenu)
|
|
1273
|
+
|| next.sortable !== this.headerMenuCapabilities.sortable || next.filterable !== this.headerMenuCapabilities.filterable) {
|
|
1274
|
+
this.closeHeaderMenu(true)
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1174
1278
|
private closeHeaderMenu(restoreFocus = false) {
|
|
1175
1279
|
if (this.activeHeaderMenu === null && !this.columnChooserOpen) return
|
|
1176
1280
|
this.activeHeaderMenu = null
|
|
@@ -1320,6 +1424,10 @@ export class DataGrid extends LitElement {
|
|
|
1320
1424
|
|
|
1321
1425
|
private announceRowSelection(rowIndex: number, intent: SelectionIntent = 'replace') {
|
|
1322
1426
|
const anchor = this.engine.getSelection().anchorRowIndex ?? rowIndex
|
|
1427
|
+
if (intent === 'range' && this.engine.getConfig().selection.rowKey !== undefined) {
|
|
1428
|
+
this.announceScreenReader(`${this.getDisplayedSelectedRowCount()} displayed rows selected.`)
|
|
1429
|
+
return
|
|
1430
|
+
}
|
|
1323
1431
|
this.announceScreenReader(intent === 'range' && anchor !== rowIndex
|
|
1324
1432
|
? `Rows ${Math.min(anchor, rowIndex) + 1} through ${Math.max(anchor, rowIndex) + 1} selected.`
|
|
1325
1433
|
: `Row ${rowIndex + 1} ${this.engine.isRowSelected(rowIndex) ? 'selected' : 'deselected'}.`)
|
|
@@ -1330,6 +1438,10 @@ export class DataGrid extends LitElement {
|
|
|
1330
1438
|
}
|
|
1331
1439
|
|
|
1332
1440
|
private announceVisibleRowCount(context: string) {
|
|
1441
|
+
if (this.engine.getConfig().queryMode === 'external') {
|
|
1442
|
+
this.announceScreenReader(`${context}. Server query requested.`)
|
|
1443
|
+
return
|
|
1444
|
+
}
|
|
1333
1445
|
const rowCount = this.engine.getRowCount()
|
|
1334
1446
|
this.announceScreenReader(rowCount === 0 ? `${context}. No matching rows.` : `${context}. ${rowCount} ${rowCount === 1 ? 'row' : 'rows'} shown.`)
|
|
1335
1447
|
}
|
|
@@ -1337,22 +1449,25 @@ export class DataGrid extends LitElement {
|
|
|
1337
1449
|
|
|
1338
1450
|
|
|
1339
1451
|
private handleSortAscending(columnKey: string) {
|
|
1452
|
+
if (!this.getColumnCapabilities(columnKey).sortable) return
|
|
1340
1453
|
this.engine.sortBy(columnKey, 'asc')
|
|
1341
|
-
this.announceScreenReader(`${this.getColumnLabel(columnKey)} sorted ascending.`)
|
|
1454
|
+
this.announceScreenReader(`${this.getColumnLabel(columnKey)} ${this.engine.getConfig().queryMode === 'external' ? 'ascending sort requested' : 'sorted ascending'}.`)
|
|
1342
1455
|
this.requestUpdate()
|
|
1343
1456
|
this.closeHeaderMenu(true)
|
|
1344
1457
|
}
|
|
1345
1458
|
|
|
1346
1459
|
private handleSortDescending(columnKey: string) {
|
|
1460
|
+
if (!this.getColumnCapabilities(columnKey).sortable) return
|
|
1347
1461
|
this.engine.sortBy(columnKey, 'desc')
|
|
1348
|
-
this.announceScreenReader(`${this.getColumnLabel(columnKey)} sorted descending.`)
|
|
1462
|
+
this.announceScreenReader(`${this.getColumnLabel(columnKey)} ${this.engine.getConfig().queryMode === 'external' ? 'descending sort requested' : 'sorted descending'}.`)
|
|
1349
1463
|
this.requestUpdate()
|
|
1350
1464
|
this.closeHeaderMenu(true)
|
|
1351
1465
|
}
|
|
1352
1466
|
|
|
1353
|
-
private handleClearSort() {
|
|
1467
|
+
private handleClearSort(columnKey: string) {
|
|
1468
|
+
if (!this.getColumnCapabilities(columnKey).sortable) return
|
|
1354
1469
|
this.engine.clearSort()
|
|
1355
|
-
this.announceScreenReader('Sorting cleared.')
|
|
1470
|
+
this.announceScreenReader(this.engine.getConfig().queryMode === 'external' ? 'Clear sorting requested.' : 'Sorting cleared.')
|
|
1356
1471
|
this.requestUpdate()
|
|
1357
1472
|
this.closeHeaderMenu(true)
|
|
1358
1473
|
}
|
|
@@ -1560,7 +1675,8 @@ export class DataGrid extends LitElement {
|
|
|
1560
1675
|
const threshold = Math.max(0, Math.floor(this.quickSearchDebounceThreshold))
|
|
1561
1676
|
const debounceMs = Math.max(0, Math.floor(this.quickSearchDebounceMs))
|
|
1562
1677
|
|
|
1563
|
-
|
|
1678
|
+
const external = this.engine.getConfig().queryMode === 'external'
|
|
1679
|
+
if (debounceMs === 0 || (!external && this.sourceRowCount < threshold)) {
|
|
1564
1680
|
this.applyQuickSearch(query)
|
|
1565
1681
|
return
|
|
1566
1682
|
}
|
|
@@ -1596,19 +1712,57 @@ export class DataGrid extends LitElement {
|
|
|
1596
1712
|
return this.engine.getFilters()
|
|
1597
1713
|
}
|
|
1598
1714
|
|
|
1715
|
+
sortBy(columnKey: string, direction: SortDirection) {
|
|
1716
|
+
this.engine.sortBy(columnKey, direction)
|
|
1717
|
+
this.resetVerticalScroll()
|
|
1718
|
+
this.requestUpdate()
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
clearSort() {
|
|
1722
|
+
this.engine.clearSort()
|
|
1723
|
+
this.resetVerticalScroll()
|
|
1724
|
+
this.requestUpdate()
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
getQuery() { return this.engine.getQuery() }
|
|
1728
|
+
refreshQuery() { this.engine.refreshQuery() }
|
|
1729
|
+
get externalState() { return this.engine.getExternalState() }
|
|
1730
|
+
set externalState(value: GridExternalState | null) {
|
|
1731
|
+
if (value) this.setExternalState(value)
|
|
1732
|
+
}
|
|
1733
|
+
setExternalState(value: GridExternalState): boolean {
|
|
1734
|
+
if (!this.engine.setExternalState(value)) return false
|
|
1735
|
+
this.clearQuickSearchDebounce()
|
|
1736
|
+
this.quickSearchDraft = this.engine.getQuickSearch()
|
|
1737
|
+
this.filterDrafts = new Map(this.engine.getFilters().map(filter => [filter.columnKey, filter]))
|
|
1738
|
+
this.sourceRowCount = this.engine.getRowCount()
|
|
1739
|
+
this.clearBestFitWidths()
|
|
1740
|
+
this.requestUpdate()
|
|
1741
|
+
return true
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
private getPageLabel() {
|
|
1745
|
+
const pagination = this.engine.getPagination()
|
|
1746
|
+
if (this.engine.getConfig().queryMode === 'external' && pagination.totalKnown && pagination.pageIndex > 0 && pagination.pageIndex >= pagination.pageCount) return `Page ${pagination.pageIndex + 1} (out of range)`
|
|
1747
|
+
return pagination.totalKnown === false ? `Page ${pagination.pageIndex + 1}`
|
|
1748
|
+
: `Page ${pagination.pageCount === 0 ? 0 : pagination.pageIndex + 1} of ${pagination.pageCount}`
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1599
1751
|
setPage(pageIndex: number) {
|
|
1752
|
+
this.rememberPaginationFocus()
|
|
1600
1753
|
this.engine.setPage(pageIndex)
|
|
1601
1754
|
this.resetVerticalScroll()
|
|
1602
1755
|
const pagination = this.engine.getPagination()
|
|
1603
|
-
this.announceScreenReader(
|
|
1756
|
+
this.announceScreenReader(`${this.getPageLabel()}.`)
|
|
1604
1757
|
this.requestUpdate()
|
|
1605
1758
|
}
|
|
1606
1759
|
|
|
1607
1760
|
setPageSize(pageSize: number) {
|
|
1761
|
+
this.rememberPaginationFocus()
|
|
1608
1762
|
this.engine.setPageSize(pageSize)
|
|
1609
1763
|
this.resetVerticalScroll()
|
|
1610
1764
|
const pagination = this.engine.getPagination()
|
|
1611
|
-
this.announceScreenReader(`${pagination.pageSize} rows per page.
|
|
1765
|
+
this.announceScreenReader(`${pagination.pageSize} rows per page. ${this.getPageLabel()}.`)
|
|
1612
1766
|
this.requestUpdate()
|
|
1613
1767
|
}
|
|
1614
1768
|
|
|
@@ -1616,6 +1770,11 @@ export class DataGrid extends LitElement {
|
|
|
1616
1770
|
return this.engine.getPagination()
|
|
1617
1771
|
}
|
|
1618
1772
|
|
|
1773
|
+
private rememberPaginationFocus() {
|
|
1774
|
+
const active = this.shadowRoot?.activeElement
|
|
1775
|
+
if (active instanceof HTMLButtonElement && active.closest('.pagination')) this.paginationFocusTarget = active
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1619
1778
|
getTotalRowCount() {
|
|
1620
1779
|
return this.engine.getTotalRowCount()
|
|
1621
1780
|
}
|
|
@@ -1634,11 +1793,17 @@ export class DataGrid extends LitElement {
|
|
|
1634
1793
|
this.requestUpdate()
|
|
1635
1794
|
}
|
|
1636
1795
|
|
|
1796
|
+
private announceAllRowsSelected() {
|
|
1797
|
+
const count = this.engine.getSelectableRowCount()
|
|
1798
|
+
const qualifier = count === this.engine.getRowCount() ? '' : 'selectable '
|
|
1799
|
+
this.announceScreenReader(`All ${count} ${qualifier}displayed rows selected.`)
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1637
1802
|
selectAllRows() {
|
|
1638
|
-
if (this.engine.getConfig().selection.mode !== 'multi-row' || this.engine.
|
|
1639
|
-
const unchanged = this.engine.getSelection().rowIndexes.size === this.engine.
|
|
1803
|
+
if (this.engine.getConfig().selection.mode !== 'multi-row' || this.engine.getSelectableRowCount() === 0) return
|
|
1804
|
+
const unchanged = this.engine.getSelection().rowIndexes.size === this.engine.getSelectableRowCount()
|
|
1640
1805
|
this.engine.selectAllRows()
|
|
1641
|
-
if (!unchanged) this.
|
|
1806
|
+
if (!unchanged) this.announceAllRowsSelected()
|
|
1642
1807
|
this.requestUpdate()
|
|
1643
1808
|
}
|
|
1644
1809
|
|
|
@@ -1654,6 +1819,22 @@ export class DataGrid extends LitElement {
|
|
|
1654
1819
|
return this.engine.getSelection()
|
|
1655
1820
|
}
|
|
1656
1821
|
|
|
1822
|
+
getSelectionSnapshot(): SelectionChangeDetail {
|
|
1823
|
+
return this.engine.getSelectionSnapshot()
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
selectRowByKey(key: GridRowKey, intent?: SelectionIntent): boolean {
|
|
1827
|
+
const previous = this.engine.getSelectionSnapshot()
|
|
1828
|
+
const selected = this.engine.selectRowByKey(key, intent)
|
|
1829
|
+
const next = this.engine.getSelectionSnapshot()
|
|
1830
|
+
if (selected && next !== previous) {
|
|
1831
|
+
const index = next.rows.find(row => row.key === key)?.rowIndex
|
|
1832
|
+
?? previous.rows.find(row => row.key === key)?.rowIndex
|
|
1833
|
+
if (index !== undefined) this.announceRowSelection(index, intent)
|
|
1834
|
+
}
|
|
1835
|
+
return selected
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1657
1838
|
isRowSelected(rowIndex: number) {
|
|
1658
1839
|
return this.engine.isRowSelected(rowIndex)
|
|
1659
1840
|
}
|
|
@@ -1713,6 +1894,7 @@ export class DataGrid extends LitElement {
|
|
|
1713
1894
|
}
|
|
1714
1895
|
|
|
1715
1896
|
private applyHeaderFilter(columnKey: string) {
|
|
1897
|
+
if (!this.getColumnCapabilities(columnKey).filterable) return
|
|
1716
1898
|
const filter = this.getFilterDraft(columnKey)
|
|
1717
1899
|
this.setFilter({
|
|
1718
1900
|
...filter,
|
|
@@ -1722,6 +1904,7 @@ export class DataGrid extends LitElement {
|
|
|
1722
1904
|
}
|
|
1723
1905
|
|
|
1724
1906
|
private clearHeaderFilter(columnKey: string) {
|
|
1907
|
+
if (!this.getColumnCapabilities(columnKey).filterable) return
|
|
1725
1908
|
this.clearFilter(columnKey)
|
|
1726
1909
|
this.closeHeaderMenu(true)
|
|
1727
1910
|
}
|
|
@@ -2534,6 +2717,7 @@ export class DataGrid extends LitElement {
|
|
|
2534
2717
|
}
|
|
2535
2718
|
|
|
2536
2719
|
set data(value: unknown[]) {
|
|
2720
|
+
if (this.engine.getConfig().queryMode === 'external') return
|
|
2537
2721
|
this.clearQuickSearchDebounce()
|
|
2538
2722
|
|
|
2539
2723
|
this.sourceRowCount = Array.isArray(value) ? value.length : 0
|
|
@@ -2549,26 +2733,17 @@ export class DataGrid extends LitElement {
|
|
|
2549
2733
|
}
|
|
2550
2734
|
|
|
2551
2735
|
private getDisplayedSelectedRowCount() {
|
|
2552
|
-
|
|
2553
|
-
let selectedCount = 0
|
|
2554
|
-
|
|
2555
|
-
for (let rowIndex = 0; rowIndex < rowCount; rowIndex += 1) {
|
|
2556
|
-
if (this.engine.isRowSelected(rowIndex)) {
|
|
2557
|
-
selectedCount += 1
|
|
2558
|
-
}
|
|
2559
|
-
}
|
|
2560
|
-
|
|
2561
|
-
return selectedCount
|
|
2736
|
+
return this.engine.getSelectionSnapshot().rowIndexes.length
|
|
2562
2737
|
}
|
|
2563
2738
|
|
|
2564
2739
|
private areAllDisplayedRowsSelected() {
|
|
2565
|
-
const rowCount = this.engine.
|
|
2740
|
+
const rowCount = this.engine.getSelectableRowCount()
|
|
2566
2741
|
return rowCount > 0 && this.getDisplayedSelectedRowCount() === rowCount
|
|
2567
2742
|
}
|
|
2568
2743
|
|
|
2569
2744
|
private hasPartiallySelectedDisplayedRows() {
|
|
2570
2745
|
const selectedCount = this.getDisplayedSelectedRowCount()
|
|
2571
|
-
return selectedCount > 0 && selectedCount < this.engine.
|
|
2746
|
+
return selectedCount > 0 && selectedCount < this.engine.getSelectableRowCount()
|
|
2572
2747
|
}
|
|
2573
2748
|
|
|
2574
2749
|
private handleSelectAllRows() {
|
|
@@ -2577,7 +2752,7 @@ export class DataGrid extends LitElement {
|
|
|
2577
2752
|
this.announceScreenReader('All displayed rows deselected.')
|
|
2578
2753
|
} else {
|
|
2579
2754
|
this.engine.selectAllRows()
|
|
2580
|
-
this.
|
|
2755
|
+
this.announceAllRowsSelected()
|
|
2581
2756
|
}
|
|
2582
2757
|
this.requestUpdate()
|
|
2583
2758
|
}
|
|
@@ -2758,6 +2933,12 @@ export class DataGrid extends LitElement {
|
|
|
2758
2933
|
render() {
|
|
2759
2934
|
const count = this.engine.getRowCount()
|
|
2760
2935
|
const pagination = this.engine.getPagination()
|
|
2936
|
+
const external = this.engine.getConfig().queryMode === 'external'
|
|
2937
|
+
const externalState = external ? this.engine.getExternalState() : null
|
|
2938
|
+
const outOfRange = external && pagination.totalKnown && pagination.pageIndex > 0 && pagination.pageIndex >= pagination.pageCount
|
|
2939
|
+
const externalStatus = externalState?.loading ? 'Loading results.' : externalState?.error
|
|
2940
|
+
? externalState.error : outOfRange ? 'Requested page is out of range. Choose a previous page.'
|
|
2941
|
+
: count === 0 ? (pagination.totalRows === 0 ? 'No results.' : 'No rows on this page.') : ''
|
|
2761
2942
|
const pageSizeOptions = [25, 50, 100].includes(pagination.pageSize)
|
|
2762
2943
|
? [25, 50, 100]
|
|
2763
2944
|
: [pagination.pageSize, 25, 50, 100].sort((left, right) => left - right)
|
|
@@ -2881,6 +3062,7 @@ export class DataGrid extends LitElement {
|
|
|
2881
3062
|
return html`
|
|
2882
3063
|
<div
|
|
2883
3064
|
class="shell"
|
|
3065
|
+
aria-busy=${external ? String(!!externalState?.loading) : nothing}
|
|
2884
3066
|
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;`}
|
|
2885
3067
|
@keydown=${this.handleShellKeyDown}
|
|
2886
3068
|
>
|
|
@@ -2888,7 +3070,7 @@ export class DataGrid extends LitElement {
|
|
|
2888
3070
|
${this.screenReaderAnnouncements
|
|
2889
3071
|
? html`<div class="screen-reader-only" role="status" aria-live="polite" aria-atomic="true">${this.screenReaderMessage}</div>`
|
|
2890
3072
|
: ''}
|
|
2891
|
-
${this.quickSearchVisible
|
|
3073
|
+
${this.quickSearchVisible || external
|
|
2892
3074
|
? html`
|
|
2893
3075
|
<div class="quick-search" role="search">
|
|
2894
3076
|
<input
|
|
@@ -2938,7 +3120,7 @@ export class DataGrid extends LitElement {
|
|
|
2938
3120
|
aria-label="Select all displayed rows"
|
|
2939
3121
|
.checked=${this.areAllDisplayedRowsSelected()}
|
|
2940
3122
|
.indeterminate=${this.hasPartiallySelectedDisplayedRows()}
|
|
2941
|
-
?disabled=${this.engine.
|
|
3123
|
+
?disabled=${this.engine.getSelectableRowCount() === 0}
|
|
2942
3124
|
@change=${() => this.handleSelectAllRows()}
|
|
2943
3125
|
/>`
|
|
2944
3126
|
: html`<span>#</span>`}
|
|
@@ -3006,19 +3188,22 @@ export class DataGrid extends LitElement {
|
|
|
3006
3188
|
${this.quickSearchVisible ? 'Hide Quick Search' : 'Show Quick Search'}
|
|
3007
3189
|
</button>
|
|
3008
3190
|
<hr class="header-menu-divider" />
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3191
|
+
${column.sortable === false ? '' : html`
|
|
3192
|
+
<button type="button" @click=${() => this.handleSortAscending(column.key)}>
|
|
3193
|
+
Sort Asc
|
|
3194
|
+
</button>
|
|
3195
|
+
<button type="button" @click=${() => this.handleSortDescending(column.key)}>
|
|
3196
|
+
Sort Desc
|
|
3197
|
+
</button>
|
|
3198
|
+
<button type="button" @click=${() => this.handleClearSort(column.key)}>
|
|
3199
|
+
Clear Sort
|
|
3200
|
+
</button>
|
|
3201
|
+
`}
|
|
3018
3202
|
<button type="button" @click=${(event: MouseEvent) => this.toggleColumnChooser(event)}>
|
|
3019
3203
|
Columns
|
|
3020
3204
|
</button>
|
|
3021
3205
|
${(() => {
|
|
3206
|
+
if (column.filterable === false) return ''
|
|
3022
3207
|
const draft = this.getFilterDraft(column.key)
|
|
3023
3208
|
const valueFree = this.isValueFreeFilter(draft.operator)
|
|
3024
3209
|
|
|
@@ -3089,6 +3274,7 @@ export class DataGrid extends LitElement {
|
|
|
3089
3274
|
<button class="columns-button" type="button" @click=${(event: MouseEvent) => this.toggleColumnChooser(event)}>Columns</button>
|
|
3090
3275
|
</div>`
|
|
3091
3276
|
: ''}
|
|
3277
|
+
<div class="viewport-frame">
|
|
3092
3278
|
<div
|
|
3093
3279
|
class="viewport"
|
|
3094
3280
|
role="grid"
|
|
@@ -3142,11 +3328,15 @@ export class DataGrid extends LitElement {
|
|
|
3142
3328
|
</div>
|
|
3143
3329
|
</div>
|
|
3144
3330
|
</div>
|
|
3331
|
+
${external && externalStatus
|
|
3332
|
+
? html`<div class=${externalState?.loading ? 'external-status loading' : 'external-status'} role=${externalState?.error ? 'alert' : 'status'} aria-atomic="true"><span class="external-status-label">${externalStatus}</span></div>`
|
|
3333
|
+
: nothing}
|
|
3334
|
+
</div>
|
|
3145
3335
|
${pagination.enabled
|
|
3146
3336
|
? html`
|
|
3147
3337
|
<div class="pagination" role="navigation" aria-label="Pagination">
|
|
3148
3338
|
<div class="pagination-summary">
|
|
3149
|
-
<span>${pagination.totalRows} rows</span>
|
|
3339
|
+
<span>${pagination.totalKnown === false ? 'Total unknown' : `${pagination.totalRows} rows`}</span>
|
|
3150
3340
|
<label>
|
|
3151
3341
|
Rows per page
|
|
3152
3342
|
<select
|
|
@@ -3163,7 +3353,7 @@ export class DataGrid extends LitElement {
|
|
|
3163
3353
|
</label>
|
|
3164
3354
|
</div>
|
|
3165
3355
|
<div class="pagination-navigation">
|
|
3166
|
-
<span
|
|
3356
|
+
<span>${this.getPageLabel()}</span>
|
|
3167
3357
|
<button
|
|
3168
3358
|
type="button"
|
|
3169
3359
|
?disabled=${pagination.pageIndex === 0}
|
|
@@ -3171,7 +3361,7 @@ export class DataGrid extends LitElement {
|
|
|
3171
3361
|
>Previous</button>
|
|
3172
3362
|
<button
|
|
3173
3363
|
type="button"
|
|
3174
|
-
?disabled=${pagination.pageCount === 0 || pagination.pageIndex >= pagination.pageCount - 1}
|
|
3364
|
+
?disabled=${pagination.totalKnown === false ? !pagination.hasMore : pagination.pageCount === 0 || pagination.pageIndex >= pagination.pageCount - 1}
|
|
3175
3365
|
@click=${() => this.setPage(pagination.pageIndex + 1)}
|
|
3176
3366
|
>Next</button>
|
|
3177
3367
|
</div>
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './components/DataGrid.js'
|
|
2
|
+
export type { GridQuery, GridQueryFilter, GridQueryReason, GridQueryChangeDetail, GridQuerySnapshot, GridExternalTotal, GridExternalState, SortDirection } from './types.js'
|
|
2
3
|
export type {
|
|
3
4
|
BestFitColumnWidth,
|
|
4
5
|
ColumnReorderDetail,
|
|
@@ -8,6 +9,9 @@ export type {
|
|
|
8
9
|
GridFilter,
|
|
9
10
|
GridPaginationState,
|
|
10
11
|
GridSelection,
|
|
12
|
+
GridRowKey,
|
|
13
|
+
SelectedRowSnapshot,
|
|
14
|
+
SelectionChangeDetail,
|
|
11
15
|
SelectionIntent,
|
|
12
16
|
GridColumn,
|
|
13
17
|
GridTheme,
|
package/src/types.ts
CHANGED
|
@@ -3,13 +3,23 @@ import type {
|
|
|
3
3
|
GridFilter,
|
|
4
4
|
GridPaginationState,
|
|
5
5
|
GridSelection,
|
|
6
|
+
GridRowKey,
|
|
7
|
+
SelectionChangeDetail,
|
|
8
|
+
GridExternalState,
|
|
9
|
+
GridQuerySnapshot,
|
|
10
|
+
GridQueryChangeDetail,
|
|
11
|
+
SortDirection,
|
|
6
12
|
SelectionIntent
|
|
7
13
|
} from '@tipolox/litgrid-core'
|
|
8
14
|
import type { ColumnStateChangeDetail, GridColumnState } from './columnState/columnState.js'
|
|
9
15
|
|
|
10
16
|
export type { ColumnStateChangeDetail, GridColumnState }
|
|
17
|
+
export type { GridQuery, GridQueryFilter, GridQueryReason, GridQueryChangeDetail, GridQuerySnapshot, GridExternalTotal, GridExternalState, SortDirection } from '@tipolox/litgrid-core'
|
|
11
18
|
|
|
12
19
|
export type {
|
|
20
|
+
GridRowKey,
|
|
21
|
+
SelectedRowSnapshot,
|
|
22
|
+
SelectionChangeDetail,
|
|
13
23
|
GridConfig,
|
|
14
24
|
GridFilter,
|
|
15
25
|
GridPaginationState,
|
|
@@ -24,6 +34,10 @@ export type GridColumn = {
|
|
|
24
34
|
header?: string
|
|
25
35
|
width?: number | string
|
|
26
36
|
hidden?: boolean
|
|
37
|
+
/** Show local sorting actions in the header menu. Defaults to true; does not change query state or imperative APIs. */
|
|
38
|
+
sortable?: boolean
|
|
39
|
+
/** Show local column filtering controls. Defaults to true; does not affect quick search or imperative APIs. */
|
|
40
|
+
filterable?: boolean
|
|
27
41
|
accessor?: (row: unknown, rowIndex: number) => unknown
|
|
28
42
|
render?: (value: unknown, row: unknown, rowIndex: number) => unknown
|
|
29
43
|
}
|
|
@@ -47,6 +61,13 @@ export type ColumnVisibilityChangeDetail = {
|
|
|
47
61
|
}
|
|
48
62
|
|
|
49
63
|
export type DataGridElement = HTMLElement & {
|
|
64
|
+
/** Additive external-query capabilities; production DataGrid implements them. */
|
|
65
|
+
externalState?: GridExternalState | null
|
|
66
|
+
setExternalState?: (state: GridExternalState) => boolean
|
|
67
|
+
getQuery?: () => GridQuerySnapshot
|
|
68
|
+
refreshQuery?: () => void
|
|
69
|
+
sortBy?: (columnKey: string, direction: SortDirection) => void
|
|
70
|
+
clearSort?: () => void
|
|
50
71
|
data: unknown[]
|
|
51
72
|
columns: GridColumn[]
|
|
52
73
|
config: GridConfig
|
|
@@ -103,6 +124,15 @@ export type DataGridElement = HTMLElement & {
|
|
|
103
124
|
selectAllRows: () => void
|
|
104
125
|
clearSelection: () => void
|
|
105
126
|
getSelection: () => GridSelection
|
|
127
|
+
getSelectionSnapshot: () => SelectionChangeDetail
|
|
128
|
+
selectRowByKey: (key: GridRowKey, intent?: SelectionIntent) => boolean
|
|
106
129
|
isRowSelected: (rowIndex: number) => boolean
|
|
107
130
|
isCellSelected: (rowIndex: number, columnKey: string) => boolean
|
|
108
131
|
}
|
|
132
|
+
|
|
133
|
+
declare global {
|
|
134
|
+
interface HTMLElementEventMap {
|
|
135
|
+
'selection-change': CustomEvent<SelectionChangeDetail>
|
|
136
|
+
'query-change': CustomEvent<GridQueryChangeDetail>
|
|
137
|
+
}
|
|
138
|
+
}
|