@rozie-ui/data-table-solid 0.5.0 → 0.6.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 +18 -10
- package/dist/index.cjs +3153 -1095
- package/dist/index.d.cts +64 -1
- package/dist/index.d.mts +64 -1
- package/dist/index.mjs +3077 -1019
- package/dist/source/index.jsx +6368 -0
- package/package.json +8 -3
- package/src/Column.tsx +1 -1
- package/src/DataTable.tsx +1199 -134
- package/src/EditorCheckbox.tsx +38 -4
- package/src/EditorDate.tsx +79 -8
- package/src/EditorNumber.tsx +52 -6
- package/src/EditorSelect.tsx +51 -6
- package/src/EditorText.tsx +34 -5
- package/src/FilterNumberRange.tsx +79 -12
- package/src/FilterSelect.tsx +18 -3
- package/src/FilterText.tsx +48 -9
- package/src/GroupBar.tsx +75 -3
- package/src/helpers/columnDefUtils.ts +73 -1
- package/src/helpers/dateValue.ts +55 -0
- package/src/themes/base.css +96 -75
- package/src/themes/bootstrap.css +59 -34
- package/src/themes/material.css +53 -32
- package/src/themes/shadcn.css +58 -33
package/src/DataTable.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import { Show, createEffect, createSignal, mergeProps, on, onCleanup, onMount, s
|
|
|
3
3
|
import { Key } from '@solid-primitives/keyed';
|
|
4
4
|
import { __rozieInjectStyle, createControllableSignal, parseInlineStyle, rozieAttr, rozieClass, rozieContext, rozieDisplay } from '@rozie/runtime-solid';
|
|
5
5
|
import Popover from '@rozie-ui/popover-solid';
|
|
6
|
-
import { isSafeKey, wrapAggregationFn, indexDefsById, collectGroupableLeafDefs } from './helpers/columnDefUtils';
|
|
6
|
+
import { isSafeKey, wrapAggregationFn, indexDefsById, editorKindWarning, collectGroupableLeafDefs } from './helpers/columnDefUtils';
|
|
7
7
|
import { applyUpdater, clamp, focusables } from './helpers/indexMath';
|
|
8
8
|
import { escapeTsvField, parseTsv, tileGridToBox, tileIndex } from './helpers/tsvGrid';
|
|
9
9
|
import { replaceRowValue, indexOfRowIn, replaceRowValues } from './helpers/rowValueUtils';
|
|
@@ -35,33 +35,40 @@ import { Virtualizer, elementScroll, observeElementRect, observeElementOffset, m
|
|
|
35
35
|
// snapshot (the rete stale-closure anti-pattern — a top-level $computed/useCallback
|
|
36
36
|
// freezes the table at the empty-initial state on React).
|
|
37
37
|
|
|
38
|
+
// C-09's value-equality guard. Imported HERE, in DataTable.rozie's OWN script, because that
|
|
39
|
+
// is where it is USED: a `.rzts` partial's import of a symbol the partial itself never
|
|
40
|
+
// references is dropped as unused, and the host script then reaches a free identifier. That
|
|
41
|
+
// failed at RUNTIME, not at compile time — `ReferenceError: columnSpecsEquivalent is not
|
|
42
|
+
// defined` on the first paste — which is the only way this class of mistake shows up.
|
|
43
|
+
import { columnSpecsEquivalent } from './helpers/columnDefUtils';
|
|
44
|
+
|
|
45
|
+
// The registry API handed to <Column> children (whole-object-replace — T-48-PP guard).
|
|
46
|
+
|
|
38
47
|
__rozieInjectStyle('DataTable-d5dcab4c', `[data-rozie-s-d5dcab4c]:host {
|
|
39
|
-
--rdt-
|
|
40
|
-
--rdt-
|
|
41
|
-
--rdt-
|
|
42
|
-
--rdt-border: 1px solid var(--rozie-data-table-border-color);
|
|
43
|
-
--rdt-
|
|
44
|
-
--rdt-
|
|
45
|
-
--rdt-
|
|
46
|
-
--rdt-
|
|
47
|
-
--rdt-
|
|
48
|
-
--rdt-
|
|
49
|
-
--rdt-
|
|
50
|
-
--rdt-
|
|
51
|
-
--rdt-
|
|
52
|
-
--rdt-
|
|
53
|
-
--rdt-
|
|
54
|
-
--rdt-
|
|
55
|
-
--rdt-
|
|
56
|
-
--rdt-
|
|
57
|
-
--rdt-
|
|
58
|
-
--rdt-
|
|
59
|
-
--rdt-
|
|
60
|
-
--rdt-
|
|
61
|
-
--rdt-
|
|
62
|
-
--rdt-
|
|
63
|
-
--rdt-colvis-menu-shadow: var(--rozie-data-table-menu-shadow);
|
|
64
|
-
--rdt-select-accent: var(--rozie-data-table-select-accent);
|
|
48
|
+
--rdt-color: var(--rozie-data-table-fg, var(--rdt-ds-fg));
|
|
49
|
+
--rdt-select-accent: var(--rozie-data-table-select-accent, var(--rozie-data-table-accent, var(--rdt-ds-select-accent)));
|
|
50
|
+
--rdt-border: 1px solid var(--rozie-data-table-border-color, var(--rdt-ds-border-color));
|
|
51
|
+
--rdt-filter-border: 1px solid var(--rozie-data-table-control-border-color, var(--rdt-ds-control-border-color));
|
|
52
|
+
--rdt-filter-radius: var(--rozie-data-table-radius, var(--rdt-ds-radius));
|
|
53
|
+
--rdt-font: var(--rozie-data-table-font, var(--rdt-ds-font));
|
|
54
|
+
--rdt-cell-padding: var(--rozie-data-table-cell-padding, var(--rdt-ds-cell-padding));
|
|
55
|
+
--rdt-header-bg: var(--rozie-data-table-header-bg, var(--rdt-ds-header-bg));
|
|
56
|
+
--rdt-header-weight: var(--rozie-data-table-header-weight, var(--rdt-ds-header-weight));
|
|
57
|
+
--rdt-sort-ind-opacity: var(--rozie-data-table-sort-indicator-opacity, var(--rdt-ds-sort-indicator-opacity));
|
|
58
|
+
--rdt-resize-grip-color: var(--rozie-data-table-resize-grip-color, var(--rdt-ds-resize-grip-color));
|
|
59
|
+
--rdt-resize-grip-active: var(--rozie-data-table-resize-grip-active, var(--rozie-data-table-accent, var(--rdt-ds-resize-grip-active)));
|
|
60
|
+
--rdt-pin-btn-active-bg: var(--rozie-data-table-pin-active-bg, var(--rdt-ds-pin-active-bg));
|
|
61
|
+
--rdt-colvis-menu-bg: var(--rozie-data-table-menu-bg, var(--rdt-ds-menu-bg));
|
|
62
|
+
--rdt-colvis-menu-shadow: var(--rozie-data-table-menu-shadow, var(--rdt-ds-menu-shadow));
|
|
63
|
+
--rdt-colvis-menu-border: 1px solid var(--rozie-data-table-border-color, var(--rdt-ds-border-color));
|
|
64
|
+
--rdt-colvis-menu-radius: var(--rozie-data-table-radius, var(--rdt-ds-radius));
|
|
65
|
+
--rdt-colvis-summary-border: 1px solid var(--rozie-data-table-control-border-color, var(--rdt-ds-control-border-color));
|
|
66
|
+
--rdt-colvis-summary-radius: var(--rozie-data-table-radius, var(--rdt-ds-radius));
|
|
67
|
+
--rdt-page-btn-disabled-opacity: var(--rozie-data-table-disabled-opacity, var(--rdt-ds-disabled-opacity));
|
|
68
|
+
--rdt-page-btn-border: 1px solid var(--rozie-data-table-control-border-color, var(--rdt-ds-control-border-color));
|
|
69
|
+
--rdt-page-btn-radius: var(--rozie-data-table-radius, var(--rdt-ds-radius));
|
|
70
|
+
--rdt-page-size-border: 1px solid var(--rozie-data-table-control-border-color, var(--rdt-ds-control-border-color));
|
|
71
|
+
--rdt-page-size-radius: var(--rozie-data-table-radius, var(--rdt-ds-radius));
|
|
65
72
|
}
|
|
66
73
|
.rozie-data-table[data-rozie-s-d5dcab4c] {
|
|
67
74
|
border-collapse: collapse;
|
|
@@ -189,6 +196,16 @@ __rozieInjectStyle('DataTable-d5dcab4c', `[data-rozie-s-d5dcab4c]:host {
|
|
|
189
196
|
flex-direction: column;
|
|
190
197
|
gap: var(--rdt-chrome-gap, 0.5rem);
|
|
191
198
|
}
|
|
199
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] .rdt-skeleton[data-rozie-s-d5dcab4c] {
|
|
200
|
+
/* inline-block on the cell's own line box, exactly like a text value (a block box was 2px
|
|
201
|
+
short of a text row). */
|
|
202
|
+
display: inline-block;
|
|
203
|
+
width: 100%;
|
|
204
|
+
vertical-align: baseline;
|
|
205
|
+
border-radius: 4px;
|
|
206
|
+
background: color-mix(in srgb, currentColor 12%, transparent);
|
|
207
|
+
color: transparent;
|
|
208
|
+
}
|
|
192
209
|
.rozie-data-table-wrap[data-rozie-s-d5dcab4c] .rdt-toolbar[data-rozie-s-d5dcab4c] {
|
|
193
210
|
display: flex;
|
|
194
211
|
gap: var(--rdt-toolbar-gap, 0.5rem);
|
|
@@ -214,6 +231,9 @@ __rozieInjectStyle('DataTable-d5dcab4c', `[data-rozie-s-d5dcab4c]:host {
|
|
|
214
231
|
.rozie-data-table[data-rozie-s-d5dcab4c] .rdt-filter-row[data-rozie-s-d5dcab4c] {
|
|
215
232
|
background: var(--rdt-filter-row-bg, rgba(0, 0, 0, 0.015));
|
|
216
233
|
}
|
|
234
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] .rdt-pinned[data-rozie-s-d5dcab4c] {
|
|
235
|
+
box-sizing: border-box;
|
|
236
|
+
}
|
|
217
237
|
.rozie-data-table[data-rozie-s-d5dcab4c] .rdt-filter-cell[data-rozie-s-d5dcab4c] {
|
|
218
238
|
padding: var(--rdt-filter-cell-padding, 0.35rem 0.75rem);
|
|
219
239
|
border-bottom: var(--rdt-border, 1px solid rgba(0, 0, 0, 0.08));
|
|
@@ -333,6 +353,9 @@ __rozieInjectStyle('DataTable-d5dcab4c', `[data-rozie-s-d5dcab4c]:host {
|
|
|
333
353
|
.rozie-data-table-wrap[data-rozie-s-d5dcab4c] .rdt-colvis-summary[data-rozie-s-d5dcab4c] {
|
|
334
354
|
cursor: pointer;
|
|
335
355
|
font: inherit;
|
|
356
|
+
/* Own the margin too: content-site typography styles bare \`summary\` (VitePress sets
|
|
357
|
+
\`margin: 16px 0\`), which grew the <details> and stretched the toolbar's search input. */
|
|
358
|
+
margin: 0;
|
|
336
359
|
padding: var(--rdt-colvis-summary-padding, 0.25rem 0.6rem);
|
|
337
360
|
border: var(--rdt-colvis-summary-border, 1px solid rgba(0, 0, 0, 0.2));
|
|
338
361
|
border-radius: var(--rdt-colvis-summary-radius, 4px);
|
|
@@ -371,6 +394,51 @@ __rozieInjectStyle('DataTable-d5dcab4c', `[data-rozie-s-d5dcab4c]:host {
|
|
|
371
394
|
text-align: var(--rdt-expander-col-align, center);
|
|
372
395
|
white-space: nowrap;
|
|
373
396
|
}
|
|
397
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] tbody[data-rozie-s-d5dcab4c] tr[data-depth="1"][data-rozie-s-d5dcab4c] > .rdt-expander-td[data-rozie-s-d5dcab4c] + .rdt-td[data-rozie-s-d5dcab4c]::before,
|
|
398
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] tbody[data-rozie-s-d5dcab4c] tr[data-depth="2"][data-rozie-s-d5dcab4c] > .rdt-expander-td[data-rozie-s-d5dcab4c] + .rdt-td[data-rozie-s-d5dcab4c]::before,
|
|
399
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] tbody[data-rozie-s-d5dcab4c] tr[data-depth="3"][data-rozie-s-d5dcab4c] > .rdt-expander-td[data-rozie-s-d5dcab4c] + .rdt-td[data-rozie-s-d5dcab4c]::before,
|
|
400
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] tbody[data-rozie-s-d5dcab4c] tr[data-depth="4"][data-rozie-s-d5dcab4c] > .rdt-expander-td[data-rozie-s-d5dcab4c] + .rdt-td[data-rozie-s-d5dcab4c]::before,
|
|
401
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] tbody[data-rozie-s-d5dcab4c] tr[data-depth="5"][data-rozie-s-d5dcab4c] > .rdt-expander-td[data-rozie-s-d5dcab4c] + .rdt-td[data-rozie-s-d5dcab4c]::before,
|
|
402
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] tbody[data-rozie-s-d5dcab4c] tr[data-depth="6"][data-rozie-s-d5dcab4c] > .rdt-expander-td[data-rozie-s-d5dcab4c] + .rdt-td[data-rozie-s-d5dcab4c]::before,
|
|
403
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] tbody[data-rozie-s-d5dcab4c] tr[data-depth="7"][data-rozie-s-d5dcab4c] > .rdt-expander-td[data-rozie-s-d5dcab4c] + .rdt-td[data-rozie-s-d5dcab4c]::before,
|
|
404
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] tbody[data-rozie-s-d5dcab4c] tr[data-depth="8"][data-rozie-s-d5dcab4c] > .rdt-expander-td[data-rozie-s-d5dcab4c] + .rdt-td[data-rozie-s-d5dcab4c]::before {
|
|
405
|
+
content: '';
|
|
406
|
+
display: inline-block;
|
|
407
|
+
vertical-align: middle;
|
|
408
|
+
}
|
|
409
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] tbody[data-rozie-s-d5dcab4c] tr[data-depth="1"][data-rozie-s-d5dcab4c] > .rdt-expander-td[data-rozie-s-d5dcab4c] + .rdt-td[data-rozie-s-d5dcab4c]::before {
|
|
410
|
+
width: calc(1 * var(--rdt-tree-indent, 1.25rem));
|
|
411
|
+
}
|
|
412
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] tbody[data-rozie-s-d5dcab4c] tr[data-depth="2"][data-rozie-s-d5dcab4c] > .rdt-expander-td[data-rozie-s-d5dcab4c] + .rdt-td[data-rozie-s-d5dcab4c]::before {
|
|
413
|
+
width: calc(2 * var(--rdt-tree-indent, 1.25rem));
|
|
414
|
+
}
|
|
415
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] tbody[data-rozie-s-d5dcab4c] tr[data-depth="3"][data-rozie-s-d5dcab4c] > .rdt-expander-td[data-rozie-s-d5dcab4c] + .rdt-td[data-rozie-s-d5dcab4c]::before {
|
|
416
|
+
width: calc(3 * var(--rdt-tree-indent, 1.25rem));
|
|
417
|
+
}
|
|
418
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] tbody[data-rozie-s-d5dcab4c] tr[data-depth="4"][data-rozie-s-d5dcab4c] > .rdt-expander-td[data-rozie-s-d5dcab4c] + .rdt-td[data-rozie-s-d5dcab4c]::before {
|
|
419
|
+
width: calc(4 * var(--rdt-tree-indent, 1.25rem));
|
|
420
|
+
}
|
|
421
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] tbody[data-rozie-s-d5dcab4c] tr[data-depth="5"][data-rozie-s-d5dcab4c] > .rdt-expander-td[data-rozie-s-d5dcab4c] + .rdt-td[data-rozie-s-d5dcab4c]::before {
|
|
422
|
+
width: calc(5 * var(--rdt-tree-indent, 1.25rem));
|
|
423
|
+
}
|
|
424
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] tbody[data-rozie-s-d5dcab4c] tr[data-depth="6"][data-rozie-s-d5dcab4c] > .rdt-expander-td[data-rozie-s-d5dcab4c] + .rdt-td[data-rozie-s-d5dcab4c]::before {
|
|
425
|
+
width: calc(6 * var(--rdt-tree-indent, 1.25rem));
|
|
426
|
+
}
|
|
427
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] tbody[data-rozie-s-d5dcab4c] tr[data-depth="7"][data-rozie-s-d5dcab4c] > .rdt-expander-td[data-rozie-s-d5dcab4c] + .rdt-td[data-rozie-s-d5dcab4c]::before {
|
|
428
|
+
width: calc(7 * var(--rdt-tree-indent, 1.25rem));
|
|
429
|
+
}
|
|
430
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] tbody[data-rozie-s-d5dcab4c] tr[data-depth="8"][data-rozie-s-d5dcab4c] > .rdt-expander-td[data-rozie-s-d5dcab4c] + .rdt-td[data-rozie-s-d5dcab4c]::before {
|
|
431
|
+
width: calc(8 * var(--rdt-tree-indent, 1.25rem));
|
|
432
|
+
}
|
|
433
|
+
.rozie-data-table[role="grid"][data-rozie-s-d5dcab4c] {
|
|
434
|
+
user-select: none;
|
|
435
|
+
}
|
|
436
|
+
.rozie-data-table[role="grid"][data-rozie-s-d5dcab4c] input[data-rozie-s-d5dcab4c],
|
|
437
|
+
.rozie-data-table[role="grid"][data-rozie-s-d5dcab4c] textarea[data-rozie-s-d5dcab4c],
|
|
438
|
+
.rozie-data-table[role="grid"][data-rozie-s-d5dcab4c] select[data-rozie-s-d5dcab4c],
|
|
439
|
+
.rozie-data-table[role="grid"][data-rozie-s-d5dcab4c] [contenteditable="true"][data-rozie-s-d5dcab4c] {
|
|
440
|
+
user-select: text;
|
|
441
|
+
}
|
|
374
442
|
.rozie-data-table[data-rozie-s-d5dcab4c] .rdt-select-all[data-rozie-s-d5dcab4c],
|
|
375
443
|
.rozie-data-table[data-rozie-s-d5dcab4c] .rdt-select-row[data-rozie-s-d5dcab4c] {
|
|
376
444
|
cursor: pointer;
|
|
@@ -381,17 +449,19 @@ interface GroupBarSlotCtx { grouping: any; groupableColumns: any; applyGrouping:
|
|
|
381
449
|
|
|
382
450
|
interface SelectAllSlotCtx { checked: any; indeterminate: any; toggle: any; }
|
|
383
451
|
|
|
452
|
+
interface PlaceholderSlotCtx { index: any; columnId: any; }
|
|
453
|
+
|
|
384
454
|
interface SelectCellSlotCtx { row: any; checked: any; toggle: any; }
|
|
385
455
|
|
|
386
456
|
interface DetailSlotCtx { row: any; }
|
|
387
457
|
|
|
388
458
|
interface ColHeaderSlotCtx { columnId: any; column: any; label: any; }
|
|
389
459
|
|
|
390
|
-
interface FilterSlotCtx { columnId: any; value: any; uniqueValues: any; minMax: any; setFilter: any; }
|
|
460
|
+
interface FilterSlotCtx { columnId: any; value: any; uniqueValues: any; minMax: any; columnLabel: any; setFilter: any; }
|
|
391
461
|
|
|
392
462
|
interface CellSlotCtx { columnId: any; column: any; row: any; value: any; }
|
|
393
463
|
|
|
394
|
-
interface EditorSlotCtx { columnId: any; column: any; row: any; value: any; commit: any; cancel: any; autofocus: any; }
|
|
464
|
+
interface EditorSlotCtx { columnId: any; column: any; row: any; value: any; commit: any; cancel: any; columnLabel: any; autofocus: any; }
|
|
395
465
|
|
|
396
466
|
interface DataTableProps {
|
|
397
467
|
/**
|
|
@@ -456,6 +526,10 @@ interface DataTableProps {
|
|
|
456
526
|
expanded?: (Record<string, any> | boolean) | null;
|
|
457
527
|
defaultExpanded?: (Record<string, any> | boolean) | null;
|
|
458
528
|
onExpandedChange?: (expanded: (Record<string, any> | boolean) | null) => void;
|
|
529
|
+
/**
|
|
530
|
+
* Row identity `(originalRow, index, parentRow?) => string | number` — the key `rowSelection`, `expanded` and the per-row caches use. Default null → table-core keys rows by position (`"0"`, `"1"`, … and `"0.1"` for sub-rows), so inserting or removing rows above a selected or expanded row moves that state onto whatever row now sits at its index. Supply it whenever rows can be added or removed while state is held (server push, live lists); the result is coerced to a string.
|
|
531
|
+
*/
|
|
532
|
+
getRowId?: ((...args: any[]) => any) | null;
|
|
459
533
|
/**
|
|
460
534
|
* Table-level child-row accessor `(originalRow, index) => TData[] | undefined` that drives nested sub-rows. When supplied (with `expandable`), table-core flattens the hierarchy and the expand seam reveals depth-indented child rows. Null → the `#detail` scoped slot is the expand mode.
|
|
461
535
|
*/
|
|
@@ -547,7 +621,9 @@ interface DataTableProps {
|
|
|
547
621
|
onReorderChange?: (...args: unknown[]) => void;
|
|
548
622
|
onPinChange?: (...args: unknown[]) => void;
|
|
549
623
|
onHistoryChange?: (...args: unknown[]) => void;
|
|
624
|
+
onVisibleRangeChange?: (...args: unknown[]) => void;
|
|
550
625
|
onActivecellChange?: (...args: unknown[]) => void;
|
|
626
|
+
onRowActivate?: (...args: unknown[]) => void;
|
|
551
627
|
onRangeChange?: (...args: unknown[]) => void;
|
|
552
628
|
onCellEditCommit?: (...args: unknown[]) => void;
|
|
553
629
|
onRowEditCommit?: (...args: unknown[]) => void;
|
|
@@ -555,13 +631,14 @@ interface DataTableProps {
|
|
|
555
631
|
children?: JSX.Element;
|
|
556
632
|
groupBarSlot?: (ctx: GroupBarSlotCtx) => JSX.Element;
|
|
557
633
|
selectAllSlot?: (ctx: SelectAllSlotCtx) => JSX.Element;
|
|
634
|
+
placeholderSlot?: (ctx: PlaceholderSlotCtx) => JSX.Element;
|
|
558
635
|
selectCellSlot?: (ctx: SelectCellSlotCtx) => JSX.Element;
|
|
559
636
|
detailSlot?: (ctx: DetailSlotCtx) => JSX.Element;
|
|
560
637
|
colHeaderSlot?: (ctx: ColHeaderSlotCtx) => JSX.Element;
|
|
561
638
|
filterSlot?: (ctx: FilterSlotCtx) => JSX.Element;
|
|
562
639
|
cellSlot?: (ctx: CellSlotCtx) => JSX.Element;
|
|
563
640
|
editorSlot?: (ctx: EditorSlotCtx) => JSX.Element;
|
|
564
|
-
slots?: { [key: `colHeader-${string}`]: ((ctx: { columnId: any; column: any; label: any }) => JSX.Element) | undefined; [key: `filter-${string}`]: ((ctx: { columnId: any; value: any; uniqueValues: any; minMax: any; setFilter: any }) => JSX.Element) | undefined; [key: `cell-${string}`]: ((ctx: { columnId: any; column: any; row: any; value: any }) => JSX.Element) | undefined; [key: `editor-${string}`]: ((ctx: { columnId: any; column: any; row: any; value: any; commit: any; cancel: any; autofocus: any }) => JSX.Element) | undefined; [key: string]: ((...args: any[]) => JSX.Element) | undefined; };
|
|
641
|
+
slots?: { [key: `colHeader-${string}`]: ((ctx: { columnId: any; column: any; label: any }) => JSX.Element) | undefined; [key: `filter-${string}`]: ((ctx: { columnId: any; value: any; uniqueValues: any; minMax: any; columnLabel: any; setFilter: any }) => JSX.Element) | undefined; [key: `cell-${string}`]: ((ctx: { columnId: any; column: any; row: any; value: any }) => JSX.Element) | undefined; [key: `editor-${string}`]: ((ctx: { columnId: any; column: any; row: any; value: any; commit: any; cancel: any; columnLabel: any; autofocus: any }) => JSX.Element) | undefined; [key: string]: ((...args: any[]) => JSX.Element) | undefined; };
|
|
565
642
|
ref?: (h: DataTableHandle) => void;
|
|
566
643
|
}
|
|
567
644
|
|
|
@@ -603,8 +680,8 @@ export interface DataTableHandle {
|
|
|
603
680
|
}
|
|
604
681
|
|
|
605
682
|
export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
606
|
-
const _merged = mergeProps({ columns: (() => [])() as any[], selectionMode: 'none', manual: false, rowCount: null, pageCount: null, expandable: false, getSubRows: null, groupable: false, stickyHeader: false, interactionMode: 'table', singleClickEdit: false, undoable: false, undoLimit: 100, virtual: false, estimateRowHeight: 40, autoMeasure: false, maxHeight: '' }, _props);
|
|
607
|
-
const [local, attrs] = splitProps(_merged, ['data', 'columns', 'selectionMode', 'sorting', 'globalFilter', 'columnFilters', 'pagination', 'manual', 'rowCount', 'pageCount', 'expandable', 'expanded', 'getSubRows', 'groupable', 'grouping', 'rowSelection', 'columnVisibility', 'columnSizing', 'columnOrder', 'columnPinning', 'stickyHeader', 'interactionMode', 'singleClickEdit', 'undoable', 'undoLimit', 'virtual', 'estimateRowHeight', 'autoMeasure', 'maxHeight', 'children', 'ref', 'onSortChange', 'onExpandChange', 'onGroupChange', 'onFilterChange', 'onPageChange', 'onSelectionChange', 'onVisibilityChange', 'onResizeChange', 'onReorderChange', 'onPinChange', 'onHistoryChange', 'onActivecellChange', 'onRangeChange', 'onCellEditCommit', 'onRowEditCommit']);
|
|
683
|
+
const _merged = mergeProps({ columns: (() => [])() as any[], selectionMode: 'none', manual: false, rowCount: null, pageCount: null, expandable: false, getRowId: null, getSubRows: null, groupable: false, stickyHeader: false, interactionMode: 'table', singleClickEdit: false, undoable: false, undoLimit: 100, virtual: false, estimateRowHeight: 40, autoMeasure: false, maxHeight: '' }, _props);
|
|
684
|
+
const [local, attrs] = splitProps(_merged, ['data', 'columns', 'selectionMode', 'sorting', 'globalFilter', 'columnFilters', 'pagination', 'manual', 'rowCount', 'pageCount', 'expandable', 'expanded', 'getRowId', 'getSubRows', 'groupable', 'grouping', 'rowSelection', 'columnVisibility', 'columnSizing', 'columnOrder', 'columnPinning', 'stickyHeader', 'interactionMode', 'singleClickEdit', 'undoable', 'undoLimit', 'virtual', 'estimateRowHeight', 'autoMeasure', 'maxHeight', 'children', 'ref', 'onSortChange', 'onExpandChange', 'onGroupChange', 'onFilterChange', 'onPageChange', 'onSelectionChange', 'onVisibilityChange', 'onResizeChange', 'onReorderChange', 'onPinChange', 'onHistoryChange', 'onVisibleRangeChange', 'onActivecellChange', 'onRowActivate', 'onRangeChange', 'onCellEditCommit', 'onRowEditCommit']);
|
|
608
685
|
const resolved = () => local.children;
|
|
609
686
|
onMount(() => { local.ref?.({ sortColumn, clearSorting, toggleRowExpanded, expandAll, collapseAll, getExpandedRows, applyGrouping, clearGrouping, getFacetedUniqueValues, getFacetedMinMaxValues, getColumnDefs, toggleAllRows, clearSelection, getSelectedRows, setPage, setRowsPerPage, toggleColumnVisibility, applyColumnOrder, resetColumnSizing, pinColumn, focusCell, getActiveCell, clearActiveCell, getRowIndexRelativeToPage, editCell, commitEditing, editRow, getSelectedRange, cut, undo, redo, canUndo, canRedo, clearHistory }); });
|
|
610
687
|
|
|
@@ -674,6 +751,7 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
674
751
|
const [rangeAnchor, setRangeAnchor] = createSignal<any>(null);
|
|
675
752
|
const [rangeFocus, setRangeFocus] = createSignal<any>(null);
|
|
676
753
|
const [pasteAnnounce, setPasteAnnounce] = createSignal('');
|
|
754
|
+
const [rangeAnnounce, setRangeAnnounce] = createSignal('');
|
|
677
755
|
const [liveAnnounce, setLiveAnnounce] = createSignal('');
|
|
678
756
|
onMount(() => {
|
|
679
757
|
// Seed the uncontrolled `data` fallback (Phase 51 req-4) from the initial prop so an
|
|
@@ -698,7 +776,7 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
698
776
|
// the getter bought nothing. Snapshot the initial data here; setOptions owns updates.
|
|
699
777
|
// currentData() = the bound prop when controlled, else the uncontrolled $data.dataDefault
|
|
700
778
|
// (Phase 51 req-4 — so a committed edit's writeData re-feed is observed either way).
|
|
701
|
-
data:
|
|
779
|
+
data: feedData(),
|
|
702
780
|
columns: tableColumns(),
|
|
703
781
|
state: currentState(),
|
|
704
782
|
getCoreRowModel: getCoreRowModel(),
|
|
@@ -713,7 +791,10 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
713
791
|
// default `!!subRows.length` rule applies (only parents with children expand).
|
|
714
792
|
getExpandedRowModel: getExpandedRowModel(),
|
|
715
793
|
getSubRows: (local.getSubRows || undefined) as any,
|
|
716
|
-
|
|
794
|
+
// Row identity (quick 260925-rew) — see rowIdOption(). Re-passed on re-feed like the
|
|
795
|
+
// other row-model fns.
|
|
796
|
+
getRowId: rowIdOption(),
|
|
797
|
+
getRowCanExpand: local.expandable === true && local.getSubRows == null ? (row: any) => !rowIsLazyPlaceholder(row) : undefined,
|
|
717
798
|
onExpandedChange: onExpandedChangeCb,
|
|
718
799
|
// Grouping auto-expand (phase 50 req-4): table-core's autoResetExpanded defaults TRUE, so a
|
|
719
800
|
// POST-MOUNT setGrouping (the consumer #groupBar / applyGrouping verb) auto-fires
|
|
@@ -758,7 +839,7 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
758
839
|
// Row selection (req-7): enabled unless 'none'; 'single' caps at ≤1
|
|
759
840
|
// (enableMultiRowSelection:false). Select-all scope = filtered rows (TanStack
|
|
760
841
|
// default, D-06 — NOT overridden).
|
|
761
|
-
enableRowSelection:
|
|
842
|
+
enableRowSelection: rowSelectionOption(),
|
|
762
843
|
enableMultiRowSelection: local.selectionMode === 'multiple',
|
|
763
844
|
// PER-SLICE callbacks (Open-Q1: each maps 1:1 to a slice's r-model + change event,
|
|
764
845
|
// no global onStateChange diff) — hoisted top-level consts, re-passed by the re-feed
|
|
@@ -802,6 +883,10 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
802
883
|
// NEVER in a render helper (Pitfall 1). Pass the COMPLETE options set (virtual-core's
|
|
803
884
|
// setOptions replaces, not merges). Guarded so the off path executes no virtual-core code.
|
|
804
885
|
if (rowsWindowed() && virtualizer) {
|
|
886
|
+
// Rekey any placeholder rows this re-feed just filled (quick 260926, VR probe) BEFORE
|
|
887
|
+
// handing the virtualizer its fresh options — see migratePlaceholderSizeCache()'s own
|
|
888
|
+
// comment (virtualization.rzts) for the itemSizeCache/getMeasurements memo root cause.
|
|
889
|
+
migratePlaceholderSizeCache();
|
|
805
890
|
virtualizer.setOptions(virtualizerOptions());
|
|
806
891
|
virtualizer._willUpdate();
|
|
807
892
|
}
|
|
@@ -872,6 +957,21 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
872
957
|
// cell selectors hang off (the exact idiom proven ×6 by plan 01's probe). Captured here
|
|
873
958
|
// (post-mount) so it is non-null and ROZ123-clean.
|
|
874
959
|
gridRoot = __rozieRootRef! ? __rozieRootRef!.querySelector('.rozie-data-table') : null;
|
|
960
|
+
// B-02: the click-away detector. `composedPath()` rather than `ev.target` because on Lit the
|
|
961
|
+
// grid lives in a shadow root and a document-level listener sees the retargeted HOST element,
|
|
962
|
+
// which is never `gridRoot.contains(...)` — every in-grid click would read as "outside".
|
|
963
|
+
if (typeof document !== 'undefined') {
|
|
964
|
+
docPointerDown = (ev: any) => {
|
|
965
|
+
if (!gridRoot) {
|
|
966
|
+
outsidePointerDown = false;
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
const path = ev && typeof ev.composedPath === 'function' ? ev.composedPath() : null;
|
|
970
|
+
const inside = path ? path.indexOf(gridRoot) !== -1 : !!(ev && ev.target && gridRoot.contains && gridRoot.contains(ev.target));
|
|
971
|
+
outsidePointerDown = !inside;
|
|
972
|
+
};
|
|
973
|
+
document.addEventListener('pointerdown', docPointerDown, true);
|
|
974
|
+
}
|
|
875
975
|
// WR-04: NO on-mount auto-focus of the entry cell. Auto-focusing here stole focus on
|
|
876
976
|
// page load AND was non-deterministic on React/Solid (the entry cell may not be
|
|
877
977
|
// committed to the DOM yet at the $onMount microtask). The roving tabindex="0" entry
|
|
@@ -1001,6 +1101,12 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
1001
1101
|
announceState.globalFilter = effectiveGlobalFilter();
|
|
1002
1102
|
});
|
|
1003
1103
|
onCleanup(() => {
|
|
1104
|
+
// B-02: drop the document click-away listener (it is attached for the instance's lifetime,
|
|
1105
|
+
// not per edit session — attaching it per session would freeze a stale closure on React).
|
|
1106
|
+
if (docPointerDown && typeof document !== 'undefined') {
|
|
1107
|
+
document.removeEventListener('pointerdown', docPointerDown, true);
|
|
1108
|
+
docPointerDown = null;
|
|
1109
|
+
}
|
|
1004
1110
|
if (virtualizerCleanup) virtualizerCleanup();
|
|
1005
1111
|
// Phase 87 87-04: tear down the column virtualizer's scroll-element ResizeObserver too.
|
|
1006
1112
|
// No-op when column windowing was off (cleanup stays null).
|
|
@@ -1020,6 +1126,17 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
1020
1126
|
});
|
|
1021
1127
|
createEffect(() => {
|
|
1022
1128
|
maybeClearHistoryOnExternalSwap();
|
|
1129
|
+
// visible-range-change backstop (quick 260925-dtl part 2): a re-render from new data can move
|
|
1130
|
+
// the rendered window WITHOUT a window-version bump (measured on React: filling placeholders
|
|
1131
|
+
// shifted the window by two rows and nothing was reported). Deduped, so a render that leaves
|
|
1132
|
+
// the window where it was emits nothing.
|
|
1133
|
+
emitVisibleRangeIfChanged();
|
|
1134
|
+
// A-02: the post-render hook is the one place that observes EVERY column-width change on all
|
|
1135
|
+
// six targets — an interactive resize writes `columnSizing` through table-core under
|
|
1136
|
+
// `columnResizeMode: 'onChange'` and never reaches the coarse re-feed watch (which keys on
|
|
1137
|
+
// `$props.columnSizing`, undefined on the uncontrolled path). Self-guarded on the width
|
|
1138
|
+
// signature, so a tick that changed no width costs one loop over the leaf columns.
|
|
1139
|
+
remeasureColumnSizes();
|
|
1023
1140
|
if (!table) return;
|
|
1024
1141
|
// Phase 51 req-4: track currentData() (the bound prop OR the uncontrolled
|
|
1025
1142
|
// $data.dataDefault) so a committed edit re-feeds on Lit whether or not the two-way
|
|
@@ -1031,6 +1148,7 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
1031
1148
|
lastDataLen = d.length;
|
|
1032
1149
|
reFeed();
|
|
1033
1150
|
});
|
|
1151
|
+
createEffect(on(() => (() => windowVer())(), (v) => untrack(() => (() => emitVisibleRangeIfChanged())()), { defer: true }));
|
|
1034
1152
|
createEffect(on(() => (() => [sorting(), globalFilter(), columnFilters(), pagination(),
|
|
1035
1153
|
// Server-side page-count sources (#2): re-feed when the consumer's rowCount/pageCount
|
|
1036
1154
|
// changes at runtime (e.g. a server response updates the total) so getPageCount() and the
|
|
@@ -1105,6 +1223,12 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
1105
1223
|
// handle and TS2322s on a numeric seed.
|
|
1106
1224
|
let remeasureRaf: any = null;
|
|
1107
1225
|
let remeasureDisposed = false;
|
|
1226
|
+
// D-19 (quick 260923-rrr): "the user scrolled to the end" — recorded only when the scroll
|
|
1227
|
+
// offset actually MOVED (a growth-driven onChange leaves it where it was), with the row count
|
|
1228
|
+
// and offset at that moment. See scheduleRemeasure() in virtualization.rzts.
|
|
1229
|
+
let scrollEndPinned: boolean = false;
|
|
1230
|
+
let scrollEndPinnedCount: number = -1;
|
|
1231
|
+
let scrollEndPinnedTop: number = -1;
|
|
1108
1232
|
|
|
1109
1233
|
// ── Grid interaction-mode constants + DOM root (phase 49, REQ-2/6) ────────────────────
|
|
1110
1234
|
// Fixed PageUp/PageDown row step (D-06). Phase 53 swaps this for the visible-window size
|
|
@@ -1117,6 +1241,18 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
1117
1241
|
// querySelector-off-root precedent, proven ×6 by plan 01's probe). NEVER read in a
|
|
1118
1242
|
// computed/template binding (ROZ123).
|
|
1119
1243
|
let gridRoot: any = null;
|
|
1244
|
+
// ── B-02: "did the user just click OUTSIDE the grid?" ──────────────────────────────────
|
|
1245
|
+
// Set by the document-level pointerdown listener registered in $onMount, consumed by
|
|
1246
|
+
// onEditorBlur's null-relatedTarget branch (editorBindings.rzts), which explains in full why
|
|
1247
|
+
// the decision has to come from here and be routed back there. In short: a null relatedTarget
|
|
1248
|
+
// on its own cannot tell a click onto a non-focusable page region (the edit must commit) from
|
|
1249
|
+
// an editor being recycled out of the virtual window (it must NOT), and a document listener
|
|
1250
|
+
// cannot commit for itself on React because it closes over the render that attached it — the
|
|
1251
|
+
// draft it would write is the one from editor-open. A plain module-let is hoisted to a
|
|
1252
|
+
// per-instance `useRef` there, so it is read FRESH from the template-bound blur handler.
|
|
1253
|
+
// Capture phase, so a stopPropagation anywhere in the page cannot hide the click from us.
|
|
1254
|
+
let outsidePointerDown = false;
|
|
1255
|
+
let docPointerDown: any = null;
|
|
1120
1256
|
|
|
1121
1257
|
// Echo-guard: while WE are writing a slice back, the re-feed watcher must not re-enter
|
|
1122
1258
|
// the funnel. A counter (not a boolean) so nested writes are safe.
|
|
@@ -1360,6 +1496,54 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
1360
1496
|
// ({ id?, header, columns: [...] }) maps to a multi-level header GROUP column
|
|
1361
1497
|
// whose children are built recursively (B12 — grouped/multi-level column headers).
|
|
1362
1498
|
// Returns null for an unusable entry (no id/field, unsafe key, empty group).
|
|
1499
|
+
/**
|
|
1500
|
+
* A-01 — translate the public per-column `width` into table-core's `size`.
|
|
1501
|
+
*
|
|
1502
|
+
* `width` was written onto the ColumnDef in both build branches and read by NOTHING:
|
|
1503
|
+
* table-core resolves column width from `size` (a NUMBER of px), which only the two
|
|
1504
|
+
* chrome columns ever set. So the documented API was completely inert — the exact
|
|
1505
|
+
* sibling of the inert `pinned` that 0.5.0 closed.
|
|
1506
|
+
*
|
|
1507
|
+
* `size` is numeric px by construction, so only a number or a px string can map onto
|
|
1508
|
+
* it; any other CSS length (`12rem`, `20%`, `auto`) has no numeric px value at build
|
|
1509
|
+
* time and is deliberately NOT guessed — it returns null and the column keeps
|
|
1510
|
+
* table-core's default. The `width` prop's docs say exactly this.
|
|
1511
|
+
*
|
|
1512
|
+
* Returns null (not undefined) for "no usable width" so callers can test `!= null`
|
|
1513
|
+
* without tripping over a legitimate 0.
|
|
1514
|
+
*/
|
|
1515
|
+
function parseWidthToSize(w: any) {
|
|
1516
|
+
if (typeof w === 'number') return Number.isFinite(w) && w > 0 ? w : null;
|
|
1517
|
+
if (typeof w !== 'string') return null;
|
|
1518
|
+
const t = w.trim();
|
|
1519
|
+
if (t === '') return null;
|
|
1520
|
+
const m = /^(\d+(?:\.\d+)?)(px)?$/i.exec(t);
|
|
1521
|
+
if (!m) return null;
|
|
1522
|
+
const n = Number.parseFloat(m[1]);
|
|
1523
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
// ── E-03 ────────────────────────────────────────────────────────────────────────────────
|
|
1527
|
+
// The union check itself is `editorKindWarning` in helpers/columnDefUtils.ts (pure, so the
|
|
1528
|
+
// contract is unit-testable). Here we own the two things that are not pure: WHEN it runs and
|
|
1529
|
+
// how often it speaks.
|
|
1530
|
+
//
|
|
1531
|
+
// WHEN: at column-BUILD time, not in `editorTypeOf`, which is called per cell per render (D-23).
|
|
1532
|
+
// HOW OFTEN: latched per `id:value`, so a rebuild — the memo misses whenever the column set
|
|
1533
|
+
// changes identity — cannot turn one misconfiguration into a console flood.
|
|
1534
|
+
// Unguarded console.warn, matching the D-07/D-08 virtual-misconfig warns in DataTable.rozie:
|
|
1535
|
+
// a process.env guard is not bundler-portable across six targets, and always-warn-on-misconfig
|
|
1536
|
+
// is the established call in this component.
|
|
1537
|
+
const editorWarned = Object.create(null);
|
|
1538
|
+
function checkEditorKind(id: any, editor: any) {
|
|
1539
|
+
const msg = editorKindWarning(id, editor);
|
|
1540
|
+
if (!msg) return editor;
|
|
1541
|
+
const key = id + '\u0000' + String(editor);
|
|
1542
|
+
if (editorWarned[key]) return editor;
|
|
1543
|
+
editorWarned[key] = true;
|
|
1544
|
+
console.warn(msg);
|
|
1545
|
+
return editor;
|
|
1546
|
+
}
|
|
1363
1547
|
function buildConfigDef(c: any) {
|
|
1364
1548
|
if (!c) return null;
|
|
1365
1549
|
// Grouped (multi-level) header column: an entry carrying a `columns` array. table-core's
|
|
@@ -1415,11 +1599,17 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
1415
1599
|
aggregationFn: wrapAggregationFn(c.aggregationFn),
|
|
1416
1600
|
pinned: c.pinned != null ? c.pinned : '',
|
|
1417
1601
|
width: c.width != null ? c.width : '',
|
|
1602
|
+
// A-01 — `width` alone is inert; table-core sizes from `size`. Spread so an
|
|
1603
|
+
// unusable width leaves `size` ABSENT rather than explicitly undefined, which
|
|
1604
|
+
// would override a consumer's `defaultColumn.size`.
|
|
1605
|
+
...(parseWidthToSize(c.width) != null ? {
|
|
1606
|
+
size: parseWidthToSize(c.width)
|
|
1607
|
+
} : {}),
|
|
1418
1608
|
// Editable-cell config (Phase 51) → ColumnDef.meta, the table-core per-column
|
|
1419
1609
|
// metadata carrier the display↔editor branch + runValidator read. Off by default.
|
|
1420
1610
|
meta: {
|
|
1421
1611
|
editable: c.editable === true,
|
|
1422
|
-
editor: c.editor != null ? c.editor : 'text',
|
|
1612
|
+
editor: c.editor != null ? checkEditorKind(id, c.editor) : 'text',
|
|
1423
1613
|
editorOptions: c.editorOptions != null ? c.editorOptions : [],
|
|
1424
1614
|
validate: typeof c.validate === 'function' ? c.validate : null
|
|
1425
1615
|
}
|
|
@@ -1499,10 +1689,14 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
1499
1689
|
aggregationFn: wrapAggregationFn(spec.aggregationFn),
|
|
1500
1690
|
pinned: spec.pinned != null ? spec.pinned : '',
|
|
1501
1691
|
width: spec.width != null ? spec.width : '',
|
|
1692
|
+
// A-01 — see buildConfigDef; same mapping for the <Column> registry branch.
|
|
1693
|
+
...(parseWidthToSize(spec.width) != null ? {
|
|
1694
|
+
size: parseWidthToSize(spec.width)
|
|
1695
|
+
} : {}),
|
|
1502
1696
|
// Editable-cell config (Phase 51) → ColumnDef.meta from the <Column> registry spec.
|
|
1503
1697
|
meta: {
|
|
1504
1698
|
editable: spec.editable === true,
|
|
1505
|
-
editor: spec.editor != null ? spec.editor : 'text',
|
|
1699
|
+
editor: spec.editor != null ? checkEditorKind(id, spec.editor) : 'text',
|
|
1506
1700
|
editorOptions: spec.editorOptions != null ? spec.editorOptions : [],
|
|
1507
1701
|
validate: typeof spec.validate === 'function' ? spec.validate : null
|
|
1508
1702
|
}
|
|
@@ -2130,9 +2324,18 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
2130
2324
|
}
|
|
2131
2325
|
}
|
|
2132
2326
|
// Active cell's column (D-10): required or focusActiveCell()'s [data-col-index="N"]
|
|
2133
|
-
// querySelector cannot resolve for an off-window active cell.
|
|
2134
|
-
//
|
|
2135
|
-
|
|
2327
|
+
// querySelector cannot resolve for an off-window active cell.
|
|
2328
|
+
//
|
|
2329
|
+
// B-03: this used to be body-cells-only, on the stated grounds that "a header-active cell
|
|
2330
|
+
// has no column-axis rendering dependency here (headers window separately)". That is false —
|
|
2331
|
+
// `windowedHeadersFor()` slices the header row from `windowedColIndices()`, the very set this
|
|
2332
|
+
// function feeds, so a header cell has EXACTLY the same dependency a body cell does. With the
|
|
2333
|
+
// active header column unforced, focusing a leaf header in a 60-column windowed grid and
|
|
2334
|
+
// pressing End left `resolveCellEl` with nothing to find: focus froze on the old `<th>` while
|
|
2335
|
+
// the roving model moved on, and `cellTabindex` handed `tabindex="0"` to a cell that does not
|
|
2336
|
+
// exist — so the grid had no tab stop at all. The gate is gone; the column is forced for
|
|
2337
|
+
// whichever axis the active cell is on.
|
|
2338
|
+
if (activeColIndex() >= 0 && out.indexOf(activeColIndex()) === -1) {
|
|
2136
2339
|
out.push(activeColIndex());
|
|
2137
2340
|
}
|
|
2138
2341
|
// Editing column (D-10): required or an open editor unmounts mid-keystroke when a
|
|
@@ -2210,21 +2413,64 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
2210
2413
|
// committed yet), so the rAF pass is what observes React's recycled rows.
|
|
2211
2414
|
// Each pass only OBSERVES + measures the live window; measureElement is idempotent on an
|
|
2212
2415
|
// already-observed node, so running both is cheap and loop-free.
|
|
2416
|
+
// Upper bound on the frames scheduleRemeasure() will wait for the framework to commit a recycled
|
|
2417
|
+
// window (N-05). Measured commit lag is 1 frame on React/Angular; 10 is generous headroom.
|
|
2418
|
+
const REMEASURE_MAX_FRAMES = 10;
|
|
2213
2419
|
function scheduleRemeasure() {
|
|
2214
2420
|
// 87-13: never arm a new sweep after teardown — an onChange can still fire from a
|
|
2215
2421
|
// virtualizer whose own ResizeObserver cleanup has run but whose queued tick was already
|
|
2216
2422
|
// in flight.
|
|
2217
2423
|
if (remeasureDisposed) return;
|
|
2424
|
+
// D-19 (quick 260923-rrr): remember whether the view is at the END — but only when the scroll
|
|
2425
|
+
// offset MOVED since the last record. A measurement-driven onChange (rows growing) arrives
|
|
2426
|
+
// with the offset unchanged, and it is exactly that growth that moves the view off the end,
|
|
2427
|
+
// so it must not clear the pin. (virtual-core's isScrolling cannot tell the two apart: it
|
|
2428
|
+
// stays true for 150ms after the user's scroll, spanning the growth.)
|
|
2429
|
+
if (virtualizer && gridScrollEl && !virtualizer.scrollState && gridScrollEl.scrollTop !== scrollEndPinnedTop) {
|
|
2430
|
+
scrollEndPinnedTop = gridScrollEl.scrollTop;
|
|
2431
|
+
// Judged on virtual-core's MODEL (total size − viewport − offset), not DOM geometry: its own
|
|
2432
|
+
// above-viewport adjustments move scrollTop before the framework commits the matching
|
|
2433
|
+
// spacer, so the DOM transiently reads "not at the end" for a view that is (measured on
|
|
2434
|
+
// Angular: a 12px adjustment cleared a DOM-judged pin and left the view 12px short).
|
|
2435
|
+
scrollEndPinned = virtualizer.getVirtualDistanceFromEnd() <= 1;
|
|
2436
|
+
scrollEndPinnedCount = windowSource().length;
|
|
2437
|
+
}
|
|
2218
2438
|
if (remeasurePending) return;
|
|
2219
2439
|
remeasurePending = true;
|
|
2220
2440
|
let ranMicro = false;
|
|
2221
2441
|
const microPass = () => {
|
|
2222
2442
|
remeasureWindow();
|
|
2223
2443
|
};
|
|
2444
|
+
// N-05 (quick 260923-rrr): the rAF pass keys on the OUTCOME, not on "a frame went by". React
|
|
2445
|
+
// and Angular commit the recycled window AFTER that first rAF, so a single pass measured the
|
|
2446
|
+
// OLD rows and the new ones were only handed to measureElement when virtual-core's 150ms
|
|
2447
|
+
// scrolling-ended tick fired an onChange — the user saw the un-adjusted layout, then a 1–3 row
|
|
2448
|
+
// jump (measured). While the committed <tr> set does not yet cover the virtualizer's window,
|
|
2449
|
+
// re-run next frame, bounded so a window that can never be covered cannot spin forever.
|
|
2450
|
+
let rafAttempts = 0;
|
|
2224
2451
|
const rafPass = () => {
|
|
2225
2452
|
remeasureRaf = null;
|
|
2453
|
+
const covered = remeasureWindow();
|
|
2454
|
+
rafAttempts = rafAttempts + 1;
|
|
2455
|
+
if (!covered && rafAttempts < REMEASURE_MAX_FRAMES && !remeasureDisposed && typeof requestAnimationFrame === 'function') {
|
|
2456
|
+
remeasureRaf = requestAnimationFrame(rafPass);
|
|
2457
|
+
return;
|
|
2458
|
+
}
|
|
2459
|
+
// D-19: keep a user who scrolled to the END at the end. When rows at/above the viewport
|
|
2460
|
+
// measure taller than their estimate, virtual-core compensates with a scroll adjustment — but
|
|
2461
|
+
// on React/Angular that adjustment is written BEFORE the framework commits the taller spacer,
|
|
2462
|
+
// so the browser clamps it to the OLD maximum and it is lost (measured: the view ended
|
|
2463
|
+
// 593–1025px above the bottom on React, 30px on Angular). This pass runs after the commit, so
|
|
2464
|
+
// the real maximum is known. Not when the row count changed: appending rows must not
|
|
2465
|
+
// auto-follow them.
|
|
2466
|
+
if (scrollEndPinned && gridScrollEl && !remeasureDisposed && windowSource().length === scrollEndPinnedCount) {
|
|
2467
|
+
const maxTop = gridScrollEl.scrollHeight - gridScrollEl.clientHeight;
|
|
2468
|
+
if (maxTop - gridScrollEl.scrollTop > 1) {
|
|
2469
|
+
gridScrollEl.scrollTop = maxTop;
|
|
2470
|
+
scrollEndPinnedTop = gridScrollEl.scrollTop;
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2226
2473
|
remeasurePending = false;
|
|
2227
|
-
remeasureWindow();
|
|
2228
2474
|
};
|
|
2229
2475
|
if (typeof queueMicrotask !== 'undefined') {
|
|
2230
2476
|
ranMicro = true;
|
|
@@ -2258,9 +2504,25 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
2258
2504
|
// unioned into the windowed slice when it scrolls off-window and its height is subtracted from
|
|
2259
2505
|
// the appropriate spacer so the total stays exactly getTotalSize() (the 51-01-proven mechanism).
|
|
2260
2506
|
// This is the data-table half of the D-05 windowing.rzts pin-extension hook (listbox provides none).
|
|
2507
|
+
//
|
|
2508
|
+
// B-06 widens this from "the editing row" to "the row that must not recycle out of the
|
|
2509
|
+
// window", which now includes the ACTIVE row in grid mode. The active cell is the grid's ONLY
|
|
2510
|
+
// `tabindex="0"` — `cellTabindex()` returns 0 for it and -1 for every other cell — so once a
|
|
2511
|
+
// wheel-scroll carried it out of the window the grid had NO tab stop at all: Tab could not
|
|
2512
|
+
// re-enter it, and the roving model still pointed at a cell with no DOM node, so
|
|
2513
|
+
// `resolveCellEl` returned null and a subsequent arrow key moved state with nothing to focus.
|
|
2514
|
+
// The column axis has forced the active-cell COLUMN since D-10 (`forcedColumns`); this is the
|
|
2515
|
+
// row-axis twin of that decision, and it costs at most one extra rendered row, in flow, with
|
|
2516
|
+
// its height subtracted from the spacer by the same 51-01 mechanism the editing pin uses.
|
|
2517
|
+
//
|
|
2518
|
+
// `$props.interactionMode` is read directly rather than through `isGrid()` (gridFocusNav.rzts):
|
|
2519
|
+
// that partial is imported AFTER this one, and this file's header documents the
|
|
2520
|
+
// forward-cross-partial-reference TDZ on React that the 87-02 lesson established. A prop read
|
|
2521
|
+
// has no such hazard.
|
|
2261
2522
|
function pinnedEditIndex() {
|
|
2262
2523
|
if (editingRow() >= 0) return editingRow();
|
|
2263
2524
|
if (editingRowIndex() != null) return editingRowIndex();
|
|
2525
|
+
if (local.interactionMode === 'grid' && !activeIsHeader() && activeRow() >= 0) return activeRow();
|
|
2264
2526
|
return -1;
|
|
2265
2527
|
}
|
|
2266
2528
|
// pinnedMeasurement(pin): the virtual-core measurement { index, start, size, end, key } for the
|
|
@@ -2287,8 +2549,8 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
2287
2549
|
// cover unmount — virtualizer/gridRoot are assigned in $onMount and never nulled — so this
|
|
2288
2550
|
// is the only thing standing between a pass queued pre-unmount and refineRowEstimate()'s
|
|
2289
2551
|
// setOptions/scrollTop/$data.windowVer writes against a torn-down instance.
|
|
2290
|
-
if (remeasureDisposed) return;
|
|
2291
|
-
if (!virtualizer || !gridRoot) return;
|
|
2552
|
+
if (remeasureDisposed) return true;
|
|
2553
|
+
if (!virtualizer || !gridRoot) return true;
|
|
2292
2554
|
// Bail ONLY while a PROGRAMMATIC scroll is in flight: virtualizer.scrollState is non-null
|
|
2293
2555
|
// exclusively during scrollToIndex / scrollToOffset (the D-12 scroll-then-focus seam) and
|
|
2294
2556
|
// null for ordinary user/scrollTop-driven scrolling (verified virtual-core@3.17.1: set in
|
|
@@ -2296,9 +2558,23 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
2296
2558
|
// resizeItem nudge the offset and starve the scroll target (the Solid off-window focus
|
|
2297
2559
|
// regression); the next settled onChange re-measures the stable window. Manual-scroll
|
|
2298
2560
|
// recycling (the CR-01 case) has scrollState === null, so it measures normally.
|
|
2299
|
-
if (virtualizer.scrollState) return;
|
|
2561
|
+
if (virtualizer.scrollState) return true;
|
|
2300
2562
|
const trs = gridRoot.querySelectorAll('tbody.rdt-tbody > tr[data-index]');
|
|
2301
|
-
|
|
2563
|
+
const rendered = new Set();
|
|
2564
|
+
for (const tr of trs as any) {
|
|
2565
|
+
virtualizer.measureElement(tr);
|
|
2566
|
+
rendered.add(tr.getAttribute('data-index'));
|
|
2567
|
+
}
|
|
2568
|
+
// N-05: did the committed rows cover the virtualizer's CURRENT window? False while the
|
|
2569
|
+
// framework has not yet committed the recycled set (see scheduleRemeasure).
|
|
2570
|
+
let covered = true;
|
|
2571
|
+
const items = virtualizer.getVirtualItems();
|
|
2572
|
+
for (let i = 0; i < items.length; i++) {
|
|
2573
|
+
if (!rendered.has(String(items[i].index))) {
|
|
2574
|
+
covered = false;
|
|
2575
|
+
break;
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2302
2578
|
// D-15 (Phase 87 87-07): trigger the shared engine's post-measurement fold + hysteresis
|
|
2303
2579
|
// re-feed via the afterRowRemeasure mutable-let hook (DataTable.rozie's own script,
|
|
2304
2580
|
// assigned to windowing.rzts's refineRowEstimate()) — NEVER a direct call from this file
|
|
@@ -2307,6 +2583,7 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
2307
2583
|
// documents for columnVirtualizerOptions()). Inert (no-op) until the host assigns it; the
|
|
2308
2584
|
// off/pre-mount path never reaches this line since virtualizer is null there.
|
|
2309
2585
|
if (afterRowRemeasure) afterRowRemeasure();
|
|
2586
|
+
return covered;
|
|
2310
2587
|
}
|
|
2311
2588
|
|
|
2312
2589
|
// D-04: this shell exports ONLY the impure, data-table-specific host pieces. The pure windowing
|
|
@@ -2315,6 +2592,113 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
2315
2592
|
// `@rozie-ui/headless-core/windowing.rzts` via bare specifier — the P0-proven cross-package inline
|
|
2316
2593
|
// path that DISSOLVES the partial into the leaf (a re-export-from THROUGH this shell would survive as
|
|
2317
2594
|
// a runtime import, not inline — verified). The math closes over these host symbols by convention.
|
|
2595
|
+
// ── Lazy rows (quick 260925-dtl part 2, oinbox dogfooding) ─────────────────────────────────────
|
|
2596
|
+
// `virtual` + `manual` + `rowCount`: the row space is rowCount long and `data` may be SPARSE —
|
|
2597
|
+
// an `undefined`/`null` entry (or anything past data.length) is a row not loaded yet. Such holes
|
|
2598
|
+
// are fed to table-core as placeholder sentinels (one cached object per index, so a re-feed does
|
|
2599
|
+
// not churn them) that render as placeholder rows and can't be selected, expanded, edited or
|
|
2600
|
+
// activated. ONLY the table-core feed is padded: every write-back derives from currentData(), the
|
|
2601
|
+
// consumer's own array, so a sentinel can never leak into `data`.
|
|
2602
|
+
const LAZY_PLACEHOLDER_KEY = '__rdtLazyPlaceholder';
|
|
2603
|
+
// LAZY_PLACEHOLDER_ROW_ID_PREFIX: the table-core row-id table.rowIdOption() (DataTable.rozie)
|
|
2604
|
+
// assigns to a placeholder ('__rdt_ph_' + index — no consumer row to ask, so it keys by
|
|
2605
|
+
// position). Shared here (not re-literaled) because migratePlaceholderSizeCache() below and
|
|
2606
|
+
// rowIdOption() must agree byte-for-byte on the prefix or the migration's key scan silently
|
|
2607
|
+
// finds nothing.
|
|
2608
|
+
const LAZY_PLACEHOLDER_ROW_ID_PREFIX = '__rdt_ph_';
|
|
2609
|
+
let lazyPlaceholders: Record<number, any> = {};
|
|
2610
|
+
function lazyRowsActive() {
|
|
2611
|
+
return rowsWindowed() && !!local.manual && local.rowCount != null && local.rowCount > 0;
|
|
2612
|
+
}
|
|
2613
|
+
function isLazyPlaceholder(orig: any): boolean {
|
|
2614
|
+
return !!orig && orig[LAZY_PLACEHOLDER_KEY] === true;
|
|
2615
|
+
}
|
|
2616
|
+
function rowIsLazyPlaceholder(row: any): boolean {
|
|
2617
|
+
return !!row && isLazyPlaceholder(row.original);
|
|
2618
|
+
}
|
|
2619
|
+
function feedData() {
|
|
2620
|
+
const d: any[] = currentData() || [];
|
|
2621
|
+
if (!lazyRowsActive()) return d;
|
|
2622
|
+
const n = Math.max(d.length, Number(local.rowCount));
|
|
2623
|
+
const out: any[] = new Array(n);
|
|
2624
|
+
for (let i = 0; i < n; i++) {
|
|
2625
|
+
const v = d[i];
|
|
2626
|
+
if (v != null) {
|
|
2627
|
+
out[i] = v;
|
|
2628
|
+
} else {
|
|
2629
|
+
if (!lazyPlaceholders[i]) lazyPlaceholders[i] = {
|
|
2630
|
+
[LAZY_PLACEHOLDER_KEY]: true,
|
|
2631
|
+
index: i
|
|
2632
|
+
};
|
|
2633
|
+
out[i] = lazyPlaceholders[i];
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
return out;
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
// migratePlaceholderSizeCache() (quick 260926, VR probe on data-table-lazy [react]): a filled
|
|
2640
|
+
// placeholder's row id changes ('__rdt_ph_' + i -> the real getRowId id), and virtual-core's
|
|
2641
|
+
// getItemKey re-keys by it — but virtual-core's itemSizeCache (dist/esm/index.js@3.17.1) is a
|
|
2642
|
+
// bare Map keyed by the OLD key, so the new key has no entry. The next getMeasurements() recompute
|
|
2643
|
+
// then estimates that row at $props.estimateRowHeight instead of its already-known measured size;
|
|
2644
|
+
// the CR-01 sweep re-measures it moments later and each delta fires one applyScrollAdjustment,
|
|
2645
|
+
// walking the window's anchor off by their sum (measured: 9 rows x -4px = -36px, row 5000 -> 4999
|
|
2646
|
+
// on React — the other 5 targets happened to keep reading the stale pre-refeed measurements array
|
|
2647
|
+
// and never saw the estimate at all). Fix: carry the cached size across the rekey, keyed off
|
|
2648
|
+
// table-core's OWN freshly-assigned id (table.getCoreRowModel().rows[i].id, NOT a locally
|
|
2649
|
+
// recomputed getRowId() call — must be byte-identical to what virtualItemKey(i) reads next) so
|
|
2650
|
+
// the cache is already correct before the caller's virtualizer.setOptions()/_willUpdate() re-feed.
|
|
2651
|
+
// Invalidating the memo needs BOTH strategies below, not just one: getMeasurements() is
|
|
2652
|
+
// memoized on [getMeasurementOptions(), itemSizeCacheVersion] on 3.15+ (confirmed from the
|
|
2653
|
+
// installed source; the same fact windowing.rzts's refineRowEstimate() comment documents) —
|
|
2654
|
+
// but our peer range is `^3`, and versions <=3.14 have NO itemSizeCacheVersion at all and key
|
|
2655
|
+
// the SAME memo on `this.itemSizeCache` (Map IDENTITY) instead — verified against the 3.14.0
|
|
2656
|
+
// tarball, whose own resizeItem() invalidates by reassigning `this.itemSizeCache = new Map(...)`,
|
|
2657
|
+
// never by bumping a version counter. Mutating the Map in place would be silently invisible to
|
|
2658
|
+
// that memo on <=3.14 (a stray `itemSizeCacheVersion++` there just creates a NaN-valued own
|
|
2659
|
+
// property nobody reads). So this reassigns the Map (a public field on every 3.x we support —
|
|
2660
|
+
// harmless on 3.15+, whose memo body re-reads `this.itemSizeCache` fresh) AND bumps the version
|
|
2661
|
+
// counter only where it actually exists. Scans the CACHE's own placeholder-prefixed keys
|
|
2662
|
+
// (bounded by rows actually measured so far), never rowCount (unbounded — 10,000+ here).
|
|
2663
|
+
function migratePlaceholderSizeCache(): void {
|
|
2664
|
+
if (!virtualizer || !virtualizer.itemSizeCache || !table) return;
|
|
2665
|
+
const cache = virtualizer.itemSizeCache;
|
|
2666
|
+
const coreRows = table.getCoreRowModel().rows;
|
|
2667
|
+
let migrated = false;
|
|
2668
|
+
for (const oldKey of Array.from(cache.keys()) as any) {
|
|
2669
|
+
if (typeof oldKey !== 'string' || oldKey.indexOf(LAZY_PLACEHOLDER_ROW_ID_PREFIX) !== 0) continue;
|
|
2670
|
+
const i = Number(oldKey.slice(LAZY_PLACEHOLDER_ROW_ID_PREFIX.length));
|
|
2671
|
+
const row = coreRows[i];
|
|
2672
|
+
if (!row || rowIsLazyPlaceholder(row) || row.id === oldKey) continue;
|
|
2673
|
+
cache.set(row.id, cache.get(oldKey));
|
|
2674
|
+
cache.delete(oldKey);
|
|
2675
|
+
migrated = true;
|
|
2676
|
+
}
|
|
2677
|
+
if (!migrated) return;
|
|
2678
|
+
virtualizer.itemSizeCache = new Map(cache);
|
|
2679
|
+
if (typeof virtualizer.itemSizeCacheVersion === 'number') virtualizer.itemSizeCacheVersion++;
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
// `visible-range-change { start, end }` — the RENDERED row window (overscan included), `end`
|
|
2683
|
+
// exclusive, over the full row space. Driven by the window-version bump every virtualizer change
|
|
2684
|
+
// routes through, deduped here so a scroll that doesn't move the window emits nothing.
|
|
2685
|
+
let lastRangeStart = -1;
|
|
2686
|
+
let lastRangeEnd = -1;
|
|
2687
|
+
function emitVisibleRangeIfChanged() {
|
|
2688
|
+
if (!rowsWindowed() || !virtualizer) return;
|
|
2689
|
+
const items = virtualizer.getVirtualItems();
|
|
2690
|
+
if (!items.length) return;
|
|
2691
|
+
const start = items[0].index;
|
|
2692
|
+
const end = items[items.length - 1].index + 1;
|
|
2693
|
+
if (start === lastRangeStart && end === lastRangeEnd) return;
|
|
2694
|
+
lastRangeStart = start;
|
|
2695
|
+
lastRangeEnd = end;
|
|
2696
|
+
_props.onVisibleRangeChange?.({
|
|
2697
|
+
start,
|
|
2698
|
+
end
|
|
2699
|
+
});
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2318
2702
|
// ══ Generic vertical windowing math (Phase 64, D-04) — the target-agnostic virtual-core bridge ══
|
|
2319
2703
|
// Lifted verbatim from the DataTable virtualization.rzts (the Phase 53/63 B13 baseline). This partial
|
|
2320
2704
|
// holds ONLY the PURE windowing math; every DOM/refs/virtualizer-instance impurity stays per-consumer
|
|
@@ -2691,6 +3075,10 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
2691
3075
|
// makes the eventual render land strictly AFTER this fold + re-feed has fully landed, on
|
|
2692
3076
|
// every target (the 87-10 fix for the confirmed Solid-specific rendering gap).
|
|
2693
3077
|
bumpWindowVer();
|
|
3078
|
+
// D-19 (quick 260923-rrr): a re-feed changes every unmeasured row's size, but — unlike a
|
|
3079
|
+
// measurement — it does not go through virtual-core's onChange, so the host's post-commit
|
|
3080
|
+
// pass (which restores an at-the-end view the growth moved off) never ran for it. Schedule it.
|
|
3081
|
+
scheduleRemeasure();
|
|
2694
3082
|
}
|
|
2695
3083
|
|
|
2696
3084
|
// The FULL virtualizer options. virtual-core's setOptions REPLACES options with
|
|
@@ -3090,6 +3478,25 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
3090
3478
|
}
|
|
3091
3479
|
return '';
|
|
3092
3480
|
}
|
|
3481
|
+
|
|
3482
|
+
// The table-core `getRowId` option (quick 260925-rew). Without it table-core keys
|
|
3483
|
+
// rowSelection / expanded by position, so a row inserted above a selected one moves the
|
|
3484
|
+
// selection onto another row. The prop is captured into a local so the null check narrows
|
|
3485
|
+
// inside the arrow (TS2721 on the Vue leaf otherwise), and String() because table-core
|
|
3486
|
+
// compares ids as strings.
|
|
3487
|
+
function rowIdOption() {
|
|
3488
|
+
const f = local.getRowId;
|
|
3489
|
+
// A lazy placeholder has no consumer row to ask — key it by its index (see feedData()).
|
|
3490
|
+
// Prefix shared with migratePlaceholderSizeCache() (virtualization.rzts) — see its own comment.
|
|
3491
|
+
return f ? (originalRow: any, index: any, parent: any) => isLazyPlaceholder(originalRow) ? LAZY_PLACEHOLDER_ROW_ID_PREFIX + originalRow.index : String(f(originalRow, index, parent)) : undefined;
|
|
3492
|
+
}
|
|
3493
|
+
|
|
3494
|
+
// table-core `enableRowSelection`: off for selectionMode 'none'; with lazy rows a placeholder
|
|
3495
|
+
// (a row not loaded yet) is never selectable, so select-all takes the loaded rows only.
|
|
3496
|
+
function rowSelectionOption() {
|
|
3497
|
+
if (local.selectionMode === 'none') return false;
|
|
3498
|
+
return lazyRowsActive() ? (row: any) => !rowIsLazyPlaceholder(row) : true;
|
|
3499
|
+
}
|
|
3093
3500
|
// Push fresh options into table-core + re-pull the row model. Extracted so BOTH the
|
|
3094
3501
|
// re-feed $watch (above) and the Lit data-change $onUpdate (below) call it.
|
|
3095
3502
|
function reFeed() {
|
|
@@ -3102,10 +3509,10 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
3102
3509
|
// below (`maybeClearHistoryOnExternalSwap`), which runs on all six targets.
|
|
3103
3510
|
table.setOptions((prev: any) => ({
|
|
3104
3511
|
...prev,
|
|
3105
|
-
data:
|
|
3512
|
+
data: feedData(),
|
|
3106
3513
|
columns: tableColumns(),
|
|
3107
3514
|
state: currentState(),
|
|
3108
|
-
enableRowSelection:
|
|
3515
|
+
enableRowSelection: rowSelectionOption(),
|
|
3109
3516
|
enableMultiRowSelection: local.selectionMode === 'multiple',
|
|
3110
3517
|
// Re-pass the server-side page-count sources (#2) so a RUNTIME rowCount/pageCount change
|
|
3111
3518
|
// takes effect: setOptions REPLACES via `...prev`, which holds the value captured at
|
|
@@ -3118,7 +3525,10 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
3118
3525
|
// onExpandedChange callback must re-capture fresh currentState each cycle, F6).
|
|
3119
3526
|
getExpandedRowModel: getExpandedRowModel(),
|
|
3120
3527
|
getSubRows: (local.getSubRows || undefined) as any,
|
|
3121
|
-
|
|
3528
|
+
// Row identity (quick 260925-rew) — see rowIdOption(). Re-passed on re-feed like the
|
|
3529
|
+
// other row-model fns.
|
|
3530
|
+
getRowId: rowIdOption(),
|
|
3531
|
+
getRowCanExpand: local.expandable === true && local.getSubRows == null ? (row: any) => !rowIsLazyPlaceholder(row) : undefined,
|
|
3122
3532
|
onExpandedChange: onExpandedChangeCb,
|
|
3123
3533
|
// Grouping auto-expand (phase 50 req-4): table-core's autoResetExpanded defaults TRUE, so a
|
|
3124
3534
|
// POST-MOUNT setGrouping (the consumer #groupBar / applyGrouping verb) auto-fires
|
|
@@ -3444,9 +3854,17 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
3444
3854
|
// read header.getSize() — the group-aware size (see headerWidth's own comment). pinStyle still
|
|
3445
3855
|
// keys off the plain column id (pinning is a leaf-column-only concern; a grouped header never
|
|
3446
3856
|
// pins as a unit in this codebase's model).
|
|
3447
|
-
|
|
3857
|
+
// A-03: `widthPx` is the in-window leaf-width sum windowedHeadersFor() computed alongside the
|
|
3858
|
+
// clamped colspan. When it is present it WINS over headerWidth(), which reports
|
|
3859
|
+
// Header.getSize() — every descendant leaf, in-window or not — and so over-declares a straddling
|
|
3860
|
+
// group by exactly the leaves the colspan already excluded. Null on the unwindowed path and from
|
|
3861
|
+
// the non-windowed header body, which both keep headerWidth() verbatim.
|
|
3862
|
+
// `widthPx` carries an EXPLICIT `= null` default for the same reason setRangeFocus's anchor
|
|
3863
|
+
// params do: the emitted signature types a declared param as required, and the non-windowed
|
|
3864
|
+
// header body calls `thStyle(header)` with one argument — TS2554 on the strict leaf typecheck.
|
|
3865
|
+
function thStyle(header: any, widthPx = null) {
|
|
3448
3866
|
let s = '';
|
|
3449
|
-
const w = headerWidth(header);
|
|
3867
|
+
const w = widthPx != null && widthPx > 0 ? widthPx + 'px' : headerWidth(header);
|
|
3450
3868
|
if (w) s += 'width:' + w + ';';
|
|
3451
3869
|
s += pinStyle(header && header.column ? header.column.id : null, 2);
|
|
3452
3870
|
return s;
|
|
@@ -3830,7 +4248,11 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
3830
4248
|
if (selectAllBox) selectAllBox.indeterminate = isSomeRowsSelected() && !isAllRowsSelected();
|
|
3831
4249
|
}
|
|
3832
4250
|
|
|
3833
|
-
//
|
|
4251
|
+
// C-09's value-equality guard. Imported HERE, in DataTable.rozie's OWN script, because that
|
|
4252
|
+
// is where it is USED: a `.rzts` partial's import of a symbol the partial itself never
|
|
4253
|
+
// references is dropped as unused, and the host script then reaches a free identifier. That
|
|
4254
|
+
// failed at RUNTIME, not at compile time — `ReferenceError: columnSpecsEquivalent is not
|
|
4255
|
+
// defined` on the first paste — which is the only way this class of mistake shows up.
|
|
3834
4256
|
|
|
3835
4257
|
// Imperative handle (consumer-callable). Each verb is a PRE-DECLARED top-level
|
|
3836
4258
|
// `const` (the canonical $expose contract — `$expose({ name })` references a
|
|
@@ -3955,6 +4377,23 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
3955
4377
|
return (hg && hg.headers ? hg.headers : []).indexOf(header);
|
|
3956
4378
|
}
|
|
3957
4379
|
|
|
4380
|
+
// E-05 — a header's ABSOLUTE LEAF-column start index, which is NOT the same as its position
|
|
4381
|
+
// in the group's header array: a grouped header occupies ONE array slot but spans colSpan leaf
|
|
4382
|
+
// columns, so positions and leaf indices diverge the moment a header group exists. Walk the
|
|
4383
|
+
// group accumulating colSpan to the target. Window-independent by construction (a header's
|
|
4384
|
+
// leaf start is a full-model property), so both the windowed grid header and the plain
|
|
4385
|
+
// non-grid header can share it.
|
|
4386
|
+
function headerLeafStart(hg: any, header: any) {
|
|
4387
|
+
const list = hg && hg.headers ? hg.headers : [];
|
|
4388
|
+
let leaf = 0;
|
|
4389
|
+
for (let i = 0; i < list.length; i++) {
|
|
4390
|
+
if (list[i] === header) return leaf;
|
|
4391
|
+
const h = list[i];
|
|
4392
|
+
leaf = leaf + (h && h.colSpan > 1 ? h.colSpan : 1);
|
|
4393
|
+
}
|
|
4394
|
+
return -1;
|
|
4395
|
+
}
|
|
4396
|
+
|
|
3958
4397
|
// ── C1 (phase 63 wave-6) absolute-index bridge ─────────────────────────────────────────
|
|
3959
4398
|
// The PUBLIC active-cell rowIndex (focusCell/getActiveCell/activecell-change) is the ABSOLUTE
|
|
3960
4399
|
// display-order position in getPrePaginationRowModel().rows (filter+sort+expand applied, BEFORE
|
|
@@ -4041,7 +4480,10 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
4041
4480
|
// post-mount root. Uniform on all six, shadow-safe (the query runs from inside the
|
|
4042
4481
|
// component's own scope). rowKey is the literal '__header' or a String(integer index) and
|
|
4043
4482
|
// colIndex is an integer — NO consumer string is interpolated into the selector (T-49-01).
|
|
4044
|
-
|
|
4483
|
+
// `level` is ANNOTATED, not just defaulted: an untyped `level = null` infers the param type as
|
|
4484
|
+
// `null`, so every header call site passing a real level (`lvl`, activeHeaderLevel) was a
|
|
4485
|
+
// TS2345 under the strict leaf typecheck (quick 260922-mkb — B-03 added one more such call).
|
|
4486
|
+
function resolveCellEl(rowKey: any, colIndex: any, level: number | null = null) {
|
|
4045
4487
|
if (!gridRoot) return null;
|
|
4046
4488
|
// B12: a grouped multi-level header has MULTIPLE cells sharing data-row="__header" at the
|
|
4047
4489
|
// same data-col-index across levels (parent vs leaf). Disambiguate header lookups by the
|
|
@@ -4101,10 +4543,18 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
4101
4543
|
// both are out, and the ONE existing poll below is reused unchanged — it already resolves the
|
|
4102
4544
|
// target cell by BOTH (row, col) via resolveCellEl(String(r), c), so a cell that becomes
|
|
4103
4545
|
// reachable via the column axis needs no new poll logic (per the plan's own key_links note).
|
|
4546
|
+
// B-03: the COLUMN axis applies to HEADER cells too. This guard used to be `!header &&
|
|
4547
|
+
// (rowOut || colOut)`, so a header-active move to an off-window column never scrolled it in:
|
|
4548
|
+
// `resolveCellEl` returned null, focus froze on the old `<th>` while the state and the ring
|
|
4549
|
+
// ran away to a column with no DOM node, and `cellTabindex` then reported 0 for an unrendered
|
|
4550
|
+
// cell — leaving the grid with ZERO `tabindex="0"` cells and no way back in with Tab. Header
|
|
4551
|
+
// rows genuinely have no ROW-axis window (headers are not row-virtualized), so the row half
|
|
4552
|
+
// stays body-only; only the column half widens.
|
|
4104
4553
|
const rowOut = rowsWindowed() && virtualizer && rowIsOutsideWindow(r);
|
|
4105
4554
|
const colOut = colsWindowed() && colVirtualizer && colIsOutsideWindow(c);
|
|
4106
|
-
|
|
4107
|
-
|
|
4555
|
+
const rowScroll = !header && rowOut;
|
|
4556
|
+
if (rowScroll || colOut) {
|
|
4557
|
+
if (rowScroll) virtualizer.scrollToIndex(r, {
|
|
4108
4558
|
align: 'center'
|
|
4109
4559
|
});
|
|
4110
4560
|
if (colOut) colVirtualizer.scrollToIndex(c, {
|
|
@@ -4128,7 +4578,9 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
4128
4578
|
// A newer focus intent superseded this poll — abort WITHOUT focusing (the user has since
|
|
4129
4579
|
// navigated / clicked elsewhere; re-focusing this off-window target would yank focus back).
|
|
4130
4580
|
if (focusIntentEpoch !== myEpoch) return;
|
|
4131
|
-
|
|
4581
|
+
// B-03: resolve with the SAME key the synchronous path below uses, so the poll can
|
|
4582
|
+
// find a header cell. `String(r)` alone addresses body rows only.
|
|
4583
|
+
const el = resolveCellEl(header ? '__header' : String(r), c, header ? lvl : null);
|
|
4132
4584
|
if (el) {
|
|
4133
4585
|
el.focus();
|
|
4134
4586
|
return;
|
|
@@ -4161,8 +4613,23 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
4161
4613
|
// HTMLElement ARIA-reflected property (`Element.ariaRowCount: string`), so a same-named method
|
|
4162
4614
|
// becomes a class field that shadows it on Lit → TS2416 cascades to EVERY @property decorator
|
|
4163
4615
|
// (the `valueOf`/`nodeType` inherited-DOM-member collision class, authoring playbook §6).
|
|
4616
|
+
// A-04: under `manual` the filtered row model is NOT the total — table-core holds only the
|
|
4617
|
+
// page the consumer handed it, so getFilteredRowModel().rows.length is the PAGE SIZE. The
|
|
4618
|
+
// server-side total lives in the props the consumer passes for exactly this purpose
|
|
4619
|
+
// (`rowCount`, else `pageCount`), which is also table-core's own precedence for getPageCount()
|
|
4620
|
+
// (explicit pageCount wins, else ⌈rowCount / pageSize⌉). Taking them here keeps aria-rowcount
|
|
4621
|
+
// consistent with bodyAriaRowIndex, which already offsets by pageIndex * pageSize and would
|
|
4622
|
+
// otherwise hand a screen reader an index LARGER than the advertised count on any page past
|
|
4623
|
+
// the first. `pageCount * pageSize` is an upper bound (the last page is usually partial) —
|
|
4624
|
+
// an over-count is the safe direction: an aria-rowindex can never exceed it.
|
|
4625
|
+
// With `manual` and NEITHER count set, table-core pins getCanNextPage() to false, so pageIndex
|
|
4626
|
+
// stays 0, the offset is 0 and page-slice-as-total is self-consistent; we leave it alone.
|
|
4164
4627
|
function totalRowCount() {
|
|
4165
4628
|
if (!table) return (rows() || []).length;
|
|
4629
|
+
if (local.manual === true) {
|
|
4630
|
+
if (local.rowCount != null) return local.rowCount;
|
|
4631
|
+
if (local.pageCount != null) return local.pageCount * pageSize();
|
|
4632
|
+
}
|
|
4166
4633
|
const fm = table.getFilteredRowModel();
|
|
4167
4634
|
return fm && fm.rows ? fm.rows.length : (rows() || []).length;
|
|
4168
4635
|
}
|
|
@@ -4201,6 +4668,27 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
4201
4668
|
return headerRowCount() + rowIndexOf(row) + ariaPageOffset() + 1;
|
|
4202
4669
|
}
|
|
4203
4670
|
|
|
4671
|
+
// ── E-05: the COLUMN-axis twin of gridAriaRowCount / bodyAriaRowIndex ────────────────
|
|
4672
|
+
// WAI-ARIA: when aria-colcount is set, every cell must carry an aria-colindex and the count
|
|
4673
|
+
// must equal the largest index any cell declares. The row axis has had this since #13; the
|
|
4674
|
+
// column axis had NOTHING repo-wide (0 occurrences of aria-colcount/aria-colindex), which
|
|
4675
|
+
// matters most under column windowing — the rendered <td>s are a WINDOW, so without an
|
|
4676
|
+
// explicit index a screen reader counts the window and announces "column 3 of 6" while the
|
|
4677
|
+
// user sits in absolute column 41.
|
|
4678
|
+
//
|
|
4679
|
+
// The index space is `visibleCellsFor(row)` / the header group's leaf headers — the same one
|
|
4680
|
+
// `colIndexOf` and `data-col-index` already use, INCLUDING the auto-injected select/expander
|
|
4681
|
+
// chrome columns (they are real columns in that list). `visibleColCount()` counts the same
|
|
4682
|
+
// list, so count and indices are mutually consistent by construction.
|
|
4683
|
+
//
|
|
4684
|
+
// NB named gridAriaColCount, NOT ariaColCount: `Element.ariaColCount` / `.ariaColIndex` are
|
|
4685
|
+
// inherited ARIA-reflected properties, so a same-named method becomes a Lit class field that
|
|
4686
|
+
// shadows them → TS2416 cascading to every @property decorator. Exactly the rename discipline
|
|
4687
|
+
// totalRowCount / gridAriaRowCount / bodyAriaRowIndex already document above.
|
|
4688
|
+
function gridAriaColCount() {
|
|
4689
|
+
return visibleColCount();
|
|
4690
|
+
}
|
|
4691
|
+
|
|
4204
4692
|
// Column count = the visible cell list length (uniform header+body in a flat grid). Reads
|
|
4205
4693
|
// $data.rows (reactive) so it is fine-grained-correct on Solid/Lit; falls back to the
|
|
4206
4694
|
// header leaf count when there are no body rows.
|
|
@@ -4458,6 +4946,11 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
4458
4946
|
// Enter/F2 → enter interaction mode: focus the active cell's FIRST interactive control
|
|
4459
4947
|
// (D-07 — uniform for header sort buttons and body controls; Enter does NOT sort directly).
|
|
4460
4948
|
// No-op (stay in navigation mode) if the cell has no focusable control.
|
|
4949
|
+
// True when the active cell holds its own focusable controls (a link, a button, a drop-in
|
|
4950
|
+
// widget). Enter then ENTERS them (enterControl) rather than activating the row (row-activate).
|
|
4951
|
+
function activeCellHasControls() {
|
|
4952
|
+
return focusables(currentCellEl()).length > 0;
|
|
4953
|
+
}
|
|
4461
4954
|
function enterControl() {
|
|
4462
4955
|
const cellEl = currentCellEl();
|
|
4463
4956
|
const list = focusables(cellEl);
|
|
@@ -4550,7 +5043,9 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
4550
5043
|
// nextRow/nextCol locals and FALLS THROUGH to the shared focus seam (like Ctrl+Home/End). ──
|
|
4551
5044
|
if ((e.ctrlKey || e.metaKey) && e.shiftKey && !activeIsHeader() && (key === 'ArrowUp' || key === 'ArrowDown' || key === 'ArrowLeft' || key === 'ArrowRight')) {
|
|
4552
5045
|
e.preventDefault();
|
|
4553
|
-
|
|
5046
|
+
// B-04: measured from the range's FOCUS corner, which is what extendRange moves — not
|
|
5047
|
+
// from the active cell, which a drag-select or Ctrl+A leaves somewhere else entirely.
|
|
5048
|
+
if (key === 'ArrowUp') extendRange(-rangeFocusRow(), 0);else if (key === 'ArrowDown') extendRange(bodyRowCount() - 1 - rangeFocusRow(), 0);else if (key === 'ArrowLeft') extendRange(0, -rangeFocusCol());else extendRange(0, visibleColCount() - 1 - rangeFocusCol());
|
|
4554
5049
|
return;
|
|
4555
5050
|
} else if ((e.ctrlKey || e.metaKey) && !activeIsHeader() && (key === 'ArrowUp' || key === 'ArrowDown' || key === 'ArrowLeft' || key === 'ArrowRight')) {
|
|
4556
5051
|
e.preventDefault();
|
|
@@ -4648,11 +5143,17 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
4648
5143
|
// gated by clipboardActiveAllowed() (== !activeIsHeader) so a header-active Ctrl+C/Ctrl+V
|
|
4649
5144
|
// falls through to NATIVE behavior — never preventDefault'd, never a silent body mutation
|
|
4650
5145
|
// (copyRange/pasteRange also self-guard; the verb guard is what plan 63-09's Cut reuses). ──
|
|
4651
|
-
|
|
5146
|
+
// B-07: `clipboardWriteAvailable()` / `clipboardReadAvailable()` are part of the BRANCH
|
|
5147
|
+
// CONDITION, not a guard inside the verb. preventDefault ran first and copyRange/pasteRange
|
|
5148
|
+
// then returned early when `navigator.clipboard` was absent — an insecure `http://` origin, a
|
|
5149
|
+
// permissions policy, an older embedded webview — so the browser's own copy/paste was
|
|
5150
|
+
// suppressed and nothing replaced it: Ctrl+C did nothing whatsoever. Falling through leaves
|
|
5151
|
+
// the native default in charge, which is the only correct behaviour when we cannot act.
|
|
5152
|
+
else if ((key === 'c' || key === 'C') && (e.ctrlKey || e.metaKey) && clipboardActiveAllowed() && clipboardWriteAvailable()) {
|
|
4652
5153
|
e.preventDefault();
|
|
4653
5154
|
copyRange();
|
|
4654
5155
|
return;
|
|
4655
|
-
} else if ((key === 'v' || key === 'V') && (e.ctrlKey || e.metaKey) && clipboardActiveAllowed()) {
|
|
5156
|
+
} else if ((key === 'v' || key === 'V') && (e.ctrlKey || e.metaKey) && clipboardActiveAllowed() && clipboardReadAvailable()) {
|
|
4656
5157
|
e.preventDefault();
|
|
4657
5158
|
pasteRange();
|
|
4658
5159
|
return;
|
|
@@ -4662,7 +5163,9 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
4662
5163
|
// Ctrl+C/Ctrl+V (clipboardActiveAllowed) so a header-active Ctrl+X falls through to NATIVE cut
|
|
4663
5164
|
// and never silently clears a body cell (cutRange also self-guards). Placed beside the C/V
|
|
4664
5165
|
// shortcuts, BEFORE the printable-key edit-entry branch (which excludes ctrl/meta). ──
|
|
4665
|
-
|
|
5166
|
+
// B-07 applies to Cut too, and gating it on WRITE availability is deliberate: a cut that
|
|
5167
|
+
// cannot copy is data destruction with nothing on the clipboard to paste back.
|
|
5168
|
+
else if ((key === 'x' || key === 'X') && (e.ctrlKey || e.metaKey) && clipboardActiveAllowed() && clipboardWriteAvailable()) {
|
|
4666
5169
|
e.preventDefault();
|
|
4667
5170
|
cutRange();
|
|
4668
5171
|
return;
|
|
@@ -4714,6 +5217,44 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
4714
5217
|
if (!activeIsHeader()) selectAllBody();
|
|
4715
5218
|
return;
|
|
4716
5219
|
}
|
|
5220
|
+
// ── B-15: the APG grid pattern's row/column selection keys, and Escape in NAVIGATION mode.
|
|
5221
|
+
// All three were simply absent. They are placed HERE, before every `key === ' '` branch
|
|
5222
|
+
// below, because the boolean in-place toggle matches a bare `' '` without excluding
|
|
5223
|
+
// modifiers — so a Shift+Space on a checkbox cell would have toggled the checkbox instead
|
|
5224
|
+
// of selecting the row, and Ctrl+Space likewise.
|
|
5225
|
+
//
|
|
5226
|
+
// Shift+Space selects the ROW (table-core's own rowSelection slice, through the row's
|
|
5227
|
+
// toggleSelected — the same funnel the per-row checkbox uses, so the change event and the
|
|
5228
|
+
// two-way model behave identically). A no-op when selectionMode is 'none', which is the
|
|
5229
|
+
// honest behaviour: there is no row selection to make.
|
|
5230
|
+
//
|
|
5231
|
+
// Ctrl+Space selects the COLUMN as a full-height CELL RANGE rather than inventing a third
|
|
5232
|
+
// selection concept — the same corners Ctrl+A and shift+arrow drive, so Ctrl+C / Ctrl+X /
|
|
5233
|
+
// Delete / the fill handle all act on it unchanged.
|
|
5234
|
+
//
|
|
5235
|
+
// Escape in navigation mode collapses the range. In-control mode already consumed Escape at
|
|
5236
|
+
// the top of this handler; in navigation mode it did nothing at all, leaving a user who had
|
|
5237
|
+
// shift-arrowed out a rectangle with no keyboard way to drop it short of an arrow key, which
|
|
5238
|
+
// also MOVES. Only preventDefault'd when there is a range to clear, so a consumer's own
|
|
5239
|
+
// Escape handling on a table with no selection is untouched.
|
|
5240
|
+
else if (key === ' ' && e.shiftKey && !e.ctrlKey && !e.metaKey && !activeIsHeader()) {
|
|
5241
|
+
e.preventDefault();
|
|
5242
|
+
if (local.selectionMode !== 'none') {
|
|
5243
|
+
const selRow = (rows() || [])[activeRow()];
|
|
5244
|
+
if (selRow && selRow.toggleSelected) selRow.toggleSelected(!rowIsSelected(selRow));
|
|
5245
|
+
}
|
|
5246
|
+
return;
|
|
5247
|
+
} else if (key === ' ' && (e.ctrlKey || e.metaKey) && !e.shiftKey && !activeIsHeader()) {
|
|
5248
|
+
e.preventDefault();
|
|
5249
|
+
selectActiveColumn();
|
|
5250
|
+
return;
|
|
5251
|
+
} else if (key === 'Escape') {
|
|
5252
|
+
if (rangeActive || rangeAnchor() != null) {
|
|
5253
|
+
e.preventDefault();
|
|
5254
|
+
clearRange();
|
|
5255
|
+
}
|
|
5256
|
+
return;
|
|
5257
|
+
}
|
|
4717
5258
|
// ── Full-row edit entry (phase 51 req-6 / D-06) — Shift+F2 on an editable active cell puts
|
|
4718
5259
|
// EVERY editable cell in the active row into edit at once. Tested BEFORE the plain F2 branch
|
|
4719
5260
|
// (a Shift+F2 must NOT fall through to single-cell F2). Shift+F2 was chosen for the lowest
|
|
@@ -4793,6 +5334,14 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
4793
5334
|
// rAF poll must not steal focus back after the user has already ArrowDown'd to another row.
|
|
4794
5335
|
recoverGridFocus(String(grpRow), grpCol, null, true);
|
|
4795
5336
|
return;
|
|
5337
|
+
}
|
|
5338
|
+
// ── row-activate (quick 260925-dtl): Enter on a NON-editable body cell with no controls of
|
|
5339
|
+
// its own opens the row (a list-style "open this item"). Reached only after every edit-entry
|
|
5340
|
+
// and group-toggle branch above has declined; a cell WITH controls keeps entering them.
|
|
5341
|
+
else if (key === 'Enter' && !activeIsHeader() && !activeCellHasControls()) {
|
|
5342
|
+
e.preventDefault();
|
|
5343
|
+
activateRowAt(activeRow(), 'keyboard');
|
|
5344
|
+
return;
|
|
4796
5345
|
} else if (key === 'Enter' || key === 'F2') {
|
|
4797
5346
|
e.preventDefault();
|
|
4798
5347
|
enterControl();
|
|
@@ -4887,11 +5436,18 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
4887
5436
|
// focusin that follows a Shift+Click (rangeClickPending): @mousedown already set the range
|
|
4888
5437
|
// BEFORE this focusin fires, and a focusin carries no reliable shiftKey, so the @mousedown
|
|
4889
5438
|
// path owns the shift case and flags it here so the collapse is skipped.
|
|
5439
|
+
// B-11: `rangeClickPending` carries the CELL the Shift+Click landed on, not a bare boolean.
|
|
5440
|
+
// A boolean leaks: Shift+Click on the cell that ALREADY holds focus fires no focusin at all,
|
|
5441
|
+
// so nothing consumed the flag, and the next ordinary navigation click (or Tab) hit this
|
|
5442
|
+
// branch instead and skipped its collapse — leaving a stale rectangle painted while the
|
|
5443
|
+
// active cell moved away. Matching on the coordinates makes a leak self-healing: a focusin on
|
|
5444
|
+
// any OTHER cell falls through to clearRange() and clears the flag on the way past.
|
|
4890
5445
|
if (rangeTransition) {
|
|
4891
5446
|
rangeTransition = false;
|
|
4892
|
-
} else if (rangeClickPending) {
|
|
4893
|
-
rangeClickPending =
|
|
5447
|
+
} else if (rangeClickPending && rangeClickPending.isHeader === isHeader && rangeClickPending.r === movedRow && rangeClickPending.c === col) {
|
|
5448
|
+
rangeClickPending = null;
|
|
4894
5449
|
} else {
|
|
5450
|
+
rangeClickPending = null;
|
|
4895
5451
|
clearRange();
|
|
4896
5452
|
}
|
|
4897
5453
|
// The cell box (not an inner control) receiving focus = navigation mode.
|
|
@@ -4929,13 +5485,26 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
4929
5485
|
setActiveIsHeader(false);
|
|
4930
5486
|
setActiveRow(row);
|
|
4931
5487
|
setActiveColIndex(col);
|
|
4932
|
-
|
|
5488
|
+
// B-11: arm the skip for THIS cell only (see syncActiveFromEvent). Body cell → isHeader
|
|
5489
|
+
// false; the header path never reaches here (rowAttr === '__header' returned above).
|
|
5490
|
+
rangeClickPending = {
|
|
5491
|
+
isHeader: false,
|
|
5492
|
+
r: row,
|
|
5493
|
+
c: col
|
|
5494
|
+
};
|
|
4933
5495
|
return;
|
|
4934
5496
|
}
|
|
4935
|
-
//
|
|
4936
|
-
//
|
|
4937
|
-
//
|
|
4938
|
-
//
|
|
5497
|
+
// B-16: a mousedown INSIDE the cell that is currently in edit belongs to the editor — it is
|
|
5498
|
+
// the user placing a caret or drag-selecting TEXT. Starting a cell drag-select from it paints
|
|
5499
|
+
// a grid range over the very cell being typed into, and (with `undoable`) a drag that leaves
|
|
5500
|
+
// the cell then lets Delete/Ctrl+X act on a rectangle the user was never selecting. The
|
|
5501
|
+
// editor owns the gesture; return before any document listener is attached.
|
|
5502
|
+
if (isEditing(row, col)) return;
|
|
5503
|
+
// §6 plain mousedown → begin a document-level drag-select anchored at this cell. The anchor is
|
|
5504
|
+
// passed EXPLICITLY into setRangeFocus by beginRangeDrag (B-01) rather than seeded from the
|
|
5505
|
+
// active cell, which is still the PRE-mousedown one at this point (native focus is a default
|
|
5506
|
+
// action that runs after this handler). A mousedown with no move collapses to a single active
|
|
5507
|
+
// cell (no range).
|
|
4939
5508
|
beginRangeDrag(row, col);
|
|
4940
5509
|
}
|
|
4941
5510
|
|
|
@@ -4992,7 +5561,52 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
4992
5561
|
// editor). A plain click on an EDITABLE body cell opens its editor via the SAME beginEdit funnel;
|
|
4993
5562
|
// shift+click (range extend) and non-editable cells are unaffected. Same closest/parse/header-skip
|
|
4994
5563
|
// /finite guards as onGridDblClick. isGrid()-gated so 'table' mode never runs it.
|
|
5564
|
+
// ── row-activate (quick 260925-dtl, oinbox dogfooding) ─────────────────────────────────────
|
|
5565
|
+
// `row-activate { row, index, trigger }`: `row` = the original data object, `index` = its
|
|
5566
|
+
// position in the rendered model ($data.rows — page-relative, or the full model when virtual).
|
|
5567
|
+
// Group-header rows never activate; detail rows carry no data-row so a click there never
|
|
5568
|
+
// resolves a row.
|
|
5569
|
+
function activateRowAt(index: any, trigger: any) {
|
|
5570
|
+
const row = (rows() || [])[index];
|
|
5571
|
+
if (!row || rowIsGrouped(row) || rowIsLazyPlaceholder(row)) return;
|
|
5572
|
+
_props.onRowActivate?.({
|
|
5573
|
+
row: row.original,
|
|
5574
|
+
index,
|
|
5575
|
+
trigger
|
|
5576
|
+
});
|
|
5577
|
+
}
|
|
5578
|
+
|
|
5579
|
+
// Controls a click must not activate THROUGH — the selection checkbox, the expander, links,
|
|
5580
|
+
// drop-in widgets and an open editor. Matched only INSIDE the clicked cell, so a host page that
|
|
5581
|
+
// happens to wrap the table in one of these does not silence every row.
|
|
5582
|
+
const ROW_ACTIVATE_CONTROLS = 'input, button, a[href], select, textarea, label, summary, [contenteditable=""], [contenteditable="true"], [role="button"], [role="checkbox"], [role="link"], [data-editing-cell]';
|
|
5583
|
+
|
|
5584
|
+
// Both interaction modes: a plain click on a body data cell, outside any control. Not a
|
|
5585
|
+
// modified click (shift/ctrl/meta are selection gestures), not the end of a drag-select, and
|
|
5586
|
+
// not a singleClickEdit gesture on an editable grid cell (that click opens the editor instead).
|
|
5587
|
+
function maybeActivateRowFromClick(e: any) {
|
|
5588
|
+
if (!e || e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) return;
|
|
5589
|
+
if (rangeDragMoved) return;
|
|
5590
|
+
const tgt = e.target;
|
|
5591
|
+
if (!tgt || !tgt.closest) return;
|
|
5592
|
+
const cellEl = tgt.closest('[data-row]');
|
|
5593
|
+
if (!cellEl) return;
|
|
5594
|
+
const control = tgt.closest(ROW_ACTIVATE_CONTROLS);
|
|
5595
|
+
if (control && cellEl.contains(control)) return;
|
|
5596
|
+
const rowAttr = cellEl.getAttribute('data-row');
|
|
5597
|
+
if (rowAttr == null || rowAttr === '__header') return;
|
|
5598
|
+
const index = parseInt(rowAttr, 10);
|
|
5599
|
+
if (!Number.isFinite(index)) return;
|
|
5600
|
+
if (isGrid() && local.singleClickEdit) {
|
|
5601
|
+
const colAttr = cellEl.getAttribute('data-col-index');
|
|
5602
|
+
const col = colAttr == null ? NaN : parseInt(colAttr, 10);
|
|
5603
|
+
const colId = Number.isFinite(col) ? columnIdAt(index, col) : null;
|
|
5604
|
+
if (colId != null && columnEditable(colId)) return;
|
|
5605
|
+
}
|
|
5606
|
+
activateRowAt(index, 'click');
|
|
5607
|
+
}
|
|
4995
5608
|
function onGridClick(e: any) {
|
|
5609
|
+
maybeActivateRowFromClick(e);
|
|
4996
5610
|
if (!isGrid() || !e) return;
|
|
4997
5611
|
if (!local.singleClickEdit) return;
|
|
4998
5612
|
if (e.shiftKey) return;
|
|
@@ -5188,12 +5802,26 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5188
5802
|
// UNCHANGED (span = the header's own colSpan, or 1) when columns are not windowed — the
|
|
5189
5803
|
// off-path the template's `:colspan="wh.span > 1 ? wh.span : null"` binding preserves
|
|
5190
5804
|
// byte-identically (a bare colspan="1" would change the emitted markup on the unwindowed path).
|
|
5805
|
+
//
|
|
5806
|
+
// A-03: the emitted entry also carries the WIDTH of the in-window leaves, because the colspan
|
|
5807
|
+
// clamp above is only half the job. `thStyle` declared `Header.getSize()` — the sum of ALL
|
|
5808
|
+
// descendant leaves — against a colspan clamped to the leaves actually rendered. Under
|
|
5809
|
+
// `table-layout: fixed` a first-row spanning cell's declared width is divided across the
|
|
5810
|
+
// columns it spans, and only the first row establishes widths, so a group straddling the window
|
|
5811
|
+
// edge inflated every column it still rendered: measured on "Group A" (cols 10-14 @150px,
|
|
5812
|
+
// getSize() 750), each rendered group column went 150 -> 187.5 -> 250 -> 375 -> 750px as the
|
|
5813
|
+
// group scrolled out, and scrollWidth with it, 9000 -> 9600. Identical on all five light-DOM
|
|
5814
|
+
// targets. This is the SAME getSize()-vs-colspan mismatch the earlier Column.getSize() ->
|
|
5815
|
+
// Header.getSize() fix cured for the fully-in-window case, inverted: that one was a 5x
|
|
5816
|
+
// COLLAPSE, this is a 5x EXPANSION for the partial case. Summing the same leaves the clamp
|
|
5817
|
+
// counted keeps the declared width and the colspan describing the same set by construction.
|
|
5191
5818
|
function windowedHeadersFor(hg: any, hgLevel: any) {
|
|
5192
5819
|
const headers = hg && hg.headers || [];
|
|
5193
5820
|
if (!colsWindowed()) {
|
|
5194
5821
|
return headers.map((h: any) => ({
|
|
5195
5822
|
header: h,
|
|
5196
|
-
span: h && h.colSpan > 1 ? h.colSpan : 1
|
|
5823
|
+
span: h && h.colSpan > 1 ? h.colSpan : 1,
|
|
5824
|
+
width: null
|
|
5197
5825
|
}));
|
|
5198
5826
|
}
|
|
5199
5827
|
const idx = windowedColIndices();
|
|
@@ -5208,10 +5836,15 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5208
5836
|
const end = cursor + leafCount;
|
|
5209
5837
|
cursor = end;
|
|
5210
5838
|
let span = 0;
|
|
5211
|
-
|
|
5839
|
+
let width = 0;
|
|
5840
|
+
for (let c = start; c < end; c++) if (idxSet[c]) {
|
|
5841
|
+
span = span + 1;
|
|
5842
|
+
width = width + columnSize(c);
|
|
5843
|
+
}
|
|
5212
5844
|
if (span > 0) out.push({
|
|
5213
5845
|
header: h,
|
|
5214
|
-
span
|
|
5846
|
+
span,
|
|
5847
|
+
width
|
|
5215
5848
|
});
|
|
5216
5849
|
}
|
|
5217
5850
|
return out;
|
|
@@ -5224,6 +5857,36 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5224
5857
|
function windowedColSpan() {
|
|
5225
5858
|
return colsWindowed() ? windowedColIndices().length + 2 : visibleColCount();
|
|
5226
5859
|
}
|
|
5860
|
+
// ── A-02: a column RESIZE must invalidate the column virtualizer's measurement cache ──────
|
|
5861
|
+
// `setOptions` is not enough, and this is a property of virtual-core, not of how we call it.
|
|
5862
|
+
// `getMeasurements()` is memoized on `[getMeasurementOptions(), itemSizeCacheVersion]`, and
|
|
5863
|
+
// `getMeasurementOptions()` is itself memoized on count / paddingStart / scrollMargin /
|
|
5864
|
+
// getItemKey / enabled / lanes / laneAssignmentMode — `estimateSize` is NOT among them. The
|
|
5865
|
+
// column axis feeds its widths ENTIRELY through `estimateSize: (i) => columnSize(i)` (it never
|
|
5866
|
+
// calls `measureElement`), so after a resize the memo returns the array built from the OLD
|
|
5867
|
+
// widths: measured on a 60x150px grid, dragging one column +1200px left `scrollWidth`
|
|
5868
|
+
// oscillating 9000 <-> 10200 as that column entered and left the window, and at scrollLeft 6000
|
|
5869
|
+
// the window started at index 37 where fresh geometry predicts 29. `measure()` is the only API
|
|
5870
|
+
// that bumps `itemSizeCacheVersion`, and it appeared ZERO times in this package or in
|
|
5871
|
+
// headless-core. Same defect for `resetColumnSizing()` and any programmatic `columnSizing` write.
|
|
5872
|
+
//
|
|
5873
|
+
// Guarded on a SIGNATURE rather than called unconditionally. `columnResizeMode` is 'onChange',
|
|
5874
|
+
// so `writeColumnSizing` fires on every pointermove of a drag; `measure()` clears the size cache
|
|
5875
|
+
// and calls `notify(false)`, which bumps `$data.windowVer` and therefore re-renders. Hashing the
|
|
5876
|
+
// widths (order-sensitive, so a pure A<->B swap that preserves the sum still trips it) makes the
|
|
5877
|
+
// steady state a ~N-integer-op no-op and guarantees the notify cannot feed itself: the second
|
|
5878
|
+
// pass sees an unchanged signature and returns.
|
|
5879
|
+
let lastColSizeSig = 0;
|
|
5880
|
+
function remeasureColumnSizes() {
|
|
5881
|
+
if (!colsWindowed() || !colVirtualizer || !colVirtualizer.measure) return;
|
|
5882
|
+
const n = columnCount();
|
|
5883
|
+
let sig = n;
|
|
5884
|
+
for (let i = 0; i < n; i++) sig = Math.imul(sig, 31) + columnSize(i) | 0;
|
|
5885
|
+
if (sig === lastColSizeSig) return;
|
|
5886
|
+
lastColSizeSig = sig;
|
|
5887
|
+
colVirtualizer.measure();
|
|
5888
|
+
}
|
|
5889
|
+
|
|
5227
5890
|
// remeasureColumnWindow() (Phase 87 87-04): push a fresh column count/size into the SECOND
|
|
5228
5891
|
// (horizontal) virtualizer on every row-model refresh — mirrors the row axis's
|
|
5229
5892
|
// virtualizer.setOptions(virtualizerOptions()) + _willUpdate() re-feed in refreshRowModel, so a
|
|
@@ -5237,6 +5900,7 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5237
5900
|
function remeasureColumnWindow() {
|
|
5238
5901
|
if (!colsWindowed() || !colVirtualizer) return;
|
|
5239
5902
|
colVirtualizer.setOptions(columnVirtualizerOptions());
|
|
5903
|
+
remeasureColumnSizes();
|
|
5240
5904
|
colVirtualizer._willUpdate();
|
|
5241
5905
|
}
|
|
5242
5906
|
|
|
@@ -5273,8 +5937,13 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5273
5937
|
let rangeTransition = false;
|
|
5274
5938
|
// rangeClickPending: set by onGridMouseDown on a Shift+Click (the range is set off the
|
|
5275
5939
|
// pointer event's shiftKey BEFORE the cell's focusin fires); the follow-up focusin reads it
|
|
5276
|
-
// to SKIP the range-collapse (a focusin carries no reliable shiftKey).
|
|
5277
|
-
|
|
5940
|
+
// to SKIP the range-collapse (a focusin carries no reliable shiftKey).
|
|
5941
|
+
// B-11: it holds the CELL `{ isHeader, r, c }` the Shift+Click landed on, not a boolean, and
|
|
5942
|
+
// is null when idle. Shift+Click on the already-focused cell fires no focusin, so a boolean
|
|
5943
|
+
// was left armed and the NEXT ordinary click/Tab consumed it and skipped its own collapse,
|
|
5944
|
+
// stranding a painted rectangle. Coordinate-matched, that leak is self-healing: a focusin on
|
|
5945
|
+
// any other cell clears it and collapses normally. Cleared on BOTH branches of the consume.
|
|
5946
|
+
let rangeClickPending: any = null;
|
|
5278
5947
|
// B19: a SYNCHRONOUS mirror of "a range currently exists" — extendRange/setRangeFocus set it
|
|
5279
5948
|
// true, clearRange/clampRange-to-empty set it false. clearRange is invoked TWICE in one plain-
|
|
5280
5949
|
// arrow keydown (the explicit collapse + the focusin that follows the programmatic focus move);
|
|
@@ -5343,7 +6012,26 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5343
6012
|
// pre-write value (ROZ138). extendRange/setRangeFocus thread the just-computed locals through
|
|
5344
6013
|
// here so the emitted payload matches the write. The single call site keeps the count
|
|
5345
6014
|
// predictable (React multi-emit dedup, D-07). One-way notification.
|
|
6015
|
+
// B-10 — announce the selection alongside the emit. This is the SINGLE site every range
|
|
6016
|
+
// mutation funnels through (shift+arrow via extendRange, shift+click and drag-select and
|
|
6017
|
+
// fill-drag via setRangeFocus, Ctrl+A via selectAllBody), so one announce here covers all of
|
|
6018
|
+
// them; guarding the callers individually would be five chances to miss one.
|
|
6019
|
+
//
|
|
6020
|
+
// Writes $data.rangeAnnounce directly rather than importing clipboardFill's announce(): that
|
|
6021
|
+
// helper drives pasteAnnounce, and a range move must not overwrite a paste/validation message
|
|
6022
|
+
// the user has not heard yet (hence the separate key + region). `polite` + `aria-atomic` means
|
|
6023
|
+
// a fast drag replaces the pending text instead of queueing every intermediate rectangle.
|
|
6024
|
+
function rangeSummary(anchor: any, focus: any) {
|
|
6025
|
+
if (!anchor || !focus) return '';
|
|
6026
|
+
const rows = Math.abs(focus.rowIndex - anchor.rowIndex) + 1;
|
|
6027
|
+
const cols = Math.abs(focus.colIndex - anchor.colIndex) + 1;
|
|
6028
|
+
if (rows === 1 && cols === 1) return '1 cell selected';
|
|
6029
|
+
const rowPart = rows + (rows === 1 ? ' row' : ' rows');
|
|
6030
|
+
const colPart = cols + (cols === 1 ? ' column' : ' columns');
|
|
6031
|
+
return rowPart + ' by ' + colPart + ' selected, ' + rows * cols + ' cells';
|
|
6032
|
+
}
|
|
5346
6033
|
function emitRangeChange(anchor: any, focus: any) {
|
|
6034
|
+
setRangeAnnounce(rangeSummary(anchor, focus));
|
|
5347
6035
|
_props.onRangeChange?.({
|
|
5348
6036
|
anchor,
|
|
5349
6037
|
focus
|
|
@@ -5399,14 +6087,39 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5399
6087
|
}
|
|
5400
6088
|
}
|
|
5401
6089
|
|
|
5402
|
-
// setRangeFocus(rIdx, cIdx): set the moving corner to an explicit cell
|
|
5403
|
-
//
|
|
5404
|
-
// Emits range-change from this single site.
|
|
5405
|
-
|
|
6090
|
+
// setRangeFocus(rIdx, cIdx, anchorR, anchorC): set the moving corner to an explicit cell
|
|
6091
|
+
// (Shift+Click, or a drag-select's pointermove), seeding the anchor when no range exists yet.
|
|
6092
|
+
// Clamped to bounds. Emits range-change from this single site.
|
|
6093
|
+
//
|
|
6094
|
+
// B-01: the anchor seed is EXPLICIT when the caller knows it. Seeding it from
|
|
6095
|
+
// `$data.activeRow`/`$data.activeColIndex` is only correct if that state is live at the moment
|
|
6096
|
+
// the seed happens — and inside a drag-select it is not on React, where the document
|
|
6097
|
+
// pointermove closure captures the render-scoped consts from the render that ran BEFORE the
|
|
6098
|
+
// mousedown. Native focus moves as mousedown's DEFAULT ACTION, after handlers return, so at
|
|
6099
|
+
// `beginRangeDrag` time the active cell is still the PRE-mousedown one and React can never
|
|
6100
|
+
// show that closure a newer value. Dragging (5,2)→(7,3) on a fresh grid anchored at (0,0) and
|
|
6101
|
+
// selected the whole rectangle from there — which Ctrl+X and Delete then destroy.
|
|
6102
|
+
// The other five targets read `$data.activeRow` LIVE inside `move` (by then focusin has synced
|
|
6103
|
+
// it), so they were accidentally correct; taking the anchor from the caller removes the read
|
|
6104
|
+
// entirely and makes all six correct for the same reason. Falls back to the active cell when no
|
|
6105
|
+
// explicit anchor is passed — Shift+Click, where "the anchor is where you already were" IS the
|
|
6106
|
+
// active cell and the read happens synchronously in the same handler.
|
|
6107
|
+
// The two anchor params carry EXPLICIT `= null` defaults, and that is load-bearing, not style:
|
|
6108
|
+
// the emitted per-target signature types every declared param as REQUIRED, so the Shift+Click
|
|
6109
|
+
// call site's two-argument `setRangeFocus(row, col)` fails the strict leaf typecheck with
|
|
6110
|
+
// TS2554 "Expected 4 arguments, but got 2" on react and solid. Same rule the `focusActiveCell`
|
|
6111
|
+
// comment in gridKeydownHandlers records for its own optional params.
|
|
6112
|
+
function setRangeFocus$local(rIdx: any, cIdx: any, anchorR = null, anchorC = null) {
|
|
5406
6113
|
const maxRow = bodyRowCount() - 1;
|
|
5407
6114
|
const maxCol = visibleColCount() - 1;
|
|
5408
6115
|
if (maxRow < 0 || maxCol < 0) return;
|
|
5409
6116
|
let anchor = rangeAnchor();
|
|
6117
|
+
if (!anchor && anchorR != null && anchorC != null) {
|
|
6118
|
+
anchor = {
|
|
6119
|
+
rowIndex: clamp(Math.trunc(Number(anchorR)) || 0, 0, maxRow),
|
|
6120
|
+
colIndex: clamp(Math.trunc(Number(anchorC)) || 0, 0, maxCol)
|
|
6121
|
+
};
|
|
6122
|
+
}
|
|
5410
6123
|
if (!anchor) anchor = {
|
|
5411
6124
|
rowIndex: activeRow(),
|
|
5412
6125
|
colIndex: activeColIndex()
|
|
@@ -5447,6 +6160,53 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5447
6160
|
emitRangeChange(anchor, focus);
|
|
5448
6161
|
}
|
|
5449
6162
|
|
|
6163
|
+
// ── B-04: where does a range EXTENSION measure its delta from? ──────────────────────────
|
|
6164
|
+
// `extendRange(dRow, dCol)` applies its delta to the range's FOCUS corner, not to the active
|
|
6165
|
+
// cell. For shift+arrow the two always agree (extendRange writes the active cell to the new
|
|
6166
|
+
// focus on every step), but two paths deliberately move the focus corner WITHOUT syncing the
|
|
6167
|
+
// active cell: a drag-select (`setRangeFocus`, which leaves the active cell on the mousedown
|
|
6168
|
+
// anchor) and Ctrl+A (`selectAllBody`, which sets both corners outright). After either,
|
|
6169
|
+
// Ctrl/Cmd+Shift+Arrow computed "distance from the active cell to the edge" and then added it
|
|
6170
|
+
// to a DIFFERENT cell. Drag (5,2)->(7,3) then Ctrl+Shift+ArrowUp extended to row 2 instead of
|
|
6171
|
+
// row 0; Ctrl+Shift+ArrowLeft to column 1 instead of column 0 — an extend-to-edge that stops
|
|
6172
|
+
// short of the edge by exactly the gap between the two.
|
|
6173
|
+
//
|
|
6174
|
+
// These report the corner extendRange will actually move, so the delta and its target are the
|
|
6175
|
+
// same cell by construction. No range yet -> the active cell IS the focus corner.
|
|
6176
|
+
function rangeFocusRow() {
|
|
6177
|
+
const f = rangeFocus();
|
|
6178
|
+
return f && f.rowIndex != null ? f.rowIndex : activeRow();
|
|
6179
|
+
}
|
|
6180
|
+
function rangeFocusCol() {
|
|
6181
|
+
const f = rangeFocus();
|
|
6182
|
+
return f && f.colIndex != null ? f.colIndex : activeColIndex();
|
|
6183
|
+
}
|
|
6184
|
+
|
|
6185
|
+
// B-15: the APG grid pattern's Ctrl+Space — select the COLUMN containing the focused cell.
|
|
6186
|
+
// Expressed as a full-height rectangle over the SAME range corners shift+arrow and Ctrl+A
|
|
6187
|
+
// drive, so Ctrl+C / Ctrl+X / Delete / the fill handle all act on it with no new machinery
|
|
6188
|
+
// and no second selection concept for a consumer to reason about. Emits through the single
|
|
6189
|
+
// emitRangeChange funnel (fresh corners, never a $data re-read). No-op on an empty grid, and
|
|
6190
|
+
// header-active is gated OUT by the caller, exactly as selectAllBody is.
|
|
6191
|
+
function selectActiveColumn() {
|
|
6192
|
+
const maxRow = bodyRowCount() - 1;
|
|
6193
|
+
const maxCol = visibleColCount() - 1;
|
|
6194
|
+
if (maxRow < 0 || maxCol < 0) return;
|
|
6195
|
+
const c = clamp(Math.trunc(Number(activeColIndex())) || 0, 0, maxCol);
|
|
6196
|
+
const anchor = {
|
|
6197
|
+
rowIndex: 0,
|
|
6198
|
+
colIndex: c
|
|
6199
|
+
};
|
|
6200
|
+
const focus = {
|
|
6201
|
+
rowIndex: maxRow,
|
|
6202
|
+
colIndex: c
|
|
6203
|
+
};
|
|
6204
|
+
setRangeAnchor(anchor);
|
|
6205
|
+
setRangeFocus(focus);
|
|
6206
|
+
rangeActive = true;
|
|
6207
|
+
emitRangeChange(anchor, focus);
|
|
6208
|
+
}
|
|
6209
|
+
|
|
5450
6210
|
// clearRange(): drop the rectangle (a non-shift navigation / edit-entry collapses any
|
|
5451
6211
|
// range back to a single active cell). Cheap no-op when no range is set (the guard keeps a
|
|
5452
6212
|
// plain navigation with no active range from emitting). B19: when a range DID exist, emit
|
|
@@ -5583,6 +6343,20 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5583
6343
|
|
|
5584
6344
|
// copyRange(): write the current range as TSV to the clipboard (async). No-op when the
|
|
5585
6345
|
// async Clipboard API is unavailable (older/insecure contexts) — a copy is best-effort.
|
|
6346
|
+
// B-07: is the async Clipboard API actually usable? Ctrl+C / Ctrl+X / Ctrl+V used to
|
|
6347
|
+
// `e.preventDefault()` FIRST and only then call a verb that self-guards on
|
|
6348
|
+
// `navigator.clipboard`, so on any page where the API is absent — an insecure `http://` origin,
|
|
6349
|
+
// a permissions policy, an older embedded webview — the browser's OWN copy/cut/paste was
|
|
6350
|
+
// suppressed and nothing took its place. The keystroke did nothing at all. These predicates
|
|
6351
|
+
// move the check into the keymap's branch CONDITION (gridKeydownHandlers.rzts), so an
|
|
6352
|
+
// unavailable API means the branch does not match and the native default runs. Separate
|
|
6353
|
+
// read/write predicates because a context can grant one and not the other.
|
|
6354
|
+
function clipboardWriteAvailable() {
|
|
6355
|
+
return typeof navigator !== 'undefined' && !!navigator.clipboard && typeof navigator.clipboard.writeText === 'function';
|
|
6356
|
+
}
|
|
6357
|
+
function clipboardReadAvailable() {
|
|
6358
|
+
return typeof navigator !== 'undefined' && !!navigator.clipboard && typeof navigator.clipboard.readText === 'function';
|
|
6359
|
+
}
|
|
5586
6360
|
function copyRange() {
|
|
5587
6361
|
// B11: never copy from a header-active state (the reusable clipboard guard).
|
|
5588
6362
|
if (!clipboardActiveAllowed()) return;
|
|
@@ -5601,15 +6375,33 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5601
6375
|
// running fresh array (replaceRowValue) and record the committed cell. After the walk: ONE
|
|
5602
6376
|
// writeData (the single r-model:data write), ONE cell-edit-commit per COMMITTED cell, and the
|
|
5603
6377
|
// N-of-M aria-live announce. Returns { wrote, total }.
|
|
5604
|
-
|
|
6378
|
+
// B-09 — `verb` names the OPERATION in the aria-live summary. This funnel serves paste, cut,
|
|
6379
|
+
// range-delete AND fill, so hardcoding "pasted" made a Cut announce "3 of 3 cells pasted" and a
|
|
6380
|
+
// Delete announce the same — actively wrong for a screen-reader user, who has no other signal
|
|
6381
|
+
// that the destructive operation they invoked actually happened. Defaults to 'pasted' so the
|
|
6382
|
+
// paste path's wording is unchanged.
|
|
6383
|
+
function applyGridToRange(grid: any, originRow: any, originCol: any, verb: any) {
|
|
6384
|
+
const opVerb = typeof verb === 'string' && verb !== '' ? verb : 'pasted';
|
|
5605
6385
|
const maxRow = bodyRowCount() - 1;
|
|
5606
6386
|
const maxCol = visibleColCount() - 1;
|
|
5607
6387
|
if (maxRow < 0 || maxCol < 0) return {
|
|
5608
6388
|
wrote: 0,
|
|
6389
|
+
changed: 0,
|
|
5609
6390
|
total: 0
|
|
5610
6391
|
};
|
|
6392
|
+
// B-08: `applied` and `committed` are DIFFERENT counts and used to be one.
|
|
6393
|
+
// applied — targets whose value now equals what was pasted, INCLUDING the ones that
|
|
6394
|
+
// already did. This is the announce's numerator: from the user's point of
|
|
6395
|
+
// view a cell that already held the pasted value was pasted successfully, and
|
|
6396
|
+
// reporting "3 of 5" for a 5-cell paste where 2 matched would be a lie.
|
|
6397
|
+
// committed — targets whose value actually CHANGED. This drives the write, the per-cell
|
|
6398
|
+
// `cell-edit-commit` emits and (through writeData) the undo step.
|
|
6399
|
+
// Conflating them meant an unchanged cell emitted an edit event and recorded an undo entry:
|
|
6400
|
+
// a 1->5 fill fired 5 commits when 4 cells already held the value, and a Delete over an
|
|
6401
|
+
// already-empty cell pushed an undo step that undoes nothing. Consumers persisting on
|
|
6402
|
+
// `cell-edit-commit` were issuing writes for data that had not moved.
|
|
5611
6403
|
let total = 0;
|
|
5612
|
-
let
|
|
6404
|
+
let applied = 0;
|
|
5613
6405
|
const committed = [];
|
|
5614
6406
|
// Build the fresh data array incrementally so the whole paste is ONE writeData.
|
|
5615
6407
|
let next = currentData();
|
|
@@ -5623,15 +6415,25 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5623
6415
|
// it to this single funnel, so guarding the four callers individually would be four
|
|
5624
6416
|
// chances to miss one.
|
|
5625
6417
|
const rowGrouped = rowIndexIsGrouped(r);
|
|
6418
|
+
// Debug 260926-dtl-edit-guard: a LAZY PLACEHOLDER row (virtual+manual+rowCount, a row not
|
|
6419
|
+
// loaded yet) has no real underlying record — rowOriginalAt(r) returns the placeholder
|
|
6420
|
+
// sentinel ({__rdtLazyPlaceholder:true,index:i}, virtualization.rzts), which is never IN
|
|
6421
|
+
// currentData() (only the consumer's own array is), so sourceIndexOfRow's data.indexOf(...)
|
|
6422
|
+
// is always -1 and falls back to the VISIBLE index — either a no-op write with a FABRICATED
|
|
6423
|
+
// cell-edit-commit, or, inside a sparse hole, the hole gets clobbered with a partial row via
|
|
6424
|
+
// replaceRowValue. Hoisted per-ROW exactly like rowGrouped above (the SAME single placement
|
|
6425
|
+
// closes pasteRange/cutRange/clearActiveRange/fillRange all at once).
|
|
6426
|
+
const rowPlaceholder = rowIsLazyPlaceholder((rows() || [])[r]);
|
|
5626
6427
|
const cols = grid[gr] || [];
|
|
5627
6428
|
for (let gc = 0; gc < cols.length; gc++) {
|
|
5628
6429
|
const c = originCol + gc;
|
|
5629
6430
|
if (c > maxCol) break;
|
|
5630
|
-
// A group-row cell still counts toward `total` — it is a skipped
|
|
5631
|
-
// a non-editable or validator-rejected cell — so the "N of M cells
|
|
5632
|
-
// pasted" announce keeps its existing semantics (never silently
|
|
6431
|
+
// A group-row / placeholder-row cell still counts toward `total` — it is a skipped
|
|
6432
|
+
// TARGET, exactly like a non-editable or validator-rejected cell — so the "N of M cells
|
|
6433
|
+
// pasted" / "no cells pasted" announce keeps its existing semantics (never silently
|
|
6434
|
+
// under-reports M).
|
|
5633
6435
|
total = total + 1;
|
|
5634
|
-
if (rowGrouped) continue;
|
|
6436
|
+
if (rowGrouped || rowPlaceholder) continue;
|
|
5635
6437
|
const colId = columnIdAt(r, c);
|
|
5636
6438
|
if (colId == null || !columnEditable(colId)) continue;
|
|
5637
6439
|
const rowObj = rowOriginalAt(r);
|
|
@@ -5645,6 +6447,10 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5645
6447
|
const field = fieldOfColId(colId);
|
|
5646
6448
|
const srcIndex = sourceIndexOfRow(r);
|
|
5647
6449
|
const oldValue = rowObj ? rowObj[field] : null;
|
|
6450
|
+
applied = applied + 1;
|
|
6451
|
+
// B-08: the cell already holds this value — it counts as applied, but there is nothing
|
|
6452
|
+
// to write, nothing to emit and nothing to undo.
|
|
6453
|
+
if (oldValue === value) continue;
|
|
5648
6454
|
next = replaceRowValue(next, srcIndex, field, value);
|
|
5649
6455
|
committed.push({
|
|
5650
6456
|
rowId: rowIdAt(r),
|
|
@@ -5652,22 +6458,37 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5652
6458
|
oldValue,
|
|
5653
6459
|
newValue: value
|
|
5654
6460
|
});
|
|
5655
|
-
wrote = wrote + 1;
|
|
5656
6461
|
}
|
|
5657
6462
|
}
|
|
5658
|
-
if (
|
|
6463
|
+
if (committed.length > 0) {
|
|
5659
6464
|
editTransition = true;
|
|
5660
6465
|
writeData(next);
|
|
5661
6466
|
editTransition = false;
|
|
5662
6467
|
// One cell-edit-commit per COMMITTED cell (the per-cell event contract, D-03).
|
|
5663
|
-
|
|
6468
|
+
// A-08: each emit is guarded INDIVIDUALLY. These listeners are consumer code, and a single
|
|
6469
|
+
// throwing one used to abort the loop — so on a 40-cell paste, cells 0..k notified and
|
|
6470
|
+
// k+1..39 silently never did, leaving a consumer persisting on this event with a partial
|
|
6471
|
+
// write of a model that had already been fully updated. Measured while building this: a
|
|
6472
|
+
// throwing VALIDATOR is NOT the reachable vehicle here — `runValidator`
|
|
6473
|
+
// (editCellLifecycle.rzts) already catches and reports 'Invalid value' — so the listener
|
|
6474
|
+
// is the first-party code that actually escapes.
|
|
6475
|
+
for (let i = 0; i < committed.length; i++) {
|
|
6476
|
+
try {
|
|
6477
|
+
_props.onCellEditCommit?.(committed[i]);
|
|
6478
|
+
} catch (err: any) {
|
|
6479
|
+
console.error('[rozie-data-table] paste failed partway: a cell-edit-commit listener threw. The ' + 'model was already written; the remaining cells still notify.', err);
|
|
6480
|
+
}
|
|
6481
|
+
}
|
|
5664
6482
|
}
|
|
5665
6483
|
// WR-02: announce the N-of-M summary only when at least one cell was written. When the paste
|
|
5666
6484
|
// targeted real cells but every one was skipped (validation-failed / non-editable), announce a
|
|
5667
6485
|
// distinct validation-failed message instead of a misleading "0 of M cells pasted".
|
|
5668
|
-
if (
|
|
6486
|
+
if (applied > 0) announce(applied + ' of ' + total + ' cells ' + opVerb);else if (total > 0) announce('No cells ' + opVerb + ' — ' + total + ' cells were invalid or read-only');
|
|
6487
|
+
// `wrote` stays the ANNOUNCED count (targets satisfied), not the written one, so every
|
|
6488
|
+
// existing caller and assertion keeps its meaning; `changed` is the new, narrower number.
|
|
5669
6489
|
return {
|
|
5670
|
-
wrote,
|
|
6490
|
+
wrote: applied,
|
|
6491
|
+
changed: committed.length,
|
|
5671
6492
|
total
|
|
5672
6493
|
};
|
|
5673
6494
|
}
|
|
@@ -5714,14 +6535,26 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5714
6535
|
return;
|
|
5715
6536
|
}
|
|
5716
6537
|
if (!p || !p.then) return;
|
|
6538
|
+
// A-08: the two failure modes are separated. A REJECTED clipboard read (permission denied,
|
|
6539
|
+
// insecure context, an empty selection) is a legitimate silent no-op — that is the rejection
|
|
6540
|
+
// handler, the second argument. Anything the fulfilment handler throws is FIRST-PARTY: a
|
|
6541
|
+
// consumer's row accessor, a `data`-model setter, or a `cell-edit-commit` listener (which
|
|
6542
|
+
// `applyGridToRange` now also guards per-emit). A single trailing `.catch(() => {})`
|
|
6543
|
+
// swallowed both, so consumer code failing inside a paste produced no error, no warning and
|
|
6544
|
+
// nothing in the console to search for. NOT a column validator: `runValidator` already
|
|
6545
|
+
// catches a throwing one and reports 'Invalid value' — checked rather than assumed.
|
|
5717
6546
|
p.then((text: any) => {
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
6547
|
+
try {
|
|
6548
|
+
const grid = parseTsv(text);
|
|
6549
|
+
if (!grid.length) return;
|
|
6550
|
+
// C3: tile the clipboard block to fill the destination range (single→range fill,
|
|
6551
|
+
// smaller-tiles-into-larger); a clipboard larger than the box pastes its full block.
|
|
6552
|
+
const tiled = tileGridToBox(grid, destBox);
|
|
6553
|
+
applyGridToRange(tiled, anchorRow, anchorCol, 'pasted');
|
|
6554
|
+
} catch (err: any) {
|
|
6555
|
+
console.error('[rozie-data-table] paste failed: a row accessor, the data model setter or other ' + 'consumer code threw. The paste was abandoned.', err);
|
|
6556
|
+
}
|
|
6557
|
+
}, () => {/* clipboard read rejected (permission / insecure context) — silent by design */});
|
|
5725
6558
|
}
|
|
5726
6559
|
|
|
5727
6560
|
// cutRange(): C3 Cut — copy the current range to the clipboard (rangeToTsv — the SAME escaped
|
|
@@ -5755,7 +6588,7 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5755
6588
|
for (let c = c0; c <= c1; c++) cols.push('');
|
|
5756
6589
|
grid.push(cols);
|
|
5757
6590
|
}
|
|
5758
|
-
applyGridToRange(grid, r0, c0);
|
|
6591
|
+
applyGridToRange(grid, r0, c0, 'cut');
|
|
5759
6592
|
}
|
|
5760
6593
|
|
|
5761
6594
|
// clearActiveRange(): the §7 (260709-3qt) Delete/Backspace clear — cutRange() MINUS the clipboard
|
|
@@ -5781,7 +6614,7 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5781
6614
|
for (let c = c0; c <= c1; c++) cols.push('');
|
|
5782
6615
|
grid.push(cols);
|
|
5783
6616
|
}
|
|
5784
|
-
applyGridToRange(grid, r0, c0);
|
|
6617
|
+
applyGridToRange(grid, r0, c0, 'cleared');
|
|
5785
6618
|
}
|
|
5786
6619
|
|
|
5787
6620
|
// fillRange(sourceBox): drag-fill (D-04 — VALUE-COPY ONLY, no series detection). B7: the fill
|
|
@@ -5836,7 +6669,7 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5836
6669
|
}
|
|
5837
6670
|
grid.push(cols);
|
|
5838
6671
|
}
|
|
5839
|
-
applyGridToRange(grid, box.r0, box.c0);
|
|
6672
|
+
applyGridToRange(grid, box.r0, box.c0, 'filled');
|
|
5840
6673
|
}
|
|
5841
6674
|
|
|
5842
6675
|
// onFillHandlePointerDown: begin a fill-handle drag (req-8 / D-04). The handle sits on the
|
|
@@ -6111,10 +6944,14 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
6111
6944
|
rangeDragging = false;
|
|
6112
6945
|
}
|
|
6113
6946
|
|
|
6114
|
-
// beginRangeDrag(anchorR, anchorC): start a drag-select anchored at the mousedown cell.
|
|
6115
|
-
//
|
|
6116
|
-
//
|
|
6117
|
-
//
|
|
6947
|
+
// beginRangeDrag(anchorR, anchorC): start a drag-select anchored at the mousedown cell.
|
|
6948
|
+
// B-01: the anchor is handed to setRangeFocus EXPLICITLY, never left to be seeded from the
|
|
6949
|
+
// active cell. The mousedown's native focus is a DEFAULT ACTION that runs after this handler
|
|
6950
|
+
// returns, so at this point $data.activeRow/activeColIndex still hold the PRE-mousedown cell;
|
|
6951
|
+
// on React the pointermove closure below is frozen at that render and can never observe the
|
|
6952
|
+
// post-focusin value, so the whole gesture anchored at wherever the user happened to be
|
|
6953
|
+
// (a fresh grid: (0,0)). We still never WRITE $data.rangeAnchor directly (React-stale,
|
|
6954
|
+
// ROZ138) — setRangeFocus owns that write. rangeDragMoved starts
|
|
6118
6955
|
// false and flips true only once the pointer reaches a DIFFERENT cell, so a mousedown-with-no-move
|
|
6119
6956
|
// leaves a single active cell + no range (a normal click). lastCell dedups the many pointermove
|
|
6120
6957
|
// events per cell (setRangeFocus emits range-change — only extend on a NEW cell, mirroring fillDrag's
|
|
@@ -6139,7 +6976,7 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
6139
6976
|
if (cell && (cell.r !== lastCell.r || cell.c !== lastCell.c)) {
|
|
6140
6977
|
lastCell = cell;
|
|
6141
6978
|
rangeDragMoved = true;
|
|
6142
|
-
setRangeFocus$local(cell.r, cell.c);
|
|
6979
|
+
setRangeFocus$local(cell.r, cell.c, anchorR, anchorC);
|
|
6143
6980
|
}
|
|
6144
6981
|
};
|
|
6145
6982
|
const up = () => {
|
|
@@ -6177,6 +7014,8 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
6177
7014
|
// row reaches the group-toggle branch below them instead of opening an editor.
|
|
6178
7015
|
function isActiveCellEditable() {
|
|
6179
7016
|
if (rowIndexIsGrouped(activeRow())) return false;
|
|
7017
|
+
// A lazy placeholder (a row not loaded yet) has nothing to edit (quick 260925-dtl part 2).
|
|
7018
|
+
if (rowIsLazyPlaceholder((rows() || [])[activeRow()])) return false;
|
|
6180
7019
|
const colId = activeCellColumnId();
|
|
6181
7020
|
return colId != null && columnEditable(colId);
|
|
6182
7021
|
}
|
|
@@ -6408,6 +7247,7 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
6408
7247
|
// calls beginEdit directly gated ONLY on columnEditable — isActiveCellEditable (Layer 1)
|
|
6409
7248
|
// does not run on that path at all.
|
|
6410
7249
|
if (rowIndexIsGrouped(rowIndex)) return;
|
|
7250
|
+
if (rowIsLazyPlaceholder((rows() || [])[rowIndex])) return;
|
|
6411
7251
|
const colId = columnIdAt(rowIndex, colIndex);
|
|
6412
7252
|
if (colId == null || !columnEditable(colId)) return;
|
|
6413
7253
|
// A new edit session starts — reset the sync idempotency latch so THIS session's eventual
|
|
@@ -6418,6 +7258,7 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
6418
7258
|
// editor clears any row-edit state so isEditing never resolves both modes for one cell.
|
|
6419
7259
|
setEditingRowIndex(null);
|
|
6420
7260
|
setRowDraft({});
|
|
7261
|
+
rowDraftSync = {};
|
|
6421
7262
|
setEditingRow(rowIndex);
|
|
6422
7263
|
setEditingCol(colIndex);
|
|
6423
7264
|
setDraftValue(seed != null ? seed : cellValueAt(rowIndex, colIndex));
|
|
@@ -6490,6 +7331,7 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
6490
7331
|
function endRowEdit() {
|
|
6491
7332
|
setEditingRowIndex(null);
|
|
6492
7333
|
setRowDraft({});
|
|
7334
|
+
rowDraftSync = {};
|
|
6493
7335
|
setInvalidMsg('');
|
|
6494
7336
|
setActiveInControl(false);
|
|
6495
7337
|
setEditVer(editVer() + 1);
|
|
@@ -6518,7 +7360,28 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
6518
7360
|
// other editor type commits the value verbatim. Idempotent for the #editor drop-in path
|
|
6519
7361
|
// (an already-numeric override passes through; an explicit null stays null).
|
|
6520
7362
|
function coerceCellValue(colId: any, raw: any) {
|
|
6521
|
-
|
|
7363
|
+
const kind = editorTypeOf(colId);
|
|
7364
|
+
// ── C-13 ────────────────────────────────────────────────────────────────────────────
|
|
7365
|
+
// This funnel exists so "no mixed/garbage types ever reach the model" (B9, T-63-03-01), and
|
|
7366
|
+
// it covered ONE of the four built-in editor types. Every path that writes without opening
|
|
7367
|
+
// the built-in control — paste, Cut, Delete/Backspace over a range, a fill-drag — arrives
|
|
7368
|
+
// here with a plain TSV STRING, so a `checkbox` column took `''` from a Delete and the
|
|
7369
|
+
// literal text `'true'` from a paste, straight into a field the consumer declared boolean.
|
|
7370
|
+
// `select` is deliberately NOT coerced: its model type IS the option string, so a string is
|
|
7371
|
+
// already correct there, and an out-of-range option is a validation question (C-03's class),
|
|
7372
|
+
// not a type one.
|
|
7373
|
+
//
|
|
7374
|
+
// Recognised spellings both ways; ANYTHING else — including '' and unrecognised text —
|
|
7375
|
+
// becomes `false`. That is lossy by construction and deterministic by design: the
|
|
7376
|
+
// alternative is letting a non-boolean reach a boolean field, which is the defect. It is
|
|
7377
|
+
// documented in the editing page's emptied-cell table.
|
|
7378
|
+
if (kind === 'checkbox') {
|
|
7379
|
+
if (typeof raw === 'boolean') return raw;
|
|
7380
|
+
if (raw == null) return false;
|
|
7381
|
+
if (typeof raw === 'number') return raw !== 0;
|
|
7382
|
+
return /^\s*(true|1|yes|y|on)\s*$/i.test(String(raw));
|
|
7383
|
+
}
|
|
7384
|
+
if (kind !== 'number') return raw;
|
|
6522
7385
|
if (raw == null) return null;
|
|
6523
7386
|
if (typeof raw === 'number') return Number.isNaN(raw) ? null : raw;
|
|
6524
7387
|
const s = String(raw).trim();
|
|
@@ -6638,11 +7501,17 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
6638
7501
|
// checkbox branch, so this line is redundant AT HEAD — it earns its place by making "no
|
|
6639
7502
|
// write ever targets a group row" a property of the write funnels themselves.
|
|
6640
7503
|
if (rowIndexIsGrouped(activeRow())) return;
|
|
6641
|
-
const colId = columnIdAt(activeRow(), activeColIndex());
|
|
6642
|
-
if (colId == null || !columnEditable(colId)) return;
|
|
6643
7504
|
const rowList = rows() || [];
|
|
6644
7505
|
const row = rowList[activeRow()];
|
|
6645
7506
|
if (!row) return;
|
|
7507
|
+
// Debug 260926-dtl-edit-guard: defense-in-depth, matching the redundant rowIndexIsGrouped
|
|
7508
|
+
// check just above (Layer 4 is reachable ONLY through the Layer-1-gated checkbox branch in
|
|
7509
|
+
// onGridKeyDown, whose isActiveCellEditable() already excludes a placeholder row — this makes
|
|
7510
|
+
// "no write ever targets a placeholder row" a property of the write funnel itself, not just of
|
|
7511
|
+
// its one caller).
|
|
7512
|
+
if (rowIsLazyPlaceholder(row)) return;
|
|
7513
|
+
const colId = columnIdAt(activeRow(), activeColIndex());
|
|
7514
|
+
if (colId == null || !columnEditable(colId)) return;
|
|
6646
7515
|
const rowOriginal = row.original;
|
|
6647
7516
|
const rowId = row.id;
|
|
6648
7517
|
const oldValue = cellValueAt(activeRow(), activeColIndex());
|
|
@@ -6775,6 +7644,13 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
6775
7644
|
// redundant at HEAD; kept for the same reason as Layer 4 — it is a mutation entry point,
|
|
6776
7645
|
// and the natural call site for a future #rowActions "Edit" drop-in button.
|
|
6777
7646
|
if (rowIndexIsGrouped(rowIndex)) return;
|
|
7647
|
+
// Debug 260926-dtl-edit-guard: a LAZY PLACEHOLDER row (a row not loaded yet, virtual+manual+
|
|
7648
|
+
// rowCount) has nothing to edit — beginEdit (editCellLifecycle.rzts) already carries this
|
|
7649
|
+
// guard for single-cell entry; this full-row twin was missing it, so the public editRow()
|
|
7650
|
+
// verb (and, redundantly with isActiveCellEditable's own placeholder check, Shift+F2) could
|
|
7651
|
+
// seed a row editor from a placeholder's undefined fields and commit through the same
|
|
7652
|
+
// sourceIndexOfRow fallback bug applyGridToRange's placeholder skip closes.
|
|
7653
|
+
if (rowIsLazyPlaceholder((rows() || [])[rowIndex])) return;
|
|
6778
7654
|
const editable = editableColumnsForRow(rowIndex);
|
|
6779
7655
|
if (editable.length === 0) return;
|
|
6780
7656
|
// A new edit session starts — reset the sync idempotency latch (see editCellLifecycle.rzts).
|
|
@@ -6794,6 +7670,7 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
6794
7670
|
draft[ec.colId] = orig ? orig[ec.field] : null;
|
|
6795
7671
|
}
|
|
6796
7672
|
setRowDraft(draft);
|
|
7673
|
+
rowDraftSync = draft;
|
|
6797
7674
|
setEditingRowIndex(rowIndex);
|
|
6798
7675
|
setActiveInControl(true);
|
|
6799
7676
|
setEditVer(editVer() + 1);
|
|
@@ -6824,7 +7701,8 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
6824
7701
|
const r = rowList[rowIndex];
|
|
6825
7702
|
const rowOriginal = r ? r.original : null;
|
|
6826
7703
|
const rowId = r ? r.id : null;
|
|
6827
|
-
|
|
7704
|
+
// C-05: the SYNC mirror, not `$data.rowDraft` — see rowDraftSync in DataTable.rozie.
|
|
7705
|
+
const draft = rowDraftSync || {};
|
|
6828
7706
|
// Validate every edited column FIRST (D-01: a single failure blocks the whole row commit).
|
|
6829
7707
|
// B3 (Rule 1): coerce each draft by the column's editor type BEFORE validation + write — a
|
|
6830
7708
|
// 'number' editor must commit a real Number/null, never the raw editor STRING (the single-cell
|
|
@@ -7016,6 +7894,25 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
7016
7894
|
// A top-level `let` (React hoists to useRef → persists).
|
|
7017
7895
|
let committedThisSession = false;
|
|
7018
7896
|
|
|
7897
|
+
// rowDraftSync (quick 260922-mkb, C-05): a SYNCHRONOUS mirror of `$data.rowDraft`, written at
|
|
7898
|
+
// every rowDraft write and read by commitRow. A custom #editor drop-in stages its value with
|
|
7899
|
+
// `commit(v)` → setRowDraft in the SAME keydown that then commits the row (the host's drop-in
|
|
7900
|
+
// wrapper runs in the bubble phase right after the drop-in's own Enter handler). On React
|
|
7901
|
+
// `$data.rowDraft` is a `useState` read, so commitRow in that same tick saw the pre-write draft,
|
|
7902
|
+
// found no change and wrote nothing. The built-in editors never hit this only because they stage
|
|
7903
|
+
// on every `input` event, long before Enter. Same top-level-`let` technique as
|
|
7904
|
+
// committedThisSession above; on the five synchronous targets it always equals $data.rowDraft.
|
|
7905
|
+
let rowDraftSync: Record<string, any> = {};
|
|
7906
|
+
|
|
7907
|
+
// dropinTabFlush (quick 260922-mkb, C-10): non-null only for the duration of the drop-in
|
|
7908
|
+
// wrapper's Tab flush (see onEditorDropinKeyDown). While set, a drop-in's `commit(v)` records
|
|
7909
|
+
// that it ran and whether it committed, and commits WITHOUT the focus return (the Tab handler
|
|
7910
|
+
// moves focus on to the next editor itself).
|
|
7911
|
+
let dropinTabFlush: {
|
|
7912
|
+
called: boolean;
|
|
7913
|
+
committed: boolean;
|
|
7914
|
+
} | null = null;
|
|
7915
|
+
|
|
7019
7916
|
// ── Per-cell editor draft source (req-6) ──────────────────────────────────────────────
|
|
7020
7917
|
// In single-cell mode every editor binds the shared $data.draftValue. In full-row mode
|
|
7021
7918
|
// (editingRowIndex != null) each editable cell owns its OWN draft keyed by columnId in
|
|
@@ -7044,6 +7941,16 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
7044
7941
|
setRowDraft$local(colId, value);
|
|
7045
7942
|
return;
|
|
7046
7943
|
}
|
|
7944
|
+
// C-10: during the drop-in wrapper's Tab flush, record the outcome and skip the focus
|
|
7945
|
+
// return — the Tab handler advances focus to the next editor itself.
|
|
7946
|
+
if (dropinTabFlush) {
|
|
7947
|
+
dropinTabFlush.called = true;
|
|
7948
|
+
// STICKY: only a successful commit may set the outcome. A re-entrant commit within the
|
|
7949
|
+
// same flush (any blur the write itself provokes) is latched to false by commitEdit and
|
|
7950
|
+
// must not clear it. Measured: before the Solid slot-fill fix this re-entry was real.
|
|
7951
|
+
if (commitEdit(value, true)) dropinTabFlush.committed = true;
|
|
7952
|
+
return;
|
|
7953
|
+
}
|
|
7047
7954
|
commitEdit(value);
|
|
7048
7955
|
};
|
|
7049
7956
|
}
|
|
@@ -7081,10 +7988,13 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
7081
7988
|
// setRowDraft: write ONE key into a FRESH rowDraft object (whole-object replace — an
|
|
7082
7989
|
// in-place mutation is silently dropped on React/Solid; the family immutable rule).
|
|
7083
7990
|
function setRowDraft$local(colId: any, value: any) {
|
|
7084
|
-
|
|
7991
|
+
// C-05: build from the SYNC mirror, so two same-tick writes (and a same-tick commitRow) never
|
|
7992
|
+
// see a React-stale `$data.rowDraft`.
|
|
7993
|
+
const src = rowDraftSync || {};
|
|
7085
7994
|
const next = {};
|
|
7086
7995
|
for (const k in src) next[k] = src[k];
|
|
7087
7996
|
next[colId] = value;
|
|
7997
|
+
rowDraftSync = next;
|
|
7088
7998
|
setRowDraft(next);
|
|
7089
7999
|
}
|
|
7090
8000
|
|
|
@@ -7174,6 +8084,105 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
7174
8084
|
}
|
|
7175
8085
|
}
|
|
7176
8086
|
|
|
8087
|
+
// DECLARED AFTER onEditorKeyDown ON PURPOSE. It delegates to it, and the React emitter lowers
|
|
8088
|
+
// both to `useCallback`s whose DEPENDENCY ARRAY is evaluated EAGERLY at declaration time — so
|
|
8089
|
+
// with this one first, its deps array referenced `onEditorKeyDown` before the const existed and
|
|
8090
|
+
// every React cell in the family died with `ReferenceError: Cannot access 'ze' before
|
|
8091
|
+
// initialization` at mount. That is the 87-02 useCallback-deps-array ordering lesson, and it is
|
|
8092
|
+
// invisible on the other five targets. Keep this block below.
|
|
8093
|
+
// ── C-10 + C-05: the grid keymap for a CUSTOM #editor drop-in ───────────────────────────
|
|
8094
|
+
// The built-in editors bind `onEditorKeyDown` directly, so Tab commits-and-advances and, in
|
|
8095
|
+
// row mode, Enter commits the row. A drop-in binds nothing of ours: it is consumer markup
|
|
8096
|
+
// inside the `#editor` slot. Its own keydown handler covers Enter and Escape (the drop-in
|
|
8097
|
+
// contract) and stops there, and the keydown then bubbles to the table root where
|
|
8098
|
+
// `onGridKeyDown` early-returns because an editor is open. Result:
|
|
8099
|
+
// C-10 — Tab in a drop-in did not commit and advance. It fell through to NATIVE tab order
|
|
8100
|
+
// and walked focus out of the grid, leaving the editor open behind it.
|
|
8101
|
+
// C-05 — in FULL-ROW mode a drop-in's `commit(v)` is draft-only BY DESIGN (committing per
|
|
8102
|
+
// cell would split the row into N writes and N events), and the row commits only
|
|
8103
|
+
// through handlers bound on the built-in inputs — which a row of drop-ins does not
|
|
8104
|
+
// have. So Shift+F2, documented as "Enter commits the whole row in one
|
|
8105
|
+
// `r-model:data` write", wrote a draft and nothing else, `editingRowIndex` stayed
|
|
8106
|
+
// set, and `onGridKeyDown`'s early-return left arrow nav dead. The only exits were
|
|
8107
|
+
// Escape (reverting the whole row) and the imperative verb.
|
|
8108
|
+
//
|
|
8109
|
+
// Bound on the `display: contents` span that wraps the slot, so it sees the bubbled event
|
|
8110
|
+
// from whatever the consumer rendered — including a bespoke editor that never heard of this
|
|
8111
|
+
// component. The host owning the GRID keymap while the drop-in owns its own CONTROL is the
|
|
8112
|
+
// same division the built-in path already has.
|
|
8113
|
+
//
|
|
8114
|
+
// Row mode ignores `defaultPrevented` for Enter/Escape on purpose: a drop-in preventDefaults
|
|
8115
|
+
// Enter to mean "do not insert a newline", not "the row is handled". Its own handler has
|
|
8116
|
+
// already run (target phase before bubble), so the draft is staged by the time we commit.
|
|
8117
|
+
// Single-cell mode honours `defaultPrevented` for Tab, so a drop-in that genuinely implements
|
|
8118
|
+
// its own Tab (a multi-field editor) keeps it.
|
|
8119
|
+
//
|
|
8120
|
+
// Single-cell Tab FLUSHES the drop-in before advancing (quick 260922-mkb). A drop-in owns its
|
|
8121
|
+
// draft: the host's `draftValue` still holds the value the editor opened with, so routing Tab
|
|
8122
|
+
// straight into onEditorKeyDown committed that stale value, opened the next cell's editor, and
|
|
8123
|
+
// then the old drop-in's unmount blur ran `commit(typed)` against the NOW-editing cell — the
|
|
8124
|
+
// typed text landed in the NEXT column (measured: `status=Zeta` from a Name edit, on four
|
|
8125
|
+
// targets). Instead the handler blurs the drop-in while its own cell is still the editing one,
|
|
8126
|
+
// so the drop-in's blur-commit (the drop-in contract) writes the right cell, and only then
|
|
8127
|
+
// advances. `dropinTabFlush` tells editorCommitFor to report the outcome and skip the focus
|
|
8128
|
+
// return. A drop-in that does not commit on blur (EditorCheckbox commits on change, so it holds
|
|
8129
|
+
// nothing pending) falls back to the host-draft path, which is exactly right in that case.
|
|
8130
|
+
function onEditorDropinKeyDown(e: any) {
|
|
8131
|
+
if (!e) return;
|
|
8132
|
+
const key = e.key;
|
|
8133
|
+
if (inRowEdit()) {
|
|
8134
|
+
if (key === 'Enter') {
|
|
8135
|
+
e.preventDefault();
|
|
8136
|
+
commitRow();
|
|
8137
|
+
return;
|
|
8138
|
+
}
|
|
8139
|
+
if (key === 'Escape') {
|
|
8140
|
+
e.preventDefault();
|
|
8141
|
+
cancelRow();
|
|
8142
|
+
return;
|
|
8143
|
+
}
|
|
8144
|
+
if (key === 'Tab') {
|
|
8145
|
+
e.preventDefault();
|
|
8146
|
+
rowEditTab(e.target, e.shiftKey);
|
|
8147
|
+
return;
|
|
8148
|
+
}
|
|
8149
|
+
return;
|
|
8150
|
+
}
|
|
8151
|
+
if (key !== 'Tab' || e.defaultPrevented) return;
|
|
8152
|
+
e.preventDefault();
|
|
8153
|
+
const fromRow = editingRow();
|
|
8154
|
+
const fromCol = editingCol();
|
|
8155
|
+
const target = e.shiftKey ? prevEditableCell(fromRow, fromCol) : nextEditableCell(fromRow, fromCol);
|
|
8156
|
+
const el = e.target;
|
|
8157
|
+
// Move focus to the drop-in's OWN cell rather than calling blur(): that still fires the
|
|
8158
|
+
// drop-in's blur-commit, but keeps focus inside the grid instead of dropping it to <body>.
|
|
8159
|
+
const ownCell = el && el.closest ? el.closest('[data-grid-cell]') : null;
|
|
8160
|
+
const flush = {
|
|
8161
|
+
called: false,
|
|
8162
|
+
committed: false
|
|
8163
|
+
};
|
|
8164
|
+
dropinTabFlush = flush;
|
|
8165
|
+
if (ownCell && ownCell.focus) ownCell.focus();else if (el && el.blur) el.blur();
|
|
8166
|
+
dropinTabFlush = null;
|
|
8167
|
+
if (!flush.called) {
|
|
8168
|
+
onEditorKeyDown(e);
|
|
8169
|
+
return;
|
|
8170
|
+
}
|
|
8171
|
+
// A validation failure keeps the editor open: give the drop-in its focus back.
|
|
8172
|
+
if (!flush.committed) {
|
|
8173
|
+
if (el && el.focus) el.focus();
|
|
8174
|
+
return;
|
|
8175
|
+
}
|
|
8176
|
+
if (target) {
|
|
8177
|
+
setActiveRow(target.row);
|
|
8178
|
+
setActiveColIndex(target.col);
|
|
8179
|
+
beginEdit(target.row, target.col, null);
|
|
8180
|
+
} else {
|
|
8181
|
+
// B5 twin: no editable cell in the Tab direction — keep focus INSIDE the grid.
|
|
8182
|
+
focusCellWhenReady(fromRow, fromCol);
|
|
8183
|
+
}
|
|
8184
|
+
}
|
|
8185
|
+
|
|
7177
8186
|
// onEditorBlur: commit on a genuine click/focus-away (D-01 — an invalid value keeps the
|
|
7178
8187
|
// editor open via commitEdit's reject path). SKIP when:
|
|
7179
8188
|
// - editTransition is set (a synchronous commit/cancel teardown is unmounting the editor), or
|
|
@@ -7210,11 +8219,52 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
7210
8219
|
}
|
|
7211
8220
|
if (editingRow() < 0 || editTransition) return;
|
|
7212
8221
|
const next = e ? e.relatedTarget : null;
|
|
7213
|
-
|
|
7214
|
-
//
|
|
7215
|
-
//
|
|
7216
|
-
//
|
|
7217
|
-
|
|
8222
|
+
const fromCell = e && e.target && e.target.closest ? e.target.closest('[data-grid-cell]') : null;
|
|
8223
|
+
// ── B-02 ────────────────────────────────────────────────────────────────────────────
|
|
8224
|
+
// A null relatedTarget used to return unconditionally. That is correct for the WR-04 case
|
|
8225
|
+
// the comment cited — a Tab-advance fires the OLD editor's blur with a TRANSIENT null
|
|
8226
|
+
// relatedTarget while it unmounts, and committing there double-counts — but it is NOT the
|
|
8227
|
+
// only shape. Clicking any non-focusable region of the page (the body, a heading, whitespace)
|
|
8228
|
+
// also blurs the editor with a null relatedTarget, and that returned WITHOUT committing and
|
|
8229
|
+
// WITHOUT endEdit(): the typed value was silently discarded, the editor stayed open, and
|
|
8230
|
+
// `$data.editingRow` stayed >= 0 — which gridKeydownHandlers' `if ($data.editingRow >= 0)
|
|
8231
|
+
// return` reads as "an editor owns the keyboard", so arrows/Enter/F2/Home/Ctrl+C were all
|
|
8232
|
+
// dead until the user double-clicked somewhere else. There is no document-level click-away
|
|
8233
|
+
// listener anywhere in the family to catch it afterwards.
|
|
8234
|
+
//
|
|
8235
|
+
// The two shapes ARE separable, with the discriminator the branch below already uses: on a
|
|
8236
|
+
// Tab-advance the keyboard path has already moved the editing pair to the NEXT cell, so the
|
|
8237
|
+
// blurring editor's own cell no longer matches ($data.editingRow/editingCol), while a
|
|
8238
|
+
// click-away leaves the pair still sitting on it. The row-edit branch twenty lines above
|
|
8239
|
+
// reaches the same conclusion by the same reasoning and commits on null; this is that branch's
|
|
8240
|
+
// single-cell twin.
|
|
8241
|
+
if (next == null) {
|
|
8242
|
+
// Not a click-away unless a pointerdown OUTSIDE the grid just said so. `outsidePointerDown`
|
|
8243
|
+
// is the discriminator, and nothing weaker works: an editor recycled out of the virtual
|
|
8244
|
+
// window (or replaced by Solid's keyed-row reconcile) blurs with a null relatedTarget too,
|
|
8245
|
+
// and `closest()` still resolves an owning `<td>` inside a DETACHED subtree, so every
|
|
8246
|
+
// synchronous DOM test reads it as a click-away — measured, it closed the pinned editor in
|
|
8247
|
+
// data-table-edit's "open editor + index-based range survive virtualization recycle" case on
|
|
8248
|
+
// solid. Deferring a frame does not help either: by then the node is reattached, focus sits
|
|
8249
|
+
// on <body>, and the state is indistinguishable from a real click-away.
|
|
8250
|
+
//
|
|
8251
|
+
// The flag is a plain module-let set by the document pointerdown listener in DataTable.rozie
|
|
8252
|
+
// (NOT a $data field, and NOT a closure over one): a document listener is attached once, so
|
|
8253
|
+
// on React any `$data` it closes over is frozen at attach time, and a commit driven from
|
|
8254
|
+
// that closure would write the draft as of editor-OPEN and lose everything typed. Routing
|
|
8255
|
+
// the decision back through THIS handler — template-bound, so React re-binds it every
|
|
8256
|
+
// render — keeps the commit on the always-fresh path. Consume it here so an unmount blur
|
|
8257
|
+
// arriving later can never inherit it.
|
|
8258
|
+
if (!outsidePointerDown) return;
|
|
8259
|
+
outsidePointerDown = false;
|
|
8260
|
+
if (!fromCell) return;
|
|
8261
|
+
if (fromCell.getAttribute('data-row') !== String(editingRow()) || fromCell.getAttribute('data-col-index') !== String(editingCol())) return;
|
|
8262
|
+
// Genuine click-away onto a non-focusable region outside the grid → commit + close (D-01's
|
|
8263
|
+
// reject path keeps the editor open on an invalid value, exactly as for a click onto another
|
|
8264
|
+
// cell). skipFocusReturn: the user deliberately put focus elsewhere; do not yank it back.
|
|
8265
|
+
commitEdit(undefined, true);
|
|
8266
|
+
return;
|
|
8267
|
+
}
|
|
7218
8268
|
// Focus moving OUTSIDE the grid (a click into another widget) → commit (D-01 reject keeps
|
|
7219
8269
|
// the editor open on an invalid value).
|
|
7220
8270
|
if (!(gridRoot && gridRoot.contains && gridRoot.contains(next))) {
|
|
@@ -7225,7 +8275,6 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
7225
8275
|
// keyboard handler already committed) from a genuine click-away to ANOTHER grid cell
|
|
7226
8276
|
// (which must commit + close so the grid is not wedged with an open editor).
|
|
7227
8277
|
const nextCell = next.closest ? next.closest('[data-grid-cell]') : null;
|
|
7228
|
-
const fromCell = e && e.target && e.target.closest ? e.target.closest('[data-grid-cell]') : null;
|
|
7229
8278
|
// Same cell (an inner control / the editing cell itself on an Enter focus-return) → a
|
|
7230
8279
|
// controlled move; skip. Also skip when either cell can't be resolved (an unmounting
|
|
7231
8280
|
// editor has no owning cell — the Tab-advance remount-blur path, never a click-away).
|
|
@@ -7560,6 +8609,16 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
7560
8609
|
if (id == null) return;
|
|
7561
8610
|
const key = String(id);
|
|
7562
8611
|
if (key === '__proto__' || key === 'constructor' || key === 'prototype') return;
|
|
8612
|
+
// C-09: skip the state write when the incoming spec is EQUIVALENT to the stored one.
|
|
8613
|
+
// `<Column>`'s re-register watch keys on reference-typed props whose documented wiring is
|
|
8614
|
+
// an inline literal — a fresh identity every consumer render — so without this guard every
|
|
8615
|
+
// render whole-object-replaced `$data.colReg`, which the parent's re-feed watch keys on,
|
|
8616
|
+
// which re-rendered the parent, which produced a new identity: a feedback loop, not a slow
|
|
8617
|
+
// path. Guarded HERE rather than in the watch because this is the single seam every
|
|
8618
|
+
// registration passes through, and because value-equality is the correct test for the
|
|
8619
|
+
// registry's own purpose (has anything the table renders from actually changed?).
|
|
8620
|
+
const prev = colReg() ? (colReg() as Record<string, any>)[key] : undefined;
|
|
8621
|
+
if (prev !== undefined && columnSpecsEquivalent(prev, spec)) return;
|
|
7563
8622
|
setColReg({
|
|
7564
8623
|
...colReg(),
|
|
7565
8624
|
[key]: spec
|
|
@@ -7580,7 +8639,7 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
7580
8639
|
|
|
7581
8640
|
<div class={"rdt-column-defs"} style={{ display: "none" }} aria-hidden="true" data-rozie-s-d5dcab4c="">{resolved()}</div>
|
|
7582
8641
|
|
|
7583
|
-
{<Show when={!!invalidMsg()}><div class={"rdt-sr-live"} role="status" aria-live="polite" aria-atomic="true" data-rozie-s-d5dcab4c="">{invalidMsg()}</div></Show>}{<Show when={!!pasteAnnounce()}><div class={"rdt-sr-live rdt-sr-paste"} data-testid="paste-announce" role="status" aria-live="polite" aria-atomic="true" data-rozie-s-d5dcab4c="">{pasteAnnounce()}</div></Show>}{<Show when={!!liveAnnounce()}><div class={"rdt-sr-live rdt-sr-sortfilter"} data-testid="sortfilter-announce" role="status" aria-live="polite" aria-atomic="true" data-rozie-s-d5dcab4c="">{liveAnnounce()}</div></Show>}<div class={"rdt-toolbar"} data-rozie-s-d5dcab4c="">
|
|
8642
|
+
{<Show when={!!invalidMsg()}><div class={"rdt-sr-live"} role="status" aria-live="polite" aria-atomic="true" data-rozie-s-d5dcab4c="">{invalidMsg()}</div></Show>}{<Show when={!!pasteAnnounce()}><div class={"rdt-sr-live rdt-sr-paste"} data-testid="paste-announce" role="status" aria-live="polite" aria-atomic="true" data-rozie-s-d5dcab4c="">{pasteAnnounce()}</div></Show>}{<Show when={!!rangeAnnounce()}><div class={"rdt-sr-live rdt-sr-range"} data-testid="range-announce" role="status" aria-live="polite" aria-atomic="true" data-rozie-s-d5dcab4c="">{rangeAnnounce()}</div></Show>}{<Show when={!!liveAnnounce()}><div class={"rdt-sr-live rdt-sr-sortfilter"} data-testid="sortfilter-announce" role="status" aria-live="polite" aria-atomic="true" data-rozie-s-d5dcab4c="">{liveAnnounce()}</div></Show>}<div class={"rdt-toolbar"} data-rozie-s-d5dcab4c="">
|
|
7584
8643
|
<input type="text" role="searchbox" aria-label="Search table" class={"rdt-global-filter"} value={globalFilterValue()} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onGlobalFilterInput($event); }} data-rozie-s-d5dcab4c="" />
|
|
7585
8644
|
|
|
7586
8645
|
{<Show when={allLeafColumns().length}><details class={"rdt-colvis"} data-rozie-s-d5dcab4c="">
|
|
@@ -7596,10 +8655,10 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
7596
8655
|
|
|
7597
8656
|
{<Show when={local.groupable}><div class={"rdt-group-bar-host"} data-rozie-s-d5dcab4c="">
|
|
7598
8657
|
{(_props.groupBarSlot ?? _props.slots?.['groupBar'])?.({ get grouping() { return groupingKeys(); }, get groupableColumns() { return groupableColumns(); }, applyGrouping, clearGrouping }) ?? <Key each={groupingKeys() as readonly any[]} by={(gk) => gk}>{(gk) => <span class={"rdt-group-token"} data-group-token="" data-rozie-s-d5dcab4c="">{rozieDisplay(gk())}</span>}</Key>}
|
|
7599
|
-
</div></Show>}{<Show when={isWindowed()} fallback={<table aria-rowcount={rozieAttr(gridAriaRowCount())} class={"rozie-data-table" + " " + rozieClass({ 'rdt-sticky': local.stickyHeader })} role={rozieAttr(tableRole())} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridKeyDown($event); }} onFocusIn={($event: FocusEvent & { currentTarget: HTMLTableElement; target: Element }) => { syncActiveFromEvent($event); }} onFocusOut={($event: FocusEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridFocusOut($event); }} onMouseDown={($event: MouseEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridMouseDown($event); }} onDblClick={($event: MouseEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridDblClick($event); }} onClick={($event: MouseEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridClick($event); }} data-rozie-s-d5dcab4c="">
|
|
8658
|
+
</div></Show>}{<Show when={isWindowed()} fallback={<table aria-rowcount={rozieAttr(gridAriaRowCount())} aria-colcount={rozieAttr(gridAriaColCount())} class={"rozie-data-table" + " " + rozieClass({ 'rdt-sticky': local.stickyHeader })} role={rozieAttr(tableRole())} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridKeyDown($event); }} onFocusIn={($event: FocusEvent & { currentTarget: HTMLTableElement; target: Element }) => { syncActiveFromEvent($event); }} onFocusOut={($event: FocusEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridFocusOut($event); }} onMouseDown={($event: MouseEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridMouseDown($event); }} onDblClick={($event: MouseEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridDblClick($event); }} onClick={($event: MouseEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridClick($event); }} data-rozie-s-d5dcab4c="">
|
|
7600
8659
|
<thead class={"rdt-thead"} role="rowgroup" data-rozie-s-d5dcab4c="">
|
|
7601
8660
|
<Key each={headerGroups() as readonly any[]} by={(hg) => hg.id}>{(hg, hgLevel) => <tr class={"rdt-tr"} role="row" aria-rowindex={rozieAttr(hgLevel() + 1)} data-rozie-s-d5dcab4c="">
|
|
7602
|
-
<Key each={hg().headers as readonly any[]} by={(header) => header.id}>{(header) => <th class={"rdt-th" + " " + rozieClass({ 'rdt-select-th': isSelectColumn(header().column.id), 'rdt-expander-th': isExpanderColumn(header().column.id), 'rdt-th-resizing': columnIsResizing(header().column.id), 'rdt-cell-active': isActiveCell('__header', headerColIndexOf(hg(), header()), hgLevel()) })} role="columnheader" data-col={rozieAttr(header().column.id)} data-grid-cell="" data-row="__header" data-header-level={rozieAttr(hgLevel())} colSpan={rozieAttr(header().colSpan > 1 ? header().colSpan : null)} data-col-index={rozieAttr(headerColIndexOf(hg(), header()))} tabIndex={rozieAttr(cellTabindex('__header', headerColIndexOf(hg(), header()), hgLevel()))} aria-sort={rozieAttr(ariaSortFor(header().column.id))} style={parseInlineStyle(thStyle(header()))} data-rozie-s-d5dcab4c="">
|
|
8661
|
+
<Key each={hg().headers as readonly any[]} by={(header) => header.id}>{(header) => <th class={"rdt-th" + " " + rozieClass({ 'rdt-select-th': isSelectColumn(header().column.id), 'rdt-expander-th': isExpanderColumn(header().column.id), 'rdt-pinned': columnPinSide(header().column.id) !== '', 'rdt-th-resizing': columnIsResizing(header().column.id), 'rdt-cell-active': isActiveCell('__header', headerColIndexOf(hg(), header()), hgLevel()) })} role="columnheader" data-col={rozieAttr(header().column.id)} data-grid-cell="" data-row="__header" data-header-level={rozieAttr(hgLevel())} colSpan={rozieAttr(header().colSpan > 1 ? header().colSpan : null)} data-col-index={rozieAttr(headerColIndexOf(hg(), header()))} aria-colindex={rozieAttr(headerLeafStart(hg(), header()) + 1)} tabIndex={rozieAttr(cellTabindex('__header', headerColIndexOf(hg(), header()), hgLevel()))} aria-sort={rozieAttr(ariaSortFor(header().column.id))} style={parseInlineStyle(thStyle(header()))} data-rozie-s-d5dcab4c="">
|
|
7603
8662
|
|
|
7604
8663
|
|
|
7605
8664
|
{<Show when={isSelectColumn(header().column.id)} fallback={<Show when={isExpanderColumn(header().column.id)} fallback={<span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
@@ -7631,10 +8690,10 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
7631
8690
|
</tr>}</Key>
|
|
7632
8691
|
|
|
7633
8692
|
{<Show when={hasAnyFilterableColumn()}><tr class={"rdt-filter-row"} data-rozie-s-d5dcab4c="">
|
|
7634
|
-
<Key each={headerGroups()[headerGroups().length - 1].headers as readonly any[]} by={(header) => header.id}>{(header) => <th class={"rdt-filter-cell"} role="presentation" style={parseInlineStyle(pinStyle(header().column.id))} data-rozie-s-d5dcab4c="">
|
|
8693
|
+
<Key each={headerGroups()[headerGroups().length - 1].headers as readonly any[]} by={(header) => header.id}>{(header) => <th class={"rdt-filter-cell" + " " + rozieClass({ 'rdt-pinned': columnPinSide(header().column.id) !== '' })} role="presentation" style={parseInlineStyle(pinStyle(header().column.id))} data-rozie-s-d5dcab4c="">
|
|
7635
8694
|
{<Show when={isSelectColumn(header().column.id)} fallback={<Show when={isExpanderColumn(header().column.id)} fallback={<span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7636
8695
|
{<Show when={columnIsFilterable(header().column.id)}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7637
|
-
{_props.slots?.[`filter-${header().column.id}`]?.({ get columnId() { return header().column.id; }, get value() { return columnFilterValue(header().column.id); }, get uniqueValues() { return getFacetedUniqueValues(header().column.id); }, get minMax() { return getFacetedMinMaxValues(header().column.id); }, get setFilter() { return setColumnFilter; } }) ?? (_props.filterSlot ?? _props.slots?.['filter'])?.({ get columnId() { return header().column.id; }, get value() { return columnFilterValue(header().column.id); }, get uniqueValues() { return getFacetedUniqueValues(header().column.id); }, get minMax() { return getFacetedMinMaxValues(header().column.id); }, get setFilter() { return setColumnFilter; } }) ?? <input type="text" aria-label={rozieAttr('Filter ' + headerLabel(header().column.id))} class={"rdt-col-filter"} value={columnFilterValue(header().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onColumnFilterInput(header().column.id, $event); }} onClick={($event: MouseEvent & { currentTarget: HTMLInputElement; target: Element }) => { stopEvent($event); }} data-rozie-s-d5dcab4c="" />}
|
|
8696
|
+
{_props.slots?.[`filter-${header().column.id}`]?.({ get columnId() { return header().column.id; }, get value() { return columnFilterValue(header().column.id); }, get uniqueValues() { return getFacetedUniqueValues(header().column.id); }, get minMax() { return getFacetedMinMaxValues(header().column.id); }, get columnLabel() { return headerLabel(header().column.id); }, get setFilter() { return setColumnFilter; } }) ?? (_props.filterSlot ?? _props.slots?.['filter'])?.({ get columnId() { return header().column.id; }, get value() { return columnFilterValue(header().column.id); }, get uniqueValues() { return getFacetedUniqueValues(header().column.id); }, get minMax() { return getFacetedMinMaxValues(header().column.id); }, get columnLabel() { return headerLabel(header().column.id); }, get setFilter() { return setColumnFilter; } }) ?? <input type="text" aria-label={rozieAttr('Filter ' + headerLabel(header().column.id))} class={"rdt-col-filter"} value={columnFilterValue(header().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onColumnFilterInput(header().column.id, $event); }} onClick={($event: MouseEvent & { currentTarget: HTMLInputElement; target: Element }) => { stopEvent($event); }} data-rozie-s-d5dcab4c="" />}
|
|
7638
8697
|
</span></Show>}</span>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Show>}</th>}</Key>
|
|
7639
8698
|
</tr></Show>}</thead>
|
|
7640
8699
|
|
|
@@ -7642,16 +8701,16 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
7642
8701
|
|
|
7643
8702
|
<Key each={rows() as readonly any[]} by={(row) => row.id}>{(row) => <>
|
|
7644
8703
|
<tr class={"rdt-tr" + " " + rozieClass({ 'rdt-group-header': rowIsGrouped(row()) })} role="row" data-depth={rozieAttr(row().depth)} aria-rowindex={rozieAttr(bodyAriaRowIndex(row()))} data-group-header={rozieAttr(rowIsGrouped(row()) ? row().id : null)} data-group-leaf={rozieAttr(groupingActive() && !rowIsGrouped(row()) ? row().id : null)} aria-expanded={(rowIsGrouped(row()) ? !!rowIsExpanded(row()) : null) ?? undefined} aria-selected={(local.selectionMode !== 'none' ? !!rowIsSelected(row()) : null) ?? undefined} aria-level={rozieAttr(groupingActive() ? row().depth + 1 : null)} data-rozie-s-d5dcab4c="">
|
|
7645
|
-
<Key each={visibleCellsFor(row()) as readonly any[]} by={(cell) => cell.id}>{(cell) => <td class={"rdt-td" + " " + rozieClass({ 'rdt-select-td': isSelectColumn(cell().column.id), 'rdt-expander-td': isExpanderColumn(cell().column.id), 'rdt-in-range': inRange(rowIndexOf(row()), colIndexOf(row(), cell())), 'rdt-cell-active': isActiveCell(String(rowIndexOf(row())), colIndexOf(row(), cell())) })} role={rozieAttr(cellRole())} data-col={rozieAttr(cell().column.id)} data-grid-cell="" data-row={rozieAttr(rowIndexOf(row()))} data-col-index={rozieAttr(colIndexOf(row(), cell()))} tabIndex={rozieAttr(cellTabindex(String(rowIndexOf(row())), colIndexOf(row(), cell())))} style={parseInlineStyle(bodyCellStyle(row(), cell().column.id))} aria-invalid={rozieAttr(cellAriaInvalid(rowIndexOf(row()), colIndexOf(row(), cell())))} data-in-range={rozieAttr(inRange(rowIndexOf(row()), colIndexOf(row(), cell())) ? 'true' : null)} data-agg-cell={rozieAttr(cellIsAggregated(cell()) ? cell().column.id : null)} data-rozie-s-d5dcab4c="">
|
|
8704
|
+
<Key each={visibleCellsFor(row()) as readonly any[]} by={(cell) => cell.id}>{(cell) => <td class={"rdt-td" + " " + rozieClass({ 'rdt-select-td': isSelectColumn(cell().column.id), 'rdt-expander-td': isExpanderColumn(cell().column.id), 'rdt-pinned': columnPinSide(cell().column.id) !== '', 'rdt-in-range': inRange(rowIndexOf(row()), colIndexOf(row(), cell())), 'rdt-cell-active': isActiveCell(String(rowIndexOf(row())), colIndexOf(row(), cell())) })} role={rozieAttr(cellRole())} data-col={rozieAttr(cell().column.id)} data-grid-cell="" data-row={rozieAttr(rowIndexOf(row()))} data-col-index={rozieAttr(colIndexOf(row(), cell()))} tabIndex={rozieAttr(cellTabindex(String(rowIndexOf(row())), colIndexOf(row(), cell())))} style={parseInlineStyle(bodyCellStyle(row(), cell().column.id))} aria-invalid={rozieAttr(cellAriaInvalid(rowIndexOf(row()), colIndexOf(row(), cell())))} aria-colindex={rozieAttr(colIndexOf(row(), cell()) + 1)} aria-selected={(inRange(rowIndexOf(row()), colIndexOf(row(), cell())) ? 'true' : null) ?? undefined} data-in-range={rozieAttr(inRange(rowIndexOf(row()), colIndexOf(row(), cell())) ? 'true' : null)} data-agg-cell={rozieAttr(cellIsAggregated(cell()) ? cell().column.id : null)} data-rozie-s-d5dcab4c="">
|
|
7646
8705
|
|
|
7647
8706
|
{<Show when={isExpanderColumn(cell().column.id)} fallback={<Show when={isSelectColumn(cell().column.id)} fallback={<Show when={cellIsGrouped(cell())} fallback={<Show when={isEditing(rowIndexOf(row()), colIndexOf(row(), cell()))} fallback={<Show when={cellIsPlaceholder(cell())} fallback={<span class={"rdt-cell-value"} data-rozie-s-d5dcab4c="">
|
|
7648
8707
|
{_props.slots?.[`cell-${cell().column.id}`]?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(row()); }, get value() { return cell().getValue(); } }) ?? (_props.cellSlot ?? _props.slots?.['cell'])?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(row()); }, get value() { return cell().getValue(); } }) ?? rozieDisplay(cell().getValue())}
|
|
7649
8708
|
</span>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7650
|
-
{<Show when={editorTypeOf(cell().column.id) === 'number'} fallback={<Show when={editorTypeOf(cell().column.id) === 'select'} fallback={<Show when={editorTypeOf(cell().column.id) === 'checkbox'} fallback={<Show when={editorTypeOf(cell().column.id) === 'custom'} fallback={<input type="text" data-editing-cell="" data-builtin-editor="" class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" />}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7651
|
-
{_props.slots?.[`editor-${cell().column.id}`]?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(row()); }, get value() { return editorValueFor(cell().column.id); }, get commit() { return editorCommitFor(cell().column.id); }, get cancel() { return editorCancelFor(); }, get autofocus() { return editorAutofocusFor(cell().column.id, rowIndexOf(row())); } }) ?? (_props.editorSlot ?? _props.slots?.['editor'])?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(row()); }, get value() { return editorValueFor(cell().column.id); }, get commit() { return editorCommitFor(cell().column.id); }, get cancel() { return editorCancelFor(); }, get autofocus() { return editorAutofocusFor(cell().column.id, rowIndexOf(row())); } }) ?? <input type="text" data-editing-cell="" data-builtin-editor="" class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" />}
|
|
7652
|
-
</span></Show>}><input type="checkbox" data-editing-cell="" data-builtin-editor="" class={"rdt-cell-editor"} checked={editorCheckedFor(cell().column.id)} onChange={($event: Event & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorCheckbox(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" /></Show>}><select data-editing-cell="" data-builtin-editor="" class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onChange={($event: Event & { currentTarget: HTMLSelectElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLSelectElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLSelectElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="">
|
|
8709
|
+
{<Show when={editorTypeOf(cell().column.id) === 'number'} fallback={<Show when={editorTypeOf(cell().column.id) === 'select'} fallback={<Show when={editorTypeOf(cell().column.id) === 'checkbox'} fallback={<Show when={editorTypeOf(cell().column.id) === 'custom'} fallback={<input type="text" data-editing-cell="" data-builtin-editor="" aria-invalid={rozieAttr(invalidMsg() ? 'true' : null)} class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" />}><span style={{ display: "contents" }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLSpanElement; target: Element }) => { onEditorDropinKeyDown($event); }} data-rozie-s-d5dcab4c="">
|
|
8710
|
+
{_props.slots?.[`editor-${cell().column.id}`]?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(row()); }, get value() { return editorValueFor(cell().column.id); }, get commit() { return editorCommitFor(cell().column.id); }, get cancel() { return editorCancelFor(); }, get columnLabel() { return headerLabel(cell().column.id); }, get autofocus() { return editorAutofocusFor(cell().column.id, rowIndexOf(row())); } }) ?? (_props.editorSlot ?? _props.slots?.['editor'])?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(row()); }, get value() { return editorValueFor(cell().column.id); }, get commit() { return editorCommitFor(cell().column.id); }, get cancel() { return editorCancelFor(); }, get columnLabel() { return headerLabel(cell().column.id); }, get autofocus() { return editorAutofocusFor(cell().column.id, rowIndexOf(row())); } }) ?? <input type="text" data-editing-cell="" data-builtin-editor="" aria-invalid={rozieAttr(invalidMsg() ? 'true' : null)} class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" />}
|
|
8711
|
+
</span></Show>}><input type="checkbox" data-editing-cell="" data-builtin-editor="" aria-invalid={rozieAttr(invalidMsg() ? 'true' : null)} class={"rdt-cell-editor"} checked={editorCheckedFor(cell().column.id)} onChange={($event: Event & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorCheckbox(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" /></Show>}><select data-editing-cell="" data-builtin-editor="" aria-invalid={rozieAttr(invalidMsg() ? 'true' : null)} class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onChange={($event: Event & { currentTarget: HTMLSelectElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLSelectElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLSelectElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="">
|
|
7653
8712
|
<Key each={editorOptionsOf(cell().column.id) as readonly any[]} by={(opt) => opt.value}>{(opt) => <option value={rozieAttr(opt().value)} data-rozie-s-d5dcab4c="">{rozieDisplay(opt().label)}</option>}</Key>
|
|
7654
|
-
</select></Show>}><input type="number" data-editing-cell="" data-builtin-editor="" class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" /></Show>}</span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
8713
|
+
</select></Show>}><input type="number" data-editing-cell="" data-builtin-editor="" aria-invalid={rozieAttr(invalidMsg() ? 'true' : null)} class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" /></Show>}</span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7655
8714
|
<button type="button" data-expander="" aria-expanded={!!rowIsExpanded(row())} aria-label={rozieAttr(rowIsExpanded(row()) ? 'Collapse group' : 'Expand group')} class={"rdt-expander rdt-group-toggle"} onClick={($event: MouseEvent & { currentTarget: HTMLButtonElement; target: Element }) => { onToggleExpand(row(), $event); }} data-rozie-s-d5dcab4c="">{rozieDisplay(rowIsExpanded(row()) ? '▾' : '▸')}</button>
|
|
7656
8715
|
<span class={"rdt-group-value"} data-rozie-s-d5dcab4c="">
|
|
7657
8716
|
{_props.slots?.[`cell-${cell().column.id}`]?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(row()); }, get value() { return cell().getValue(); } }) ?? (_props.cellSlot ?? _props.slots?.['cell'])?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(row()); }, get value() { return cell().getValue(); } }) ?? rozieDisplay(cell().getValue())}
|
|
@@ -7670,11 +8729,11 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
7670
8729
|
</tr></Show>}</>}</Key>
|
|
7671
8730
|
</tbody>
|
|
7672
8731
|
</table>}><div class={"rdt-scroll"} style={parseInlineStyle(rowsWindowed() && local.maxHeight ? 'max-height:' + local.maxHeight + ';overflow:auto;--rozie-data-table-max-height:' + local.maxHeight : 'overflow:auto')} data-rozie-s-d5dcab4c="">
|
|
7673
|
-
<table aria-rowcount={rozieAttr(gridAriaRowCount())} class={"rozie-data-table" + " " + rozieClass({ 'rdt-sticky': local.stickyHeader, 'rdt-col-windowed': colsWindowed() })} role={rozieAttr(tableRole())} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridKeyDown($event); }} onFocusIn={($event: FocusEvent & { currentTarget: HTMLTableElement; target: Element }) => { syncActiveFromEvent($event); }} onFocusOut={($event: FocusEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridFocusOut($event); }} onMouseDown={($event: MouseEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridMouseDown($event); }} onDblClick={($event: MouseEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridDblClick($event); }} onClick={($event: MouseEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridClick($event); }} data-rozie-s-d5dcab4c="">
|
|
8732
|
+
<table aria-rowcount={rozieAttr(gridAriaRowCount())} aria-colcount={rozieAttr(gridAriaColCount())} class={"rozie-data-table" + " " + rozieClass({ 'rdt-sticky': local.stickyHeader, 'rdt-col-windowed': colsWindowed() })} role={rozieAttr(tableRole())} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridKeyDown($event); }} onFocusIn={($event: FocusEvent & { currentTarget: HTMLTableElement; target: Element }) => { syncActiveFromEvent($event); }} onFocusOut={($event: FocusEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridFocusOut($event); }} onMouseDown={($event: MouseEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridMouseDown($event); }} onDblClick={($event: MouseEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridDblClick($event); }} onClick={($event: MouseEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridClick($event); }} data-rozie-s-d5dcab4c="">
|
|
7674
8733
|
<thead class={"rdt-thead"} role="rowgroup" data-rozie-s-d5dcab4c="">
|
|
7675
8734
|
<Key each={headerGroups() as readonly any[]} by={(hg) => hg.id}>{(hg, hgLevel) => <tr class={"rdt-tr"} role="row" aria-rowindex={rozieAttr(hgLevel() + 1)} data-rozie-s-d5dcab4c="">
|
|
7676
8735
|
|
|
7677
|
-
{<Show when={colsWindowed()}><th class={"rdt-col-spacer"} aria-hidden="true" style={parseInlineStyle('width:' + colPadLeft() + 'px;padding:0;border:0')} data-rozie-s-d5dcab4c="" /></Show>}<Key each={windowedHeadersFor(hg(), hgLevel()) as readonly any[]} by={(wh) => wh.header.id}>{(wh) => <th class={"rdt-th" + " " + rozieClass({ 'rdt-select-th': isSelectColumn(wh().header.column.id), 'rdt-expander-th': isExpanderColumn(wh().header.column.id), 'rdt-th-resizing': columnIsResizing(wh().header.column.id), 'rdt-cell-active': isActiveCell('__header', headerColIndexOf(hg(), wh().header), hgLevel()) })} role="columnheader" data-col={rozieAttr(wh().header.column.id)} data-grid-cell="" data-row="__header" data-header-level={rozieAttr(hgLevel())} colSpan={rozieAttr(wh().span > 1 ? wh().span : null)} data-col-index={rozieAttr(headerColIndexOf(hg(), wh().header))} tabIndex={rozieAttr(cellTabindex('__header', headerColIndexOf(hg(), wh().header), hgLevel()))} aria-sort={rozieAttr(ariaSortFor(wh().header.column.id))} style={parseInlineStyle(thStyle(wh().header))} data-rozie-s-d5dcab4c="">
|
|
8736
|
+
{<Show when={colsWindowed()}><th class={"rdt-col-spacer"} aria-hidden="true" style={parseInlineStyle('width:' + colPadLeft() + 'px;padding:0;border:0')} data-rozie-s-d5dcab4c="" /></Show>}<Key each={windowedHeadersFor(hg(), hgLevel()) as readonly any[]} by={(wh) => wh.header.id}>{(wh) => <th class={"rdt-th" + " " + rozieClass({ 'rdt-select-th': isSelectColumn(wh().header.column.id), 'rdt-expander-th': isExpanderColumn(wh().header.column.id), 'rdt-pinned': columnPinSide(wh().header.column.id) !== '', 'rdt-th-resizing': columnIsResizing(wh().header.column.id), 'rdt-cell-active': isActiveCell('__header', headerColIndexOf(hg(), wh().header), hgLevel()) })} role="columnheader" data-col={rozieAttr(wh().header.column.id)} data-grid-cell="" data-row="__header" data-header-level={rozieAttr(hgLevel())} colSpan={rozieAttr(wh().span > 1 ? wh().span : null)} data-col-index={rozieAttr(headerColIndexOf(hg(), wh().header))} aria-colindex={rozieAttr(headerLeafStart(hg(), wh().header) + 1)} tabIndex={rozieAttr(cellTabindex('__header', headerColIndexOf(hg(), wh().header), hgLevel()))} aria-sort={rozieAttr(ariaSortFor(wh().header.column.id))} style={parseInlineStyle(thStyle(wh().header, wh().width))} data-rozie-s-d5dcab4c="">
|
|
7678
8737
|
{<Show when={isSelectColumn(wh().header.column.id)} fallback={<Show when={isExpanderColumn(wh().header.column.id)} fallback={<span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7679
8738
|
{<Show when={wh().header.column.getCanSort && wh().header.column.getCanSort()} fallback={<span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7680
8739
|
<span class={"rdt-header-label"} data-rozie-s-d5dcab4c="">
|
|
@@ -7702,10 +8761,10 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
7702
8761
|
{<Show when={colsWindowed()}><th class={"rdt-col-spacer"} aria-hidden="true" style={parseInlineStyle('width:' + colPadRight() + 'px;padding:0;border:0')} data-rozie-s-d5dcab4c="" /></Show>}</tr>}</Key>
|
|
7703
8762
|
|
|
7704
8763
|
{<Show when={hasAnyFilterableColumn()}><tr class={"rdt-filter-row"} data-rozie-s-d5dcab4c="">
|
|
7705
|
-
{<Show when={colsWindowed()}><th class={"rdt-col-spacer"} aria-hidden="true" style={parseInlineStyle('width:' + colPadLeft() + 'px;padding:0;border:0')} data-rozie-s-d5dcab4c="" /></Show>}<Key each={windowedHeadersFor(headerGroups()[headerGroups().length - 1], headerGroups().length - 1) as readonly any[]} by={(wh) => wh.header.id}>{(wh) => <th class={"rdt-filter-cell"} role="presentation" data-col={rozieAttr(wh().header.column.id)} style={parseInlineStyle(pinStyle(wh().header.column.id))} data-rozie-s-d5dcab4c="">
|
|
8764
|
+
{<Show when={colsWindowed()}><th class={"rdt-col-spacer"} aria-hidden="true" style={parseInlineStyle('width:' + colPadLeft() + 'px;padding:0;border:0')} data-rozie-s-d5dcab4c="" /></Show>}<Key each={windowedHeadersFor(headerGroups()[headerGroups().length - 1], headerGroups().length - 1) as readonly any[]} by={(wh) => wh.header.id}>{(wh) => <th class={"rdt-filter-cell" + " " + rozieClass({ 'rdt-pinned': columnPinSide(wh().header.column.id) !== '' })} role="presentation" data-col={rozieAttr(wh().header.column.id)} style={parseInlineStyle(pinStyle(wh().header.column.id))} data-rozie-s-d5dcab4c="">
|
|
7706
8765
|
{<Show when={isSelectColumn(wh().header.column.id)} fallback={<Show when={isExpanderColumn(wh().header.column.id)} fallback={<span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7707
8766
|
{<Show when={columnIsFilterable(wh().header.column.id)}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7708
|
-
{_props.slots?.[`filter-${wh().header.column.id}`]?.({ get columnId() { return wh().header.column.id; }, get value() { return columnFilterValue(wh().header.column.id); }, get uniqueValues() { return getFacetedUniqueValues(wh().header.column.id); }, get minMax() { return getFacetedMinMaxValues(wh().header.column.id); }, get setFilter() { return setColumnFilter; } }) ?? (_props.filterSlot ?? _props.slots?.['filter'])?.({ get columnId() { return wh().header.column.id; }, get value() { return columnFilterValue(wh().header.column.id); }, get uniqueValues() { return getFacetedUniqueValues(wh().header.column.id); }, get minMax() { return getFacetedMinMaxValues(wh().header.column.id); }, get setFilter() { return setColumnFilter; } }) ?? <input type="text" aria-label={rozieAttr('Filter ' + headerLabel(wh().header.column.id))} class={"rdt-col-filter"} value={columnFilterValue(wh().header.column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onColumnFilterInput(wh().header.column.id, $event); }} onClick={($event: MouseEvent & { currentTarget: HTMLInputElement; target: Element }) => { stopEvent($event); }} data-rozie-s-d5dcab4c="" />}
|
|
8767
|
+
{_props.slots?.[`filter-${wh().header.column.id}`]?.({ get columnId() { return wh().header.column.id; }, get value() { return columnFilterValue(wh().header.column.id); }, get uniqueValues() { return getFacetedUniqueValues(wh().header.column.id); }, get minMax() { return getFacetedMinMaxValues(wh().header.column.id); }, get columnLabel() { return headerLabel(wh().header.column.id); }, get setFilter() { return setColumnFilter; } }) ?? (_props.filterSlot ?? _props.slots?.['filter'])?.({ get columnId() { return wh().header.column.id; }, get value() { return columnFilterValue(wh().header.column.id); }, get uniqueValues() { return getFacetedUniqueValues(wh().header.column.id); }, get minMax() { return getFacetedMinMaxValues(wh().header.column.id); }, get columnLabel() { return headerLabel(wh().header.column.id); }, get setFilter() { return setColumnFilter; } }) ?? <input type="text" aria-label={rozieAttr('Filter ' + headerLabel(wh().header.column.id))} class={"rdt-col-filter"} value={columnFilterValue(wh().header.column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onColumnFilterInput(wh().header.column.id, $event); }} onClick={($event: MouseEvent & { currentTarget: HTMLInputElement; target: Element }) => { stopEvent($event); }} data-rozie-s-d5dcab4c="" />}
|
|
7709
8768
|
</span></Show>}</span>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Show>}</th>}</Key>
|
|
7710
8769
|
{<Show when={colsWindowed()}><th class={"rdt-col-spacer"} aria-hidden="true" style={parseInlineStyle('width:' + colPadRight() + 'px;padding:0;border:0')} data-rozie-s-d5dcab4c="" /></Show>}</tr></Show>}</thead>
|
|
7711
8770
|
|
|
@@ -7716,18 +8775,19 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
7716
8775
|
</tr>
|
|
7717
8776
|
|
|
7718
8777
|
<Key each={windowedRows() as readonly any[]} by={(wr) => wr.row.id}>{(wr) => <>
|
|
7719
|
-
<tr class={"rdt-tr" + " " + rozieClass({ 'rdt-group-header': rowIsGrouped(wr().row), 'rdt-row-pinned': wr().pinned })} role="row" data-row={rozieAttr(wr().vi.index)} aria-rowindex={rozieAttr(headerRowCount() + wr().vi.index + 1)} data-index={rozieAttr(wr().vi.index)} data-pinned={rozieAttr(wr().pinned ? 'true' : null)} data-depth={rozieAttr(wr().row.depth)} data-group-header={rozieAttr(rowIsGrouped(wr().row) ? wr().row.id : null)} data-group-leaf={rozieAttr(groupingActive() && !rowIsGrouped(wr().row) ? wr().row.id : null)} aria-expanded={(rowIsGrouped(wr().row) ? !!rowIsExpanded(wr().row) : null) ?? undefined} aria-selected={(local.selectionMode !== 'none' ? !!rowIsSelected(wr().row) : null) ?? undefined} aria-level={rozieAttr(groupingActive() ? wr().row.depth + 1 : null)} data-rozie-s-d5dcab4c="">
|
|
8778
|
+
<tr class={"rdt-tr" + " " + rozieClass({ 'rdt-group-header': rowIsGrouped(wr().row), 'rdt-row-pinned': wr().pinned, 'rdt-placeholder-row': rowIsLazyPlaceholder(wr().row) })} role="row" data-row={rozieAttr(wr().vi.index)} aria-rowindex={rozieAttr(headerRowCount() + wr().vi.index + 1)} data-index={rozieAttr(wr().vi.index)} data-pinned={rozieAttr(wr().pinned ? 'true' : null)} data-depth={rozieAttr(wr().row.depth)} data-group-header={rozieAttr(rowIsGrouped(wr().row) ? wr().row.id : null)} data-group-leaf={rozieAttr(groupingActive() && !rowIsGrouped(wr().row) ? wr().row.id : null)} aria-expanded={(rowIsGrouped(wr().row) ? !!rowIsExpanded(wr().row) : null) ?? undefined} aria-selected={(local.selectionMode !== 'none' ? !!rowIsSelected(wr().row) : null) ?? undefined} aria-level={rozieAttr(groupingActive() ? wr().row.depth + 1 : null)} aria-busy={(rowIsLazyPlaceholder(wr().row) ? 'true' : null) ?? undefined} data-rozie-s-d5dcab4c="">
|
|
7720
8779
|
|
|
7721
|
-
{<Show when={colsWindowed()}><td class={"rdt-col-spacer"} aria-hidden="true" style={parseInlineStyle('width:' + colPadLeft() + 'px;padding:0;border:0')} data-rozie-s-d5dcab4c="" /></Show>}<Key each={windowedCells(wr().row) as readonly any[]} by={(cell) => cell.id}>{(cell) => <td class={"rdt-td" + " " + rozieClass({ 'rdt-select-td': isSelectColumn(cell().column.id), 'rdt-expander-td': isExpanderColumn(cell().column.id), 'rdt-in-range': inRange(wr().vi.index, colIndexOf(wr().row, cell())), 'rdt-cell-active': isActiveCell(String(wr().vi.index), colIndexOf(wr().row, cell())) })} role={rozieAttr(cellRole())} data-col={rozieAttr(cell().column.id)} data-grid-cell="" data-row={rozieAttr(wr().vi.index)} data-col-index={rozieAttr(colIndexOf(wr().row, cell()))} tabIndex={rozieAttr(cellTabindex(String(wr().vi.index), colIndexOf(wr().row, cell())))} style={parseInlineStyle(bodyCellStyle(wr().row, cell().column.id))} aria-invalid={rozieAttr(cellAriaInvalid(wr().vi.index, colIndexOf(wr().row, cell())))} data-in-range={rozieAttr(inRange(wr().vi.index, colIndexOf(wr().row, cell())) ? 'true' : null)} data-agg-cell={rozieAttr(cellIsAggregated(cell()) ? cell().column.id : null)} data-rozie-s-d5dcab4c="">
|
|
8780
|
+
{<Show when={colsWindowed()}><td class={"rdt-col-spacer"} aria-hidden="true" style={parseInlineStyle('width:' + colPadLeft() + 'px;padding:0;border:0')} data-rozie-s-d5dcab4c="" /></Show>}<Key each={windowedCells(wr().row) as readonly any[]} by={(cell) => cell.id}>{(cell) => <td class={"rdt-td" + " " + rozieClass({ 'rdt-select-td': isSelectColumn(cell().column.id), 'rdt-expander-td': isExpanderColumn(cell().column.id), 'rdt-pinned': columnPinSide(cell().column.id) !== '', 'rdt-in-range': inRange(wr().vi.index, colIndexOf(wr().row, cell())), 'rdt-cell-active': isActiveCell(String(wr().vi.index), colIndexOf(wr().row, cell())) })} role={rozieAttr(cellRole())} data-col={rozieAttr(cell().column.id)} data-grid-cell="" data-row={rozieAttr(wr().vi.index)} data-col-index={rozieAttr(colIndexOf(wr().row, cell()))} tabIndex={rozieAttr(cellTabindex(String(wr().vi.index), colIndexOf(wr().row, cell())))} style={parseInlineStyle(bodyCellStyle(wr().row, cell().column.id))} aria-invalid={rozieAttr(cellAriaInvalid(wr().vi.index, colIndexOf(wr().row, cell())))} aria-colindex={rozieAttr(colIndexOf(wr().row, cell()) + 1)} aria-selected={(inRange(wr().vi.index, colIndexOf(wr().row, cell())) ? 'true' : null) ?? undefined} data-in-range={rozieAttr(inRange(wr().vi.index, colIndexOf(wr().row, cell())) ? 'true' : null)} data-agg-cell={rozieAttr(cellIsAggregated(cell()) ? cell().column.id : null)} data-rozie-s-d5dcab4c="">
|
|
7722
8781
|
|
|
7723
|
-
|
|
8782
|
+
|
|
8783
|
+
{<Show when={rowIsLazyPlaceholder(wr().row) && isSelectColumn(cell().column.id)} fallback={<Show when={rowIsLazyPlaceholder(wr().row) && isExpanderColumn(cell().column.id)} fallback={<Show when={rowIsLazyPlaceholder(wr().row)} fallback={<Show when={isExpanderColumn(cell().column.id)} fallback={<Show when={isSelectColumn(cell().column.id)} fallback={<Show when={cellIsGrouped(cell())} fallback={<Show when={isEditing(wr().vi.index, colIndexOf(wr().row, cell()))} fallback={<Show when={cellIsPlaceholder(cell())} fallback={<span class={"rdt-cell-value"} data-rozie-s-d5dcab4c="">
|
|
7724
8784
|
{_props.slots?.[`cell-${cell().column.id}`]?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(wr().row); }, get value() { return cell().getValue(); } }) ?? (_props.cellSlot ?? _props.slots?.['cell'])?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(wr().row); }, get value() { return cell().getValue(); } }) ?? rozieDisplay(cell().getValue())}
|
|
7725
8785
|
</span>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7726
|
-
{<Show when={editorTypeOf(cell().column.id) === 'number'} fallback={<Show when={editorTypeOf(cell().column.id) === 'select'} fallback={<Show when={editorTypeOf(cell().column.id) === 'checkbox'} fallback={<Show when={editorTypeOf(cell().column.id) === 'custom'} fallback={<input type="text" data-editing-cell="" data-builtin-editor="" class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" />}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7727
|
-
{_props.slots?.[`editor-${cell().column.id}`]?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(wr().row); }, get value() { return editorValueFor(cell().column.id); }, get commit() { return editorCommitFor(cell().column.id); }, get cancel() { return editorCancelFor(); }, get autofocus() { return editorAutofocusFor(cell().column.id, wr().vi.index); } }) ?? (_props.editorSlot ?? _props.slots?.['editor'])?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(wr().row); }, get value() { return editorValueFor(cell().column.id); }, get commit() { return editorCommitFor(cell().column.id); }, get cancel() { return editorCancelFor(); }, get autofocus() { return editorAutofocusFor(cell().column.id, wr().vi.index); } }) ?? <input type="text" data-editing-cell="" data-builtin-editor="" class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" />}
|
|
7728
|
-
</span></Show>}><input type="checkbox" data-editing-cell="" data-builtin-editor="" class={"rdt-cell-editor"} checked={editorCheckedFor(cell().column.id)} onChange={($event: Event & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorCheckbox(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" /></Show>}><select data-editing-cell="" data-builtin-editor="" class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onChange={($event: Event & { currentTarget: HTMLSelectElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLSelectElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLSelectElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="">
|
|
8786
|
+
{<Show when={editorTypeOf(cell().column.id) === 'number'} fallback={<Show when={editorTypeOf(cell().column.id) === 'select'} fallback={<Show when={editorTypeOf(cell().column.id) === 'checkbox'} fallback={<Show when={editorTypeOf(cell().column.id) === 'custom'} fallback={<input type="text" data-editing-cell="" data-builtin-editor="" aria-invalid={rozieAttr(invalidMsg() ? 'true' : null)} class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" />}><span style={{ display: "contents" }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLSpanElement; target: Element }) => { onEditorDropinKeyDown($event); }} data-rozie-s-d5dcab4c="">
|
|
8787
|
+
{_props.slots?.[`editor-${cell().column.id}`]?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(wr().row); }, get value() { return editorValueFor(cell().column.id); }, get commit() { return editorCommitFor(cell().column.id); }, get cancel() { return editorCancelFor(); }, get columnLabel() { return headerLabel(cell().column.id); }, get autofocus() { return editorAutofocusFor(cell().column.id, wr().vi.index); } }) ?? (_props.editorSlot ?? _props.slots?.['editor'])?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(wr().row); }, get value() { return editorValueFor(cell().column.id); }, get commit() { return editorCommitFor(cell().column.id); }, get cancel() { return editorCancelFor(); }, get columnLabel() { return headerLabel(cell().column.id); }, get autofocus() { return editorAutofocusFor(cell().column.id, wr().vi.index); } }) ?? <input type="text" data-editing-cell="" data-builtin-editor="" aria-invalid={rozieAttr(invalidMsg() ? 'true' : null)} class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" />}
|
|
8788
|
+
</span></Show>}><input type="checkbox" data-editing-cell="" data-builtin-editor="" aria-invalid={rozieAttr(invalidMsg() ? 'true' : null)} class={"rdt-cell-editor"} checked={editorCheckedFor(cell().column.id)} onChange={($event: Event & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorCheckbox(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" /></Show>}><select data-editing-cell="" data-builtin-editor="" aria-invalid={rozieAttr(invalidMsg() ? 'true' : null)} class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onChange={($event: Event & { currentTarget: HTMLSelectElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLSelectElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLSelectElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="">
|
|
7729
8789
|
<Key each={editorOptionsOf(cell().column.id) as readonly any[]} by={(opt) => opt.value}>{(opt) => <option value={rozieAttr(opt().value)} data-rozie-s-d5dcab4c="">{rozieDisplay(opt().label)}</option>}</Key>
|
|
7730
|
-
</select></Show>}><input type="number" data-editing-cell="" data-builtin-editor="" class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" /></Show>}</span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
8790
|
+
</select></Show>}><input type="number" data-editing-cell="" data-builtin-editor="" aria-invalid={rozieAttr(invalidMsg() ? 'true' : null)} class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" /></Show>}</span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7731
8791
|
<button type="button" data-expander="" aria-expanded={!!rowIsExpanded(wr().row)} aria-label={rozieAttr(rowIsExpanded(wr().row) ? 'Collapse group' : 'Expand group')} class={"rdt-expander rdt-group-toggle"} onClick={($event: MouseEvent & { currentTarget: HTMLButtonElement; target: Element }) => { onToggleExpand(wr().row, $event); }} data-rozie-s-d5dcab4c="">{rozieDisplay(rowIsExpanded(wr().row) ? '▾' : '▸')}</button>
|
|
7732
8792
|
<span class={"rdt-group-value"} data-rozie-s-d5dcab4c="">
|
|
7733
8793
|
{_props.slots?.[`cell-${cell().column.id}`]?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(wr().row); }, get value() { return cell().getValue(); } }) ?? (_props.cellSlot ?? _props.slots?.['cell'])?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(wr().row); }, get value() { return cell().getValue(); } }) ?? rozieDisplay(cell().getValue())}
|
|
@@ -7736,7 +8796,12 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
7736
8796
|
</span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7737
8797
|
{(_props.selectCellSlot ?? _props.slots?.['selectCell'])?.({ get row() { return cellSlotRow(wr().row); }, get checked() { return rowIsSelected(wr().row); }, toggle: e => onToggleRow(wr().row, e) }) ?? <input type="checkbox" aria-label="Select row" class={"rdt-select-row"} checked={rowIsSelected(wr().row)} onChange={($event: Event & { currentTarget: HTMLInputElement; target: Element }) => { onToggleRow(wr().row, $event); }} data-rozie-s-d5dcab4c="" />}
|
|
7738
8798
|
</span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7739
|
-
{<Show when={rowCanExpand(wr().row)}><button type="button" data-expander="" aria-expanded={!!rowIsExpanded(wr().row)} aria-label={rozieAttr(rowIsExpanded(wr().row) ? 'Collapse row' : 'Expand row')} class={"rdt-expander"} onClick={($event: MouseEvent & { currentTarget: HTMLButtonElement; target: Element }) => { onToggleExpand(wr().row, $event); }} data-rozie-s-d5dcab4c="">{rozieDisplay(rowIsExpanded(wr().row) ? '▾' : '▸')}</button></Show>}</span></Show>}
|
|
8799
|
+
{<Show when={rowCanExpand(wr().row)}><button type="button" data-expander="" aria-expanded={!!rowIsExpanded(wr().row)} aria-label={rozieAttr(rowIsExpanded(wr().row) ? 'Collapse row' : 'Expand row')} class={"rdt-expander"} onClick={($event: MouseEvent & { currentTarget: HTMLButtonElement; target: Element }) => { onToggleExpand(wr().row, $event); }} data-rozie-s-d5dcab4c="">{rozieDisplay(rowIsExpanded(wr().row) ? '▾' : '▸')}</button></Show>}</span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
8800
|
+
{(_props.placeholderSlot ?? _props.slots?.['placeholder'])?.({ get index() { return wr().vi.index; }, get columnId() { return cell().column.id; } }) ?? <span class={"rdt-skeleton"} aria-hidden="true" data-rozie-s-d5dcab4c="">​</span>}
|
|
8801
|
+
</span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
8802
|
+
|
|
8803
|
+
<input class={"rdt-select-placeholder"} type="checkbox" disabled={true} tabIndex={-1} aria-hidden="true" style={{ visibility: "hidden" }} data-rozie-s-d5dcab4c="" />
|
|
8804
|
+
</span></Show>}{<Show when={isFillHandleCell(wr().vi.index, colIndexOf(wr().row, cell()))}><span data-fill-handle="" data-testid="fill-handle" aria-hidden="true" class={"rdt-fill-handle"} onPointerDown={($event: PointerEvent & { currentTarget: HTMLSpanElement; target: Element }) => { onFillHandlePointerDown($event); }} data-rozie-s-d5dcab4c="" /></Show>}</td>}</Key>
|
|
7740
8805
|
|
|
7741
8806
|
{<Show when={colsWindowed()}><td class={"rdt-col-spacer"} aria-hidden="true" style={parseInlineStyle('width:' + colPadRight() + 'px;padding:0;border:0')} data-rozie-s-d5dcab4c="" /></Show>}</tr>
|
|
7742
8807
|
|