@jielga/tmdatagrid 2.0.0-beta.9 → 2.0.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.
Files changed (154) hide show
  1. package/README.md +5 -212
  2. package/dist/index.d.ts +1281 -768
  3. package/dist/index.js +4607 -3250
  4. package/dist/index.js.map +1 -1
  5. package/dist/styles.css +1 -1
  6. package/docs/adding-rows.md +132 -0
  7. package/docs/anatomy.md +119 -0
  8. package/docs/card-view.md +108 -0
  9. package/docs/cell-selection.md +194 -0
  10. package/docs/column-layout.md +182 -0
  11. package/docs/column-menu.md +66 -0
  12. package/docs/columns.md +268 -0
  13. package/docs/components.md +311 -0
  14. package/docs/draft-store.md +242 -0
  15. package/docs/editing.md +303 -0
  16. package/docs/editors.md +250 -0
  17. package/docs/export.md +319 -0
  18. package/docs/filtering.md +362 -0
  19. package/docs/getting-started.md +123 -0
  20. package/docs/grouping.md +165 -0
  21. package/docs/loading-and-empty.md +92 -0
  22. package/docs/localization.md +79 -0
  23. package/docs/menu.md +143 -0
  24. package/docs/migrating-to-2.md +163 -0
  25. package/docs/pagination.md +144 -0
  26. package/docs/persistence.md +114 -0
  27. package/docs/portfolio-rebalancer.md +94 -0
  28. package/docs/query-builder.md +179 -0
  29. package/docs/quick-search.md +84 -0
  30. package/docs/row-details.md +115 -0
  31. package/docs/row-interaction.md +149 -0
  32. package/docs/row-pinning.md +132 -0
  33. package/docs/row-selection.md +136 -0
  34. package/docs/row-styling.md +133 -0
  35. package/docs/scrolling.md +112 -0
  36. package/docs/server-query.md +246 -0
  37. package/docs/server-side.md +206 -0
  38. package/docs/sorting.md +101 -0
  39. package/docs/styling.md +126 -0
  40. package/docs/summary-row.md +76 -0
  41. package/docs/testing.md +744 -0
  42. package/docs/toolbar.md +161 -0
  43. package/docs/use-tm-data-grid.md +361 -0
  44. package/package.json +22 -46
  45. package/skills/appearance/SKILL.md +72 -19
  46. package/skills/cell-selection/SKILL.md +46 -47
  47. package/skills/columns/SKILL.md +90 -34
  48. package/skills/data/SKILL.md +86 -16
  49. package/skills/editing/SKILL.md +67 -40
  50. package/skills/editing/references/common-mistakes.md +77 -69
  51. package/skills/editing/references/editing-api.md +22 -19
  52. package/skills/editing/references/editors-and-validation.md +24 -17
  53. package/skills/filtering/SKILL.md +148 -40
  54. package/skills/getting-started/SKILL.md +17 -15
  55. package/skills/grouping/SKILL.md +31 -16
  56. package/skills/options/SKILL.md +7 -7
  57. package/skills/rows/SKILL.md +22 -18
  58. package/skills/server-side/SKILL.md +170 -17
  59. package/skills/testing/SKILL.md +150 -32
  60. package/skills/testing-components/SKILL.md +230 -0
  61. package/skills/testing-editing/SKILL.md +240 -0
  62. package/src/{tmdatagrid/components → components}/TMDataGrid.tsx +38 -21
  63. package/src/{tmdatagrid/components → components}/TMDataGridCellEditor.tsx +54 -8
  64. package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.module.css +5 -1
  65. package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.tsx +47 -55
  66. package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.tsx +5 -51
  67. package/src/{tmdatagrid/components → components}/TMDataGridDraftActions.tsx +41 -24
  68. package/src/{tmdatagrid/components → components}/TMDataGridEditColumn.tsx +12 -59
  69. package/src/{tmdatagrid/components → components}/TMDataGridEntryRows.tsx +164 -92
  70. package/src/components/TMDataGridExportPicker.module.css +77 -0
  71. package/src/components/TMDataGridExportPicker.tsx +234 -0
  72. package/src/components/TMDataGridFilterPanel.module.css +54 -0
  73. package/src/components/TMDataGridFilterPanel.tsx +348 -0
  74. package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.tsx +15 -8
  75. package/src/components/TMDataGridFilterSurface.module.css +54 -0
  76. package/src/components/TMDataGridFilterSurface.tsx +167 -0
  77. package/src/{tmdatagrid/components → components}/TMDataGridFooter.tsx +53 -90
  78. package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.tsx +5 -69
  79. package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.module.css +12 -2
  80. package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.tsx +58 -21
  81. package/src/components/TMDataGridHeaderFilterRow.module.css +51 -0
  82. package/src/components/TMDataGridHeaderFilterRow.tsx +301 -0
  83. package/src/components/TMDataGridMenu.tsx +357 -0
  84. package/src/{tmdatagrid/components → components}/TMDataGridSelectColumn.tsx +11 -48
  85. package/src/{tmdatagrid/components → components}/TMDataGridTable.module.css +69 -56
  86. package/src/{tmdatagrid/components → components}/TMDataGridTable.tsx +240 -138
  87. package/src/{tmdatagrid/components → components}/TMDataGridToolbar.module.css +5 -0
  88. package/src/components/TMDataGridToolbar.tsx +181 -0
  89. package/src/{tmdatagrid/components → components}/editors/TMDataGridBooleanEditor.tsx +3 -3
  90. package/src/{tmdatagrid/components → components}/editors/TMDataGridDateEditor.tsx +3 -3
  91. package/src/{tmdatagrid/components → components}/editors/TMDataGridMultiSelectEditor.tsx +3 -3
  92. package/src/{tmdatagrid/components → components}/editors/TMDataGridNumberEditor.tsx +3 -3
  93. package/src/{tmdatagrid/components → components}/editors/TMDataGridSelectEditor.tsx +3 -3
  94. package/src/{tmdatagrid/components → components}/editors/TMDataGridStringEditor.tsx +3 -3
  95. package/src/{tmdatagrid/components → components}/editors/editorShared.ts +16 -2
  96. package/src/{tmdatagrid/components → components}/filters/DgAutocompleteFilter.tsx +5 -4
  97. package/src/{tmdatagrid/components → components}/filters/DgDateRangeFilter.tsx +22 -5
  98. package/src/{tmdatagrid/components → components}/filters/DgRangeSliderFilter.tsx +5 -1
  99. package/src/{tmdatagrid/components → components}/filters/DgTriStateFilter.tsx +5 -1
  100. package/src/{tmdatagrid/components → components}/filters/TMDataGridFilterValueInput.tsx +44 -28
  101. package/src/components/filters/controlLayout.ts +32 -0
  102. package/src/components/filters/filterControlFor.ts +65 -0
  103. package/src/components/generatedColumns.tsx +187 -0
  104. package/src/{tmdatagrid/components → components}/icons.ts +1 -0
  105. package/src/{tmdatagrid/components → components}/sticky.module.css +44 -0
  106. package/src/components/useHideableColumns.ts +52 -0
  107. package/src/{tmdatagrid/core → core}/autosize.ts +5 -2
  108. package/src/{tmdatagrid/core → core}/columnOptions.ts +60 -8
  109. package/src/{tmdatagrid/core → core}/columnOrdering.ts +33 -14
  110. package/src/{tmdatagrid/core → core}/columnUtils.ts +44 -5
  111. package/src/core/controlledStateSync.ts +108 -0
  112. package/src/core/deletedRows.ts +34 -0
  113. package/src/core/dom.ts +74 -0
  114. package/src/{tmdatagrid/core → core}/editEngine.ts +1107 -460
  115. package/src/core/export.ts +704 -0
  116. package/src/{tmdatagrid/core → core}/filterControls.ts +38 -1
  117. package/src/{tmdatagrid/core → core}/filterOperators.ts +64 -1
  118. package/src/core/filterSurface.ts +99 -0
  119. package/src/{tmdatagrid/core → core}/grouping.ts +21 -0
  120. package/src/{tmdatagrid/core → core}/labels.ts +51 -6
  121. package/src/{tmdatagrid/core → core}/labelsSv.ts +27 -6
  122. package/src/core/pageReset.ts +120 -0
  123. package/src/core/pagination.ts +81 -0
  124. package/src/{tmdatagrid/core → core}/persistence.ts +22 -5
  125. package/src/{tmdatagrid/core → core}/summary.ts +20 -4
  126. package/src/{tmdatagrid/index.ts → index.ts} +69 -35
  127. package/src/{tmdatagrid/useTMDataGrid.tsx → useTMDataGrid.tsx} +428 -109
  128. package/src/useTMDataGridExport.ts +78 -0
  129. package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +0 -35
  130. package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +0 -354
  131. package/src/tmdatagrid/components/TMDataGridToolbar.tsx +0 -161
  132. package/src/tmdatagrid/core/cellExport.ts +0 -320
  133. /package/src/{tmdatagrid/TMDataGridContext.ts → TMDataGridContext.ts} +0 -0
  134. /package/src/{tmdatagrid/components → components}/TMDataGrid.module.css +0 -0
  135. /package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.module.css +0 -0
  136. /package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.module.css +0 -0
  137. /package/src/{tmdatagrid/components → components}/TMDataGridFooter.module.css +0 -0
  138. /package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.module.css +0 -0
  139. /package/src/{tmdatagrid/components → components}/TMDataGridRowNumberColumn.tsx +0 -0
  140. /package/src/{tmdatagrid/components → components}/TMDataGridSearch.tsx +0 -0
  141. /package/src/{tmdatagrid/core → core}/capabilities.ts +0 -0
  142. /package/src/{tmdatagrid/core → core}/cellNavigation.ts +0 -0
  143. /package/src/{tmdatagrid/core → core}/cellRange.ts +0 -0
  144. /package/src/{tmdatagrid/core → core}/controlledState.ts +0 -0
  145. /package/src/{tmdatagrid/core → core}/draftCellContext.ts +0 -0
  146. /package/src/{tmdatagrid/core → core}/editorFocus.ts +0 -0
  147. /package/src/{tmdatagrid/core → core}/expanding.ts +0 -0
  148. /package/src/{tmdatagrid/core → core}/matchHighlight.ts +0 -0
  149. /package/src/{tmdatagrid/core → core}/quickSearch.ts +0 -0
  150. /package/src/{tmdatagrid/core → core}/resizePreview.ts +0 -0
  151. /package/src/{tmdatagrid/core → core}/rowPinning.ts +0 -0
  152. /package/src/{tmdatagrid/core → core}/rowSelection.ts +0 -0
  153. /package/src/{tmdatagrid/core → core}/sizes.ts +0 -0
  154. /package/src/{tmdatagrid/core → core}/useSettledTableState.ts +0 -0
