strike-fw-datagrid 0.1.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/data-grid.js ADDED
@@ -0,0 +1,1426 @@
1
+ import { h } from 'strike-fw';
2
+ import { useState, useLayoutEffect, useRef, useEffect } from 'strike-fw/hooks';
3
+ import { css } from 'strike-fw/css';
4
+ import { Field, Check, NumberField, Select, Btn, cls } from 'strike-fw/ui';
5
+ import { Table } from 'strike-fw-ui/display/table.js';
6
+ import { Pagination } from 'strike-fw-ui/navigation/pagination.js';
7
+ import { useControllable } from './lib/controllable.js';
8
+ import {
9
+ normalizeColumn,
10
+ getCellValue,
11
+ formatCell,
12
+ isColumnEditable,
13
+ defaultAlign,
14
+ clampColumnWidth
15
+ } from './lib/grid-columns.js';
16
+ import { applyQuickFilter, applyFilterModel } from './lib/grid-filter.js';
17
+ import { applySort, cycleSortModel } from './lib/grid-sort.js';
18
+ import {
19
+ applyPagination,
20
+ pageCount,
21
+ clampPage
22
+ } from './lib/grid-paginate.js';
23
+ import {
24
+ toggleId,
25
+ setVisibleSelection,
26
+ selectionState,
27
+ selectableVisibleIds
28
+ } from './lib/grid-selection.js';
29
+ import { buildUpdatedRow, parseByType } from './lib/grid-edit.js';
30
+ import {
31
+ moveItem,
32
+ applyRowOrder,
33
+ applyColumnOrder
34
+ } from './lib/grid-order.js';
35
+ import { windowRange } from './lib/grid-window.js';
36
+ import { moveFocus } from './lib/grid-keyboard.js';
37
+
38
+ css`
39
+ .strike-data-grid {
40
+ --strike-grid-header-bg: var(--strike-fill, #f6f6f4);
41
+ --strike-grid-stripe: var(--strike-fill, #f6f6f4);
42
+ --strike-grid-selected: var(--strike-fill, #f6f6f4);
43
+ display: flex;
44
+ flex-direction: column;
45
+ gap: 0.5rem;
46
+ font: inherit;
47
+ position: relative;
48
+ }
49
+ .strike-data-grid__toolbar {
50
+ display: flex;
51
+ flex-wrap: wrap;
52
+ gap: 0.5rem;
53
+ align-items: center;
54
+ }
55
+ .strike-data-grid__scroller .strike-table {
56
+ table-layout: fixed;
57
+ width: max-content;
58
+ min-width: 100%;
59
+ }
60
+ .strike-data-grid .strike-table th {
61
+ position: relative;
62
+ }
63
+ .strike-data-grid__col-resizer {
64
+ position: absolute;
65
+ top: 0;
66
+ right: -3px;
67
+ width: 8px;
68
+ height: 100%;
69
+ cursor: col-resize;
70
+ touch-action: none;
71
+ z-index: 3;
72
+ padding: 0;
73
+ border: 0;
74
+ background: transparent;
75
+ }
76
+ .strike-data-grid__col-resizer:hover,
77
+ .strike-data-grid__col-resizer:focus-visible,
78
+ .strike-data-grid--resizing .strike-data-grid__col-resizer--active {
79
+ background: var(--strike-accent, #0b6e4f);
80
+ opacity: 0.4;
81
+ }
82
+ .strike-data-grid__col-resizer:focus-visible {
83
+ outline: 2px solid var(--strike-accent, #0b6e4f);
84
+ outline-offset: 0;
85
+ opacity: 0.55;
86
+ }
87
+ .strike-data-grid--resizing {
88
+ user-select: none;
89
+ cursor: col-resize;
90
+ }
91
+ .strike-data-grid--resizing .strike-data-grid__scroller {
92
+ cursor: col-resize;
93
+ }
94
+ .strike-data-grid__footer {
95
+ display: flex;
96
+ flex-wrap: wrap;
97
+ gap: 0.75rem;
98
+ align-items: center;
99
+ justify-content: space-between;
100
+ }
101
+ .strike-data-grid__loading {
102
+ position: absolute;
103
+ inset: 0;
104
+ display: flex;
105
+ align-items: center;
106
+ justify-content: center;
107
+ background: rgba(255, 255, 255, 0.65);
108
+ z-index: 2;
109
+ }
110
+ .strike-data-grid--header-shade thead th {
111
+ background: var(--strike-grid-header-bg);
112
+ }
113
+ .strike-data-grid--striped-rows tbody tr:nth-child(even) td {
114
+ background: var(--strike-grid-stripe);
115
+ }
116
+ .strike-data-grid--striped-dataset tbody tr.strike-data-grid__row--stripe td {
117
+ background: var(--strike-grid-stripe);
118
+ }
119
+ .strike-data-grid--striped-columns tbody td:not(.strike-data-grid__cell--select):not(.strike-data-grid__cell--grip):nth-child(even),
120
+ .strike-data-grid--striped-columns thead th:not(.strike-data-grid__cell--select):not(.strike-data-grid__cell--grip):nth-child(even) {
121
+ background: var(--strike-grid-stripe);
122
+ }
123
+ .strike-data-grid__row--selected td {
124
+ background: var(--strike-grid-selected);
125
+ }
126
+ .strike-data-grid__sort {
127
+ font: inherit;
128
+ font-weight: 600;
129
+ background: none;
130
+ border: 0;
131
+ padding: 0;
132
+ cursor: pointer;
133
+ color: inherit;
134
+ text-align: inherit;
135
+ }
136
+ .strike-data-grid__sort:focus-visible {
137
+ outline: 2px solid var(--strike-accent, #0b6e4f);
138
+ outline-offset: 1px;
139
+ }
140
+ .strike-data-grid__editor .strike-field,
141
+ .strike-data-grid__editor .strike-number,
142
+ .strike-data-grid__editor .strike-check {
143
+ gap: 0;
144
+ width: 100%;
145
+ }
146
+ .strike-data-grid__editor .strike-field__label,
147
+ .strike-data-grid__editor .strike-number__label {
148
+ display: none;
149
+ }
150
+ .strike-data-grid__editor .strike-field__input,
151
+ .strike-data-grid__editor .strike-number__input {
152
+ padding: 0.25rem 0.35rem;
153
+ width: 100%;
154
+ box-sizing: border-box;
155
+ }
156
+ .strike-data-grid__cell--select,
157
+ .strike-data-grid__cell--grip {
158
+ width: 2.25rem;
159
+ text-align: center;
160
+ padding-left: 0.35rem;
161
+ padding-right: 0.35rem;
162
+ }
163
+ .strike-data-grid__cell--editable {
164
+ cursor: cell;
165
+ }
166
+ .strike-data-grid__grip {
167
+ cursor: grab;
168
+ user-select: none;
169
+ touch-action: none;
170
+ border: 0;
171
+ background: none;
172
+ font: inherit;
173
+ padding: 0.35rem 0.25rem;
174
+ margin: 0;
175
+ color: var(--strike-muted, #5c5c5c);
176
+ border-radius: 0.25rem;
177
+ line-height: 0;
178
+ vertical-align: middle;
179
+ }
180
+ .strike-data-grid__grip:hover,
181
+ .strike-data-grid__grip:focus-visible {
182
+ color: var(--strike-fg, #1a1a1a);
183
+ background: var(--strike-fill, #f0f0ee);
184
+ }
185
+ .strike-data-grid__grip:focus-visible {
186
+ outline: 2px solid var(--strike-accent, #0b6e4f);
187
+ outline-offset: 1px;
188
+ }
189
+ .strike-data-grid__grip:active,
190
+ .strike-data-grid--dnd .strike-data-grid__grip {
191
+ cursor: grabbing;
192
+ }
193
+ .strike-data-grid__grip-icon {
194
+ display: block;
195
+ width: 0.65rem;
196
+ height: 1rem;
197
+ background-image: radial-gradient(
198
+ circle closest-side,
199
+ currentColor 1.15px,
200
+ transparent 1.25px
201
+ );
202
+ background-size: 0.325rem 0.325rem;
203
+ background-position: 0 0;
204
+ opacity: 0.7;
205
+ }
206
+ .strike-data-grid__header {
207
+ display: inline-flex;
208
+ align-items: center;
209
+ gap: 0.35rem;
210
+ min-width: 0;
211
+ }
212
+ .strike-data-grid__row--dragging td {
213
+ opacity: 0.45;
214
+ }
215
+ .strike-data-grid__row--drop td {
216
+ box-shadow: inset 0 2px 0 0 var(--strike-accent, #0b6e4f);
217
+ }
218
+ .strike-data-grid__th--dragging {
219
+ opacity: 0.45;
220
+ }
221
+ .strike-data-grid__th--drop {
222
+ box-shadow: inset 2px 0 0 0 var(--strike-accent, #0b6e4f);
223
+ }
224
+ .strike-data-grid--dnd {
225
+ user-select: none;
226
+ }
227
+ .strike-data-grid--dnd .strike-data-grid__scroller {
228
+ cursor: grabbing;
229
+ }
230
+ .strike-data-grid__cell--focus {
231
+ outline: 2px solid var(--strike-accent, #0b6e4f);
232
+ outline-offset: -2px;
233
+ }
234
+ `;
235
+
236
+ function sortCue(item) {
237
+ if (!item) return '';
238
+ return item.sort === 'desc' ? ' v' : ' ^';
239
+ }
240
+
241
+ function ariaSortValue(item) {
242
+ if (!item) return 'none';
243
+ return item.sort === 'desc' ? 'descending' : 'ascending';
244
+ }
245
+
246
+ function GripIcon() {
247
+ return h('span', {
248
+ class: 'strike-data-grid__grip-icon',
249
+ 'aria-hidden': 'true'
250
+ });
251
+ }
252
+
253
+ function FocusEditor({ children }) {
254
+ const ref = useRef(null);
255
+ useLayoutEffect(() => {
256
+ const root = ref.current;
257
+ if (!root) return;
258
+ const el =
259
+ root.querySelector('input, select, textarea, button') || root;
260
+ if (el && el.focus) el.focus();
261
+ }, []);
262
+ return h('div', { class: 'strike-data-grid__editor', ref }, children);
263
+ }
264
+
265
+ function editKeys(onCommit, onCancel) {
266
+ return e => {
267
+ if (e.key === 'Enter') {
268
+ e.preventDefault();
269
+ onCommit();
270
+ }
271
+ if (e.key === 'Escape') {
272
+ e.preventDefault();
273
+ onCancel();
274
+ }
275
+ };
276
+ }
277
+
278
+ function DefaultEditor({ col, value, onValueChange, onCommit, onCancel }) {
279
+ const type = col.type || 'string';
280
+ const label = String(col.headerName || col.field);
281
+ const onKeyDown = editKeys(onCommit, onCancel);
282
+ if (type === 'boolean') {
283
+ return h(Check, {
284
+ checked: !!value,
285
+ 'aria-label': label,
286
+ onChange: e => {
287
+ onValueChange(e.target.checked);
288
+ onCommit();
289
+ }
290
+ });
291
+ }
292
+ if (type === 'number') {
293
+ return h(NumberField, {
294
+ value: value == null ? '' : value,
295
+ 'aria-label': label,
296
+ onChange: e => onValueChange(e.target.valueAsNumber),
297
+ onBlur: () => onCommit(),
298
+ onKeyDown
299
+ });
300
+ }
301
+ if (type === 'date') {
302
+ let dateVal = '';
303
+ if (value != null && value !== '') {
304
+ const d = value instanceof Date ? value : new Date(value);
305
+ if (!Number.isNaN(d.getTime())) {
306
+ dateVal = d.toISOString().slice(0, 10);
307
+ } else {
308
+ dateVal = String(value).slice(0, 10);
309
+ }
310
+ }
311
+ return h(Field, {
312
+ type: 'date',
313
+ value: dateVal,
314
+ 'aria-label': label,
315
+ onInput: e => onValueChange(e.target.value),
316
+ onKeyDown,
317
+ onBlur: () => onCommit()
318
+ });
319
+ }
320
+ return h(Field, {
321
+ value: value == null ? '' : value,
322
+ 'aria-label': label,
323
+ onInput: e => onValueChange(e.target.value),
324
+ onKeyDown,
325
+ onBlur: () => onCommit()
326
+ });
327
+ }
328
+
329
+ function isCoarsePointer() {
330
+ try {
331
+ return (
332
+ typeof matchMedia === 'function' &&
333
+ matchMedia('(pointer: coarse)').matches
334
+ );
335
+ } catch (_) {
336
+ return false;
337
+ }
338
+ }
339
+
340
+ export function DataGrid({
341
+ columns,
342
+ rows = [],
343
+ getRowId,
344
+ processRowUpdate,
345
+ onProcessRowUpdateError,
346
+ caption,
347
+ class: className,
348
+ density = 'md',
349
+ striped = false,
350
+ stripedRowScope = 'page',
351
+ headerShade = 'muted',
352
+ stickyHeader = true,
353
+ getRowClassName,
354
+ checkboxSelection = false,
355
+ isRowSelectable,
356
+ disableColumnSorting = false,
357
+ disableQuickFilter = false,
358
+ hideFooter = false,
359
+ loading = false,
360
+ loadingOverlay,
361
+ empty,
362
+ toolbar,
363
+ quickFilterPlaceholder = 'Filter rows',
364
+ pageSizeOptions = [10, 25, 50],
365
+ sortingMode = 'client',
366
+ filterMode = 'client',
367
+ paginationMode = 'client',
368
+ rowCount: rowCountProp,
369
+ sortModel: sortModelProp,
370
+ defaultSortModel = [],
371
+ onSortModelChange,
372
+ quickFilterValue: quickFilterProp,
373
+ defaultQuickFilterValue = '',
374
+ onQuickFilterValueChange,
375
+ filterModel: filterModelProp,
376
+ defaultFilterModel = { items: [] },
377
+ onFilterModelChange,
378
+ paginationModel: paginationProp,
379
+ defaultPaginationModel = { page: 0, pageSize: 10 },
380
+ onPaginationModelChange,
381
+ selectionModel: selectionProp,
382
+ defaultSelectionModel = [],
383
+ onSelectionModelChange,
384
+ columnVisibilityModel: visibilityProp,
385
+ defaultColumnVisibilityModel = {},
386
+ onColumnVisibilityModelChange,
387
+ editCell: editCellProp,
388
+ defaultEditCell = null,
389
+ onEditCellChange,
390
+ editMode = 'cell',
391
+ editOnClick = false,
392
+ enableGridKeyboard = false,
393
+ rowOrderModel: rowOrderProp,
394
+ defaultRowOrderModel,
395
+ onRowOrderChange,
396
+ columnOrderModel: colOrderProp,
397
+ defaultColumnOrderModel,
398
+ onColumnOrderChange,
399
+ columnWidthModel: colWidthProp,
400
+ defaultColumnWidthModel = {},
401
+ onColumnWidthChange,
402
+ disableColumnResize = false,
403
+ rowReorderMode = 'client',
404
+ virtualize = false,
405
+ getRowHeight = 36,
406
+ onRowClick,
407
+ ...rest
408
+ }) {
409
+ if (typeof getRowId !== 'function') {
410
+ throw new Error('DataGrid getRowId is required');
411
+ }
412
+
413
+ const [sortModel, setSortModel] = useControllable(
414
+ sortModelProp,
415
+ defaultSortModel,
416
+ onSortModelChange
417
+ );
418
+ const [quickFilter, setQuickFilter] = useControllable(
419
+ quickFilterProp,
420
+ defaultQuickFilterValue,
421
+ onQuickFilterValueChange
422
+ );
423
+ const [filterModel, setFilterModel] = useControllable(
424
+ filterModelProp,
425
+ defaultFilterModel,
426
+ onFilterModelChange
427
+ );
428
+ const [paginationModel, setPaginationModel] = useControllable(
429
+ paginationProp,
430
+ defaultPaginationModel,
431
+ onPaginationModelChange
432
+ );
433
+ const [selectionModel, setSelectionModel] = useControllable(
434
+ selectionProp,
435
+ defaultSelectionModel,
436
+ onSelectionModelChange
437
+ );
438
+ const [visibilityModel, setVisibilityModel] = useControllable(
439
+ visibilityProp,
440
+ defaultColumnVisibilityModel,
441
+ onColumnVisibilityModelChange
442
+ );
443
+ const [editCell, setEditCell] = useControllable(
444
+ editCellProp,
445
+ defaultEditCell,
446
+ onEditCellChange
447
+ );
448
+ const [rowOrderModel, setRowOrderModel] = useControllable(
449
+ rowOrderProp,
450
+ defaultRowOrderModel,
451
+ onRowOrderChange
452
+ );
453
+ const [columnOrderModel, setColumnOrderModel] = useControllable(
454
+ colOrderProp,
455
+ defaultColumnOrderModel,
456
+ onColumnOrderChange
457
+ );
458
+ const [columnWidthModel, setColumnWidthModel] = useControllable(
459
+ colWidthProp,
460
+ defaultColumnWidthModel,
461
+ onColumnWidthChange
462
+ );
463
+
464
+ const [draft, setDraft] = useState(null);
465
+ const editCellRef = useRef(editCell);
466
+ editCellRef.current = editCell;
467
+ const draftRef = useRef(draft);
468
+ draftRef.current = draft;
469
+ const [focusPos, setFocusPos] = useState({ row: 0, col: 0 });
470
+ const [scrollTop, setScrollTop] = useState(0);
471
+ const [viewportH, setViewportH] = useState(280);
472
+ const scrollerRef = useRef(null);
473
+ const [dragRowId, setDragRowId] = useState(null);
474
+ const [dropRowIndex, setDropRowIndex] = useState(-1);
475
+ const [dragColField, setDragColField] = useState(null);
476
+ const [dropColIndex, setDropColIndex] = useState(-1);
477
+ const [resizeField, setResizeField] = useState(null);
478
+ const dragRowIdRef = useRef(null);
479
+ const dropRowIndexRef = useRef(-1);
480
+ const dragColFieldRef = useRef(null);
481
+ const dropColIndexRef = useRef(-1);
482
+ const displayRowsRef = useRef([]);
483
+ const colsRef = useRef([]);
484
+ const rowOrderRef = useRef(null);
485
+ const colOrderRef = useRef(null);
486
+ const colWidthRef = useRef(columnWidthModel);
487
+ colWidthRef.current = columnWidthModel;
488
+ const resizeRef = useRef(null);
489
+
490
+ const rowReorderEnabled =
491
+ rowOrderModel != null ||
492
+ defaultRowOrderModel != null ||
493
+ !!onRowOrderChange;
494
+ const colReorderEnabled =
495
+ columnOrderModel != null ||
496
+ defaultColumnOrderModel != null ||
497
+ !!onColumnOrderChange;
498
+ const sortDisabled = disableColumnSorting || rowReorderEnabled;
499
+
500
+ function updateDraft(next) {
501
+ draftRef.current = next;
502
+ setDraft(next);
503
+ }
504
+
505
+ let cols = (columns || []).map(normalizeColumn);
506
+ cols = cols.filter(c => visibilityModel[c.field] !== false);
507
+ cols = applyColumnOrder(cols, columnOrderModel);
508
+
509
+ const pageSize = Math.max(1, (paginationModel && paginationModel.pageSize) || 10);
510
+ const modelPage = (paginationModel && paginationModel.page) | 0;
511
+ const rowHeight = typeof getRowHeight === 'number' ? getRowHeight : 36;
512
+
513
+ let pipeline = rows;
514
+ if (filterMode !== 'server') {
515
+ pipeline = applyFilterModel(pipeline, cols, filterModel);
516
+ pipeline = applyQuickFilter(pipeline, cols, quickFilter);
517
+ }
518
+ if (sortingMode !== 'server' && !rowReorderEnabled) {
519
+ pipeline = applySort(pipeline, cols, sortModel);
520
+ }
521
+ if (rowReorderEnabled && rowReorderMode !== 'server') {
522
+ pipeline = applyRowOrder(pipeline, getRowId, rowOrderModel || []);
523
+ }
524
+
525
+ const filteredCount =
526
+ paginationMode === 'server'
527
+ ? rowCountProp != null
528
+ ? rowCountProp
529
+ : rows.length
530
+ : pipeline.length;
531
+ const page = clampPage(modelPage, filteredCount, pageSize);
532
+ const pages = pageCount(filteredCount, pageSize);
533
+
534
+ useLayoutEffect(() => {
535
+ if (page !== modelPage) {
536
+ setPaginationModel({ ...paginationModel, page });
537
+ }
538
+ }, [page, modelPage]);
539
+
540
+ useEffect(() => {
541
+ const el = scrollerRef.current;
542
+ if (!el || !virtualize) return;
543
+ const measure = () => setViewportH(el.clientHeight || 280);
544
+ measure();
545
+ if (typeof ResizeObserver === 'function') {
546
+ const ro = new ResizeObserver(measure);
547
+ ro.observe(el);
548
+ return () => ro.disconnect();
549
+ }
550
+ }, [virtualize]);
551
+
552
+ let visible =
553
+ paginationMode === 'server'
554
+ ? rows
555
+ : applyPagination(pipeline, page, pageSize);
556
+
557
+ const datasetIndex = new Map();
558
+ pipeline.forEach((r, i) => datasetIndex.set(getRowId(r), i));
559
+
560
+ let padTop = 0;
561
+ let padBottom = 0;
562
+ let displayRows = visible;
563
+ if (virtualize && visible.length) {
564
+ const wr = windowRange(scrollTop, viewportH, visible.length, rowHeight);
565
+ displayRows = visible.slice(wr.start, wr.end);
566
+ padTop = wr.offsetTop;
567
+ padBottom = Math.max(
568
+ 0,
569
+ wr.totalHeight - wr.offsetTop - displayRows.length * rowHeight
570
+ );
571
+ }
572
+ displayRowsRef.current = displayRows;
573
+ colsRef.current = cols;
574
+ rowOrderRef.current = rowOrderModel;
575
+ colOrderRef.current = columnOrderModel;
576
+
577
+ function elementAt(clientX, clientY) {
578
+ const el =
579
+ typeof document !== 'undefined' && document.elementFromPoint
580
+ ? document.elementFromPoint(clientX, clientY)
581
+ : null;
582
+ if (!el || typeof el.closest !== 'function') return null;
583
+ return el;
584
+ }
585
+
586
+ function visibleRowIndex(tr) {
587
+ if (!tr || !tr.parentNode || tr.getAttribute('aria-hidden') === 'true') {
588
+ return -1;
589
+ }
590
+ const kids = [...tr.parentNode.children].filter(
591
+ n => n.getAttribute('aria-hidden') !== 'true'
592
+ );
593
+ return kids.indexOf(tr);
594
+ }
595
+
596
+ function dataRowIndexFromPoint(clientX, clientY) {
597
+ const el = elementAt(clientX, clientY);
598
+ if (!el) return -1;
599
+ return visibleRowIndex(el.closest('tbody tr'));
600
+ }
601
+
602
+ function dataColIndexFromPoint(clientX, clientY) {
603
+ const el = elementAt(clientX, clientY);
604
+ if (!el) return -1;
605
+ const th = el.closest('th[data-grid-col]');
606
+ if (!th) return -1;
607
+ const n = Number(th.getAttribute('data-grid-col'));
608
+ return Number.isFinite(n) ? n : -1;
609
+ }
610
+
611
+ function clearRowDrag() {
612
+ dragRowIdRef.current = null;
613
+ dropRowIndexRef.current = -1;
614
+ setDragRowId(null);
615
+ setDropRowIndex(-1);
616
+ }
617
+
618
+ function clearColDrag() {
619
+ dragColFieldRef.current = null;
620
+ dropColIndexRef.current = -1;
621
+ setDragColField(null);
622
+ setDropColIndex(-1);
623
+ }
624
+
625
+ function clearColResize() {
626
+ resizeRef.current = null;
627
+ setResizeField(null);
628
+ }
629
+
630
+ function asPx(raw) {
631
+ if (raw == null || raw === '') return undefined;
632
+ const n = typeof raw === 'number' ? raw : Number(raw);
633
+ return Number.isFinite(n) ? n : undefined;
634
+ }
635
+
636
+ /** Model px, else col.width (number or CSS length string for Table). */
637
+ function columnPixelWidth(col) {
638
+ const fromModel =
639
+ columnWidthModel && columnWidthModel[col.field] != null
640
+ ? asPx(columnWidthModel[col.field])
641
+ : undefined;
642
+ if (fromModel != null) return fromModel;
643
+ const w = col.width;
644
+ if (w == null || w === '') return undefined;
645
+ const px = asPx(w);
646
+ if (px != null) return px;
647
+ return typeof w === 'string' ? w : undefined;
648
+ }
649
+
650
+ function resolveStartWidth(col, field, th) {
651
+ const measured = th && th.offsetWidth > 0 ? th.offsetWidth : 0;
652
+ return clampColumnWidth(
653
+ col,
654
+ measured ||
655
+ asPx(colWidthRef.current && colWidthRef.current[field]) ||
656
+ asPx(col.width) ||
657
+ 120
658
+ );
659
+ }
660
+
661
+ /**
662
+ * Snapshot every data column to px so fixed+max-content layout does not
663
+ * mix auto/% siblings with one resized column.
664
+ */
665
+ function freezeColumnWidths(activeField, activePx) {
666
+ const root = scrollerRef.current;
667
+ const list = colsRef.current || [];
668
+ const next = { ...(colWidthRef.current || {}) };
669
+ for (let i = 0; i < list.length; i++) {
670
+ const c = list[i];
671
+ const f = c.field;
672
+ if (f === activeField) {
673
+ next[f] = clampColumnWidth(c, activePx);
674
+ continue;
675
+ }
676
+ const existing = asPx(next[f]);
677
+ if (existing != null) {
678
+ next[f] = clampColumnWidth(c, existing);
679
+ continue;
680
+ }
681
+ let measured = 0;
682
+ if (root) {
683
+ const th = root.querySelector(
684
+ 'thead th[data-grid-col="' + i + '"]'
685
+ );
686
+ if (th && th.offsetWidth > 0) measured = th.offsetWidth;
687
+ }
688
+ if (measured > 0) {
689
+ next[f] = clampColumnWidth(c, measured);
690
+ continue;
691
+ }
692
+ const fromCol = asPx(c.width);
693
+ if (fromCol != null) next[f] = clampColumnWidth(c, fromCol);
694
+ }
695
+ return next;
696
+ }
697
+
698
+ function beginColumnResize(field, col, clientX, th) {
699
+ const startW = resolveStartWidth(col, field, th);
700
+ const frozen = freezeColumnWidths(field, startW);
701
+ colWidthRef.current = frozen;
702
+ setColumnWidthModel(frozen);
703
+ resizeRef.current = { field, startX: clientX, startW };
704
+ setResizeField(field);
705
+ }
706
+
707
+ function applyColumnWidth(field, col, px) {
708
+ const next = {
709
+ ...(colWidthRef.current || {}),
710
+ [field]: clampColumnWidth(col, px)
711
+ };
712
+ colWidthRef.current = next;
713
+ setColumnWidthModel(next);
714
+ }
715
+
716
+ useLayoutEffect(() => {
717
+ if (!enableGridKeyboard || editCell) return;
718
+ const root = scrollerRef.current;
719
+ if (!root) return;
720
+ const el = root.querySelector(
721
+ '[data-grid-row="' +
722
+ focusPos.row +
723
+ '"][data-grid-col="' +
724
+ focusPos.col +
725
+ '"]'
726
+ );
727
+ if (
728
+ el &&
729
+ typeof el.focus === 'function' &&
730
+ document.activeElement !== el
731
+ ) {
732
+ el.focus();
733
+ }
734
+ }, [focusPos.row, focusPos.col, enableGridKeyboard, editCell]);
735
+
736
+ function cancelEdit() {
737
+ editCellRef.current = null;
738
+ draftRef.current = null;
739
+ setEditCell(null);
740
+ setDraft(null);
741
+ }
742
+
743
+ function resetPage() {
744
+ if (modelPage !== 0) setPaginationModel({ ...paginationModel, page: 0 });
745
+ }
746
+
747
+ async function commitEdit() {
748
+ const cell = editCellRef.current;
749
+ if (!cell) return;
750
+ const row = rows.find(r => getRowId(r) === cell.id);
751
+ if (!row) {
752
+ cancelEdit();
753
+ return;
754
+ }
755
+ if (!processRowUpdate) {
756
+ cancelEdit();
757
+ return;
758
+ }
759
+ let newRow = row;
760
+ if (editMode === 'row' || (cell.field == null && draftRef.current && typeof draftRef.current === 'object')) {
761
+ const draftObj = draftRef.current || {};
762
+ newRow = { ...row };
763
+ for (const col of cols) {
764
+ if (!isColumnEditable(col)) continue;
765
+ if (!(col.field in draftObj)) continue;
766
+ const parsed = parseByType(col.type, draftObj[col.field]);
767
+ newRow = buildUpdatedRow(newRow, col.field, parsed, col);
768
+ }
769
+ } else {
770
+ const col = cols.find(c => c.field === cell.field);
771
+ if (!col) {
772
+ cancelEdit();
773
+ return;
774
+ }
775
+ const parsed = parseByType(col.type, draftRef.current);
776
+ newRow = buildUpdatedRow(row, col.field, parsed, col);
777
+ }
778
+ try {
779
+ await processRowUpdate(newRow, row);
780
+ cancelEdit();
781
+ } catch (err) {
782
+ if (onProcessRowUpdateError) onProcessRowUpdateError(err);
783
+ }
784
+ }
785
+
786
+ function startEdit(row, col) {
787
+ const id = getRowId(row);
788
+ const rIdx = displayRows.findIndex(r => getRowId(r) === id);
789
+ const prev = editCellRef.current;
790
+ if (prev && (prev.id !== id || prev.field !== (col && col.field))) {
791
+ void commitEdit();
792
+ }
793
+ if (editMode === 'row') {
794
+ const draftObj = {};
795
+ for (const c of cols) {
796
+ if (isColumnEditable(c)) draftObj[c.field] = getCellValue(row, c);
797
+ }
798
+ if (rIdx >= 0) setFocusPos({ row: rIdx, col: 0 });
799
+ setEditCell({ id, field: null });
800
+ updateDraft(draftObj);
801
+ return;
802
+ }
803
+ if (!col || !isColumnEditable(col)) return;
804
+ const ci = cols.findIndex(c => c.field === col.field);
805
+ if (rIdx >= 0 && ci >= 0) {
806
+ setFocusPos({ row: rIdx, col: ci });
807
+ }
808
+ setEditCell({ id, field: col.field });
809
+ updateDraft(getCellValue(row, col));
810
+ }
811
+
812
+ function setDraftField(field, value) {
813
+ const cur =
814
+ draftRef.current && typeof draftRef.current === 'object'
815
+ ? { ...draftRef.current }
816
+ : {};
817
+ cur[field] = value;
818
+ updateDraft(cur);
819
+ }
820
+
821
+ const selectableIds = selectableVisibleIds(
822
+ displayRows,
823
+ getRowId,
824
+ isRowSelectable
825
+ );
826
+ const selState = selectionState(selectableIds, selectionModel);
827
+
828
+ function rowClassName(row) {
829
+ const id = getRowId(row);
830
+ const selected =
831
+ selectionModel && selectionModel.indexOf(id) >= 0
832
+ ? 'strike-data-grid__row--selected'
833
+ : null;
834
+ const stripe =
835
+ striped &&
836
+ (striped === true || striped === 'rows' || striped === 'both') &&
837
+ stripedRowScope === 'dataset' &&
838
+ (datasetIndex.get(id) | 0) % 2 === 1
839
+ ? 'strike-data-grid__row--stripe'
840
+ : null;
841
+ const drop =
842
+ dragRowId != null &&
843
+ dropRowIndex >= 0 &&
844
+ displayRows[dropRowIndex] &&
845
+ getRowId(displayRows[dropRowIndex]) === id
846
+ ? 'strike-data-grid__row--drop'
847
+ : null;
848
+ const dragging =
849
+ dragRowId != null && dragRowId === id
850
+ ? 'strike-data-grid__row--dragging'
851
+ : null;
852
+ const extra = getRowClassName ? getRowClassName(row) : null;
853
+ return cls(selected, stripe, drop, dragging, extra);
854
+ }
855
+
856
+ const tableColumns = [];
857
+
858
+ if (rowReorderEnabled) {
859
+ tableColumns.push({
860
+ key: '__grip',
861
+ headerClass: 'strike-data-grid__cell--grip',
862
+ class: 'strike-data-grid__cell--grip',
863
+ label: '',
864
+ render: row => {
865
+ const id = getRowId(row);
866
+ const order = rowOrderModel || pipeline.map(getRowId);
867
+ const idx = order.indexOf(id);
868
+ const displayIdx = displayRows.findIndex(r => getRowId(r) === id);
869
+ return h(
870
+ 'button',
871
+ {
872
+ type: 'button',
873
+ class: 'strike-data-grid__grip',
874
+ 'aria-label': 'Reorder row',
875
+ title: 'Drag to reorder',
876
+ disabled: loading || !!editCell,
877
+ onPointerDown: e => {
878
+ if (loading || editCell) return;
879
+ e.preventDefault();
880
+ e.currentTarget.setPointerCapture(e.pointerId);
881
+ dragRowIdRef.current = id;
882
+ dropRowIndexRef.current = displayIdx;
883
+ setDragRowId(id);
884
+ setDropRowIndex(displayIdx);
885
+ },
886
+ onPointerMove: e => {
887
+ if (dragRowIdRef.current == null) return;
888
+ const next = dataRowIndexFromPoint(e.clientX, e.clientY);
889
+ if (next < 0 || next === dropRowIndexRef.current) return;
890
+ dropRowIndexRef.current = next;
891
+ setDropRowIndex(next);
892
+ },
893
+ onPointerUp: e => {
894
+ const dragId = dragRowIdRef.current;
895
+ if (dragId == null) return;
896
+ try {
897
+ e.currentTarget.releasePointerCapture(e.pointerId);
898
+ } catch (_) {}
899
+ const dropIdx = dropRowIndexRef.current;
900
+ const rowsNow = displayRowsRef.current;
901
+ const orderNow =
902
+ rowOrderRef.current || rowsNow.map(getRowId);
903
+ if (
904
+ rowReorderMode !== 'server' &&
905
+ dropIdx >= 0 &&
906
+ rowsNow[dropIdx]
907
+ ) {
908
+ const targetId = getRowId(rowsNow[dropIdx]);
909
+ const to = orderNow.indexOf(targetId);
910
+ setRowOrderModel(moveItem(orderNow.slice(), dragId, to));
911
+ } else if (rowReorderMode === 'server' && onRowOrderChange) {
912
+ onRowOrderChange(
913
+ moveItem(
914
+ orderNow.slice(),
915
+ dragId,
916
+ dropIdx >= 0 ? dropIdx : idx
917
+ )
918
+ );
919
+ }
920
+ clearRowDrag();
921
+ },
922
+ onPointerCancel: () => clearRowDrag(),
923
+ onKeyDown: e => {
924
+ if (e.key === 'Escape') {
925
+ clearRowDrag();
926
+ return;
927
+ }
928
+ if (loading || editCell) return;
929
+ if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return;
930
+ e.preventDefault();
931
+ const delta = e.key === 'ArrowUp' ? -1 : 1;
932
+ const next = idx + delta;
933
+ if (next < 0 || next >= order.length) return;
934
+ setRowOrderModel(moveItem(order.slice(), id, next));
935
+ }
936
+ },
937
+ h(GripIcon)
938
+ );
939
+ }
940
+ });
941
+ }
942
+
943
+ if (checkboxSelection) {
944
+ tableColumns.push({
945
+ key: '__select',
946
+ headerClass: 'strike-data-grid__cell--select',
947
+ class: 'strike-data-grid__cell--select',
948
+ label: h(Check, {
949
+ 'aria-label': 'Select all rows',
950
+ checked: selState === 'all',
951
+ indeterminate: selState === 'some',
952
+ onChange: e => {
953
+ setSelectionModel(
954
+ setVisibleSelection(
955
+ selectionModel,
956
+ selectableIds,
957
+ e.target.checked
958
+ )
959
+ );
960
+ }
961
+ }),
962
+ render: row => {
963
+ const id = getRowId(row);
964
+ const ok = !isRowSelectable || isRowSelectable(row);
965
+ return h(Check, {
966
+ 'aria-label': 'Select row',
967
+ checked: selectionModel.indexOf(id) >= 0,
968
+ disabled: !ok,
969
+ onClick: e => e.stopPropagation(),
970
+ onChange: () => {
971
+ if (!ok) return;
972
+ setSelectionModel(toggleId(selectionModel, id));
973
+ }
974
+ });
975
+ }
976
+ });
977
+ }
978
+
979
+ for (let ci = 0; ci < cols.length; ci++) {
980
+ const col = cols[ci];
981
+ const field = col.field;
982
+ const sortable = !sortDisabled && col.sortable;
983
+ const align = col.align || defaultAlign(col.type);
984
+ const sortItem = sortModel && sortModel.find(s => s.field === field);
985
+ const editingId =
986
+ editCell &&
987
+ (editMode === 'row' ? editCell.id : editCell.field === field ? editCell.id : null);
988
+
989
+ tableColumns.push({
990
+ key: field,
991
+ headerClass: cls(
992
+ col.headerClassName,
993
+ dragColField === field && 'strike-data-grid__th--dragging',
994
+ dropColIndex === ci &&
995
+ dragColField &&
996
+ dragColField !== field &&
997
+ 'strike-data-grid__th--drop'
998
+ ),
999
+ headerProps: {
1000
+ 'aria-sort': ariaSortValue(sortItem),
1001
+ 'data-grid-col': String(ci)
1002
+ },
1003
+ class: row => {
1004
+ const id = getRowId(row);
1005
+ const parts = [
1006
+ typeof col.cellClassName === 'function'
1007
+ ? col.cellClassName(row)
1008
+ : col.cellClassName,
1009
+ isColumnEditable(col) && 'strike-data-grid__cell--editable'
1010
+ ];
1011
+ const rIdx = displayRows.findIndex(r => getRowId(r) === id);
1012
+ if (
1013
+ enableGridKeyboard &&
1014
+ focusPos.row === rIdx &&
1015
+ focusPos.col === ci
1016
+ ) {
1017
+ parts.push('strike-data-grid__cell--focus');
1018
+ }
1019
+ return cls(...parts);
1020
+ },
1021
+ width: columnPixelWidth(col),
1022
+ align,
1023
+ label: h(
1024
+ 'span',
1025
+ { class: 'strike-data-grid__header' },
1026
+ colReorderEnabled
1027
+ ? h(
1028
+ 'button',
1029
+ {
1030
+ type: 'button',
1031
+ class: 'strike-data-grid__grip',
1032
+ 'aria-label':
1033
+ 'Reorder column ' + String(col.headerName || field),
1034
+ title: 'Drag to reorder',
1035
+ disabled: loading || !!editCell,
1036
+ onPointerDown: e => {
1037
+ if (loading || editCell) return;
1038
+ e.preventDefault();
1039
+ e.stopPropagation();
1040
+ e.currentTarget.setPointerCapture(e.pointerId);
1041
+ dragColFieldRef.current = field;
1042
+ dropColIndexRef.current = ci;
1043
+ setDragColField(field);
1044
+ setDropColIndex(ci);
1045
+ },
1046
+ onPointerMove: e => {
1047
+ if (dragColFieldRef.current == null) return;
1048
+ const next = dataColIndexFromPoint(
1049
+ e.clientX,
1050
+ e.clientY
1051
+ );
1052
+ if (next < 0 || next === dropColIndexRef.current) {
1053
+ return;
1054
+ }
1055
+ dropColIndexRef.current = next;
1056
+ setDropColIndex(next);
1057
+ },
1058
+ onPointerUp: e => {
1059
+ const dragField = dragColFieldRef.current;
1060
+ if (dragField == null) return;
1061
+ try {
1062
+ e.currentTarget.releasePointerCapture(e.pointerId);
1063
+ } catch (_) {}
1064
+ const order =
1065
+ colOrderRef.current ||
1066
+ colsRef.current.map(c => c.field);
1067
+ const to =
1068
+ dropColIndexRef.current >= 0
1069
+ ? dropColIndexRef.current
1070
+ : order.indexOf(dragField);
1071
+ setColumnOrderModel(
1072
+ moveItem(order.slice(), dragField, to)
1073
+ );
1074
+ clearColDrag();
1075
+ },
1076
+ onPointerCancel: () => clearColDrag(),
1077
+ onKeyDown: e => {
1078
+ if (e.key === 'Escape') {
1079
+ clearColDrag();
1080
+ return;
1081
+ }
1082
+ if (loading || editCell) return;
1083
+ if (
1084
+ e.key !== 'ArrowLeft' &&
1085
+ e.key !== 'ArrowRight'
1086
+ ) {
1087
+ return;
1088
+ }
1089
+ e.preventDefault();
1090
+ const order =
1091
+ columnOrderModel || cols.map(c => c.field);
1092
+ const i = order.indexOf(field);
1093
+ const next = i + (e.key === 'ArrowLeft' ? -1 : 1);
1094
+ if (i < 0 || next < 0 || next >= order.length) {
1095
+ return;
1096
+ }
1097
+ setColumnOrderModel(
1098
+ moveItem(order.slice(), field, next)
1099
+ );
1100
+ }
1101
+ },
1102
+ h(GripIcon)
1103
+ )
1104
+ : null,
1105
+ sortable
1106
+ ? h(
1107
+ 'button',
1108
+ {
1109
+ type: 'button',
1110
+ class: 'strike-data-grid__sort',
1111
+ 'aria-label': 'Sort by ' + String(col.headerName || field),
1112
+ onClick: e => {
1113
+ cancelEdit();
1114
+ setSortModel(
1115
+ cycleSortModel(sortModel, field, {
1116
+ append: !!e.shiftKey
1117
+ })
1118
+ );
1119
+ resetPage();
1120
+ }
1121
+ },
1122
+ col.headerName != null ? col.headerName : field,
1123
+ sortCue(sortItem)
1124
+ )
1125
+ : col.headerName != null
1126
+ ? col.headerName
1127
+ : field,
1128
+ !disableColumnResize && col.resizable !== false
1129
+ ? h('button', {
1130
+ type: 'button',
1131
+ class: cls(
1132
+ 'strike-data-grid__col-resizer',
1133
+ resizeField === field &&
1134
+ 'strike-data-grid__col-resizer--active'
1135
+ ),
1136
+ 'aria-label':
1137
+ 'Resize column ' + String(col.headerName || field),
1138
+ 'aria-orientation': 'vertical',
1139
+ disabled: loading,
1140
+ onPointerDown: e => {
1141
+ if (loading) return;
1142
+ e.preventDefault();
1143
+ e.stopPropagation();
1144
+ clearColDrag();
1145
+ beginColumnResize(
1146
+ field,
1147
+ col,
1148
+ e.clientX,
1149
+ e.currentTarget.closest('th')
1150
+ );
1151
+ e.currentTarget.setPointerCapture(e.pointerId);
1152
+ },
1153
+ onPointerMove: e => {
1154
+ const st = resizeRef.current;
1155
+ if (!st || st.field !== field) return;
1156
+ applyColumnWidth(
1157
+ field,
1158
+ col,
1159
+ st.startW + (e.clientX - st.startX)
1160
+ );
1161
+ },
1162
+ onPointerUp: e => {
1163
+ if (resizeRef.current == null) return;
1164
+ try {
1165
+ e.currentTarget.releasePointerCapture(e.pointerId);
1166
+ } catch (_) {}
1167
+ clearColResize();
1168
+ },
1169
+ onPointerCancel: () => clearColResize(),
1170
+ onKeyDown: e => {
1171
+ if (e.key === 'Escape') {
1172
+ clearColResize();
1173
+ return;
1174
+ }
1175
+ if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') {
1176
+ return;
1177
+ }
1178
+ e.preventDefault();
1179
+ const th = e.currentTarget.closest('th');
1180
+ const cur = resolveStartWidth(col, field, th);
1181
+ const frozen = freezeColumnWidths(field, cur);
1182
+ const delta = e.key === 'ArrowLeft' ? -8 : 8;
1183
+ const next = {
1184
+ ...frozen,
1185
+ [field]: clampColumnWidth(col, cur + delta)
1186
+ };
1187
+ colWidthRef.current = next;
1188
+ setColumnWidthModel(next);
1189
+ }
1190
+ })
1191
+ : null
1192
+ ),
1193
+ render: row => {
1194
+ const id = getRowId(row);
1195
+ const rowEditing = editingId === id;
1196
+ const rIdx = displayRows.findIndex(r => getRowId(r) === id);
1197
+ const isFocused =
1198
+ enableGridKeyboard &&
1199
+ focusPos.row === rIdx &&
1200
+ focusPos.col === ci;
1201
+ if (
1202
+ rowEditing &&
1203
+ (editMode !== 'row' || isColumnEditable(col))
1204
+ ) {
1205
+ const rowMode = editMode === 'row';
1206
+ const params = {
1207
+ row,
1208
+ value: rowMode
1209
+ ? (draftRef.current || {})[field]
1210
+ : draft,
1211
+ field,
1212
+ onValueChange: rowMode
1213
+ ? v => setDraftField(field, v)
1214
+ : updateDraft,
1215
+ onCommit: commitEdit,
1216
+ onCancel: cancelEdit
1217
+ };
1218
+ return h(
1219
+ FocusEditor,
1220
+ null,
1221
+ col.renderEditCell
1222
+ ? col.renderEditCell(params)
1223
+ : h(DefaultEditor, { col, ...params })
1224
+ );
1225
+ }
1226
+ const content = col.renderCell
1227
+ ? col.renderCell(row)
1228
+ : formatCell(row, col);
1229
+ if (!isColumnEditable(col)) return content;
1230
+ const allowClick = editOnClick || isCoarsePointer();
1231
+ return h(
1232
+ 'div',
1233
+ {
1234
+ tabIndex: enableGridKeyboard ? (isFocused ? 0 : -1) : 0,
1235
+ role: enableGridKeyboard ? 'gridcell' : 'button',
1236
+ 'data-grid-cell': '1',
1237
+ 'data-grid-row': String(rIdx),
1238
+ 'data-grid-col': String(ci),
1239
+ 'aria-label': 'Edit ' + String(col.headerName || field),
1240
+ onFocus: () => {
1241
+ if (!enableGridKeyboard || rIdx < 0) return;
1242
+ if (focusPos.row === rIdx && focusPos.col === ci) return;
1243
+ setFocusPos({ row: rIdx, col: ci });
1244
+ },
1245
+ onDblClick: e => {
1246
+ e.stopPropagation();
1247
+ startEdit(row, col);
1248
+ },
1249
+ onClick: allowClick
1250
+ ? e => {
1251
+ e.stopPropagation();
1252
+ startEdit(row, col);
1253
+ }
1254
+ : undefined,
1255
+ onKeyDown: e => {
1256
+ if (editCell) return;
1257
+ if (e.key === 'Enter') {
1258
+ e.preventDefault();
1259
+ startEdit(row, col);
1260
+ return;
1261
+ }
1262
+ if (!enableGridKeyboard || loading) return;
1263
+ const keys = [
1264
+ 'ArrowUp',
1265
+ 'ArrowDown',
1266
+ 'ArrowLeft',
1267
+ 'ArrowRight',
1268
+ 'Home',
1269
+ 'End'
1270
+ ];
1271
+ if (!keys.includes(e.key)) return;
1272
+ e.preventDefault();
1273
+ const next = moveFocus(
1274
+ { row: rIdx, col: ci },
1275
+ e.key,
1276
+ displayRows.length,
1277
+ cols.length
1278
+ );
1279
+ setFocusPos(next);
1280
+ }
1281
+ },
1282
+ content
1283
+ );
1284
+ }
1285
+ });
1286
+ }
1287
+
1288
+ const usePageStripe =
1289
+ (striped === true || striped === 'rows' || striped === 'both') &&
1290
+ stripedRowScope !== 'dataset';
1291
+ const useDatasetStripe =
1292
+ (striped === true || striped === 'rows' || striped === 'both') &&
1293
+ stripedRowScope === 'dataset';
1294
+ const stripeMod = cls(
1295
+ usePageStripe && 'strike-data-grid--striped-rows',
1296
+ useDatasetStripe && 'strike-data-grid--striped-dataset',
1297
+ (striped === 'columns' || striped === 'both') &&
1298
+ 'strike-data-grid--striped-columns'
1299
+ );
1300
+ const shadeOn = headerShade !== false && headerShade !== 'none';
1301
+
1302
+ const pageSizeOpts = pageSizeOptions.map(n => ({
1303
+ value: String(n),
1304
+ label: String(n)
1305
+ }));
1306
+
1307
+ return h(
1308
+ 'section',
1309
+ {
1310
+ ...rest,
1311
+ class: cls(
1312
+ 'strike-data-grid',
1313
+ shadeOn && 'strike-data-grid--header-shade',
1314
+ stripeMod,
1315
+ (dragRowId != null || dragColField != null) && 'strike-data-grid--dnd',
1316
+ resizeField != null && 'strike-data-grid--resizing',
1317
+ className
1318
+ )
1319
+ },
1320
+ h(
1321
+ 'div',
1322
+ { class: 'strike-data-grid__toolbar' },
1323
+ !disableQuickFilter &&
1324
+ h(Field, {
1325
+ 'aria-label': 'Filter rows',
1326
+ placeholder: quickFilterPlaceholder,
1327
+ value: quickFilter,
1328
+ onInput: e => {
1329
+ cancelEdit();
1330
+ setQuickFilter(e.target.value);
1331
+ resetPage();
1332
+ }
1333
+ }),
1334
+ editMode === 'row' &&
1335
+ editCell &&
1336
+ h(Btn, {
1337
+ type: 'button',
1338
+ size: 'sm',
1339
+ onClick: () => void commitEdit(),
1340
+ children: 'Save row'
1341
+ }),
1342
+ toolbar
1343
+ ),
1344
+ h(
1345
+ 'div',
1346
+ {
1347
+ class: 'strike-data-grid__scroller',
1348
+ ref: scrollerRef,
1349
+ 'aria-busy': loading ? 'true' : undefined,
1350
+ onScroll: virtualize
1351
+ ? e => setScrollTop(e.currentTarget.scrollTop)
1352
+ : undefined
1353
+ },
1354
+ h(Table, {
1355
+ role: enableGridKeyboard ? 'grid' : undefined,
1356
+ columns: tableColumns,
1357
+ rows: displayRows,
1358
+ getRowId,
1359
+ getRowClassName: rowClassName,
1360
+ caption,
1361
+ stickyHeader,
1362
+ size: density === 'sm' ? 'sm' : undefined,
1363
+ empty,
1364
+ padTop,
1365
+ padBottom,
1366
+ onClick: onRowClick
1367
+ ? e => {
1368
+ const tr = e.target.closest('tr');
1369
+ if (
1370
+ !tr ||
1371
+ !tr.parentNode ||
1372
+ tr.parentNode.tagName !== 'TBODY'
1373
+ ) {
1374
+ return;
1375
+ }
1376
+ if (
1377
+ e.target.closest(
1378
+ '.strike-check, .strike-data-grid__editor, button, input, select, textarea'
1379
+ )
1380
+ ) {
1381
+ return;
1382
+ }
1383
+ const idx = visibleRowIndex(tr);
1384
+ if (idx >= 0 && displayRows[idx]) {
1385
+ onRowClick(displayRows[idx], e);
1386
+ }
1387
+ }
1388
+ : undefined
1389
+ }),
1390
+ loading &&
1391
+ h(
1392
+ 'div',
1393
+ { class: 'strike-data-grid__loading' },
1394
+ loadingOverlay || 'Loading'
1395
+ )
1396
+ ),
1397
+ !hideFooter &&
1398
+ h(
1399
+ 'div',
1400
+ { class: 'strike-data-grid__footer' },
1401
+ h(Select, {
1402
+ 'aria-label': 'Rows per page',
1403
+ options: pageSizeOpts,
1404
+ value: String(pageSize),
1405
+ onChange: e => {
1406
+ cancelEdit();
1407
+ setPaginationModel({
1408
+ page: 0,
1409
+ pageSize: Number(e.target.value) || pageSize
1410
+ });
1411
+ }
1412
+ }),
1413
+ h(Pagination, {
1414
+ page: page + 1,
1415
+ count: pages,
1416
+ onChange: p => {
1417
+ cancelEdit();
1418
+ setPaginationModel({
1419
+ ...paginationModel,
1420
+ page: p - 1
1421
+ });
1422
+ }
1423
+ })
1424
+ )
1425
+ );
1426
+ }