@rozie-ui/data-table-solid 0.3.1 → 0.3.2
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/dist/index.cjs +102 -20
- package/dist/index.mjs +102 -20
- package/package.json +1 -1
- package/src/DataTable.tsx +202 -34
- package/src/helpers/columnDefUtils.ts +77 -1
package/dist/index.cjs
CHANGED
|
@@ -44,6 +44,39 @@ const wrapAggregationFn = (fn) => {
|
|
|
44
44
|
}
|
|
45
45
|
};
|
|
46
46
|
};
|
|
47
|
+
const collectNestedDefs = (list, out) => {
|
|
48
|
+
if (!Array.isArray(list)) return;
|
|
49
|
+
for (const d of list) {
|
|
50
|
+
if (!d || d.id == null) continue;
|
|
51
|
+
const id = String(d.id);
|
|
52
|
+
if (!(id in out)) out[id] = d;
|
|
53
|
+
if (Array.isArray(d.columns)) collectNestedDefs(d.columns, out);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const indexDefsById = (defs) => {
|
|
57
|
+
const out = Object.create(null);
|
|
58
|
+
if (!Array.isArray(defs)) return out;
|
|
59
|
+
for (const d of defs) {
|
|
60
|
+
if (!d || d.id == null) continue;
|
|
61
|
+
out[String(d.id)] = d;
|
|
62
|
+
}
|
|
63
|
+
for (const d of defs) if (d && Array.isArray(d.columns)) collectNestedDefs(d.columns, out);
|
|
64
|
+
return out;
|
|
65
|
+
};
|
|
66
|
+
const collectGroupableLeafDefs = (defs) => {
|
|
67
|
+
const out = [];
|
|
68
|
+
if (!Array.isArray(defs)) return out;
|
|
69
|
+
for (const d of defs) {
|
|
70
|
+
if (!d) continue;
|
|
71
|
+
if (Array.isArray(d.columns)) {
|
|
72
|
+
out.push(...collectGroupableLeafDefs(d.columns));
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (d.groupable === false) continue;
|
|
76
|
+
out.push(d);
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
};
|
|
47
80
|
//#endregion
|
|
48
81
|
//#region src/helpers/indexMath.ts
|
|
49
82
|
const clamp = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
|
|
@@ -933,6 +966,7 @@ function DataTable(_props) {
|
|
|
933
966
|
let columnDefsCache = null;
|
|
934
967
|
let columnDefsCacheColumnsRef = void 0;
|
|
935
968
|
let columnDefsCacheColRegRef = void 0;
|
|
969
|
+
let columnDefsIndexCache = null;
|
|
936
970
|
function columnDefs() {
|
|
937
971
|
const cfg = local.columns || [];
|
|
938
972
|
const reg = colReg() || {};
|
|
@@ -976,8 +1010,13 @@ function DataTable(_props) {
|
|
|
976
1010
|
columnDefsCache = out;
|
|
977
1011
|
columnDefsCacheColumnsRef = local.columns;
|
|
978
1012
|
columnDefsCacheColRegRef = colReg();
|
|
1013
|
+
columnDefsIndexCache = indexDefsById(out);
|
|
979
1014
|
return out;
|
|
980
1015
|
}
|
|
1016
|
+
function defIndex() {
|
|
1017
|
+
columnDefs();
|
|
1018
|
+
return columnDefsIndexCache || Object.create(null);
|
|
1019
|
+
}
|
|
981
1020
|
const SELECT_COL_ID = "__rdt_select";
|
|
982
1021
|
const EXPANDER_COL_ID = "__rdt_expander";
|
|
983
1022
|
function selectionEnabled() {
|
|
@@ -1695,9 +1734,9 @@ function DataTable(_props) {
|
|
|
1695
1734
|
return "";
|
|
1696
1735
|
}
|
|
1697
1736
|
function defFor(colId) {
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
return null;
|
|
1737
|
+
if (colId == null) return null;
|
|
1738
|
+
const d = defIndex()[String(colId)];
|
|
1739
|
+
return d != null ? d : null;
|
|
1701
1740
|
}
|
|
1702
1741
|
function visibleCellsFor(row) {
|
|
1703
1742
|
return rowModelVer() >= 0 ? row.getVisibleCells() : [];
|
|
@@ -1890,6 +1929,9 @@ function DataTable(_props) {
|
|
|
1890
1929
|
function rowIsGrouped(row) {
|
|
1891
1930
|
return !!(tick() >= 0 && row && row.getIsGrouped && row.getIsGrouped());
|
|
1892
1931
|
}
|
|
1932
|
+
function rowIndexIsGrouped(rowIndex) {
|
|
1933
|
+
return rowIsGrouped((rows() || [])[rowIndex]);
|
|
1934
|
+
}
|
|
1893
1935
|
function groupingActive() {
|
|
1894
1936
|
return tick() >= 0 && (currentState().grouping || []).length > 0;
|
|
1895
1937
|
}
|
|
@@ -1903,21 +1945,54 @@ function DataTable(_props) {
|
|
|
1903
1945
|
return !!(tick() >= 0 && cellCtx && cellCtx.getIsPlaceholder && cellCtx.getIsPlaceholder());
|
|
1904
1946
|
}
|
|
1905
1947
|
function groupSubRowCount(row) {
|
|
1906
|
-
|
|
1948
|
+
if (row && row.getLeafRows) {
|
|
1949
|
+
const leaves = row.getLeafRows();
|
|
1950
|
+
let n = 0;
|
|
1951
|
+
for (let i = 0; i < leaves.length; i++) {
|
|
1952
|
+
const r = leaves[i];
|
|
1953
|
+
if (!(r && r.getIsGrouped && r.getIsGrouped())) n = n + 1;
|
|
1954
|
+
}
|
|
1955
|
+
return n;
|
|
1956
|
+
}
|
|
1957
|
+
return row && row.subRows ? row.subRows.length : 0;
|
|
1958
|
+
}
|
|
1959
|
+
let groupRowDescriptorCache = null;
|
|
1960
|
+
let groupRowDescriptorCacheVer = void 0;
|
|
1961
|
+
function groupRowDescriptor(row) {
|
|
1962
|
+
const ver = rowModelVer();
|
|
1963
|
+
if (!groupRowDescriptorCache || groupRowDescriptorCacheVer !== ver) {
|
|
1964
|
+
groupRowDescriptorCache = Object.create(null);
|
|
1965
|
+
groupRowDescriptorCacheVer = ver;
|
|
1966
|
+
}
|
|
1967
|
+
const key = String(row.id);
|
|
1968
|
+
let d = groupRowDescriptorCache[key];
|
|
1969
|
+
if (!d) {
|
|
1970
|
+
const groupingColumnId = row.groupingColumnId != null ? row.groupingColumnId : "";
|
|
1971
|
+
const groupingValue = row.getGroupingValue ? row.getGroupingValue(groupingColumnId) : row.groupingValue;
|
|
1972
|
+
d = {
|
|
1973
|
+
isGroupRow: true,
|
|
1974
|
+
groupId: row.id,
|
|
1975
|
+
groupingColumnId,
|
|
1976
|
+
groupingValue,
|
|
1977
|
+
leafCount: groupSubRowCount(row)
|
|
1978
|
+
};
|
|
1979
|
+
groupRowDescriptorCache[key] = d;
|
|
1980
|
+
}
|
|
1981
|
+
return d;
|
|
1982
|
+
}
|
|
1983
|
+
function cellSlotRow(row) {
|
|
1984
|
+
return rowIsGrouped(row) ? groupRowDescriptor(row) : row ? row.original : null;
|
|
1907
1985
|
}
|
|
1908
1986
|
function groupingKeys() {
|
|
1909
1987
|
return currentState().grouping || [];
|
|
1910
1988
|
}
|
|
1911
1989
|
function groupableColumns() {
|
|
1912
1990
|
const out = [];
|
|
1913
|
-
const defs = columnDefs();
|
|
1914
|
-
for (const d of defs) {
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
label: d.header != null ? d.header : d.id
|
|
1919
|
-
});
|
|
1920
|
-
}
|
|
1991
|
+
const defs = collectGroupableLeafDefs(columnDefs());
|
|
1992
|
+
for (const d of defs) out.push({
|
|
1993
|
+
id: d.id,
|
|
1994
|
+
label: d.header != null ? d.header : d.id
|
|
1995
|
+
});
|
|
1921
1996
|
return out;
|
|
1922
1997
|
}
|
|
1923
1998
|
function stopEvent(evt) {
|
|
@@ -2943,11 +3018,13 @@ function DataTable(_props) {
|
|
|
2943
3018
|
for (let gr = 0; gr < grid.length; gr++) {
|
|
2944
3019
|
const r = originRow + gr;
|
|
2945
3020
|
if (r > maxRow) break;
|
|
3021
|
+
const rowGrouped = rowIndexIsGrouped(r);
|
|
2946
3022
|
const cols = grid[gr] || [];
|
|
2947
3023
|
for (let gc = 0; gc < cols.length; gc++) {
|
|
2948
3024
|
const c = originCol + gc;
|
|
2949
3025
|
if (c > maxCol) break;
|
|
2950
3026
|
total = total + 1;
|
|
3027
|
+
if (rowGrouped) continue;
|
|
2951
3028
|
const colId = columnIdAt(r, c);
|
|
2952
3029
|
if (colId == null || !columnEditable(colId)) continue;
|
|
2953
3030
|
const rowObj = rowOriginalAt(r);
|
|
@@ -3250,11 +3327,13 @@ function DataTable(_props) {
|
|
|
3250
3327
|
return cell && cell.column ? cell.column.id : null;
|
|
3251
3328
|
}
|
|
3252
3329
|
function isActiveCellEditable() {
|
|
3330
|
+
if (rowIndexIsGrouped(activeRow())) return false;
|
|
3253
3331
|
const colId = activeCellColumnId();
|
|
3254
3332
|
return colId != null && columnEditable(colId);
|
|
3255
3333
|
}
|
|
3256
3334
|
function isEditing(rowIndex, colIndex) {
|
|
3257
3335
|
if (editVer() < 0) return false;
|
|
3336
|
+
if (rowIndexIsGrouped(rowIndex)) return false;
|
|
3258
3337
|
if (editingRowIndex() != null && editingRowIndex() === rowIndex) {
|
|
3259
3338
|
const colId = columnIdAt(rowIndex, colIndex);
|
|
3260
3339
|
return colId != null && columnEditable(colId);
|
|
@@ -3356,6 +3435,7 @@ function DataTable(_props) {
|
|
|
3356
3435
|
return cell ? cell.getValue() : null;
|
|
3357
3436
|
}
|
|
3358
3437
|
function beginEdit(rowIndex, colIndex, seed) {
|
|
3438
|
+
if (rowIndexIsGrouped(rowIndex)) return;
|
|
3359
3439
|
const colId = columnIdAt(rowIndex, colIndex);
|
|
3360
3440
|
if (colId == null || !columnEditable(colId)) return;
|
|
3361
3441
|
committedThisSession = false;
|
|
@@ -3467,6 +3547,7 @@ function DataTable(_props) {
|
|
|
3467
3547
|
return true;
|
|
3468
3548
|
}
|
|
3469
3549
|
function toggleActiveBooleanCell() {
|
|
3550
|
+
if (rowIndexIsGrouped(activeRow())) return;
|
|
3470
3551
|
const colId = columnIdAt(activeRow(), activeColIndex());
|
|
3471
3552
|
if (colId == null || !columnEditable(colId)) return;
|
|
3472
3553
|
const row = (rows() || [])[activeRow()];
|
|
@@ -3552,6 +3633,7 @@ function DataTable(_props) {
|
|
|
3552
3633
|
function beginRowEdit(row) {
|
|
3553
3634
|
const rowIndex = rowIndexOf(row);
|
|
3554
3635
|
if (rowIndex < 0) return;
|
|
3636
|
+
if (rowIndexIsGrouped(rowIndex)) return;
|
|
3555
3637
|
const editable = editableColumnsForRow(rowIndex);
|
|
3556
3638
|
if (editable.length === 0) return;
|
|
3557
3639
|
committedThisSession = false;
|
|
@@ -4140,7 +4222,7 @@ function DataTable(_props) {
|
|
|
4140
4222
|
{(_props.cellSlot ?? _props.slots?.["cell"])?.({
|
|
4141
4223
|
columnId: cell().column.id,
|
|
4142
4224
|
column: cell().column,
|
|
4143
|
-
row: row()
|
|
4225
|
+
row: cellSlotRow(row()),
|
|
4144
4226
|
value: cell().getValue()
|
|
4145
4227
|
}) ?? (0, _rozie_runtime_solid.rozieDisplay)(cell().getValue())}
|
|
4146
4228
|
</span>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Solid_js.Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
@@ -4174,7 +4256,7 @@ function DataTable(_props) {
|
|
|
4174
4256
|
{(_props.editorSlot ?? _props.slots?.["editor"])?.({
|
|
4175
4257
|
columnId: cell().column.id,
|
|
4176
4258
|
column: cell().column,
|
|
4177
|
-
row: row()
|
|
4259
|
+
row: cellSlotRow(row()),
|
|
4178
4260
|
value: editorValueFor(cell().column.id),
|
|
4179
4261
|
commit: editorCommitFor(cell().column.id),
|
|
4180
4262
|
cancel: editorCancelFor(),
|
|
@@ -4188,14 +4270,14 @@ function DataTable(_props) {
|
|
|
4188
4270
|
{(_props.cellSlot ?? _props.slots?.["cell"])?.({
|
|
4189
4271
|
columnId: cell().column.id,
|
|
4190
4272
|
column: cell().column,
|
|
4191
|
-
row: row()
|
|
4273
|
+
row: cellSlotRow(row()),
|
|
4192
4274
|
value: cell().getValue()
|
|
4193
4275
|
}) ?? (0, _rozie_runtime_solid.rozieDisplay)(cell().getValue())}
|
|
4194
4276
|
</span>
|
|
4195
4277
|
<span class={"rdt-group-count"} data-rozie-s-d5dcab4c="">{(0, _rozie_runtime_solid.rozieDisplay)("(" + groupSubRowCount(row()) + ")")}</span>
|
|
4196
4278
|
</span></Solid_js.Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
4197
4279
|
{(_props.selectCellSlot ?? _props.slots?.["selectCell"])?.({
|
|
4198
|
-
row: row()
|
|
4280
|
+
row: cellSlotRow(row()),
|
|
4199
4281
|
checked: rowIsSelected(row()),
|
|
4200
4282
|
toggle: (e) => onToggleRow(row(), e)
|
|
4201
4283
|
}) ?? <input type="checkbox" aria-label="Select row" class={"rdt-select-row"} checked={rowIsSelected(row())} onChange={($event) => {
|
|
@@ -4336,7 +4418,7 @@ function DataTable(_props) {
|
|
|
4336
4418
|
{(_props.cellSlot ?? _props.slots?.["cell"])?.({
|
|
4337
4419
|
columnId: cell().column.id,
|
|
4338
4420
|
column: cell().column,
|
|
4339
|
-
row: wr().row
|
|
4421
|
+
row: cellSlotRow(wr().row),
|
|
4340
4422
|
value: cell().getValue()
|
|
4341
4423
|
}) ?? (0, _rozie_runtime_solid.rozieDisplay)(cell().getValue())}
|
|
4342
4424
|
</span>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Solid_js.Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
@@ -4370,7 +4452,7 @@ function DataTable(_props) {
|
|
|
4370
4452
|
{(_props.editorSlot ?? _props.slots?.["editor"])?.({
|
|
4371
4453
|
columnId: cell().column.id,
|
|
4372
4454
|
column: cell().column,
|
|
4373
|
-
row: wr().row
|
|
4455
|
+
row: cellSlotRow(wr().row),
|
|
4374
4456
|
value: editorValueFor(cell().column.id),
|
|
4375
4457
|
commit: editorCommitFor(cell().column.id),
|
|
4376
4458
|
cancel: editorCancelFor(),
|
|
@@ -4384,14 +4466,14 @@ function DataTable(_props) {
|
|
|
4384
4466
|
{(_props.cellSlot ?? _props.slots?.["cell"])?.({
|
|
4385
4467
|
columnId: cell().column.id,
|
|
4386
4468
|
column: cell().column,
|
|
4387
|
-
row: wr().row
|
|
4469
|
+
row: cellSlotRow(wr().row),
|
|
4388
4470
|
value: cell().getValue()
|
|
4389
4471
|
}) ?? (0, _rozie_runtime_solid.rozieDisplay)(cell().getValue())}
|
|
4390
4472
|
</span>
|
|
4391
4473
|
<span class={"rdt-group-count"} data-rozie-s-d5dcab4c="">{(0, _rozie_runtime_solid.rozieDisplay)("(" + groupSubRowCount(wr().row) + ")")}</span>
|
|
4392
4474
|
</span></Solid_js.Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
4393
4475
|
{(_props.selectCellSlot ?? _props.slots?.["selectCell"])?.({
|
|
4394
|
-
row: wr().row
|
|
4476
|
+
row: cellSlotRow(wr().row),
|
|
4395
4477
|
checked: rowIsSelected(wr().row),
|
|
4396
4478
|
toggle: (e) => onToggleRow(wr().row, e)
|
|
4397
4479
|
}) ?? <input type="checkbox" aria-label="Select row" class={"rdt-select-row"} checked={rowIsSelected(wr().row)} onChange={($event) => {
|
package/dist/index.mjs
CHANGED
|
@@ -17,6 +17,39 @@ const wrapAggregationFn = (fn) => {
|
|
|
17
17
|
}
|
|
18
18
|
};
|
|
19
19
|
};
|
|
20
|
+
const collectNestedDefs = (list, out) => {
|
|
21
|
+
if (!Array.isArray(list)) return;
|
|
22
|
+
for (const d of list) {
|
|
23
|
+
if (!d || d.id == null) continue;
|
|
24
|
+
const id = String(d.id);
|
|
25
|
+
if (!(id in out)) out[id] = d;
|
|
26
|
+
if (Array.isArray(d.columns)) collectNestedDefs(d.columns, out);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const indexDefsById = (defs) => {
|
|
30
|
+
const out = Object.create(null);
|
|
31
|
+
if (!Array.isArray(defs)) return out;
|
|
32
|
+
for (const d of defs) {
|
|
33
|
+
if (!d || d.id == null) continue;
|
|
34
|
+
out[String(d.id)] = d;
|
|
35
|
+
}
|
|
36
|
+
for (const d of defs) if (d && Array.isArray(d.columns)) collectNestedDefs(d.columns, out);
|
|
37
|
+
return out;
|
|
38
|
+
};
|
|
39
|
+
const collectGroupableLeafDefs = (defs) => {
|
|
40
|
+
const out = [];
|
|
41
|
+
if (!Array.isArray(defs)) return out;
|
|
42
|
+
for (const d of defs) {
|
|
43
|
+
if (!d) continue;
|
|
44
|
+
if (Array.isArray(d.columns)) {
|
|
45
|
+
out.push(...collectGroupableLeafDefs(d.columns));
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (d.groupable === false) continue;
|
|
49
|
+
out.push(d);
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
};
|
|
20
53
|
//#endregion
|
|
21
54
|
//#region src/helpers/indexMath.ts
|
|
22
55
|
const clamp = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
|
|
@@ -906,6 +939,7 @@ function DataTable(_props) {
|
|
|
906
939
|
let columnDefsCache = null;
|
|
907
940
|
let columnDefsCacheColumnsRef = void 0;
|
|
908
941
|
let columnDefsCacheColRegRef = void 0;
|
|
942
|
+
let columnDefsIndexCache = null;
|
|
909
943
|
function columnDefs() {
|
|
910
944
|
const cfg = local.columns || [];
|
|
911
945
|
const reg = colReg() || {};
|
|
@@ -949,8 +983,13 @@ function DataTable(_props) {
|
|
|
949
983
|
columnDefsCache = out;
|
|
950
984
|
columnDefsCacheColumnsRef = local.columns;
|
|
951
985
|
columnDefsCacheColRegRef = colReg();
|
|
986
|
+
columnDefsIndexCache = indexDefsById(out);
|
|
952
987
|
return out;
|
|
953
988
|
}
|
|
989
|
+
function defIndex() {
|
|
990
|
+
columnDefs();
|
|
991
|
+
return columnDefsIndexCache || Object.create(null);
|
|
992
|
+
}
|
|
954
993
|
const SELECT_COL_ID = "__rdt_select";
|
|
955
994
|
const EXPANDER_COL_ID = "__rdt_expander";
|
|
956
995
|
function selectionEnabled() {
|
|
@@ -1668,9 +1707,9 @@ function DataTable(_props) {
|
|
|
1668
1707
|
return "";
|
|
1669
1708
|
}
|
|
1670
1709
|
function defFor(colId) {
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
return null;
|
|
1710
|
+
if (colId == null) return null;
|
|
1711
|
+
const d = defIndex()[String(colId)];
|
|
1712
|
+
return d != null ? d : null;
|
|
1674
1713
|
}
|
|
1675
1714
|
function visibleCellsFor(row) {
|
|
1676
1715
|
return rowModelVer() >= 0 ? row.getVisibleCells() : [];
|
|
@@ -1863,6 +1902,9 @@ function DataTable(_props) {
|
|
|
1863
1902
|
function rowIsGrouped(row) {
|
|
1864
1903
|
return !!(tick() >= 0 && row && row.getIsGrouped && row.getIsGrouped());
|
|
1865
1904
|
}
|
|
1905
|
+
function rowIndexIsGrouped(rowIndex) {
|
|
1906
|
+
return rowIsGrouped((rows() || [])[rowIndex]);
|
|
1907
|
+
}
|
|
1866
1908
|
function groupingActive() {
|
|
1867
1909
|
return tick() >= 0 && (currentState().grouping || []).length > 0;
|
|
1868
1910
|
}
|
|
@@ -1876,21 +1918,54 @@ function DataTable(_props) {
|
|
|
1876
1918
|
return !!(tick() >= 0 && cellCtx && cellCtx.getIsPlaceholder && cellCtx.getIsPlaceholder());
|
|
1877
1919
|
}
|
|
1878
1920
|
function groupSubRowCount(row) {
|
|
1879
|
-
|
|
1921
|
+
if (row && row.getLeafRows) {
|
|
1922
|
+
const leaves = row.getLeafRows();
|
|
1923
|
+
let n = 0;
|
|
1924
|
+
for (let i = 0; i < leaves.length; i++) {
|
|
1925
|
+
const r = leaves[i];
|
|
1926
|
+
if (!(r && r.getIsGrouped && r.getIsGrouped())) n = n + 1;
|
|
1927
|
+
}
|
|
1928
|
+
return n;
|
|
1929
|
+
}
|
|
1930
|
+
return row && row.subRows ? row.subRows.length : 0;
|
|
1931
|
+
}
|
|
1932
|
+
let groupRowDescriptorCache = null;
|
|
1933
|
+
let groupRowDescriptorCacheVer = void 0;
|
|
1934
|
+
function groupRowDescriptor(row) {
|
|
1935
|
+
const ver = rowModelVer();
|
|
1936
|
+
if (!groupRowDescriptorCache || groupRowDescriptorCacheVer !== ver) {
|
|
1937
|
+
groupRowDescriptorCache = Object.create(null);
|
|
1938
|
+
groupRowDescriptorCacheVer = ver;
|
|
1939
|
+
}
|
|
1940
|
+
const key = String(row.id);
|
|
1941
|
+
let d = groupRowDescriptorCache[key];
|
|
1942
|
+
if (!d) {
|
|
1943
|
+
const groupingColumnId = row.groupingColumnId != null ? row.groupingColumnId : "";
|
|
1944
|
+
const groupingValue = row.getGroupingValue ? row.getGroupingValue(groupingColumnId) : row.groupingValue;
|
|
1945
|
+
d = {
|
|
1946
|
+
isGroupRow: true,
|
|
1947
|
+
groupId: row.id,
|
|
1948
|
+
groupingColumnId,
|
|
1949
|
+
groupingValue,
|
|
1950
|
+
leafCount: groupSubRowCount(row)
|
|
1951
|
+
};
|
|
1952
|
+
groupRowDescriptorCache[key] = d;
|
|
1953
|
+
}
|
|
1954
|
+
return d;
|
|
1955
|
+
}
|
|
1956
|
+
function cellSlotRow(row) {
|
|
1957
|
+
return rowIsGrouped(row) ? groupRowDescriptor(row) : row ? row.original : null;
|
|
1880
1958
|
}
|
|
1881
1959
|
function groupingKeys() {
|
|
1882
1960
|
return currentState().grouping || [];
|
|
1883
1961
|
}
|
|
1884
1962
|
function groupableColumns() {
|
|
1885
1963
|
const out = [];
|
|
1886
|
-
const defs = columnDefs();
|
|
1887
|
-
for (const d of defs) {
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
label: d.header != null ? d.header : d.id
|
|
1892
|
-
});
|
|
1893
|
-
}
|
|
1964
|
+
const defs = collectGroupableLeafDefs(columnDefs());
|
|
1965
|
+
for (const d of defs) out.push({
|
|
1966
|
+
id: d.id,
|
|
1967
|
+
label: d.header != null ? d.header : d.id
|
|
1968
|
+
});
|
|
1894
1969
|
return out;
|
|
1895
1970
|
}
|
|
1896
1971
|
function stopEvent(evt) {
|
|
@@ -2916,11 +2991,13 @@ function DataTable(_props) {
|
|
|
2916
2991
|
for (let gr = 0; gr < grid.length; gr++) {
|
|
2917
2992
|
const r = originRow + gr;
|
|
2918
2993
|
if (r > maxRow) break;
|
|
2994
|
+
const rowGrouped = rowIndexIsGrouped(r);
|
|
2919
2995
|
const cols = grid[gr] || [];
|
|
2920
2996
|
for (let gc = 0; gc < cols.length; gc++) {
|
|
2921
2997
|
const c = originCol + gc;
|
|
2922
2998
|
if (c > maxCol) break;
|
|
2923
2999
|
total = total + 1;
|
|
3000
|
+
if (rowGrouped) continue;
|
|
2924
3001
|
const colId = columnIdAt(r, c);
|
|
2925
3002
|
if (colId == null || !columnEditable(colId)) continue;
|
|
2926
3003
|
const rowObj = rowOriginalAt(r);
|
|
@@ -3223,11 +3300,13 @@ function DataTable(_props) {
|
|
|
3223
3300
|
return cell && cell.column ? cell.column.id : null;
|
|
3224
3301
|
}
|
|
3225
3302
|
function isActiveCellEditable() {
|
|
3303
|
+
if (rowIndexIsGrouped(activeRow())) return false;
|
|
3226
3304
|
const colId = activeCellColumnId();
|
|
3227
3305
|
return colId != null && columnEditable(colId);
|
|
3228
3306
|
}
|
|
3229
3307
|
function isEditing(rowIndex, colIndex) {
|
|
3230
3308
|
if (editVer() < 0) return false;
|
|
3309
|
+
if (rowIndexIsGrouped(rowIndex)) return false;
|
|
3231
3310
|
if (editingRowIndex() != null && editingRowIndex() === rowIndex) {
|
|
3232
3311
|
const colId = columnIdAt(rowIndex, colIndex);
|
|
3233
3312
|
return colId != null && columnEditable(colId);
|
|
@@ -3329,6 +3408,7 @@ function DataTable(_props) {
|
|
|
3329
3408
|
return cell ? cell.getValue() : null;
|
|
3330
3409
|
}
|
|
3331
3410
|
function beginEdit(rowIndex, colIndex, seed) {
|
|
3411
|
+
if (rowIndexIsGrouped(rowIndex)) return;
|
|
3332
3412
|
const colId = columnIdAt(rowIndex, colIndex);
|
|
3333
3413
|
if (colId == null || !columnEditable(colId)) return;
|
|
3334
3414
|
committedThisSession = false;
|
|
@@ -3440,6 +3520,7 @@ function DataTable(_props) {
|
|
|
3440
3520
|
return true;
|
|
3441
3521
|
}
|
|
3442
3522
|
function toggleActiveBooleanCell() {
|
|
3523
|
+
if (rowIndexIsGrouped(activeRow())) return;
|
|
3443
3524
|
const colId = columnIdAt(activeRow(), activeColIndex());
|
|
3444
3525
|
if (colId == null || !columnEditable(colId)) return;
|
|
3445
3526
|
const row = (rows() || [])[activeRow()];
|
|
@@ -3525,6 +3606,7 @@ function DataTable(_props) {
|
|
|
3525
3606
|
function beginRowEdit(row) {
|
|
3526
3607
|
const rowIndex = rowIndexOf(row);
|
|
3527
3608
|
if (rowIndex < 0) return;
|
|
3609
|
+
if (rowIndexIsGrouped(rowIndex)) return;
|
|
3528
3610
|
const editable = editableColumnsForRow(rowIndex);
|
|
3529
3611
|
if (editable.length === 0) return;
|
|
3530
3612
|
committedThisSession = false;
|
|
@@ -4113,7 +4195,7 @@ function DataTable(_props) {
|
|
|
4113
4195
|
{(_props.cellSlot ?? _props.slots?.["cell"])?.({
|
|
4114
4196
|
columnId: cell().column.id,
|
|
4115
4197
|
column: cell().column,
|
|
4116
|
-
row: row()
|
|
4198
|
+
row: cellSlotRow(row()),
|
|
4117
4199
|
value: cell().getValue()
|
|
4118
4200
|
}) ?? rozieDisplay(cell().getValue())}
|
|
4119
4201
|
</span>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
@@ -4147,7 +4229,7 @@ function DataTable(_props) {
|
|
|
4147
4229
|
{(_props.editorSlot ?? _props.slots?.["editor"])?.({
|
|
4148
4230
|
columnId: cell().column.id,
|
|
4149
4231
|
column: cell().column,
|
|
4150
|
-
row: row()
|
|
4232
|
+
row: cellSlotRow(row()),
|
|
4151
4233
|
value: editorValueFor(cell().column.id),
|
|
4152
4234
|
commit: editorCommitFor(cell().column.id),
|
|
4153
4235
|
cancel: editorCancelFor(),
|
|
@@ -4161,14 +4243,14 @@ function DataTable(_props) {
|
|
|
4161
4243
|
{(_props.cellSlot ?? _props.slots?.["cell"])?.({
|
|
4162
4244
|
columnId: cell().column.id,
|
|
4163
4245
|
column: cell().column,
|
|
4164
|
-
row: row()
|
|
4246
|
+
row: cellSlotRow(row()),
|
|
4165
4247
|
value: cell().getValue()
|
|
4166
4248
|
}) ?? rozieDisplay(cell().getValue())}
|
|
4167
4249
|
</span>
|
|
4168
4250
|
<span class={"rdt-group-count"} data-rozie-s-d5dcab4c="">{rozieDisplay("(" + groupSubRowCount(row()) + ")")}</span>
|
|
4169
4251
|
</span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
4170
4252
|
{(_props.selectCellSlot ?? _props.slots?.["selectCell"])?.({
|
|
4171
|
-
row: row()
|
|
4253
|
+
row: cellSlotRow(row()),
|
|
4172
4254
|
checked: rowIsSelected(row()),
|
|
4173
4255
|
toggle: (e) => onToggleRow(row(), e)
|
|
4174
4256
|
}) ?? <input type="checkbox" aria-label="Select row" class={"rdt-select-row"} checked={rowIsSelected(row())} onChange={($event) => {
|
|
@@ -4309,7 +4391,7 @@ function DataTable(_props) {
|
|
|
4309
4391
|
{(_props.cellSlot ?? _props.slots?.["cell"])?.({
|
|
4310
4392
|
columnId: cell().column.id,
|
|
4311
4393
|
column: cell().column,
|
|
4312
|
-
row: wr().row
|
|
4394
|
+
row: cellSlotRow(wr().row),
|
|
4313
4395
|
value: cell().getValue()
|
|
4314
4396
|
}) ?? rozieDisplay(cell().getValue())}
|
|
4315
4397
|
</span>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
@@ -4343,7 +4425,7 @@ function DataTable(_props) {
|
|
|
4343
4425
|
{(_props.editorSlot ?? _props.slots?.["editor"])?.({
|
|
4344
4426
|
columnId: cell().column.id,
|
|
4345
4427
|
column: cell().column,
|
|
4346
|
-
row: wr().row
|
|
4428
|
+
row: cellSlotRow(wr().row),
|
|
4347
4429
|
value: editorValueFor(cell().column.id),
|
|
4348
4430
|
commit: editorCommitFor(cell().column.id),
|
|
4349
4431
|
cancel: editorCancelFor(),
|
|
@@ -4357,14 +4439,14 @@ function DataTable(_props) {
|
|
|
4357
4439
|
{(_props.cellSlot ?? _props.slots?.["cell"])?.({
|
|
4358
4440
|
columnId: cell().column.id,
|
|
4359
4441
|
column: cell().column,
|
|
4360
|
-
row: wr().row
|
|
4442
|
+
row: cellSlotRow(wr().row),
|
|
4361
4443
|
value: cell().getValue()
|
|
4362
4444
|
}) ?? rozieDisplay(cell().getValue())}
|
|
4363
4445
|
</span>
|
|
4364
4446
|
<span class={"rdt-group-count"} data-rozie-s-d5dcab4c="">{rozieDisplay("(" + groupSubRowCount(wr().row) + ")")}</span>
|
|
4365
4447
|
</span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
4366
4448
|
{(_props.selectCellSlot ?? _props.slots?.["selectCell"])?.({
|
|
4367
|
-
row: wr().row
|
|
4449
|
+
row: cellSlotRow(wr().row),
|
|
4368
4450
|
checked: rowIsSelected(wr().row),
|
|
4369
4451
|
toggle: (e) => onToggleRow(wr().row, e)
|
|
4370
4452
|
}) ?? <input type="checkbox" aria-label="Select row" class={"rdt-select-row"} checked={rowIsSelected(wr().row)} onChange={($event) => {
|
package/package.json
CHANGED
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 } from './helpers/columnDefUtils';
|
|
6
|
+
import { isSafeKey, wrapAggregationFn, indexDefsById, 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';
|
|
@@ -1304,6 +1304,12 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
1304
1304
|
let columnDefsCache: any[] | null = null;
|
|
1305
1305
|
let columnDefsCacheColumnsRef: any = undefined;
|
|
1306
1306
|
let columnDefsCacheColRegRef: any = undefined;
|
|
1307
|
+
// B1 (quick 260906-afh) — the def-lookup index, populated INSIDE this SAME memo block on
|
|
1308
|
+
// a cache MISS only (never rebuilt per call — a per-call recursive scan would restore the
|
|
1309
|
+
// O(N^2 x M) blowup D-23 removed, T-AFH-03). A top-level `let` lowers to a per-instance
|
|
1310
|
+
// `useRef` on React (verified in the emitted leaf), so this is instance-scoped like
|
|
1311
|
+
// columnDefsCache above.
|
|
1312
|
+
let columnDefsIndexCache: any = null;
|
|
1307
1313
|
function columnDefs() {
|
|
1308
1314
|
const cfg = local.columns || [];
|
|
1309
1315
|
const reg = colReg() || {};
|
|
@@ -1352,9 +1358,20 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
1352
1358
|
columnDefsCache = out;
|
|
1353
1359
|
columnDefsCacheColumnsRef = local.columns;
|
|
1354
1360
|
columnDefsCacheColRegRef = colReg();
|
|
1361
|
+
columnDefsIndexCache = indexDefsById(out);
|
|
1355
1362
|
return out;
|
|
1356
1363
|
}
|
|
1357
1364
|
|
|
1365
|
+
// defIndex: the def-lookup map for the CURRENT columnDefs() array. Calling columnDefs()
|
|
1366
|
+
// first is deliberate — either an O(1) reference-equality cache hit (columnDefsIndexCache
|
|
1367
|
+
// is already fresh) or a miss that repopulates BOTH slots together, so the index can never
|
|
1368
|
+
// go stale relative to the array. Falls back to an empty null-prototype map (never null/
|
|
1369
|
+
// undefined) so a caller can read it unconditionally.
|
|
1370
|
+
function defIndex() {
|
|
1371
|
+
columnDefs();
|
|
1372
|
+
return columnDefsIndexCache || Object.create(null);
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1358
1375
|
// The constant id of the auto-injected leading checkbox column (D-04). Distinct from
|
|
1359
1376
|
// any consumer column id (the registry/config guard never produces a leading "__").
|
|
1360
1377
|
const SELECT_COL_ID = '__rdt_select';
|
|
@@ -3034,10 +3051,18 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
3034
3051
|
|
|
3035
3052
|
// Template helpers reading the resolved column-def metadata by id (plain fns — used
|
|
3036
3053
|
// in template predicates + interpolation; uniform on all 6, no $computed alias trap).
|
|
3054
|
+
// B1 (quick 260906-afh): reads the O(1) def-lookup index (defIndex(), columnBuilders.rzts)
|
|
3055
|
+
// instead of linearly scanning columnDefs() — which only ever held TOP-LEVEL entries, so a
|
|
3056
|
+
// NESTED group leaf (a `columns:` group child) was invisible to every caller (headerLabel,
|
|
3057
|
+
// columnIsFilterable, editMetaOf/columnEditable/editorTypeOf/editorOptionsOf) and silently
|
|
3058
|
+
// took the "no def" fallback. defIndex() resolves both top-level AND nested leaf ids. This
|
|
3059
|
+
// also removes the per-cell O(N) scan D-23 left in place — defIndex() is memoized in the
|
|
3060
|
+
// SAME cache-invalidation block as columnDefs() itself, never rebuilt per call.
|
|
3037
3061
|
function defFor(colId: any) {
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3062
|
+
if (colId == null) return null;
|
|
3063
|
+
const idx = defIndex();
|
|
3064
|
+
const d = idx[String(colId)];
|
|
3065
|
+
return d != null ? d : null;
|
|
3041
3066
|
}
|
|
3042
3067
|
// Per-row visible cells for the body loop. table-core memoizes row objects by id,
|
|
3043
3068
|
// so a re-pull after a column change (visibility/reorder/pin, or the late <Column>
|
|
@@ -3390,6 +3415,16 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
3390
3415
|
function rowIsGrouped(row: any) {
|
|
3391
3416
|
return !!(tick() >= 0 && row && row.getIsGrouped && row.getIsGrouped());
|
|
3392
3417
|
}
|
|
3418
|
+
// rowIndexIsGrouped (quick 260906-cvo, C1/C2): THE single row-level editability/writability
|
|
3419
|
+
// predicate — a group-header row is a SYNTHETIC row whose `original` is table-core's
|
|
3420
|
+
// `leafRows[0].original` (the same fact cellSlotRow's header comment documents), so any
|
|
3421
|
+
// edit or bulk write resolved through it silently lands on an UNRELATED record. Every edit/
|
|
3422
|
+
// write guard in this component calls THIS predicate — never a re-implemented inline
|
|
3423
|
+
// `getIsGrouped()` check — so the invariant has one definition. Indexes into the SAME
|
|
3424
|
+
// `$data.rows` flattened row list rowIsGrouped reads.
|
|
3425
|
+
function rowIndexIsGrouped(rowIndex: any) {
|
|
3426
|
+
return rowIsGrouped((rows() || [])[rowIndex]);
|
|
3427
|
+
}
|
|
3393
3428
|
// groupingActive: grouping is currently engaged (a non-empty ordered key list). Drives the
|
|
3394
3429
|
// data-group-leaf marker so it is ABSENT when ungrouped (byte-identical-off, req-10).
|
|
3395
3430
|
function groupingActive() {
|
|
@@ -3405,22 +3440,100 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
3405
3440
|
function cellIsAggregated(cellCtx: any) {
|
|
3406
3441
|
return !!(tick() >= 0 && cellCtx && cellCtx.getIsAggregated && cellCtx.getIsAggregated());
|
|
3407
3442
|
}
|
|
3408
|
-
// cellIsPlaceholder:
|
|
3409
|
-
//
|
|
3410
|
-
//
|
|
3411
|
-
//
|
|
3412
|
-
//
|
|
3443
|
+
// cellIsPlaceholder: VERIFIED against @tanstack/table-core@8.21.3 (ColumnGrouping.js:120-121,
|
|
3444
|
+
// quick 260906-afh premise correction). A placeholder cell is a cell IN A GROUPING COLUMN
|
|
3445
|
+
// that is NOT the row's OWN grouping column — `!cell.getIsGrouped() && column.getIsGrouped()`.
|
|
3446
|
+
// That is true both on a group-header row (for every OTHER grouping column) and on every leaf
|
|
3447
|
+
// row (for every grouping column) — it is NOT "never true on a group-header row" and it is NOT
|
|
3448
|
+
// "any non-grouped, non-aggregated cell". The operative consequence for this component: a
|
|
3449
|
+
// placeholder can only EVER be a grouping-column cell, so this branch can never reach a
|
|
3450
|
+
// non-grouping column — it does NOT and CANNOT guard the AGGREGATED-cell `row` leak
|
|
3451
|
+
// (cellSlotRow, below) fixes. Where it DOES apply (e.g. under multi-level grouping, the
|
|
3452
|
+
// OTHER grouping column on a group-header row), table-core still fills cell.getValue() with
|
|
3453
|
+
// the FIRST leaf row's value (cell.getValue() leaks e.g. "Oslo" onto a region-level group
|
|
3454
|
+
// line whose grouping is 'region', not 'city'); this branch renders BLANK via a dedicated
|
|
3455
|
+
// empty template so that leaked value never paints. Tick-gated exactly like cellIsGrouped so
|
|
3456
|
+
// the group chrome re-derives on a re-pull on the fine-grained targets (Solid/Lit).
|
|
3413
3457
|
function cellIsPlaceholder(cellCtx: any) {
|
|
3414
3458
|
return !!(tick() >= 0 && cellCtx && cellCtx.getIsPlaceholder && cellCtx.getIsPlaceholder());
|
|
3415
3459
|
}
|
|
3416
|
-
// groupSubRowCount: the number of underlying LEAF RECORDS under a group-header row (the
|
|
3417
|
-
// shown in the header, e.g. "North (40)"). row.subRows is the IMMEDIATE members — for
|
|
3418
|
-
// grouping those are sub-GROUPS, not records, so "North" with 2 categories / 40
|
|
3419
|
-
// "North (2)"
|
|
3420
|
-
// subRows
|
|
3421
|
-
//
|
|
3460
|
+
// groupSubRowCount: the number of underlying TRUE LEAF RECORDS under a group-header row (the
|
|
3461
|
+
// count shown in the header, e.g. "North (40)"). row.subRows is the IMMEDIATE members — for
|
|
3462
|
+
// MULTI-LEVEL grouping those are sub-GROUPS, not records, so "North" with 2 categories / 40
|
|
3463
|
+
// records would show "North (2)" off subRows alone. row.getLeafRows() is table-core's
|
|
3464
|
+
// `flattenBy(row.subRows, d => d.subRows)` (ColumnGrouping.js / utils.js, verified against
|
|
3465
|
+
// @tanstack/table-core@8.21.3) — it pushes EVERY node it walks, INCLUDING intermediate group
|
|
3466
|
+
// nodes, so under multi-level grouping it over-counts by exactly the number of sub-group
|
|
3467
|
+
// nodes between the row and its true leaves (C4, quick 260906-cvo: North with 2 city
|
|
3468
|
+
// sub-groups + 3 records reported 5, not 3). `row.getIsGrouped()` is `!!row.groupingColumnId`
|
|
3469
|
+
// — precisely the predicate that separates a group node from a real record — so filter the
|
|
3470
|
+
// flattened list on it rather than hand-rolling a recursive `subRows` walk (getLeafRows()
|
|
3471
|
+
// already flattens; the defect is purely that it does not filter). Single-level grouping is
|
|
3472
|
+
// numerically UNCHANGED: a leaf row has no `subRows`, so no node was ever miscounted there.
|
|
3422
3473
|
function groupSubRowCount(row: any) {
|
|
3423
|
-
|
|
3474
|
+
if (row && row.getLeafRows) {
|
|
3475
|
+
const leaves = row.getLeafRows();
|
|
3476
|
+
let n = 0;
|
|
3477
|
+
for (let i = 0; i < leaves.length; i++) {
|
|
3478
|
+
const r = leaves[i];
|
|
3479
|
+
if (!(r && r.getIsGrouped && r.getIsGrouped())) n = n + 1;
|
|
3480
|
+
}
|
|
3481
|
+
return n;
|
|
3482
|
+
}
|
|
3483
|
+
return row && row.subRows ? row.subRows.length : 0;
|
|
3484
|
+
}
|
|
3485
|
+
|
|
3486
|
+
// ── B2 (quick 260906-afh) — group-header row #cell slot descriptor ─────────────────────
|
|
3487
|
+
// PROBLEM: table-core builds a flattened group-header row via
|
|
3488
|
+
// `createRow(table, id, leafRows[0].original, …)` (getGroupedRowModel.js), so `row.original`
|
|
3489
|
+
// on a group row IS the FIRST LEAF's record. The #cell scoped slot's existing 4 call sites
|
|
3490
|
+
// all bound `:row="row.original"` (or `wr.row.original`), so an AGGREGATED cell (a
|
|
3491
|
+
// non-grouping column on a group row — cellIsGrouped()===false, cellIsPlaceholder()===false,
|
|
3492
|
+
// so it takes the #cell r-else branch, same as any ordinary leaf cell) silently handed a
|
|
3493
|
+
// consumer template the WRONG record's fields (T-AFH-05). The grouped cell itself (the
|
|
3494
|
+
// group's own column, .rdt-group-value) has the same #cell binding and the same leak.
|
|
3495
|
+
//
|
|
3496
|
+
// FIX: `cellSlotRow(row)` returns a STABLE GROUP DESCRIPTOR — never `row.original` — for a
|
|
3497
|
+
// group-header row, and `row.original` unchanged for every other row. The descriptor is an
|
|
3498
|
+
// OBJECT, never `null` (T-AFH-06 — a consumer already reading `row.foo` would throw on
|
|
3499
|
+
// Vue/Angular if handed null).
|
|
3500
|
+
//
|
|
3501
|
+
// Descriptor cache (T-AFH-02/T-AFH-04): a null-prototype map keyed on `String(row.id)`,
|
|
3502
|
+
// invalidated wholesale whenever `$data.rowModelVer` (bumped by every refreshRowModel, the
|
|
3503
|
+
// SAME reactive re-derivation discipline visibleCellsFor/rowIsGrouped use) differs from the
|
|
3504
|
+
// generation the map was built for — so the descriptor's REFERENCE IDENTITY is stable
|
|
3505
|
+
// across every cell of one group row within a row-model generation (an unstable reference
|
|
3506
|
+
// would thrash the fine-grained targets, Solid/Lit) and refreshed the moment the grouping
|
|
3507
|
+
// changes. Group row ids are built from consumer grouping VALUES, so the map MUST be
|
|
3508
|
+
// null-prototype — a `__proto__` grouping value must not reach Object.prototype.
|
|
3509
|
+
let groupRowDescriptorCache: any = null;
|
|
3510
|
+
let groupRowDescriptorCacheVer: any = undefined;
|
|
3511
|
+
function groupRowDescriptor(row: any) {
|
|
3512
|
+
const ver = rowModelVer();
|
|
3513
|
+
if (!groupRowDescriptorCache || groupRowDescriptorCacheVer !== ver) {
|
|
3514
|
+
groupRowDescriptorCache = Object.create(null);
|
|
3515
|
+
groupRowDescriptorCacheVer = ver;
|
|
3516
|
+
}
|
|
3517
|
+
const key = String(row.id);
|
|
3518
|
+
let d = groupRowDescriptorCache[key];
|
|
3519
|
+
if (!d) {
|
|
3520
|
+
const groupingColumnId = row.groupingColumnId != null ? row.groupingColumnId : '';
|
|
3521
|
+
const groupingValue = row.getGroupingValue ? row.getGroupingValue(groupingColumnId) : row.groupingValue;
|
|
3522
|
+
d = {
|
|
3523
|
+
isGroupRow: true,
|
|
3524
|
+
groupId: row.id,
|
|
3525
|
+
groupingColumnId,
|
|
3526
|
+
groupingValue,
|
|
3527
|
+
leafCount: groupSubRowCount(row)
|
|
3528
|
+
};
|
|
3529
|
+
groupRowDescriptorCache[key] = d;
|
|
3530
|
+
}
|
|
3531
|
+
return d;
|
|
3532
|
+
}
|
|
3533
|
+
// cellSlotRow: the #cell slot's `:row=` value. A group-header row gets the stable
|
|
3534
|
+
// descriptor above (never the leaked first-leaf record); every other row is unchanged.
|
|
3535
|
+
function cellSlotRow(row: any) {
|
|
3536
|
+
return rowIsGrouped(row) ? groupRowDescriptor(row) : row ? row.original : null;
|
|
3424
3537
|
}
|
|
3425
3538
|
// groupingKeys: the live ordered grouping array — slot prop for the headless #groupBar + the
|
|
3426
3539
|
// default styled-token reflection. Reads currentState() ($props.grouping ?? $data.groupingDefault),
|
|
@@ -3428,14 +3541,18 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
3428
3541
|
function groupingKeys() {
|
|
3429
3542
|
return currentState().grouping || [];
|
|
3430
3543
|
}
|
|
3431
|
-
// groupableColumns: the data columns OFFERED to the headless #groupBar
|
|
3432
|
-
//
|
|
3433
|
-
//
|
|
3544
|
+
// groupableColumns: the data columns OFFERED to the headless #groupBar — `[{ id, label }]`.
|
|
3545
|
+
// Excludes the chrome columns (select/expander are not in columnDefs()). C5+C6 (quick
|
|
3546
|
+
// 260906-cvo): delegates the actual walk to collectGroupableLeafDefs (columnDefUtils.ts) —
|
|
3547
|
+
// a GROUP column (a `columns:` group's own entry) is NEVER offered (it has no accessor and
|
|
3548
|
+
// cannot produce a grouping value, regardless of any `groupable` flag on it), and every
|
|
3549
|
+
// groupable NESTED leaf (a `columns:` group child, opted out via `groupable: false` like any
|
|
3550
|
+
// top-level leaf) IS offered, in declaration order. The consumer builds any bar/drag UI from
|
|
3551
|
+
// this; the component ships none.
|
|
3434
3552
|
function groupableColumns() {
|
|
3435
3553
|
const out = [];
|
|
3436
|
-
const defs = columnDefs();
|
|
3554
|
+
const defs = collectGroupableLeafDefs(columnDefs());
|
|
3437
3555
|
for (const d of defs as any) {
|
|
3438
|
-
if (!d || d.groupable === false) continue;
|
|
3439
3556
|
out.push({
|
|
3440
3557
|
id: d.id,
|
|
3441
3558
|
label: d.header != null ? d.header : d.id
|
|
@@ -4450,11 +4567,18 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
4450
4567
|
return;
|
|
4451
4568
|
}
|
|
4452
4569
|
// ── C2 (phase 63 wave-8): Enter on a GROUP-HEADER cell toggles that group's collapse/
|
|
4453
|
-
// expand (APG treegrid).
|
|
4454
|
-
//
|
|
4455
|
-
//
|
|
4456
|
-
//
|
|
4457
|
-
//
|
|
4570
|
+
// expand (APG treegrid). C1 (quick 260906-cvo) CORRECTION: the comment here used to claim
|
|
4571
|
+
// "a group cell is non-editable" as "the verified invariant" — that was FALSE AS WRITTEN.
|
|
4572
|
+
// It held only for the grouping column's OWN cell (cellIsGrouped's branch renders before
|
|
4573
|
+
// the editor branch); a non-grouping EDITABLE column's cell on a group row fell straight
|
|
4574
|
+
// through the branch chain to isEditing(...) and opened an editor (the C1 defect this
|
|
4575
|
+
// quick task closed). What is now TRUE, and WHY: isActiveCellEditable() carries an
|
|
4576
|
+
// explicit rowIndexIsGrouped(...) row guard (filterPaginationRowChrome.rzts), so EVERY
|
|
4577
|
+
// editable column's cell on a group row is non-editable — that is what makes the four
|
|
4578
|
+
// edit-entry branches above fall through to this one, not an incidental property of the
|
|
4579
|
+
// grouping column alone. Route it to the SAME onToggleExpand path the chevron uses (group
|
|
4580
|
+
// rows ride the expand model) so one Enter toggles the group. Body cells only (a
|
|
4581
|
+
// header-active Enter is unchanged);
|
|
4458
4582
|
// ($data.rows || [])[$data.activeRow] is the active flattened row (page-relative non-virtual /
|
|
4459
4583
|
// full-model virtual — both index $data.rows). Placed BEFORE the reserved enterControl branch.
|
|
4460
4584
|
else if (key === 'Enter' && !activeIsHeader() && rowIsGrouped((rows() || [])[activeRow()])) {
|
|
@@ -5243,6 +5367,11 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5243
5367
|
// rangeToTsv(): serialize the current range to TSV — rows joined by '\n', cells by '\t',
|
|
5244
5368
|
// reading each cell's value off the visible model by index (cellValueAt). A single active
|
|
5245
5369
|
// cell (no range) serializes that one cell. Each field is B10-escaped. Pure read — never writes.
|
|
5370
|
+
// C2 (quick 260906-cvo) — DELIBERATE NON-CHANGE: this is a READ, unlike applyGridToRange's
|
|
5371
|
+
// write funnel, and stays UNGUARDED for a group-header row on purpose. cellValueAt reads the
|
|
5372
|
+
// column's DISPLAYED aggregate value on a group row (table-core's cell.getValue()), which is
|
|
5373
|
+
// exactly what the user sees — serializing it is correct behavior, not a leak. Do not "fix"
|
|
5374
|
+
// this asymmetry; it is intentional.
|
|
5246
5375
|
function rangeToTsv() {
|
|
5247
5376
|
const box = normalizedRange();
|
|
5248
5377
|
const r0 = box ? box.r0 : activeRow();
|
|
@@ -5296,11 +5425,22 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5296
5425
|
for (let gr = 0; gr < grid.length; gr++) {
|
|
5297
5426
|
const r = originRow + gr;
|
|
5298
5427
|
if (r > maxRow) break;
|
|
5428
|
+
// C2 (quick 260906-cvo) — hoisted ONCE per ROW (never per cell): a group-header row's
|
|
5429
|
+
// `original` is table-core's `leafRows[0].original` (a synthetic row), so a write
|
|
5430
|
+
// resolved through it silently lands on an unrelated record. This ONE placement closes
|
|
5431
|
+
// pasteRange, cutRange, clearActiveRange AND fillRange — all four build a grid and hand
|
|
5432
|
+
// it to this single funnel, so guarding the four callers individually would be four
|
|
5433
|
+
// chances to miss one.
|
|
5434
|
+
const rowGrouped = rowIndexIsGrouped(r);
|
|
5299
5435
|
const cols = grid[gr] || [];
|
|
5300
5436
|
for (let gc = 0; gc < cols.length; gc++) {
|
|
5301
5437
|
const c = originCol + gc;
|
|
5302
5438
|
if (c > maxCol) break;
|
|
5439
|
+
// A group-row cell still counts toward `total` — it is a skipped TARGET, exactly like
|
|
5440
|
+
// a non-editable or validator-rejected cell — so the "N of M cells pasted" / "no cells
|
|
5441
|
+
// pasted" announce keeps its existing semantics (never silently under-reports M).
|
|
5303
5442
|
total = total + 1;
|
|
5443
|
+
if (rowGrouped) continue;
|
|
5304
5444
|
const colId = columnIdAt(r, c);
|
|
5305
5445
|
if (colId == null || !columnEditable(colId)) continue;
|
|
5306
5446
|
const rowObj = rowOriginalAt(r);
|
|
@@ -5806,7 +5946,13 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5806
5946
|
// isActiveCellEditable: the active cell sits in an editable column AND is a body cell
|
|
5807
5947
|
// (req-1). Gates the F2/Enter/printable edit-entry branches in onGridKeyDown; a
|
|
5808
5948
|
// non-editable active cell falls through to the reserved enterControl path.
|
|
5949
|
+
// C1 (quick 260906-cvo) — Layer 1: a group-header row is NEVER editable, regardless of
|
|
5950
|
+
// column. This is the single highest-value guard in the cluster: it closes Shift+F2, the
|
|
5951
|
+
// checkbox toggle, and the F2/Enter/printable edit-entry branches in ONE edit (they are all
|
|
5952
|
+
// `else if` branches gated on this predicate), AND restores fallthrough so Enter on a group
|
|
5953
|
+
// row reaches the group-toggle branch below them instead of opening an editor.
|
|
5809
5954
|
function isActiveCellEditable() {
|
|
5955
|
+
if (rowIndexIsGrouped(activeRow())) return false;
|
|
5810
5956
|
const colId = activeCellColumnId();
|
|
5811
5957
|
return colId != null && columnEditable(colId);
|
|
5812
5958
|
}
|
|
@@ -5824,6 +5970,14 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5824
5970
|
// non-virtual, wr.vi.index virtual).
|
|
5825
5971
|
function isEditing(rowIndex: any, colIndex: any) {
|
|
5826
5972
|
if (editVer() < 0) return false;
|
|
5973
|
+
// C1 (quick 260906-cvo) — Layer 6: the ONLY layer whose reachability does not depend on a
|
|
5974
|
+
// keystroke. Editing state is INDEX-based over the visible model, and nothing clears it
|
|
5975
|
+
// when the row model re-derives — a consumer that changes the grouping model WHILE a cell
|
|
5976
|
+
// editor is open can move a group-header row under the index $data.editingRow/
|
|
5977
|
+
// editingRowIndex still holds, rendering the editor on a group row with no beginEdit
|
|
5978
|
+
// involved. Placed AFTER the editVer read (preserves the Svelte/Solid re-derivation
|
|
5979
|
+
// discipline the header comment above documents).
|
|
5980
|
+
if (rowIndexIsGrouped(rowIndex)) return false;
|
|
5827
5981
|
if (editingRowIndex() != null && editingRowIndex() === rowIndex) {
|
|
5828
5982
|
const colId = columnIdAt(rowIndex, colIndex);
|
|
5829
5983
|
return colId != null && columnEditable(colId);
|
|
@@ -5998,6 +6152,10 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5998
6152
|
// $data) so a Tab-advance that just setState'd activeRow/Col works on React. Clears any
|
|
5999
6153
|
// prior invalid state. Focus moves into the editor.
|
|
6000
6154
|
function beginEdit(rowIndex: any, colIndex: any, seed: any) {
|
|
6155
|
+
// C1 (quick 260906-cvo) — Layer 2: closes the `onGridClick` (singleClickEdit) path, which
|
|
6156
|
+
// calls beginEdit directly gated ONLY on columnEditable — isActiveCellEditable (Layer 1)
|
|
6157
|
+
// does not run on that path at all.
|
|
6158
|
+
if (rowIndexIsGrouped(rowIndex)) return;
|
|
6001
6159
|
const colId = columnIdAt(rowIndex, colIndex);
|
|
6002
6160
|
if (colId == null || !columnEditable(colId)) return;
|
|
6003
6161
|
// A new edit session starts — reset the sync idempotency latch so THIS session's eventual
|
|
@@ -6204,6 +6362,11 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
6204
6362
|
// editor:'checkbox' columns only (Space/Enter/F2), full-row edit mode is unaffected (the
|
|
6205
6363
|
// editingRowIndex early return in onGridKeyDown already excludes it).
|
|
6206
6364
|
function toggleActiveBooleanCell() {
|
|
6365
|
+
// C1 (quick 260906-cvo) — Layer 4: the only write funnel bypassing both beginEdit/
|
|
6366
|
+
// commitEdit and applyGridToRange. Reachable today only through the Layer-1-gated
|
|
6367
|
+
// checkbox branch, so this line is redundant AT HEAD — it earns its place by making "no
|
|
6368
|
+
// write ever targets a group row" a property of the write funnels themselves.
|
|
6369
|
+
if (rowIndexIsGrouped(activeRow())) return;
|
|
6207
6370
|
const colId = columnIdAt(activeRow(), activeColIndex());
|
|
6208
6371
|
if (colId == null || !columnEditable(colId)) return;
|
|
6209
6372
|
const rowList = rows() || [];
|
|
@@ -6331,6 +6494,11 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
6331
6494
|
function beginRowEdit(row: any) {
|
|
6332
6495
|
const rowIndex = rowIndexOf(row);
|
|
6333
6496
|
if (rowIndex < 0) return;
|
|
6497
|
+
// C1 (quick 260906-cvo) — Layer 5: a second, independent editing-state funnel that takes a
|
|
6498
|
+
// row OBJECT from its caller. Reachable today only via Shift+F2 (Layer 1), so also
|
|
6499
|
+
// redundant at HEAD; kept for the same reason as Layer 4 — it is a mutation entry point,
|
|
6500
|
+
// and the natural call site for a future #rowActions "Edit" drop-in button.
|
|
6501
|
+
if (rowIndexIsGrouped(rowIndex)) return;
|
|
6334
6502
|
const editable = editableColumnsForRow(rowIndex);
|
|
6335
6503
|
if (editable.length === 0) return;
|
|
6336
6504
|
// A new edit session starts — reset the sync idempotency latch (see editCellLifecycle.rzts).
|
|
@@ -7199,20 +7367,20 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
7199
7367
|
<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="">
|
|
7200
7368
|
|
|
7201
7369
|
{<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="">
|
|
7202
|
-
{(_props.cellSlot ?? _props.slots?.['cell'])?.({ columnId: cell().column.id, column: cell().column, row: row()
|
|
7370
|
+
{(_props.cellSlot ?? _props.slots?.['cell'])?.({ columnId: cell().column.id, column: cell().column, row: cellSlotRow(row()), value: cell().getValue() }) ?? rozieDisplay(cell().getValue())}
|
|
7203
7371
|
</span>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7204
7372
|
{<Show when={hasEditorSlot(cell().column.id)} fallback={<Show when={editorTypeOf(cell().column.id) === 'number'} fallback={<Show when={editorTypeOf(cell().column.id) === 'select'} fallback={<Show when={editorTypeOf(cell().column.id) === 'checkbox'} fallback={<input type="text" data-editing-cell="" 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="" />}><input type="checkbox" data-editing-cell="" 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="" 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="">
|
|
7205
7373
|
<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>
|
|
7206
7374
|
</select></Show>}><input type="number" data-editing-cell="" 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 style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7207
|
-
{(_props.editorSlot ?? _props.slots?.['editor'])?.({ columnId: cell().column.id, column: cell().column, row: row()
|
|
7375
|
+
{(_props.editorSlot ?? _props.slots?.['editor'])?.({ columnId: cell().column.id, column: cell().column, row: cellSlotRow(row()), value: editorValueFor(cell().column.id), commit: editorCommitFor(cell().column.id), cancel: editorCancelFor(), autofocus: editorAutofocusFor(cell().column.id, rowIndexOf(row())) })}
|
|
7208
7376
|
</span></Show>}</span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7209
7377
|
<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>
|
|
7210
7378
|
<span class={"rdt-group-value"} data-rozie-s-d5dcab4c="">
|
|
7211
|
-
{(_props.cellSlot ?? _props.slots?.['cell'])?.({ columnId: cell().column.id, column: cell().column, row: row()
|
|
7379
|
+
{(_props.cellSlot ?? _props.slots?.['cell'])?.({ columnId: cell().column.id, column: cell().column, row: cellSlotRow(row()), value: cell().getValue() }) ?? rozieDisplay(cell().getValue())}
|
|
7212
7380
|
</span>
|
|
7213
7381
|
<span class={"rdt-group-count"} data-rozie-s-d5dcab4c="">{rozieDisplay('(' + groupSubRowCount(row()) + ')')}</span>
|
|
7214
7382
|
</span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7215
|
-
{(_props.selectCellSlot ?? _props.slots?.['selectCell'])?.({ row: row()
|
|
7383
|
+
{(_props.selectCellSlot ?? _props.slots?.['selectCell'])?.({ row: cellSlotRow(row()), checked: rowIsSelected(row()), toggle: e => onToggleRow(row(), e) }) ?? <input type="checkbox" aria-label="Select row" class={"rdt-select-row"} checked={rowIsSelected(row())} onChange={($event: Event & { currentTarget: HTMLInputElement; target: Element }) => { onToggleRow(row(), $event); }} data-rozie-s-d5dcab4c="" />}
|
|
7216
7384
|
</span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7217
7385
|
{<Show when={rowCanExpand(row())}><button type="button" data-expander="" aria-expanded={!!rowIsExpanded(row())} aria-label={rozieAttr(rowIsExpanded(row()) ? 'Collapse row' : 'Expand row')} class={"rdt-expander"} onClick={($event: MouseEvent & { currentTarget: HTMLButtonElement; target: Element }) => { onToggleExpand(row(), $event); }} data-rozie-s-d5dcab4c="">{rozieDisplay(rowIsExpanded(row()) ? '▾' : '▸')}</button></Show>}</span></Show>}{<Show when={isFillHandleCell(rowIndexOf(row()), colIndexOf(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>
|
|
7218
7386
|
</tr>
|
|
@@ -7275,20 +7443,20 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
7275
7443
|
{<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="">
|
|
7276
7444
|
|
|
7277
7445
|
{<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="">
|
|
7278
|
-
{(_props.cellSlot ?? _props.slots?.['cell'])?.({ columnId: cell().column.id, column: cell().column, row: wr().row
|
|
7446
|
+
{(_props.cellSlot ?? _props.slots?.['cell'])?.({ columnId: cell().column.id, column: cell().column, row: cellSlotRow(wr().row), value: cell().getValue() }) ?? rozieDisplay(cell().getValue())}
|
|
7279
7447
|
</span>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7280
7448
|
{<Show when={hasEditorSlot(cell().column.id)} fallback={<Show when={editorTypeOf(cell().column.id) === 'number'} fallback={<Show when={editorTypeOf(cell().column.id) === 'select'} fallback={<Show when={editorTypeOf(cell().column.id) === 'checkbox'} fallback={<input type="text" data-editing-cell="" 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="" />}><input type="checkbox" data-editing-cell="" 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="" 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="">
|
|
7281
7449
|
<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>
|
|
7282
7450
|
</select></Show>}><input type="number" data-editing-cell="" 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 style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7283
|
-
{(_props.editorSlot ?? _props.slots?.['editor'])?.({ columnId: cell().column.id, column: cell().column, row: wr().row
|
|
7451
|
+
{(_props.editorSlot ?? _props.slots?.['editor'])?.({ columnId: cell().column.id, column: cell().column, row: cellSlotRow(wr().row), value: editorValueFor(cell().column.id), commit: editorCommitFor(cell().column.id), cancel: editorCancelFor(), autofocus: editorAutofocusFor(cell().column.id, wr().vi.index) })}
|
|
7284
7452
|
</span></Show>}</span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7285
7453
|
<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>
|
|
7286
7454
|
<span class={"rdt-group-value"} data-rozie-s-d5dcab4c="">
|
|
7287
|
-
{(_props.cellSlot ?? _props.slots?.['cell'])?.({ columnId: cell().column.id, column: cell().column, row: wr().row
|
|
7455
|
+
{(_props.cellSlot ?? _props.slots?.['cell'])?.({ columnId: cell().column.id, column: cell().column, row: cellSlotRow(wr().row), value: cell().getValue() }) ?? rozieDisplay(cell().getValue())}
|
|
7288
7456
|
</span>
|
|
7289
7457
|
<span class={"rdt-group-count"} data-rozie-s-d5dcab4c="">{rozieDisplay('(' + groupSubRowCount(wr().row) + ')')}</span>
|
|
7290
7458
|
</span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7291
|
-
{(_props.selectCellSlot ?? _props.slots?.['selectCell'])?.({ row: wr().row
|
|
7459
|
+
{(_props.selectCellSlot ?? _props.slots?.['selectCell'])?.({ row: cellSlotRow(wr().row), checked: 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="" />}
|
|
7292
7460
|
</span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
7293
7461
|
{<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>}{<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>
|
|
7294
7462
|
|
|
@@ -32,4 +32,80 @@ const wrapAggregationFn = (fn: any) => {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
// ── B1 (quick 260906-afh) — nested group leaf def index ─────────────────────────────
|
|
36
|
+
// LOOKUP-ONLY. This map is never fed to table-core (it does not replace/reshape the
|
|
37
|
+
// columnDefs() array table-core consumes for createTable/setOptions — that stays
|
|
38
|
+
// byte-identical); it exists purely so `defFor(colId)` (columnChrome.rzts) can resolve
|
|
39
|
+
// a NESTED leaf's def (a `columns:` group child) in O(1), the same as a top-level leaf.
|
|
40
|
+
//
|
|
41
|
+
// collectNestedDefs: depth-first walk of a `columns` array (each entry may itself carry
|
|
42
|
+
// a nested `columns` array — 3+ levels deep is supported). Writes `out[String(d.id)] = d`
|
|
43
|
+
// ONLY when the id is ABSENT from `out` — first-write-wins so a caller can pre-seed `out`
|
|
44
|
+
// with the higher-precedence top-level defs and this pass only ever fills GAPS.
|
|
45
|
+
const collectNestedDefs = (list: any, out: any) => {
|
|
46
|
+
if (!Array.isArray(list)) return
|
|
47
|
+
for (const d of list) {
|
|
48
|
+
if (!d || d.id == null) continue
|
|
49
|
+
const id = String(d.id)
|
|
50
|
+
if (!(id in out)) out[id] = d
|
|
51
|
+
if (Array.isArray(d.columns)) collectNestedDefs(d.columns, out)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// indexDefsById: builds a null-prototype `{ id: def }` map from a `columnDefs()`-shaped
|
|
56
|
+
// array in TWO passes. Pass 1 registers every TOP-LEVEL def unconditionally — so any id
|
|
57
|
+
// that already resolves at HEAD (a top-level leaf OR a top-level group's OWN id) keeps
|
|
58
|
+
// resolving to the EXACT SAME def, the byte-identical-lookup guarantee for every
|
|
59
|
+
// pre-existing caller. Pass 2 walks each top-level entry's `columns` (if any) via
|
|
60
|
+
// collectNestedDefs, so a nested leaf only fills a GAP — it can never shadow a
|
|
61
|
+
// same-named top-level id (top-level wins regardless of array order). Object.create(null)
|
|
62
|
+
// (T-AFH-01): a consumer column id of "__proto__"/"constructor" cannot pollute
|
|
63
|
+
// Object.prototype through this map — mirrors the existing `byId` T-48-PP guard in
|
|
64
|
+
// columnBuilders.rzts.
|
|
65
|
+
const indexDefsById = (defs: any): any => {
|
|
66
|
+
const out = Object.create(null)
|
|
67
|
+
if (!Array.isArray(defs)) return out
|
|
68
|
+
for (const d of defs) {
|
|
69
|
+
if (!d || d.id == null) continue
|
|
70
|
+
out[String(d.id)] = d
|
|
71
|
+
}
|
|
72
|
+
for (const d of defs) {
|
|
73
|
+
if (d && Array.isArray(d.columns)) collectNestedDefs(d.columns, out)
|
|
74
|
+
}
|
|
75
|
+
return out
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── C5+C6 (quick 260906-cvo) — groupable-leaf collection for the #groupBar ──────────────
|
|
79
|
+
// C5: buildConfigDef's GROUP branch (columnBuilders.rzts) returns `{ id, header, columns }`
|
|
80
|
+
// with NO `groupable` key and no accessor, so the OLD `groupableColumns()` loop's
|
|
81
|
+
// `d.groupable === false` skip never fired for a group column — it was wrongly OFFERED to
|
|
82
|
+
// the bar even though a group column can never produce a grouping value.
|
|
83
|
+
// C6: that same loop walked TOP-LEVEL `columnDefs()` entries only, so a genuinely groupable
|
|
84
|
+
// NESTED leaf (a `columns:` group child) was never offered at all.
|
|
85
|
+
//
|
|
86
|
+
// collectGroupableLeafDefs: a DEDICATED depth-first walk, NOT a reuse of `indexDefsById`.
|
|
87
|
+
// `indexDefsById` is a LOOKUP index — it deliberately registers group columns too (a group
|
|
88
|
+
// id must keep resolving for `defFor`), and its insertion order is all-top-level-first-then-
|
|
89
|
+
// nested, which would both offer group columns AND hand the group bar an order that does not
|
|
90
|
+
// match the rendered header order. This walk instead: recurses into a `columns:` array
|
|
91
|
+
// WITHOUT ever pushing the group entry itself (closes C5 — a group column has no accessor
|
|
92
|
+
// and cannot be a grouping target, so an explicit `groupable: true` on a group entry is
|
|
93
|
+
// meaningless and is likewise never pushed); pushes every other (leaf) entry unless it opts
|
|
94
|
+
// out via `groupable === false` (closes C6 — nested leaves are now reachable); walks in
|
|
95
|
+
// DECLARATION order, depth-first, matching the rendered header order.
|
|
96
|
+
const collectGroupableLeafDefs = (defs: any): any[] => {
|
|
97
|
+
const out: any[] = []
|
|
98
|
+
if (!Array.isArray(defs)) return out
|
|
99
|
+
for (const d of defs) {
|
|
100
|
+
if (!d) continue
|
|
101
|
+
if (Array.isArray(d.columns)) {
|
|
102
|
+
out.push(...collectGroupableLeafDefs(d.columns))
|
|
103
|
+
continue
|
|
104
|
+
}
|
|
105
|
+
if (d.groupable === false) continue
|
|
106
|
+
out.push(d)
|
|
107
|
+
}
|
|
108
|
+
return out
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export { isSafeKey, wrapAggregationFn, collectNestedDefs, indexDefsById, collectGroupableLeafDefs }
|