@tipolox/litgrid-web 1.1.0 → 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.
@@ -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`
@@ -701,7 +723,17 @@ export class DataGrid extends LitElement {
701
723
  this.updateScrollbarWidth()
702
724
  }
703
725
 
726
+ willUpdate() {
727
+ // Reconcile after schema and persisted visibility settle, before triggers disappear.
728
+ this.reconcileHeaderMenuCapabilities()
729
+ }
730
+
704
731
  updated() {
732
+ const paginationTarget = this.paginationFocusTarget
733
+ this.paginationFocusTarget = null
734
+ if (paginationTarget?.isConnected && paginationTarget.disabled) {
735
+ this.renderRoot.querySelector<HTMLElement>('.pagination button:not([disabled]), .pagination select')?.focus()
736
+ }
705
737
  this.updateScrollbarWidth()
706
738
 
707
739
  if (this.shouldFocusViewport) {
@@ -1162,6 +1194,7 @@ export class DataGrid extends LitElement {
1162
1194
  })
1163
1195
  }
1164
1196
  const isOpening = this.activeHeaderMenu !== columnKey
1197
+ this.headerMenuCapabilities = this.getColumnCapabilities(columnKey)
1165
1198
 
1166
1199
  this.activeHeaderMenu = isOpening ? columnKey : null
1167
1200
  this.headerMenuTrigger = isOpening && event.currentTarget instanceof HTMLButtonElement
@@ -1171,6 +1204,20 @@ export class DataGrid extends LitElement {
1171
1204
  this.requestUpdate()
1172
1205
  }
1173
1206
 
1207
+ private getColumnCapabilities(columnKey: string) {
1208
+ const column = this.getResolvedColumns().find(column => column.key === columnKey)
1209
+ return { sortable: !!column && column.sortable !== false, filterable: !!column && column.filterable !== false }
1210
+ }
1211
+
1212
+ private reconcileHeaderMenuCapabilities() {
1213
+ if (this.activeHeaderMenu === null) return
1214
+ const next = this.getColumnCapabilities(this.activeHeaderMenu)
1215
+ if (!this.isColumnVisible(this.activeHeaderMenu)
1216
+ || next.sortable !== this.headerMenuCapabilities.sortable || next.filterable !== this.headerMenuCapabilities.filterable) {
1217
+ this.closeHeaderMenu(true)
1218
+ }
1219
+ }
1220
+
1174
1221
  private closeHeaderMenu(restoreFocus = false) {
1175
1222
  if (this.activeHeaderMenu === null && !this.columnChooserOpen) return
1176
1223
  this.activeHeaderMenu = null
@@ -1320,6 +1367,10 @@ export class DataGrid extends LitElement {
1320
1367
 
1321
1368
  private announceRowSelection(rowIndex: number, intent: SelectionIntent = 'replace') {
1322
1369
  const anchor = this.engine.getSelection().anchorRowIndex ?? rowIndex
1370
+ if (intent === 'range' && this.engine.getConfig().selection.rowKey !== undefined) {
1371
+ this.announceScreenReader(`${this.getDisplayedSelectedRowCount()} displayed rows selected.`)
1372
+ return
1373
+ }
1323
1374
  this.announceScreenReader(intent === 'range' && anchor !== rowIndex
1324
1375
  ? `Rows ${Math.min(anchor, rowIndex) + 1} through ${Math.max(anchor, rowIndex) + 1} selected.`
1325
1376
  : `Row ${rowIndex + 1} ${this.engine.isRowSelected(rowIndex) ? 'selected' : 'deselected'}.`)
@@ -1330,6 +1381,10 @@ export class DataGrid extends LitElement {
1330
1381
  }
1331
1382
 
1332
1383
  private announceVisibleRowCount(context: string) {
1384
+ if (this.engine.getConfig().queryMode === 'external') {
1385
+ this.announceScreenReader(`${context}. Server query requested.`)
1386
+ return
1387
+ }
1333
1388
  const rowCount = this.engine.getRowCount()
1334
1389
  this.announceScreenReader(rowCount === 0 ? `${context}. No matching rows.` : `${context}. ${rowCount} ${rowCount === 1 ? 'row' : 'rows'} shown.`)
1335
1390
  }
@@ -1337,22 +1392,25 @@ export class DataGrid extends LitElement {
1337
1392
 
1338
1393
 
1339
1394
  private handleSortAscending(columnKey: string) {
1395
+ if (!this.getColumnCapabilities(columnKey).sortable) return
1340
1396
  this.engine.sortBy(columnKey, 'asc')
1341
- this.announceScreenReader(`${this.getColumnLabel(columnKey)} sorted ascending.`)
1397
+ this.announceScreenReader(`${this.getColumnLabel(columnKey)} ${this.engine.getConfig().queryMode === 'external' ? 'ascending sort requested' : 'sorted ascending'}.`)
1342
1398
  this.requestUpdate()
1343
1399
  this.closeHeaderMenu(true)
1344
1400
  }
1345
1401
 
1346
1402
  private handleSortDescending(columnKey: string) {
1403
+ if (!this.getColumnCapabilities(columnKey).sortable) return
1347
1404
  this.engine.sortBy(columnKey, 'desc')
1348
- this.announceScreenReader(`${this.getColumnLabel(columnKey)} sorted descending.`)
1405
+ this.announceScreenReader(`${this.getColumnLabel(columnKey)} ${this.engine.getConfig().queryMode === 'external' ? 'descending sort requested' : 'sorted descending'}.`)
1349
1406
  this.requestUpdate()
1350
1407
  this.closeHeaderMenu(true)
1351
1408
  }
1352
1409
 
1353
- private handleClearSort() {
1410
+ private handleClearSort(columnKey: string) {
1411
+ if (!this.getColumnCapabilities(columnKey).sortable) return
1354
1412
  this.engine.clearSort()
1355
- this.announceScreenReader('Sorting cleared.')
1413
+ this.announceScreenReader(this.engine.getConfig().queryMode === 'external' ? 'Clear sorting requested.' : 'Sorting cleared.')
1356
1414
  this.requestUpdate()
1357
1415
  this.closeHeaderMenu(true)
1358
1416
  }
@@ -1560,7 +1618,8 @@ export class DataGrid extends LitElement {
1560
1618
  const threshold = Math.max(0, Math.floor(this.quickSearchDebounceThreshold))
1561
1619
  const debounceMs = Math.max(0, Math.floor(this.quickSearchDebounceMs))
1562
1620
 
1563
- if (this.sourceRowCount < threshold || debounceMs === 0) {
1621
+ const external = this.engine.getConfig().queryMode === 'external'
1622
+ if (debounceMs === 0 || (!external && this.sourceRowCount < threshold)) {
1564
1623
  this.applyQuickSearch(query)
1565
1624
  return
1566
1625
  }
@@ -1596,19 +1655,57 @@ export class DataGrid extends LitElement {
1596
1655
  return this.engine.getFilters()
1597
1656
  }
1598
1657
 
1658
+ sortBy(columnKey: string, direction: SortDirection) {
1659
+ this.engine.sortBy(columnKey, direction)
1660
+ this.resetVerticalScroll()
1661
+ this.requestUpdate()
1662
+ }
1663
+
1664
+ clearSort() {
1665
+ this.engine.clearSort()
1666
+ this.resetVerticalScroll()
1667
+ this.requestUpdate()
1668
+ }
1669
+
1670
+ getQuery() { return this.engine.getQuery() }
1671
+ refreshQuery() { this.engine.refreshQuery() }
1672
+ get externalState() { return this.engine.getExternalState() }
1673
+ set externalState(value: GridExternalState | null) {
1674
+ if (value) this.setExternalState(value)
1675
+ }
1676
+ setExternalState(value: GridExternalState): boolean {
1677
+ if (!this.engine.setExternalState(value)) return false
1678
+ this.clearQuickSearchDebounce()
1679
+ this.quickSearchDraft = this.engine.getQuickSearch()
1680
+ this.filterDrafts = new Map(this.engine.getFilters().map(filter => [filter.columnKey, filter]))
1681
+ this.sourceRowCount = this.engine.getRowCount()
1682
+ this.clearBestFitWidths()
1683
+ this.requestUpdate()
1684
+ return true
1685
+ }
1686
+
1687
+ private getPageLabel() {
1688
+ const pagination = this.engine.getPagination()
1689
+ if (this.engine.getConfig().queryMode === 'external' && pagination.totalKnown && pagination.pageIndex > 0 && pagination.pageIndex >= pagination.pageCount) return `Page ${pagination.pageIndex + 1} (out of range)`
1690
+ return pagination.totalKnown === false ? `Page ${pagination.pageIndex + 1}`
1691
+ : `Page ${pagination.pageCount === 0 ? 0 : pagination.pageIndex + 1} of ${pagination.pageCount}`
1692
+ }
1693
+
1599
1694
  setPage(pageIndex: number) {
1695
+ this.rememberPaginationFocus()
1600
1696
  this.engine.setPage(pageIndex)
1601
1697
  this.resetVerticalScroll()
1602
1698
  const pagination = this.engine.getPagination()
1603
- this.announceScreenReader(`Page ${pagination.pageCount === 0 ? 0 : pagination.pageIndex + 1} of ${pagination.pageCount}.`)
1699
+ this.announceScreenReader(`${this.getPageLabel()}.`)
1604
1700
  this.requestUpdate()
1605
1701
  }
1606
1702
 
1607
1703
  setPageSize(pageSize: number) {
1704
+ this.rememberPaginationFocus()
1608
1705
  this.engine.setPageSize(pageSize)
1609
1706
  this.resetVerticalScroll()
1610
1707
  const pagination = this.engine.getPagination()
1611
- this.announceScreenReader(`${pagination.pageSize} rows per page. Page ${pagination.pageCount === 0 ? 0 : pagination.pageIndex + 1} of ${pagination.pageCount}.`)
1708
+ this.announceScreenReader(`${pagination.pageSize} rows per page. ${this.getPageLabel()}.`)
1612
1709
  this.requestUpdate()
1613
1710
  }
1614
1711
 
@@ -1616,6 +1713,11 @@ export class DataGrid extends LitElement {
1616
1713
  return this.engine.getPagination()
1617
1714
  }
1618
1715
 
1716
+ private rememberPaginationFocus() {
1717
+ const active = this.shadowRoot?.activeElement
1718
+ if (active instanceof HTMLButtonElement && active.closest('.pagination')) this.paginationFocusTarget = active
1719
+ }
1720
+
1619
1721
  getTotalRowCount() {
1620
1722
  return this.engine.getTotalRowCount()
1621
1723
  }
@@ -1634,11 +1736,17 @@ export class DataGrid extends LitElement {
1634
1736
  this.requestUpdate()
1635
1737
  }
1636
1738
 
1739
+ private announceAllRowsSelected() {
1740
+ const count = this.engine.getSelectableRowCount()
1741
+ const qualifier = count === this.engine.getRowCount() ? '' : 'selectable '
1742
+ this.announceScreenReader(`All ${count} ${qualifier}displayed rows selected.`)
1743
+ }
1744
+
1637
1745
  selectAllRows() {
1638
- if (this.engine.getConfig().selection.mode !== 'multi-row' || this.engine.getRowCount() === 0) return
1639
- const unchanged = this.engine.getSelection().rowIndexes.size === this.engine.getRowCount()
1746
+ if (this.engine.getConfig().selection.mode !== 'multi-row' || this.engine.getSelectableRowCount() === 0) return
1747
+ const unchanged = this.engine.getSelection().rowIndexes.size === this.engine.getSelectableRowCount()
1640
1748
  this.engine.selectAllRows()
1641
- if (!unchanged) this.announceScreenReader(`All ${this.engine.getRowCount()} displayed rows selected.`)
1749
+ if (!unchanged) this.announceAllRowsSelected()
1642
1750
  this.requestUpdate()
1643
1751
  }
1644
1752
 
@@ -1654,6 +1762,22 @@ export class DataGrid extends LitElement {
1654
1762
  return this.engine.getSelection()
1655
1763
  }
1656
1764
 
1765
+ getSelectionSnapshot(): SelectionChangeDetail {
1766
+ return this.engine.getSelectionSnapshot()
1767
+ }
1768
+
1769
+ selectRowByKey(key: GridRowKey, intent?: SelectionIntent): boolean {
1770
+ const previous = this.engine.getSelectionSnapshot()
1771
+ const selected = this.engine.selectRowByKey(key, intent)
1772
+ const next = this.engine.getSelectionSnapshot()
1773
+ if (selected && next !== previous) {
1774
+ const index = next.rows.find(row => row.key === key)?.rowIndex
1775
+ ?? previous.rows.find(row => row.key === key)?.rowIndex
1776
+ if (index !== undefined) this.announceRowSelection(index, intent)
1777
+ }
1778
+ return selected
1779
+ }
1780
+
1657
1781
  isRowSelected(rowIndex: number) {
1658
1782
  return this.engine.isRowSelected(rowIndex)
1659
1783
  }
@@ -1713,6 +1837,7 @@ export class DataGrid extends LitElement {
1713
1837
  }
1714
1838
 
1715
1839
  private applyHeaderFilter(columnKey: string) {
1840
+ if (!this.getColumnCapabilities(columnKey).filterable) return
1716
1841
  const filter = this.getFilterDraft(columnKey)
1717
1842
  this.setFilter({
1718
1843
  ...filter,
@@ -1722,6 +1847,7 @@ export class DataGrid extends LitElement {
1722
1847
  }
1723
1848
 
1724
1849
  private clearHeaderFilter(columnKey: string) {
1850
+ if (!this.getColumnCapabilities(columnKey).filterable) return
1725
1851
  this.clearFilter(columnKey)
1726
1852
  this.closeHeaderMenu(true)
1727
1853
  }
@@ -2534,6 +2660,7 @@ export class DataGrid extends LitElement {
2534
2660
  }
2535
2661
 
2536
2662
  set data(value: unknown[]) {
2663
+ if (this.engine.getConfig().queryMode === 'external') return
2537
2664
  this.clearQuickSearchDebounce()
2538
2665
 
2539
2666
  this.sourceRowCount = Array.isArray(value) ? value.length : 0
@@ -2549,26 +2676,17 @@ export class DataGrid extends LitElement {
2549
2676
  }
2550
2677
 
2551
2678
  private getDisplayedSelectedRowCount() {
2552
- const rowCount = this.engine.getRowCount()
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
2679
+ return this.engine.getSelectionSnapshot().rowIndexes.length
2562
2680
  }
2563
2681
 
2564
2682
  private areAllDisplayedRowsSelected() {
2565
- const rowCount = this.engine.getRowCount()
2683
+ const rowCount = this.engine.getSelectableRowCount()
2566
2684
  return rowCount > 0 && this.getDisplayedSelectedRowCount() === rowCount
2567
2685
  }
2568
2686
 
2569
2687
  private hasPartiallySelectedDisplayedRows() {
2570
2688
  const selectedCount = this.getDisplayedSelectedRowCount()
2571
- return selectedCount > 0 && selectedCount < this.engine.getRowCount()
2689
+ return selectedCount > 0 && selectedCount < this.engine.getSelectableRowCount()
2572
2690
  }
2573
2691
 
2574
2692
  private handleSelectAllRows() {
@@ -2577,7 +2695,7 @@ export class DataGrid extends LitElement {
2577
2695
  this.announceScreenReader('All displayed rows deselected.')
2578
2696
  } else {
2579
2697
  this.engine.selectAllRows()
2580
- this.announceScreenReader(`All ${this.engine.getRowCount()} displayed rows selected.`)
2698
+ this.announceAllRowsSelected()
2581
2699
  }
2582
2700
  this.requestUpdate()
2583
2701
  }
@@ -2758,6 +2876,12 @@ export class DataGrid extends LitElement {
2758
2876
  render() {
2759
2877
  const count = this.engine.getRowCount()
2760
2878
  const pagination = this.engine.getPagination()
2879
+ const external = this.engine.getConfig().queryMode === 'external'
2880
+ const externalState = external ? this.engine.getExternalState() : null
2881
+ const outOfRange = external && pagination.totalKnown && pagination.pageIndex > 0 && pagination.pageIndex >= pagination.pageCount
2882
+ const externalStatus = externalState?.loading ? 'Loading results.' : externalState?.error
2883
+ ? externalState.error : outOfRange ? 'Requested page is out of range. Choose a previous page.'
2884
+ : count === 0 ? (pagination.totalRows === 0 ? 'No results.' : 'No rows on this page.') : ''
2761
2885
  const pageSizeOptions = [25, 50, 100].includes(pagination.pageSize)
2762
2886
  ? [25, 50, 100]
2763
2887
  : [pagination.pageSize, 25, 50, 100].sort((left, right) => left - right)
@@ -2881,14 +3005,16 @@ export class DataGrid extends LitElement {
2881
3005
  return html`
