@coreui/data-grid 0.1.0-rc.0 → 0.3.0-rc.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 +9 -6
- package/dist/css/data-grid.css +156 -38
- package/dist/css/data-grid.css.map +1 -1
- package/dist/css/data-grid.min.css +2 -2
- package/dist/css/data-grid.min.css.map +1 -1
- package/dist/css/data-grid.standalone.css +1066 -0
- package/dist/css/data-grid.standalone.css.map +1 -0
- package/dist/css/data-grid.standalone.min.css +5 -0
- package/dist/css/data-grid.standalone.min.css.map +1 -0
- package/dist/js/clipboard.cjs +56 -0
- package/dist/js/clipboard.cjs.map +1 -0
- package/dist/js/clipboard.mjs +54 -0
- package/dist/js/clipboard.mjs.map +1 -0
- package/dist/js/csv.cjs +36 -59
- package/dist/js/csv.cjs.map +1 -1
- package/dist/js/csv.mjs +35 -55
- package/dist/js/csv.mjs.map +1 -1
- package/dist/js/data-grid.esm.js +14108 -9331
- package/dist/js/data-grid.esm.js.map +1 -1
- package/dist/js/data-grid.esm.min.js +6 -6
- package/dist/js/data-grid.esm.min.js.map +1 -1
- package/dist/js/data-grid.js +14408 -9634
- package/dist/js/data-grid.js.map +1 -1
- package/dist/js/data-grid.lite.esm.js +10507 -0
- package/dist/js/data-grid.lite.esm.js.map +1 -0
- package/dist/js/data-grid.lite.esm.min.js +8 -0
- package/dist/js/data-grid.lite.esm.min.js.map +1 -0
- package/dist/js/data-grid.lite.js +10520 -0
- package/dist/js/data-grid.lite.js.map +1 -0
- package/dist/js/data-grid.lite.min.js +8 -0
- package/dist/js/data-grid.lite.min.js.map +1 -0
- package/dist/js/data-grid.lite.mjs +10507 -0
- package/dist/js/data-grid.lite.mjs.map +1 -0
- package/dist/js/data-grid.min.js +6 -6
- package/dist/js/data-grid.min.js.map +1 -1
- package/dist/js/data-grid.mjs +14108 -9331
- package/dist/js/data-grid.mjs.map +1 -1
- package/dist/js/direction.cjs +26 -0
- package/dist/js/direction.cjs.map +1 -0
- package/dist/js/direction.mjs +24 -0
- package/dist/js/direction.mjs.map +1 -0
- package/dist/js/drag.cjs +467 -0
- package/dist/js/drag.cjs.map +1 -0
- package/dist/js/drag.mjs +464 -0
- package/dist/js/drag.mjs.map +1 -0
- package/dist/js/export-table-BdabEbJG.mjs +26 -0
- package/dist/js/export-table-BdabEbJG.mjs.map +1 -0
- package/dist/js/export-table-D762wrZ1.cjs +55 -0
- package/dist/js/export-table-D762wrZ1.cjs.map +1 -0
- package/dist/js/filters.cjs +89 -132
- package/dist/js/filters.cjs.map +1 -1
- package/dist/js/filters.mjs +88 -128
- package/dist/js/filters.mjs.map +1 -1
- package/dist/js/labels.cjs +65 -70
- package/dist/js/labels.cjs.map +1 -1
- package/dist/js/labels.mjs +64 -66
- package/dist/js/labels.mjs.map +1 -1
- package/dist/js/print.cjs +66 -0
- package/dist/js/print.cjs.map +1 -0
- package/dist/js/print.mjs +64 -0
- package/dist/js/print.mjs.map +1 -0
- package/dist/js/validate.cjs +43 -0
- package/dist/js/validate.cjs.map +1 -0
- package/dist/js/validate.mjs +42 -0
- package/dist/js/validate.mjs.map +1 -0
- package/dist/js/xlsx.cjs +240 -0
- package/dist/js/xlsx.cjs.map +1 -0
- package/dist/js/xlsx.mjs +238 -0
- package/dist/js/xlsx.mjs.map +1 -0
- package/dist/types/base-component.d.ts +28 -18
- package/dist/types/data-grid.d.ts +26 -13
- package/dist/types/dom/data.d.ts +15 -0
- package/dist/types/dom/event-handler.d.ts +21 -10
- package/dist/types/dom/manipulator.d.ts +15 -8
- package/dist/types/engine/active-cell.d.ts +1 -0
- package/dist/types/engine/data-source.d.ts +1 -1
- package/dist/types/engine/features-lite.d.ts +25 -0
- package/dist/types/engine/features.d.ts +35 -0
- package/dist/types/engine/table.d.ts +3 -3
- package/dist/types/engine/validate.d.mts +1 -0
- package/dist/types/engine/validate.d.ts +20 -0
- package/dist/types/engine/virtualizer.d.ts +2 -0
- package/dist/types/icons.d.ts +6 -4
- package/dist/types/index-lite.d.mts +1 -0
- package/dist/types/index-lite.d.ts +19 -0
- package/dist/types/index.d.ts +11 -2
- package/dist/types/types.d.ts +53 -31
- package/dist/types/ui/body.d.ts +6 -3
- package/dist/types/ui/header.d.ts +3 -2
- package/dist/types/ui/loading.d.ts +1 -1
- package/dist/types/ui/pinning.d.ts +1 -1
- package/dist/types/ui/toolbar.d.ts +10 -2
- package/dist/types/util/clipboard.d.mts +1 -0
- package/dist/types/util/clipboard.d.ts +29 -0
- package/dist/types/util/config.d.ts +27 -10
- package/dist/types/util/csv.d.ts +5 -5
- package/dist/types/util/direction.d.mts +1 -0
- package/dist/types/util/direction.d.ts +17 -0
- package/dist/types/util/drag.d.mts +1 -0
- package/dist/types/util/drag.d.ts +48 -0
- package/dist/types/util/export-table.d.ts +47 -0
- package/dist/types/util/index.d.ts +66 -10
- package/dist/types/util/print.d.mts +1 -0
- package/dist/types/util/print.d.ts +32 -0
- package/dist/types/util/sanitizer.d.ts +15 -11
- package/dist/types/util/xlsx.d.mts +1 -0
- package/dist/types/util/xlsx.d.ts +17 -0
- package/dist/types/util/zip.d.ts +11 -0
- package/package.json +101 -10
- package/scss/_base.scss +13 -0
- package/scss/_config.scss +14 -0
- package/scss/_functions.scss +1 -0
- package/scss/_mixins.scss +1 -0
- package/scss/base/_buttons.scss +113 -0
- package/scss/base/_forms.scss +157 -0
- package/scss/base/_helpers.scss +24 -0
- package/scss/base/_pagination.scss +100 -0
- package/scss/base/_reboot.scss +93 -0
- package/scss/base/_root.scss +85 -0
- package/scss/base/_spinner.scss +45 -0
- package/scss/base/_table.scss +78 -0
- package/scss/components/_data-grid.scss +807 -0
- package/scss/data-grid.scss +7 -555
- package/scss/data-grid.standalone.scss +16 -0
- package/scss/functions/_defaults.scss +25 -0
- package/scss/mixins/_tokens.scss +12 -0
- package/src/{base-component.js → base-component.ts} +21 -17
- package/src/data-grid.ts +523 -201
- package/src/dom/{data.js → data.ts} +13 -10
- package/src/dom/event-handler.ts +380 -0
- package/src/dom/{manipulator.js → manipulator.ts} +11 -11
- package/src/engine/active-cell.ts +10 -3
- package/src/engine/data-source.ts +7 -3
- package/src/engine/features-lite.ts +47 -0
- package/src/engine/features.ts +68 -0
- package/src/engine/table.ts +86 -45
- package/src/engine/validate.ts +64 -0
- package/src/engine/virtualizer.ts +17 -1
- package/src/icons.ts +11 -5
- package/src/index-lite.ts +85 -0
- package/src/index.ts +30 -2
- package/src/labels.ts +6 -4
- package/src/types.ts +87 -32
- package/src/ui/body.ts +73 -17
- package/src/ui/header.ts +51 -26
- package/src/ui/loading.ts +2 -2
- package/src/ui/pinning.ts +16 -16
- package/src/ui/toolbar.ts +27 -3
- package/src/util/clipboard.ts +112 -0
- package/src/util/{config.js → config.ts} +25 -11
- package/src/util/csv.ts +11 -30
- package/src/util/direction.ts +37 -0
- package/src/util/drag.ts +832 -0
- package/src/util/export-table.ts +86 -0
- package/src/util/{index.js → index.ts} +55 -35
- package/src/util/print.ts +125 -0
- package/src/util/{sanitizer.js → sanitizer.ts} +17 -13
- package/src/util/xlsx.ts +183 -0
- package/src/util/zip.ts +170 -0
- package/src/base-component.d.ts +0 -19
- package/src/dom/event-handler.d.ts +0 -11
- package/src/dom/event-handler.js +0 -331
- package/src/dom/manipulator.d.ts +0 -9
- package/src/util/config.d.ts +0 -11
- package/src/util/index.d.ts +0 -10
- package/src/util/sanitizer.d.ts +0 -11
package/src/types.ts
CHANGED
|
@@ -6,33 +6,58 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type {
|
|
9
|
-
Column, ColumnFiltersState, ColumnOrderState, ColumnPinningState, ColumnSizingState,
|
|
10
|
-
RowSelectionState, SortingState, Table,
|
|
9
|
+
Column, ColumnFiltersState, ColumnOrderState, ColumnPinningState, ColumnSizingState,
|
|
10
|
+
ColumnVisibilityState, PaginationState, Row, RowSelectionState, SortingState, Table,
|
|
11
|
+
TableFeatures, TableState
|
|
11
12
|
} from '@tanstack/table-core'
|
|
12
|
-
import type {
|
|
13
|
+
import type { DataGridFeatures } from './engine/features'
|
|
14
|
+
import type { DataGridExportOptions } from './ui/toolbar'
|
|
13
15
|
|
|
14
16
|
export type {
|
|
15
17
|
Column,
|
|
16
18
|
ColumnFiltersState,
|
|
17
19
|
ColumnOrderState,
|
|
18
20
|
ColumnPinningState,
|
|
21
|
+
ColumnPinningPosition,
|
|
19
22
|
ColumnSizingState,
|
|
23
|
+
ColumnVisibilityState,
|
|
20
24
|
PaginationState,
|
|
21
25
|
RowSelectionState,
|
|
22
26
|
SortingState,
|
|
23
27
|
Table,
|
|
24
|
-
|
|
28
|
+
TableFeatures
|
|
25
29
|
} from '@tanstack/table-core'
|
|
30
|
+
export type { DataGridFeatures } from './engine/features'
|
|
31
|
+
export type { DataGridLiteFeatures } from './engine/features-lite'
|
|
26
32
|
|
|
27
33
|
export type DataGridItem = Record<string, unknown>
|
|
28
34
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
// The table-core instance as this grid registers it - every public surface
|
|
36
|
+
// that hands out a table (slot contexts, CSV helpers, `grid.table`) carries
|
|
37
|
+
// the grid's feature set as TFeatures. v9's TFeatures is invariant, so a
|
|
38
|
+
// lite-set table is NOT assignable to the full-set default - the parameter is
|
|
39
|
+
// what keeps a reduced grid's surface honest.
|
|
40
|
+
export type DataGridTable<TFeatures extends TableFeatures = DataGridFeatures> =
|
|
41
|
+
Table<TFeatures, DataGridItem>
|
|
42
|
+
|
|
43
|
+
export type DataGridTableState<TFeatures extends TableFeatures = DataGridFeatures> =
|
|
44
|
+
TableState<TFeatures>
|
|
45
|
+
|
|
46
|
+
export type DataGridRow<TFeatures extends TableFeatures = DataGridFeatures> =
|
|
47
|
+
Row<TFeatures, DataGridItem>
|
|
48
|
+
|
|
49
|
+
// v9 requires both sides on ColumnPinningState; the option stays partial so
|
|
50
|
+
// `{ start: ['id'] }` is still a valid seed.
|
|
51
|
+
export type DataGridColumnPinning = Partial<ColumnPinningState>
|
|
52
|
+
|
|
53
|
+
export interface DataGridColumnFilterContext<TFeatures extends TableFeatures = DataGridFeatures> {
|
|
54
|
+
column: Column<TFeatures, DataGridItem, unknown>
|
|
55
|
+
table: DataGridTable<TFeatures>
|
|
32
56
|
labels: DataGridLabels
|
|
33
57
|
}
|
|
34
58
|
|
|
35
|
-
export type DataGridColumnFilterFactory
|
|
59
|
+
export type DataGridColumnFilterFactory<TFeatures extends TableFeatures = DataGridFeatures> =
|
|
60
|
+
(context: DataGridColumnFilterContext<TFeatures>) => DataGridSlot
|
|
36
61
|
|
|
37
62
|
export type DataGridFilterType = 'text' | 'number' | 'date' | 'select'
|
|
38
63
|
|
|
@@ -112,12 +137,12 @@ export interface DataGridEditCommitEvent {
|
|
|
112
137
|
previousValue: unknown
|
|
113
138
|
}
|
|
114
139
|
|
|
115
|
-
export interface DataGridColumn {
|
|
140
|
+
export interface DataGridColumn<TFeatures extends TableFeatures = DataGridFeatures> {
|
|
116
141
|
key: string
|
|
117
142
|
editable?: boolean | DataGridEditableOptions
|
|
118
143
|
editor?: DataGridEditorFactory
|
|
119
144
|
editValidate?: (value: unknown, item: DataGridItem) => true | string
|
|
120
|
-
filter?: DataGridColumnFilterFactory
|
|
145
|
+
filter?: DataGridColumnFilterFactory<TFeatures>
|
|
121
146
|
filterable?: boolean
|
|
122
147
|
filterFn?: (value: unknown, filterValue: unknown, item: DataGridItem) => boolean
|
|
123
148
|
filterType?: DataGridFilterType
|
|
@@ -166,8 +191,8 @@ export interface DataGridLabels {
|
|
|
166
191
|
lastPage: string
|
|
167
192
|
loadError: string
|
|
168
193
|
loading: string
|
|
169
|
-
|
|
170
|
-
|
|
194
|
+
moveEnd: string
|
|
195
|
+
moveStart: string
|
|
171
196
|
nextPage: string
|
|
172
197
|
operatorBetween: string
|
|
173
198
|
operatorBlank: string
|
|
@@ -184,10 +209,11 @@ export interface DataGridLabels {
|
|
|
184
209
|
operatorStartsWith: string
|
|
185
210
|
pageSizeLabel: string
|
|
186
211
|
paginationLabel: string
|
|
187
|
-
|
|
188
|
-
|
|
212
|
+
pinEnd: string
|
|
213
|
+
pinStart: string
|
|
189
214
|
previousPage: string
|
|
190
215
|
redoneAnnouncement: string
|
|
216
|
+
reorderRow: string
|
|
191
217
|
resetColumns: string
|
|
192
218
|
resultsAnnouncement: string
|
|
193
219
|
searchValues: string
|
|
@@ -199,6 +225,7 @@ export interface DataGridLabels {
|
|
|
199
225
|
sortDescending: string
|
|
200
226
|
toolbarColumns: string
|
|
201
227
|
toolbarExport: string
|
|
228
|
+
toolbarPrint: string
|
|
202
229
|
toolbarRedo: string
|
|
203
230
|
toolbarUndo: string
|
|
204
231
|
undoneAnnouncement: string
|
|
@@ -209,8 +236,8 @@ export interface DataGridColumnSizingOptions {
|
|
|
209
236
|
mode?: 'onChange' | 'onEnd'
|
|
210
237
|
}
|
|
211
238
|
|
|
212
|
-
export interface DataGridSlotContext {
|
|
213
|
-
table:
|
|
239
|
+
export interface DataGridSlotContext<TFeatures extends TableFeatures = DataGridFeatures> {
|
|
240
|
+
table: DataGridTable<TFeatures>
|
|
214
241
|
labels: DataGridLabels
|
|
215
242
|
}
|
|
216
243
|
|
|
@@ -220,18 +247,20 @@ export interface DataGridSlot {
|
|
|
220
247
|
dispose?: () => void
|
|
221
248
|
}
|
|
222
249
|
|
|
223
|
-
export type DataGridSlotFactory
|
|
250
|
+
export type DataGridSlotFactory<TFeatures extends TableFeatures = DataGridFeatures> =
|
|
251
|
+
(context: DataGridSlotContext<TFeatures>) => DataGridSlot
|
|
224
252
|
|
|
225
|
-
export interface DataGridSlots {
|
|
226
|
-
toolbar: DataGridSlotFactory
|
|
227
|
-
pagination: DataGridSlotFactory
|
|
228
|
-
empty: DataGridSlotFactory
|
|
253
|
+
export interface DataGridSlots<TFeatures extends TableFeatures = DataGridFeatures> {
|
|
254
|
+
toolbar: DataGridSlotFactory<TFeatures>
|
|
255
|
+
pagination: DataGridSlotFactory<TFeatures>
|
|
256
|
+
empty: DataGridSlotFactory<TFeatures>
|
|
229
257
|
}
|
|
230
258
|
|
|
231
259
|
export interface DataGridToolbarOptions {
|
|
232
260
|
columns?: boolean
|
|
233
|
-
export?: boolean |
|
|
261
|
+
export?: boolean | DataGridExportOptions
|
|
234
262
|
history?: boolean
|
|
263
|
+
print?: boolean
|
|
235
264
|
search?: boolean
|
|
236
265
|
}
|
|
237
266
|
|
|
@@ -253,6 +282,13 @@ export interface DataGridPaginationInfoState {
|
|
|
253
282
|
totalRows: number
|
|
254
283
|
}
|
|
255
284
|
|
|
285
|
+
export interface DataGridInfiniteScrollOptions {
|
|
286
|
+
pageSize?: number
|
|
287
|
+
// Rows left below the last rendered one when the next page is requested.
|
|
288
|
+
// Defaults to the grid's overscan.
|
|
289
|
+
threshold?: number
|
|
290
|
+
}
|
|
291
|
+
|
|
256
292
|
export interface DataGridPaginationOptions {
|
|
257
293
|
info?: boolean | ((state: DataGridPaginationInfoState) => string)
|
|
258
294
|
pageSize?: number
|
|
@@ -268,7 +304,7 @@ export interface DataGridState {
|
|
|
268
304
|
columnOrder: ColumnOrderState
|
|
269
305
|
columnPinning: ColumnPinningState
|
|
270
306
|
columnSizing: ColumnSizingState
|
|
271
|
-
columnVisibility:
|
|
307
|
+
columnVisibility: ColumnVisibilityState
|
|
272
308
|
globalFilter: string
|
|
273
309
|
pagination: PaginationState
|
|
274
310
|
rowSelection: RowSelectionState
|
|
@@ -287,37 +323,55 @@ export interface DataGridDataResponse {
|
|
|
287
323
|
totalRows: number
|
|
288
324
|
}
|
|
289
325
|
|
|
290
|
-
export interface DataGridOptions {
|
|
326
|
+
export interface DataGridOptions<TFeatures extends TableFeatures = DataGridFeatures> {
|
|
327
|
+
// Measure each rendered row instead of trusting rowHeight, so rows can grow
|
|
328
|
+
// with their content. rowHeight stays the estimate for unmeasured rows.
|
|
329
|
+
autoRowHeight: boolean
|
|
291
330
|
cellNavigation: boolean
|
|
331
|
+
// Spreadsheet-style cell ranges: shift-click, drag and shift+arrows, with
|
|
332
|
+
// Ctrl/Cmd+C copying the range as TSV. Implies cellNavigation.
|
|
333
|
+
cellSelection: boolean
|
|
292
334
|
columnFilters: boolean
|
|
293
335
|
columnMenu: boolean | DataGridColumnMenuBuilder
|
|
294
336
|
columnMenuIcon: string
|
|
295
337
|
columnOrder: boolean | ColumnOrderState
|
|
296
|
-
columnPinning: boolean |
|
|
338
|
+
columnPinning: boolean | DataGridColumnPinning
|
|
297
339
|
columnSizing: boolean | DataGridColumnSizingOptions
|
|
298
|
-
columnVisibility: boolean |
|
|
299
|
-
columns: DataGridColumn
|
|
340
|
+
columnVisibility: boolean | ColumnVisibilityState
|
|
341
|
+
columns: Array<DataGridColumn<TFeatures>>
|
|
300
342
|
dataProvider: ((request: DataGridDataRequest) => Promise<DataGridDataResponse>) | null
|
|
301
343
|
editing: boolean
|
|
302
344
|
empty: string | (() => Node | string)
|
|
345
|
+
// The registered table-core feature set; null means the full built-in
|
|
346
|
+
// dataGridFeatures. Presets: dataGridFeatures, dataGridLiteFeatures.
|
|
347
|
+
features: TFeatures | null
|
|
303
348
|
filterIcon: string
|
|
304
349
|
globalFilter: boolean
|
|
305
350
|
hideColumnIcon: string
|
|
306
351
|
history: boolean
|
|
352
|
+
// Append each loaded page to the rows already in view instead of replacing
|
|
353
|
+
// them. Requires dataProvider, cannot be combined with pagination.
|
|
354
|
+
infiniteScroll: boolean | DataGridInfiniteScrollOptions
|
|
307
355
|
itemKey: ((item: DataGridItem, index: number) => string) | null
|
|
308
356
|
items: DataGridItem[]
|
|
309
357
|
labels: Partial<DataGridLabels>
|
|
310
|
-
|
|
311
|
-
|
|
358
|
+
moveEndIcon: string
|
|
359
|
+
moveStartIcon: string
|
|
312
360
|
overscan: number
|
|
313
361
|
pagination: boolean | DataGridPaginationOptions
|
|
314
|
-
|
|
315
|
-
|
|
362
|
+
pinEndIcon: string
|
|
363
|
+
pinStartIcon: string
|
|
364
|
+
// Heading printed above the table; omitted when null.
|
|
365
|
+
printTitle: string | null
|
|
366
|
+
rowHandleIcon: string
|
|
316
367
|
rowHeight: number
|
|
368
|
+
// A drag handle per row; the grid never mutates `items` — it emits
|
|
369
|
+
// rowOrderChange with the reordered array for the app to apply.
|
|
370
|
+
rowOrder: boolean
|
|
317
371
|
rowSelection: boolean | DataGridRowSelectionOptions
|
|
318
372
|
sanitize: boolean
|
|
319
373
|
sanitizeFn: ((html: string) => string) | null
|
|
320
|
-
slots: Partial<DataGridSlots
|
|
374
|
+
slots: Partial<DataGridSlots<TFeatures>>
|
|
321
375
|
sortAscendingIcon: string
|
|
322
376
|
sortDescendingIcon: string
|
|
323
377
|
sortNeutralIcon: string
|
|
@@ -327,6 +381,7 @@ export interface DataGridOptions {
|
|
|
327
381
|
toolbar: boolean | DataGridToolbarOptions
|
|
328
382
|
toolbarColumnsIcon: string
|
|
329
383
|
toolbarExportIcon: string
|
|
384
|
+
toolbarPrintIcon: string
|
|
330
385
|
toolbarRedoIcon: string
|
|
331
386
|
toolbarUndoIcon: string
|
|
332
387
|
unpinIcon: string
|
package/src/ui/body.ts
CHANGED
|
@@ -5,19 +5,24 @@
|
|
|
5
5
|
* --------------------------------------------------------------------------
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { Row } from '@tanstack/table-core'
|
|
9
8
|
import type { GridVirtualizerInstance } from '../engine/virtualizer'
|
|
10
|
-
import type { DataGridColumn,
|
|
9
|
+
import type { DataGridColumn, DataGridLabels, DataGridRow } from '../types'
|
|
11
10
|
import { applyPinnedCell, type GridPinning } from './pinning'
|
|
12
11
|
|
|
13
12
|
const CLASS_NAME_EMPTY = 'data-grid-empty'
|
|
14
13
|
const FOCUSABLE_SELECTOR = 'a[href], button, input, select, textarea, [tabindex]'
|
|
15
14
|
const CLASS_NAME_ROW_ODD = 'data-grid-row-odd'
|
|
16
15
|
const CLASS_NAME_ROW_SELECTED = 'table-active'
|
|
16
|
+
const CLASS_NAME_CELL_SELECTED = 'data-grid-cell-selected'
|
|
17
|
+
const CLASS_NAME_HANDLE_CELL = 'data-grid-row-handle-cell'
|
|
18
|
+
const EDGE_CLASS_NAMES = ['top', 'right', 'bottom', 'left'] as const
|
|
17
19
|
const CLASS_NAME_SELECT_CELL = 'data-grid-select-cell'
|
|
18
20
|
const CLASS_NAME_SPACER = 'data-grid-spacer'
|
|
19
21
|
|
|
20
22
|
export interface GridBodyContext {
|
|
23
|
+
// Hands every rendered row to the virtualizer for measurement instead of
|
|
24
|
+
// assuming a fixed rowHeight.
|
|
25
|
+
autoRowHeight: boolean
|
|
21
26
|
// With cellNavigation on, rows/cells carry grid roles, every cell (and the
|
|
22
27
|
// interactive content inside it) drops out of the tab order, and cells are
|
|
23
28
|
// addressable by data-coreui-column - the roving tabindex pass in the grid
|
|
@@ -35,13 +40,16 @@ export interface GridBodyContext {
|
|
|
35
40
|
// Absolute index of the page's first row (pagination mode), else 0 - keeps
|
|
36
41
|
// aria-rowindex absolute like aria-rowcount.
|
|
37
42
|
getRowIndexOffset: () => number
|
|
38
|
-
getRows: () =>
|
|
43
|
+
getRows: () => DataGridRow[]
|
|
39
44
|
// Visible leaf columns in table order (pinning + columnOrder + visibility).
|
|
40
45
|
getVisibleColumns: () => DataGridColumn[]
|
|
41
46
|
getVirtualizer: () => GridVirtualizerInstance | null
|
|
42
47
|
renderEmpty: () => Node | string
|
|
43
48
|
// Guards every string injected via innerHTML (render results, empty state).
|
|
44
49
|
sanitizeContent: (html: string) => string
|
|
50
|
+
cellSelection: boolean
|
|
51
|
+
rowOrder: boolean
|
|
52
|
+
rowHandleIcon: string
|
|
45
53
|
selection: boolean
|
|
46
54
|
}
|
|
47
55
|
|
|
@@ -76,7 +84,10 @@ export const createBody = (context: GridBodyContext): GridBody => {
|
|
|
76
84
|
// rebuilt (not recycled) when the visible column set or order changes.
|
|
77
85
|
let currentColumns: DataGridColumn[] = context.getVisibleColumns()
|
|
78
86
|
let layoutSignature = currentColumns.map(column => column.key).join('|')
|
|
79
|
-
|
|
87
|
+
// The handle column and the select column both sit before the data columns;
|
|
88
|
+
// every cell index in this module is offset by however many are on.
|
|
89
|
+
const leadingCount = (context.rowOrder ? 1 : 0) + (context.selection ? 1 : 0)
|
|
90
|
+
let cellCount = currentColumns.length + leadingCount
|
|
80
91
|
|
|
81
92
|
const buildRowCells = (rowElement: HTMLTableRowElement): void => {
|
|
82
93
|
rowElement.replaceChildren()
|
|
@@ -85,7 +96,24 @@ export const createBody = (context: GridBodyContext): GridBody => {
|
|
|
85
96
|
rowElement.setAttribute('role', 'row')
|
|
86
97
|
}
|
|
87
98
|
|
|
88
|
-
const offset =
|
|
99
|
+
const offset = leadingCount
|
|
100
|
+
|
|
101
|
+
if (context.rowOrder) {
|
|
102
|
+
const cellElement = document.createElement('td')
|
|
103
|
+
cellElement.classList.add(CLASS_NAME_HANDLE_CELL)
|
|
104
|
+
const handle = document.createElement('span')
|
|
105
|
+
handle.classList.add('data-grid-row-handle')
|
|
106
|
+
handle.dataset.coreuiRowHandle = ''
|
|
107
|
+
handle.setAttribute('aria-hidden', 'true')
|
|
108
|
+
handle.innerHTML = context.rowHandleIcon
|
|
109
|
+
cellElement.append(handle)
|
|
110
|
+
if (context.cellNavigation) {
|
|
111
|
+
cellElement.setAttribute('role', 'gridcell')
|
|
112
|
+
cellElement.setAttribute('aria-colindex', '1')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
rowElement.append(cellElement)
|
|
116
|
+
}
|
|
89
117
|
|
|
90
118
|
if (context.selection) {
|
|
91
119
|
const cellElement = document.createElement('td')
|
|
@@ -97,7 +125,7 @@ export const createBody = (context: GridBodyContext): GridBody => {
|
|
|
97
125
|
input.dataset.coreuiRowSelect = ''
|
|
98
126
|
if (context.cellNavigation) {
|
|
99
127
|
cellElement.setAttribute('role', 'gridcell')
|
|
100
|
-
cellElement.setAttribute('aria-colindex',
|
|
128
|
+
cellElement.setAttribute('aria-colindex', String(context.rowOrder ? 2 : 1))
|
|
101
129
|
input.tabIndex = -1
|
|
102
130
|
}
|
|
103
131
|
|
|
@@ -126,8 +154,8 @@ export const createBody = (context: GridBodyContext): GridBody => {
|
|
|
126
154
|
return rowElement
|
|
127
155
|
}
|
|
128
156
|
|
|
129
|
-
const updateRowCells = (rowElement: HTMLTableRowElement, row:
|
|
130
|
-
const offset =
|
|
157
|
+
const updateRowCells = (rowElement: HTMLTableRowElement, row: DataGridRow, index: number): void => {
|
|
158
|
+
const offset = leadingCount
|
|
131
159
|
const editingCell = context.getEditingCell()
|
|
132
160
|
|
|
133
161
|
for (const [cellIndex, column] of currentColumns.entries()) {
|
|
@@ -159,14 +187,15 @@ export const createBody = (context: GridBodyContext): GridBody => {
|
|
|
159
187
|
}
|
|
160
188
|
}
|
|
161
189
|
|
|
162
|
-
const updateRowElement = (rowElement: HTMLTableRowElement, row:
|
|
190
|
+
const updateRowElement = (rowElement: HTMLTableRowElement, row: DataGridRow, index: number): void => {
|
|
163
191
|
if (rowElement.dataset.coreuiLayout !== layoutSignature) {
|
|
164
192
|
buildRowCells(rowElement)
|
|
165
193
|
delete rowElement.dataset.coreuiRow
|
|
166
194
|
}
|
|
167
195
|
|
|
168
196
|
if (context.selection) {
|
|
169
|
-
const
|
|
197
|
+
const selectCell = rowElement.children[leadingCount - 1] as HTMLTableCellElement
|
|
198
|
+
const input = selectCell.firstElementChild as HTMLInputElement
|
|
170
199
|
const selected = row.getIsSelected()
|
|
171
200
|
input.checked = selected
|
|
172
201
|
rowElement.classList.toggle(CLASS_NAME_ROW_SELECTED, selected)
|
|
@@ -183,15 +212,33 @@ export const createBody = (context: GridBodyContext): GridBody => {
|
|
|
183
212
|
rowElement.dataset.coreuiRow = rowKey
|
|
184
213
|
}
|
|
185
214
|
|
|
215
|
+
// Selection is repainted on every pass, unlike cell content: it changes
|
|
216
|
+
// without the row's data version or id changing.
|
|
217
|
+
if (context.cellSelection) {
|
|
218
|
+
const cells = new Map(row.getAllCells().map(cell => [cell.column.id, cell]))
|
|
219
|
+
for (const [cellIndex, column] of currentColumns.entries()) {
|
|
220
|
+
const cellElement = rowElement.children[cellIndex + leadingCount] as HTMLTableCellElement
|
|
221
|
+
const cell = cells.get(column.key)
|
|
222
|
+
const selected = cell?.getIsSelected() ?? false
|
|
223
|
+
cellElement.classList.toggle(CLASS_NAME_CELL_SELECTED, selected)
|
|
224
|
+
const edges = selected && cell ? cell.getSelectionEdges() : null
|
|
225
|
+
for (const edge of EDGE_CLASS_NAMES) {
|
|
226
|
+
cellElement.classList.toggle(`${CLASS_NAME_CELL_SELECTED}-${edge}`, Boolean(edges?.[edge]))
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
186
231
|
rowElement.dataset.coreuiIndex = String(index)
|
|
187
232
|
rowElement.setAttribute('aria-rowindex', String(context.headerRowCount + context.getRowIndexOffset() + index + 1))
|
|
188
233
|
rowElement.classList.toggle(CLASS_NAME_ROW_ODD, index % 2 === 1)
|
|
189
234
|
|
|
190
235
|
if (currentPinning) {
|
|
191
236
|
const pinning = currentPinning
|
|
192
|
-
const offset =
|
|
193
|
-
|
|
194
|
-
|
|
237
|
+
const offset = leadingCount
|
|
238
|
+
// Leading cells share the select column's pin — they travel together at
|
|
239
|
+
// the start edge.
|
|
240
|
+
for (let cellIndex = 0; cellIndex < leadingCount; cellIndex++) {
|
|
241
|
+
applyPinnedCell(rowElement.children[cellIndex] as HTMLTableCellElement, pinning.select)
|
|
195
242
|
}
|
|
196
243
|
|
|
197
244
|
for (const [cellIndex, column] of currentColumns.entries()) {
|
|
@@ -244,7 +291,7 @@ export const createBody = (context: GridBodyContext): GridBody => {
|
|
|
244
291
|
element.replaceChildren(emptyElement)
|
|
245
292
|
}
|
|
246
293
|
|
|
247
|
-
const renderAll = (rows:
|
|
294
|
+
const renderAll = (rows: DataGridRow[]) => {
|
|
248
295
|
if (emptyElement) {
|
|
249
296
|
emptyElement.remove()
|
|
250
297
|
}
|
|
@@ -266,7 +313,7 @@ export const createBody = (context: GridBodyContext): GridBody => {
|
|
|
266
313
|
}
|
|
267
314
|
}
|
|
268
315
|
|
|
269
|
-
const renderWindow = (rows:
|
|
316
|
+
const renderWindow = (rows: DataGridRow[], virtualizer: GridVirtualizerInstance) => {
|
|
270
317
|
if (!spacersAttached) {
|
|
271
318
|
element.replaceChildren(topSpacer, bottomSpacer)
|
|
272
319
|
spacersAttached = true
|
|
@@ -296,7 +343,16 @@ export const createBody = (context: GridBodyContext): GridBody => {
|
|
|
296
343
|
}
|
|
297
344
|
|
|
298
345
|
for (const [index, virtualItem] of virtualItems.entries()) {
|
|
299
|
-
|
|
346
|
+
const rowElement = rowElements[index]
|
|
347
|
+
updateRowElement(rowElement, rows[virtualItem.index], virtualItem.index)
|
|
348
|
+
|
|
349
|
+
if (context.autoRowHeight) {
|
|
350
|
+
// The virtualizer resolves a measurement to its row through this
|
|
351
|
+
// attribute, so it has to be current BEFORE the element is handed
|
|
352
|
+
// over - recycled elements arrive carrying the previous index.
|
|
353
|
+
rowElement.dataset.index = String(virtualItem.index)
|
|
354
|
+
virtualizer.measureElement(rowElement)
|
|
355
|
+
}
|
|
300
356
|
}
|
|
301
357
|
}
|
|
302
358
|
|
|
@@ -325,7 +381,7 @@ export const createBody = (context: GridBodyContext): GridBody => {
|
|
|
325
381
|
if (signature !== layoutSignature) {
|
|
326
382
|
currentColumns = columns
|
|
327
383
|
layoutSignature = signature
|
|
328
|
-
cellCount = currentColumns.length +
|
|
384
|
+
cellCount = currentColumns.length + leadingCount;
|
|
329
385
|
(topSpacer.firstElementChild as HTMLTableCellElement).colSpan = Math.max(cellCount, 1);
|
|
330
386
|
(bottomSpacer.firstElementChild as HTMLTableCellElement).colSpan = Math.max(cellCount, 1)
|
|
331
387
|
}
|
package/src/ui/header.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { interpolate } from '../labels'
|
|
9
9
|
import type {
|
|
10
|
-
DataGridColumn,
|
|
10
|
+
DataGridColumn, DataGridLabels, DataGridSlot, DataGridTable, SortingState
|
|
11
11
|
} from '../types'
|
|
12
12
|
import { applyPinnedCell, type GridPinning } from './pinning'
|
|
13
13
|
|
|
@@ -17,6 +17,7 @@ const CLASS_NAME_HEADER_LABEL = 'data-grid-header-label'
|
|
|
17
17
|
const CLASS_NAME_FILTER_ROW = 'data-grid-filter-row'
|
|
18
18
|
const CLASS_NAME_RESIZER = 'data-grid-resizer'
|
|
19
19
|
const CLASS_NAME_RESIZER_ACTIVE = 'data-grid-resizer-active'
|
|
20
|
+
const CLASS_NAME_HANDLE_CELL = 'data-grid-row-handle-cell'
|
|
20
21
|
const CLASS_NAME_SELECT_CELL = 'data-grid-select-cell'
|
|
21
22
|
const CLASS_NAME_SORTABLE = 'data-grid-sortable'
|
|
22
23
|
const CLASS_NAME_SORT_BUTTON = 'data-grid-sort-button'
|
|
@@ -44,9 +45,10 @@ export interface GridHeaderContext {
|
|
|
44
45
|
isSortable: (column: DataGridColumn) => boolean
|
|
45
46
|
labels: DataGridLabels
|
|
46
47
|
resizable: boolean
|
|
48
|
+
rowOrder: boolean
|
|
47
49
|
selection: boolean
|
|
48
50
|
selectAll: boolean
|
|
49
|
-
table:
|
|
51
|
+
table: DataGridTable
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
export interface GridHeaderState {
|
|
@@ -113,6 +115,15 @@ const createSelectAllCell = (context: GridHeaderContext): { cell: HTMLTableCellE
|
|
|
113
115
|
return { cell, input }
|
|
114
116
|
}
|
|
115
117
|
|
|
118
|
+
// The handle column has no header of its own; the cell only reserves the
|
|
119
|
+
// width so the body's grips line up under it.
|
|
120
|
+
const createHandleHeaderCell = (): HTMLTableCellElement => {
|
|
121
|
+
const cell = document.createElement('th')
|
|
122
|
+
cell.classList.add(CLASS_NAME_HANDLE_CELL)
|
|
123
|
+
cell.setAttribute('scope', 'col')
|
|
124
|
+
return cell
|
|
125
|
+
}
|
|
126
|
+
|
|
116
127
|
const createResizer = (): HTMLElement => {
|
|
117
128
|
const resizer = document.createElement('span')
|
|
118
129
|
resizer.classList.add(CLASS_NAME_RESIZER)
|
|
@@ -129,20 +140,30 @@ const createLabelRow = (
|
|
|
129
140
|
): {
|
|
130
141
|
row: HTMLTableRowElement
|
|
131
142
|
selectAllInput: HTMLInputElement | null
|
|
132
|
-
|
|
143
|
+
leadingCells: HTMLTableCellElement[]
|
|
133
144
|
} => {
|
|
134
145
|
const row = document.createElement('tr')
|
|
135
146
|
let selectAllInput: HTMLInputElement | null = null
|
|
136
|
-
|
|
147
|
+
const leadingCells: HTMLTableCellElement[] = []
|
|
137
148
|
|
|
138
149
|
if (context.cellNavigation) {
|
|
139
150
|
row.setAttribute('role', 'row')
|
|
140
151
|
}
|
|
141
152
|
|
|
153
|
+
if (context.rowOrder) {
|
|
154
|
+
const cell = createHandleHeaderCell()
|
|
155
|
+
if (context.cellNavigation) {
|
|
156
|
+
cell.setAttribute('role', 'columnheader')
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
leadingCells.push(cell)
|
|
160
|
+
row.append(cell)
|
|
161
|
+
}
|
|
162
|
+
|
|
142
163
|
if (context.selection) {
|
|
143
164
|
const { cell, input } = createSelectAllCell(context)
|
|
144
165
|
selectAllInput = input
|
|
145
|
-
|
|
166
|
+
leadingCells.push(cell)
|
|
146
167
|
if (context.cellNavigation) {
|
|
147
168
|
cell.setAttribute('role', 'columnheader')
|
|
148
169
|
if (input) {
|
|
@@ -240,25 +261,28 @@ const createLabelRow = (
|
|
|
240
261
|
row.append(cell)
|
|
241
262
|
}
|
|
242
263
|
|
|
243
|
-
return { row, selectAllInput,
|
|
264
|
+
return { row, selectAllInput, leadingCells }
|
|
244
265
|
}
|
|
245
266
|
|
|
246
267
|
const createFilterRow = (
|
|
247
268
|
context: GridHeaderContext,
|
|
248
269
|
filterCells: Map<string, HTMLTableCellElement>,
|
|
249
270
|
filterSlots: DataGridSlot[]
|
|
250
|
-
): { row: HTMLTableRowElement;
|
|
271
|
+
): { row: HTMLTableRowElement; leadingCells: HTMLTableCellElement[] } => {
|
|
251
272
|
const row = document.createElement('tr')
|
|
252
273
|
row.classList.add(CLASS_NAME_FILTER_ROW)
|
|
253
274
|
if (context.cellNavigation) {
|
|
254
275
|
row.setAttribute('role', 'row')
|
|
255
276
|
}
|
|
256
277
|
|
|
257
|
-
|
|
278
|
+
const leadingCells: HTMLTableCellElement[] = []
|
|
258
279
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
280
|
+
for (const leading of [context.rowOrder, context.selection]) {
|
|
281
|
+
if (leading) {
|
|
282
|
+
const cell = document.createElement('th')
|
|
283
|
+
leadingCells.push(cell)
|
|
284
|
+
row.append(cell)
|
|
285
|
+
}
|
|
262
286
|
}
|
|
263
287
|
|
|
264
288
|
for (const column of context.columns) {
|
|
@@ -274,7 +298,7 @@ const createFilterRow = (
|
|
|
274
298
|
row.append(cell)
|
|
275
299
|
}
|
|
276
300
|
|
|
277
|
-
return { row,
|
|
301
|
+
return { row, leadingCells }
|
|
278
302
|
}
|
|
279
303
|
|
|
280
304
|
export const createHeader = (context: GridHeaderContext): GridHeader => {
|
|
@@ -288,14 +312,14 @@ export const createHeader = (context: GridHeaderContext): GridHeader => {
|
|
|
288
312
|
const filterSlots: DataGridSlot[] = []
|
|
289
313
|
const movableColumns = new Map(context.columns.map(column => [column.key, column.movable !== false]))
|
|
290
314
|
|
|
291
|
-
const { row, selectAllInput,
|
|
315
|
+
const { row, selectAllInput, leadingCells } = createLabelRow(context, cells, resizers, sortIcons)
|
|
292
316
|
element.append(row)
|
|
293
317
|
|
|
294
318
|
let filterRowElement: HTMLTableRowElement | null = null
|
|
295
|
-
let
|
|
319
|
+
let filterLeadingCells: HTMLTableCellElement[] = []
|
|
296
320
|
if (context.filterRow) {
|
|
297
321
|
const filterRow = createFilterRow(context, filterCells, filterSlots)
|
|
298
|
-
|
|
322
|
+
filterLeadingCells = filterRow.leadingCells
|
|
299
323
|
filterRowElement = filterRow.row
|
|
300
324
|
element.append(filterRow.row)
|
|
301
325
|
}
|
|
@@ -309,9 +333,9 @@ export const createHeader = (context: GridHeaderContext): GridHeader => {
|
|
|
309
333
|
const reorderRow = (
|
|
310
334
|
rowElement: HTMLTableRowElement,
|
|
311
335
|
cellMap: Map<string, HTMLTableCellElement>,
|
|
312
|
-
leading: HTMLTableCellElement
|
|
336
|
+
leading: HTMLTableCellElement[]
|
|
313
337
|
) => {
|
|
314
|
-
const ordered: HTMLTableCellElement[] =
|
|
338
|
+
const ordered: HTMLTableCellElement[] = [...leading]
|
|
315
339
|
for (const id of order) {
|
|
316
340
|
const cell = cellMap.get(id)
|
|
317
341
|
if (cell) {
|
|
@@ -333,9 +357,9 @@ export const createHeader = (context: GridHeaderContext): GridHeader => {
|
|
|
333
357
|
}
|
|
334
358
|
}
|
|
335
359
|
|
|
336
|
-
reorderRow(row, cells,
|
|
360
|
+
reorderRow(row, cells, leadingCells)
|
|
337
361
|
if (filterRowElement) {
|
|
338
|
-
reorderRow(filterRowElement, filterCells,
|
|
362
|
+
reorderRow(filterRowElement, filterCells, filterLeadingCells)
|
|
339
363
|
}
|
|
340
364
|
}
|
|
341
365
|
|
|
@@ -388,11 +412,14 @@ export const createHeader = (context: GridHeaderContext): GridHeader => {
|
|
|
388
412
|
|
|
389
413
|
for (const [key, cell] of cells) {
|
|
390
414
|
const draggable = Boolean(movableColumns.get(key)) && (movablePerRegion.get(regionOf(key)) ?? 0) > 1
|
|
391
|
-
cell.draggable = draggable
|
|
392
415
|
if (draggable) {
|
|
393
416
|
cell.dataset.coreuiDraggable = ''
|
|
417
|
+
// Without this the browser scrolls (or picks text) instead of
|
|
418
|
+
// handing the pointer stream to the drag controller on touch.
|
|
419
|
+
cell.style.touchAction = 'none'
|
|
394
420
|
} else {
|
|
395
421
|
delete cell.dataset.coreuiDraggable
|
|
422
|
+
cell.style.touchAction = ''
|
|
396
423
|
}
|
|
397
424
|
}
|
|
398
425
|
}
|
|
@@ -414,12 +441,10 @@ export const createHeader = (context: GridHeaderContext): GridHeader => {
|
|
|
414
441
|
applyPinnedCell(cell, state.pinning.columns[key] ?? null)
|
|
415
442
|
}
|
|
416
443
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
if (filterSelectCell) {
|
|
422
|
-
applyPinnedCell(filterSelectCell, state.pinning.select)
|
|
444
|
+
// Every leading cell (the handle column, the select column) shares
|
|
445
|
+
// the select column's pin — they travel together at the start edge.
|
|
446
|
+
for (const cell of [...leadingCells, ...filterLeadingCells]) {
|
|
447
|
+
applyPinnedCell(cell, state.pinning.select)
|
|
423
448
|
}
|
|
424
449
|
}
|
|
425
450
|
|
package/src/ui/loading.ts
CHANGED
|
@@ -14,9 +14,9 @@ export interface GridLoading {
|
|
|
14
14
|
setVisible: (visible: boolean) => void
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export const createLoading = (labels: DataGridLabels): GridLoading => {
|
|
17
|
+
export const createLoading = (labels: DataGridLabels, className = CLASS_NAME_LOADING): GridLoading => {
|
|
18
18
|
const element = document.createElement('div')
|
|
19
|
-
element.classList.add(
|
|
19
|
+
element.classList.add(className)
|
|
20
20
|
element.hidden = true
|
|
21
21
|
|
|
22
22
|
const spinner = document.createElement('div')
|