@@ -1,10 +1,11 @@
1
1
  import { Checkbox } from "@mantine/core";
2
2
  import { useSelector } from "@tanstack/react-store";
3
- import type { ColumnDef, Row, RowData } from "@tanstack/react-table";
3
+ import type { Row, RowData } from "@tanstack/react-table";
4
4
  import {
5
5
  useBodyControlTabIndex,
6
6
  useTMDataGridContext,
7
7
  } from "../TMDataGridContext";
8
+ import { isMouseEvent } from "../core/dom";
8
9
  import {
9
10
  getDisplayedRows,
10
11
  getSelectableRowIds,
@@ -62,7 +63,7 @@ function SelectAllCheckbox<TData extends RowData>({
62
63
  * change - the same reason the rest of the chrome reads them. See
63
64
  * readFeatureFlags.
64
65
  */
65
- function SelectAllHeader<TData extends RowData>({
66
+ export function SelectAllHeader<TData extends RowData>({
66
67
  table,
67
68
  }: {
68
69
  table: TMDataGridTable<TData>;
@@ -72,7 +73,7 @@ function SelectAllHeader<TData extends RowData>({
72
73
  return <SelectAllCheckbox table={table} />;
73
74
  }
74
75
 
75
- function SelectRowCheckbox<TData extends RowData>({
76
+ export function SelectRowCheckbox<TData extends RowData>({
76
77
  row,
77
78
  }: {
78
79
  row: Row<TMDataGridFeatures, TData>;
@@ -80,9 +81,14 @@ function SelectRowCheckbox<TData extends RowData>({
80
81
  // Cells render inside the grid's provider, so the checkbox can reach the
81
82
  // chrome store - it needs the shift-click pivot, and the feature flags to know
82
83
  // which row model a range is measured over.
83
- const { ui, features, labels } = useTMDataGridContext();
84
+ const { ui, features, labels, edit } = useTMDataGridContext();
84
85
  const tabIndex = useBodyControlTabIndex();
85
86
  const isGroupRow = row.subRows.length > 0;
87
+ // A deletion mark makes the row unselectable (see `enableRowSelection` in
88
+ // the hook), and the box below reads that through `getCanSelect`. The
89
+ // mark lives in the edit store, so the subscription is what re-renders
90
+ // the box when it lands - the row and cell identities do not change.
91
+ useSelector(edit.store, (state) => state.deletedRowIds.includes(row.id));
86
92
 
87
93
  // A group row is never selected by id: `rowSelection` only holds the leaves,
88
94
  // and TanStack's `getIsSelected()` is a plain lookup in that map. A group
@@ -126,7 +132,7 @@ function SelectRowCheckbox<TData extends RowData>({
126
132
  // silently loses the clicked row, which `onChange` then toggles back off.
127
133
  onChange={(event) => {
128
134
  const native = event.nativeEvent;
129
- const isMouse = native instanceof MouseEvent;
135
+ const isMouse = isMouseEvent(native);
130
136
  const resolved = resolveRowSelectionClick({
131
137
  rows: getDisplayedRows(row.table, features),
132
138
  rowId: row.id,
@@ -148,46 +154,3 @@ function SelectRowCheckbox<TData extends RowData>({
148
154
  />
149
155
  );
150
156
  }
151
-
152
- /**
153
- * The generated checkbox column, prepended under
154
- * `selectionMode: "checkbox"` (the default) or `"checkboxAndHighlight"`.
155
- */
156
- export function createSelectColumn<TData extends RowData>(
157
- label = "Checkbox selection",
158
- ): ColumnDef<TMDataGridFeatures, TData, unknown> {
159
- return {
160
- id: SELECT_COLUMN_ID,
161
- meta: {
162
- label,
163
- align: "center",
164
- // Structurally the first column; it also anchors the left pinned lane, so
165
- // no other column can be moved in front of it.
166
- enableOrdering: false,
167
- },
168
- // A system lane: as wide as the control it holds and no wider. Fixed at
169
- // every scale - the control does not grow with the font size, so neither
170
- // should its track.
171
- size: 36,
172
- minSize: 36,
173
- maxSize: 36,
174
- enableResizing: false,
175
- enableSorting: false,
176
- enableColumnFilter: false,
177
- enableGlobalFilter: false,
178
- // Not a column the user chose, so not one they can switch off: hiding the
179
- // lane would take the grid's only way to select a row with it, with the
180
- // row-selection state left behind and no way back to it. Keeping it out of
181
- // "Manage columns" follows from this - the panel lists what can be hidden.
182
- enableHiding: false,
183
- // Structurally pinned to the left; users shouldn't be able to move it.
184
- enablePinning: false,
185
- header: ({ table }) => <SelectAllHeader table={table} />,
186
- cell: ({ row }) => <SelectRowCheckbox row={row} />,
187
- // Every cell on a group row that is not the grouped column counts as
188
- // aggregated, this lane included, and an aggregated cell with nothing
189
- // declared renders blank. Without this the checkbox would disappear from
190
- // exactly the rows that select a whole group. See renderCellContent.
191
- aggregatedCell: ({ row }) => <SelectRowCheckbox row={row} />,
192
- };
193
- }
@@ -1,4 +1,4 @@
1
- /* Anchors the filter panel, which floats over the first body rows. */
1
+ /* Anchors the filter popup, which floats over the first body rows. */
2
2
  .tableWrapper {
3
3
  position: relative;
4
4
  flex: 1;
@@ -7,9 +7,17 @@
7
7
  flex-direction: column;
8
8
  }
9
9
 
10
+ /* The sidebar surface is a second column of the wrapper rather than a layer
11
+ over the rows, so the wrapper lays its children out side by side. The
12
+ sidebar's own module decides which side it lands on. */
13
+ .tableWrapper[data-dg-filter-sidebar] {
14
+ flex-direction: row;
15
+ }
16
+
10
17
  .scrollContainer {
11
18
  position: relative;
12
19
  flex: 1;
20
+ min-width: 0;
13
21
  min-height: 0;
14
22
  overflow: auto;
15
23
  /* Lets .messageRow size itself to the scrollport (100cqi) instead of the
@@ -20,6 +28,13 @@
20
28
  /* Column tracks are set inline via gridTemplateColumns. */
21
29
  .grid {
22
30
  display: grid;
31
+ /* The grid is the containing block every sticky row inside it is held by, so
32
+ it has to reach the bottom of the scrollport even when the rows do not fill
33
+ it - that is what puts the summary row on the bottom edge of a short body.
34
+ `align-content` keeps the free space out of the row tracks; distributed
35
+ into them it would stretch every row instead. */
36
+ min-height: 100%;
37
+ align-content: start;
23
38
  }
24
39
 
25
40
  /* The pair of focusable elements bracketing the body, which is how the body
@@ -45,46 +60,6 @@
45
60
  z-index: var(--dg-z-header, 6);
46
61
  }
47
62
 
48
- /* The scrolled-under shadow: a soft band under the header, only while body
49
- rows are actually beneath it. On the last header row - the header/body
50
- boundary - not on every stacked group row. A scroll-driven animation, like
51
- the pinned-lane gradients: the shadow tracks the scroll on the compositor
52
- with no listener and no render, and an inactive timeline (nothing to
53
- scroll) leaves `opacity: 0` standing, so a grid that fits shows nothing.
54
- Where unsupported there is simply no shadow - the header's border already
55
- draws the boundary. */
56
- .headerRow[data-dg-header-last]::after {
57
- content: "";
58
- position: absolute;
59
- inset: 100% 0 auto 0;
60
- height: 6px;
61
- pointer-events: none;
62
- background: linear-gradient(
63
- to bottom,
64
- var(--dg-header-shadow-color, rgba(0, 0, 0, 0.14)),
65
- transparent
66
- );
67
- opacity: 0;
68
- }
69
-
70
- @supports (animation-timeline: scroll()) {
71
- .headerRow[data-dg-header-last]::after {
72
- animation: dg-header-shadow linear both;
73
- animation-timeline: scroll(nearest block);
74
- /* Arrives over the first rows leaving, reads as depth not as a fade. */
75
- animation-range: 0px 24px;
76
- }
77
- }
78
-
79
- @keyframes dg-header-shadow {
80
- from {
81
- opacity: 0;
82
- }
83
- to {
84
- opacity: 1;
85
- }
86
- }
87
-
88
63
  .bodyRow {
89
64
  --row-bg: var(--mantine-color-body);
90
65
  display: grid;
@@ -164,8 +139,13 @@
164
139
  align-items: center;
165
140
  padding: 0 var(--dg-padding);
166
141
  border-bottom: 1px solid var(--mantine-color-default-border);
167
- /* Inherited from .bodyRow - sticky cells need an opaque background. */
168
- background-color: var(--row-bg);
142
+ /* Two layers, not one. The base is the theme's body colour and `--row-bg`
143
+ (inherited from .bodyRow) is a tint painted over it, so a consumer whose
144
+ row token is translucent - a `color-mix(..., transparent)` - is safe under
145
+ the sticky lanes: a single translucent `background-color` would let the
146
+ scrolled body read through every pinned cell and pinned row. */
147
+ background-color: var(--mantine-color-body);
148
+ background-image: linear-gradient(var(--row-bg), var(--row-bg));
169
149
  font-size: var(--dg-font-size);
170
150
  }
171
151
 
@@ -189,13 +169,15 @@
189
169
  A tint over the row's own background rather than instead of it, so a
190
170
  selected cell on a highlighted row still reads as both. `color-mix` keeps
191
171
  one declaration for light and dark: it thins the theme's primary colour into
192
- whatever `--row-bg` the row resolved to. */
172
+ whatever `--row-bg` the row resolved to. Painted as the cell's tint layer,
173
+ over the opaque base above, for the same reason `--row-bg` is. */
193
174
  .bodyCell[data-selected="true"] {
194
- background-color: color-mix(
175
+ --dg-range-tint: color-mix(
195
176
  in srgb,
196
177
  var(--mantine-primary-color-filled) 14%,
197
178
  var(--row-bg)
198
179
  );
180
+ background-image: linear-gradient(var(--dg-range-tint), var(--dg-range-tint));
199
181
  }
200
182
 
201
183
  /* The outline around the block, drawn edge by edge: only the cells on a side
@@ -267,6 +249,15 @@
267
249
  gap: 2px;
268
250
  }
269
251
 
252
+ /* The editor's own box: the input fills it, and it is what the validation
253
+ tooltip is anchored to. */
254
+ .cellEditorField {
255
+ display: flex;
256
+ align-items: center;
257
+ flex: 1 1 auto;
258
+ min-width: 0;
259
+ }
260
+
270
261
  /* cellConfirm's ✓ / ✕ pair, beside the input. */
271
262
  .cellEditorActions {
272
263
  display: flex;
@@ -275,16 +266,19 @@
275
266
  }
276
267
 
277
268
  /* Corner triangles for a cell's edit state, drawn like a spreadsheet's: a
278
- draft not yet saved is blue, a value failing validation is red. The cell
279
- needs a positioning context; pinned and focused cells set their own inline
269
+ draft not yet saved is blue, a value failing validation is red. An entry
270
+ cell takes the red one only - everything in it is new. The cell needs a
271
+ positioning context; pinned and focused cells set their own inline
280
272
  `position`, which wins over this. */
281
273
  .bodyCell[data-dirty],
282
- .bodyCell[data-invalid] {
274
+ .bodyCell[data-invalid],
275
+ .entryCell[data-invalid] {
283
276
  position: relative;
284
277
  }
285
278
 
286
279
  .bodyCell[data-dirty]::after,
287
- .bodyCell[data-invalid]::after {
280
+ .bodyCell[data-invalid]::after,
281
+ .entryCell[data-invalid]::after {
288
282
  content: "";
289
283
  position: absolute;
290
284
  top: 0;
@@ -295,7 +289,8 @@
295
289
  }
296
290
 
297
291
  /* Invalid wins over dirty - a cell is usually both. */
298
- .bodyCell[data-invalid]::after {
292
+ .bodyCell[data-invalid]::after,
293
+ .entryCell[data-invalid]::after {
299
294
  border-top-color: var(--mantine-color-error);
300
295
  border-right-color: var(--mantine-color-error);
301
296
  }
@@ -322,7 +317,9 @@
322
317
  .detailsCell {
323
318
  grid-column: 1 / -1;
324
319
  border-bottom: 1px solid var(--mantine-color-default-border);
325
- background-color: var(--row-bg);
320
+ /* Base and tint, exactly as .bodyCell paints them. */
321
+ background-color: var(--mantine-color-body);
322
+ background-image: linear-gradient(var(--row-bg), var(--row-bg));
326
323
  padding: var(--dg-padding);
327
324
  /* The row above may be the selection control, which turns text selection off
328
325
  for everything inside it. A panel is content, so it takes both back. */
@@ -348,7 +345,7 @@
348
345
  padding: var(--mantine-spacing-xl);
349
346
  }
350
347
 
351
- /* Marked deleted under draft: struck through and inert until submitAll
348
+ /* Marked deleted under draft: struck through and inert until saveDrafts
352
349
  reports it - a row about to go away must not keep taking edits. Interaction
353
350
  comes off the data cells, not the row: `pointer-events: none` on the row
354
351
  would also kill the edit lane's Restore, which is the one control the row
@@ -389,8 +386,8 @@
389
386
  element, its rows flow normally inside it. The top block stacks under the
390
387
  entry block when both exist; `--dg-entry-height` is measured only then, so
391
388
  the offset is otherwise the header alone. The bottom block stacks above the
392
- summary row the same way. Rows are opaque already (`--row-bg` paints the
393
- body colour), so the blocks only add the edge borders. */
389
+ summary row the same way. The cells inside paint an opaque base under
390
+ `--row-bg` already, so the blocks only add the edge borders. */
394
391
  .pinnedTopBlock {
395
392
  display: grid;
396
393
  grid-column: 1 / -1;
@@ -438,7 +435,13 @@
438
435
  .entryRow[data-committed="true"] .entryCell {
439
436
  padding: 0 var(--dg-padding);
440
437
  border-bottom: 1px solid var(--mantine-color-default-border);
441
- background-color: var(--dg-row-new-bg, var(--mantine-color-body));
438
+ /* The tint over .entryCell's opaque base, not instead of it: the block is
439
+ sticky, so a translucent `--dg-row-new-bg` would otherwise show the body
440
+ scrolling underneath. */
441
+ background-image: linear-gradient(
442
+ var(--dg-row-new-bg, transparent),
443
+ var(--dg-row-new-bg, transparent)
444
+ );
442
445
  }
443
446
 
444
447
  .entryRow[data-committed="true"] .entryCell[data-control-column="true"] {
@@ -484,12 +487,22 @@
484
487
 
485
488
  /* The summary row - one sticky row of column footers along the bottom edge,
486
489
  rendered only while at least one column defines `footer`. Same subgrid
487
- placement as the header, so its cells sit exactly on the body's tracks. */
490
+ placement as the header, so its cells sit exactly on the body's tracks.
491
+
492
+ Both insets are set, so the row is on the scrollport's bottom edge whether
493
+ the body overflows or not. A percentage in a sticky inset resolves against
494
+ the scrollport: with too few rows to fill it, `top` pushes the row down
495
+ until `.grid` - which reaches the scrollport's bottom - stops it, flush with
496
+ the edge; with the body overflowing, `top` is already satisfied and
497
+ `bottom: 0` pins the row as it always did. Where the two conflict the top
498
+ one wins, which is why it has to be the row's own height short of 100% and
499
+ not 100%. `--dg-summary-height` is measured whenever this row exists. */
488
500
  .summaryRow {
489
501
  display: grid;
490
502
  grid-column: 1 / -1;
491
503
  grid-template-columns: subgrid;
492
504
  position: sticky;
505
+ top: calc(100% - var(--dg-summary-height, 0px));
493
506
  bottom: 0;
494
507
  z-index: var(--dg-z-summary-row, 4);
495
508
  }