2882
3006
  <div
2883
3007
  class="shell"
3008
+ aria-busy=${external ? String(!!externalState?.loading) : nothing}
2884
3009
  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
3010
  @keydown=${this.handleShellKeyDown}
2886
3011
  >
2887
3012
  <p id=${`${this.gridId}-description`} class="screen-reader-only">${this.getScreenReaderDescription()}</p>
3013
+ ${external && externalStatus ? html`<div class="external-status" role=${externalState?.error ? 'alert' : 'status'}>${externalStatus}</div>` : nothing}
2888
3014
  ${this.screenReaderAnnouncements
2889
3015
  ? html`<div class="screen-reader-only" role="status" aria-live="polite" aria-atomic="true">${this.screenReaderMessage}</div>`
2890
3016
  : ''}
2891
- ${this.quickSearchVisible
3017
+ ${this.quickSearchVisible || external
2892
3018
  ? html`
2893
3019
  <div class="quick-search" role="search">
2894
3020
  <input
@@ -2938,7 +3064,7 @@ export class DataGrid extends LitElement {
2938
3064
  aria-label="Select all displayed rows"
2939
3065
  .checked=${this.areAllDisplayedRowsSelected()}
2940
3066
  .indeterminate=${this.hasPartiallySelectedDisplayedRows()}
2941
- ?disabled=${this.engine.getRowCount() === 0}
3067
+ ?disabled=${this.engine.getSelectableRowCount() === 0}
2942
3068
  @change=${() => this.handleSelectAllRows()}
2943
3069
  />`
2944
3070
  : html`<span>#</span>`}
@@ -3006,19 +3132,22 @@ export class DataGrid extends LitElement {
3006
3132
  ${this.quickSearchVisible ? 'Hide Quick Search' : 'Show Quick Search'}
3007
3133
  </button>
3008
3134
  <hr class="header-menu-divider" />
3009
- <button type="button" @click=${() => this.handleSortAscending(column.key)}>
3010
- Sort Asc
3011
- </button>
3012
- <button type="button" @click=${() => this.handleSortDescending(column.key)}>
3013
- Sort Desc
3014
- </button>
3015
- <button type="button" @click=${() => this.handleClearSort()}>
3016
- Clear Sort
3017
- </button>
3135
+ ${column.sortable === false ? '' : html`
3136
+ <button type="button" @click=${() => this.handleSortAscending(column.key)}>
3137
+ Sort Asc
3138
+ </button>
3139
+ <button type="button" @click=${() => this.handleSortDescending(column.key)}>
3140
+ Sort Desc
3141
+ </button>
3142
+ <button type="button" @click=${() => this.handleClearSort(column.key)}>
3143
+ Clear Sort
3144
+ </button>
3145
+ `}
3018
3146
  <button type="button" @click=${(event: MouseEvent) => this.toggleColumnChooser(event)}>
3019
3147
  Columns
3020
3148
  </button>
3021
3149
  ${(() => {
3150
+ if (column.filterable === false) return ''
3022
3151
  const draft = this.getFilterDraft(column.key)
3023
3152
  const valueFree = this.isValueFreeFilter(draft.operator)
3024
3153
 
@@ -3146,7 +3275,7 @@ export class DataGrid extends LitElement {
3146
3275
  ? html`
3147
3276
  <div class="pagination" role="navigation" aria-label="Pagination">
3148
3277
  <div class="pagination-summary">
3149
- <span>${pagination.totalRows} rows</span>
3278
+ <span>${pagination.totalKnown === false ? 'Total unknown' : `${pagination.totalRows} rows`}</span>
3150
3279
  <label>
3151
3280
  Rows per page
3152
3281
  <select
@@ -3163,7 +3292,7 @@ export class DataGrid extends LitElement {
3163
3292
  </label>
3164
3293
  </div>
3165
3294
  <div class="pagination-navigation">
3166
- <span>Page ${pagination.pageCount === 0 ? 0 : pagination.pageIndex + 1} of ${pagination.pageCount}</span>
3295
+ <span>${this.getPageLabel()}</span>
3167
3296
  <button
3168
3297
  type="button"
3169
3298
  ?disabled=${pagination.pageIndex === 0}
@@ -3171,7 +3300,7 @@ export class DataGrid extends LitElement {
3171
3300
  >Previous</button>
3172
3301
  <button
3173
3302
  type="button"
3174
- ?disabled=${pagination.pageCount === 0 || pagination.pageIndex >= pagination.pageCount - 1}
3303
+ ?disabled=${pagination.totalKnown === false ? !pagination.hasMore : pagination.pageCount === 0 || pagination.pageIndex >= pagination.pageCount - 1}
3175
3304
  @click=${() => this.setPage(pagination.pageIndex + 1)}
3176
3305
  >Next</button>
3177
3306
  </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
+ }