@jielga/tmdatagrid 2.0.0-beta.2 → 2.0.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -212
- package/dist/index.d.ts +1664 -632
- package/dist/index.js +5226 -3223
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/docs/anatomy.md +102 -0
- package/docs/cell-selection.md +154 -0
- package/docs/column-layout.md +204 -0
- package/docs/columns.md +262 -0
- package/docs/components.md +304 -0
- package/docs/editing.md +603 -0
- package/docs/editors.md +250 -0
- package/docs/export.md +326 -0
- package/docs/filtering.md +358 -0
- package/docs/getting-started.md +123 -0
- package/docs/grouping.md +165 -0
- package/docs/loading-and-empty.md +92 -0
- package/docs/localization.md +79 -0
- package/docs/menu.md +143 -0
- package/docs/pagination.md +144 -0
- package/docs/persistence.md +111 -0
- package/docs/portfolio-rebalancer.md +94 -0
- package/docs/query-builder.md +175 -0
- package/docs/quick-search.md +83 -0
- package/docs/row-details.md +113 -0
- package/docs/row-interaction.md +148 -0
- package/docs/row-pinning.md +132 -0
- package/docs/row-selection.md +134 -0
- package/docs/row-styling.md +133 -0
- package/docs/scrolling.md +111 -0
- package/docs/server-query.md +246 -0
- package/docs/server-side.md +206 -0
- package/docs/sorting.md +101 -0
- package/docs/styling.md +126 -0
- package/docs/summary-row.md +76 -0
- package/docs/testing.md +309 -0
- package/docs/toolbar.md +161 -0
- package/docs/use-tm-data-grid.md +361 -0
- package/package.json +21 -45
- package/skills/appearance/SKILL.md +70 -17
- package/skills/cell-selection/SKILL.md +70 -76
- package/skills/columns/SKILL.md +131 -32
- package/skills/data/SKILL.md +100 -23
- package/skills/editing/SKILL.md +217 -96
- package/skills/editing/references/common-mistakes.md +111 -24
- package/skills/editing/references/editing-api.md +63 -39
- package/skills/editing/references/editors-and-validation.md +77 -19
- package/skills/filtering/SKILL.md +148 -40
- package/skills/getting-started/SKILL.md +18 -16
- package/skills/grouping/SKILL.md +32 -15
- package/skills/options/SKILL.md +39 -9
- package/skills/rows/SKILL.md +22 -18
- package/skills/server-side/SKILL.md +170 -17
- package/skills/testing/SKILL.md +10 -7
- package/src/{tmdatagrid/TMDataGridContext.ts → TMDataGridContext.ts} +7 -19
- package/src/{tmdatagrid/components → components}/TMDataGrid.module.css +7 -1
- package/src/{tmdatagrid/components → components}/TMDataGrid.tsx +39 -23
- package/src/{tmdatagrid/components → components}/TMDataGridCellEditor.tsx +106 -38
- package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.module.css +5 -1
- package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.tsx +47 -55
- package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.tsx +4 -4
- package/src/components/TMDataGridDraftActions.tsx +307 -0
- package/src/{tmdatagrid/components → components}/TMDataGridEditColumn.tsx +58 -50
- package/src/{tmdatagrid/components → components}/TMDataGridEntryRows.tsx +150 -115
- package/src/components/TMDataGridExportPicker.module.css +77 -0
- package/src/components/TMDataGridExportPicker.tsx +234 -0
- package/src/components/TMDataGridFilterPanel.module.css +54 -0
- package/src/components/TMDataGridFilterPanel.tsx +348 -0
- package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.tsx +7 -5
- package/src/components/TMDataGridFilterSurface.module.css +54 -0
- package/src/components/TMDataGridFilterSurface.tsx +167 -0
- package/src/{tmdatagrid/components → components}/TMDataGridFooter.tsx +53 -13
- package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.tsx +4 -3
- package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.module.css +10 -0
- package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.tsx +100 -28
- package/src/components/TMDataGridHeaderFilterRow.module.css +51 -0
- package/src/components/TMDataGridHeaderFilterRow.tsx +301 -0
- package/src/components/TMDataGridMenu.tsx +354 -0
- package/src/{tmdatagrid/components → components}/TMDataGridSelectColumn.tsx +12 -7
- package/src/{tmdatagrid/components → components}/TMDataGridTable.module.css +90 -67
- package/src/{tmdatagrid/components → components}/TMDataGridTable.tsx +678 -156
- package/src/components/TMDataGridToolbar.module.css +21 -0
- package/src/components/TMDataGridToolbar.tsx +181 -0
- package/src/{tmdatagrid/components → components}/editors/TMDataGridBooleanEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridDateEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridMultiSelectEditor.tsx +3 -3
- package/src/components/editors/TMDataGridNumberEditor.tsx +70 -0
- package/src/{tmdatagrid/components → components}/editors/TMDataGridSelectEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridStringEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/editorShared.ts +17 -31
- package/src/{tmdatagrid/components → components}/filters/DgAutocompleteFilter.tsx +5 -4
- package/src/{tmdatagrid/components → components}/filters/DgDateRangeFilter.tsx +22 -5
- package/src/{tmdatagrid/components → components}/filters/DgRangeSliderFilter.tsx +5 -1
- package/src/{tmdatagrid/components → components}/filters/DgTriStateFilter.tsx +5 -1
- package/src/{tmdatagrid/components → components}/filters/TMDataGridFilterValueInput.tsx +44 -28
- package/src/components/filters/controlLayout.ts +32 -0
- package/src/components/filters/filterControlFor.ts +65 -0
- package/src/{tmdatagrid/components → components}/icons.ts +1 -0
- package/src/{tmdatagrid/components → components}/sticky.module.css +44 -0
- package/src/components/useHideableColumns.ts +52 -0
- package/src/{tmdatagrid/core → core}/autosize.ts +5 -2
- package/src/{tmdatagrid/core → core}/capabilities.ts +14 -6
- package/src/{tmdatagrid/core → core}/columnOptions.ts +46 -0
- package/src/{tmdatagrid/core → core}/columnOrdering.ts +33 -14
- package/src/{tmdatagrid/core → core}/columnUtils.ts +44 -5
- package/src/core/controlledState.ts +179 -0
- package/src/core/controlledStateSync.ts +108 -0
- package/src/core/deletedRows.ts +34 -0
- package/src/core/dom.ts +74 -0
- package/src/core/editEngine.ts +2476 -0
- package/src/{tmdatagrid/core → core}/editorFocus.ts +8 -4
- package/src/core/export.ts +843 -0
- package/src/{tmdatagrid/core → core}/filterControls.ts +38 -1
- package/src/{tmdatagrid/core → core}/filterOperators.ts +64 -1
- package/src/core/filterSurface.ts +99 -0
- package/src/{tmdatagrid/core → core}/labels.ts +66 -8
- package/src/{tmdatagrid/core → core}/labelsSv.ts +26 -3
- package/src/core/pageReset.ts +120 -0
- package/src/{tmdatagrid/core → core}/persistence.ts +22 -5
- package/src/core/resizePreview.ts +141 -0
- package/src/core/summary.ts +59 -0
- package/src/core/useSettledTableState.ts +36 -0
- package/src/{tmdatagrid/index.ts → index.ts} +75 -12
- package/src/{tmdatagrid/useTMDataGrid.tsx → useTMDataGrid.tsx} +734 -135
- package/src/useTMDataGridExport.ts +78 -0
- package/src/tmdatagrid/components/TMDataGridEditActions.tsx +0 -162
- package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +0 -35
- package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +0 -354
- package/src/tmdatagrid/components/TMDataGridToolbar.module.css +0 -12
- package/src/tmdatagrid/components/TMDataGridToolbar.tsx +0 -162
- package/src/tmdatagrid/components/editors/TMDataGridNumberEditor.tsx +0 -40
- package/src/tmdatagrid/core/cellExport.ts +0 -320
- package/src/tmdatagrid/core/editEngine.ts +0 -1006
- package/src/tmdatagrid/core/summary.ts +0 -35
- /package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridFooter.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridRowNumberColumn.tsx +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridSearch.tsx +0 -0
- /package/src/{tmdatagrid/core → core}/cellNavigation.ts +0 -0
- /package/src/{tmdatagrid/core → core}/cellRange.ts +0 -0
- /package/src/{tmdatagrid/core → core}/draftCellContext.ts +0 -0
- /package/src/{tmdatagrid/core → core}/expanding.ts +0 -0
- /package/src/{tmdatagrid/core → core}/grouping.ts +0 -0
- /package/src/{tmdatagrid/core → core}/matchHighlight.ts +0 -0
- /package/src/{tmdatagrid/core → core}/quickSearch.ts +0 -0
- /package/src/{tmdatagrid/core → core}/rowPinning.ts +0 -0
- /package/src/{tmdatagrid/core → core}/rowSelection.ts +0 -0
- /package/src/{tmdatagrid/core → core}/sizes.ts +0 -0
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ActionIcon,
|
|
3
|
+
Box,
|
|
4
|
+
type BoxProps,
|
|
5
|
+
Group,
|
|
6
|
+
Select,
|
|
7
|
+
Text,
|
|
8
|
+
Tooltip,
|
|
9
|
+
} from "@mantine/core";
|
|
2
10
|
import type { RowData } from "@tanstack/react-table";
|
|
3
|
-
import { useSelector } from "@tanstack/react-store";
|
|
4
11
|
import { useMemo, type ReactNode } from "react";
|
|
5
12
|
import classes from "./TMDataGridFooter.module.css";
|
|
6
13
|
import { useTMDataGridContext } from "../TMDataGridContext";
|
|
7
14
|
import { getGridCapabilities } from "../core/capabilities";
|
|
15
|
+
import { useSettledTableState } from "../core/useSettledTableState";
|
|
8
16
|
import { isPagingActive } from "../core/rowSelection";
|
|
9
17
|
import { ChevronLeftIcon, ChevronRightIcon } from "./icons";
|
|
10
18
|
import type { TMDataGridTable } from "../useTMDataGrid";
|
|
@@ -63,6 +71,12 @@ export type TMDataGridPaginationControls = {
|
|
|
63
71
|
PageSize: () => ReactNode;
|
|
64
72
|
/** The "1–25 of 300" range label. */
|
|
65
73
|
Range: () => ReactNode;
|
|
74
|
+
/**
|
|
75
|
+
* The "Page 3 of 200" label - what a server-paged grid usually shows in
|
|
76
|
+
* place of a row range. Not in the default footer; put it in a
|
|
77
|
+
* `renderPagination` layout.
|
|
78
|
+
*/
|
|
79
|
+
PageNumber: () => ReactNode;
|
|
66
80
|
/** The previous/next buttons. */
|
|
67
81
|
Pager: () => ReactNode;
|
|
68
82
|
};
|
|
@@ -122,7 +136,7 @@ function PaginationPageSize({
|
|
|
122
136
|
pageSizeOptions: ReadonlyArray<number>;
|
|
123
137
|
}) {
|
|
124
138
|
const { table, features, labels, controlSize } = useTMDataGridContext();
|
|
125
|
-
|
|
139
|
+
useSettledTableState(table.store);
|
|
126
140
|
const paging = isPagingActive(table, features);
|
|
127
141
|
const { pageSize } = table.store.state.pagination;
|
|
128
142
|
// A page size restored from storage, or set through `initialState`, need not
|
|
@@ -163,7 +177,7 @@ function PaginationPageSize({
|
|
|
163
177
|
|
|
164
178
|
function PaginationRange() {
|
|
165
179
|
const { table, features, labels, controlSize } = useTMDataGridContext();
|
|
166
|
-
|
|
180
|
+
useSettledTableState(table.store);
|
|
167
181
|
const paging = isPagingActive(table, features);
|
|
168
182
|
const { pageIndex, pageSize } = table.store.state.pagination;
|
|
169
183
|
const total = table.getRowCount();
|
|
@@ -182,9 +196,29 @@ function PaginationRange() {
|
|
|
182
196
|
);
|
|
183
197
|
}
|
|
184
198
|
|
|
199
|
+
function PaginationPageNumber() {
|
|
200
|
+
const { table, features, labels, controlSize } = useTMDataGridContext();
|
|
201
|
+
useSettledTableState(table.store);
|
|
202
|
+
const paging = isPagingActive(table, features);
|
|
203
|
+
const { pageIndex } = table.store.state.pagination;
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<Text span size={controlSize} c="dimmed" data-dg-part="page-number">
|
|
207
|
+
{paging
|
|
208
|
+
? labels.pageNumber({
|
|
209
|
+
page: pageIndex + 1,
|
|
210
|
+
pageCount: table.getPageCount(),
|
|
211
|
+
})
|
|
212
|
+
: // Same reasoning as the range label: nothing is being sliced, so a
|
|
213
|
+
// page number would be a lie.
|
|
214
|
+
labels.groupedAllRows(table.getFilteredRowModel().rows.length)}
|
|
215
|
+
</Text>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
185
219
|
function PaginationPager() {
|
|
186
220
|
const { table, features, labels } = useTMDataGridContext();
|
|
187
|
-
|
|
221
|
+
useSettledTableState(table.store);
|
|
188
222
|
const paging = isPagingActive(table, features);
|
|
189
223
|
|
|
190
224
|
return (
|
|
@@ -213,7 +247,8 @@ function PaginationPager() {
|
|
|
213
247
|
);
|
|
214
248
|
}
|
|
215
249
|
|
|
216
|
-
|
|
250
|
+
/** Mantine's style props (`mt="sm"`, `px="md"`) are set on the footer bar. */
|
|
251
|
+
export type TMDataGridFooterProps = BoxProps & {
|
|
217
252
|
pageSizeOptions?: ReadonlyArray<number>;
|
|
218
253
|
/**
|
|
219
254
|
* Replaces the built-in pager, and is handed the pieces of it.
|
|
@@ -223,7 +258,7 @@ export type TMDataGridFooterProps = {
|
|
|
223
258
|
* renderPagination={({ state, actions, Controls }) => (
|
|
224
259
|
* <Group>
|
|
225
260
|
* <Controls.PageSize />
|
|
226
|
-
* <
|
|
261
|
+
* <Controls.PageNumber />
|
|
227
262
|
* <Controls.Pager />
|
|
228
263
|
* </Group>
|
|
229
264
|
* )}
|
|
@@ -248,9 +283,12 @@ const DEFAULT_PAGE_SIZE_OPTIONS = [10, 25, 50, 100];
|
|
|
248
283
|
export function TMDataGridFooter({
|
|
249
284
|
pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,
|
|
250
285
|
renderPagination,
|
|
286
|
+
className,
|
|
287
|
+
...others
|
|
251
288
|
}: TMDataGridFooterProps) {
|
|
289
|
+
const footerClassName = [classes.footer, className].filter(Boolean).join(" ");
|
|
252
290
|
const { table, features, labels } = useTMDataGridContext();
|
|
253
|
-
|
|
291
|
+
useSettledTableState(table.store);
|
|
254
292
|
|
|
255
293
|
// Bound once per option list rather than per render, so the controls keep
|
|
256
294
|
// their identity and the elements they render keep theirs.
|
|
@@ -258,6 +296,7 @@ export function TMDataGridFooter({
|
|
|
258
296
|
() => ({
|
|
259
297
|
PageSize: () => <PaginationPageSize pageSizeOptions={pageSizeOptions} />,
|
|
260
298
|
Range: PaginationRange,
|
|
299
|
+
PageNumber: PaginationPageNumber,
|
|
261
300
|
Pager: PaginationPager,
|
|
262
301
|
}),
|
|
263
302
|
[pageSizeOptions],
|
|
@@ -273,9 +312,9 @@ export function TMDataGridFooter({
|
|
|
273
312
|
if (renderPagination) {
|
|
274
313
|
const { state, actions } = getTMDataGridPaginationApi(table, paging);
|
|
275
314
|
return (
|
|
276
|
-
<
|
|
315
|
+
<Box data-dg-part="footer" className={footerClassName} {...others}>
|
|
277
316
|
{renderPagination({ state, actions, Controls })}
|
|
278
|
-
</
|
|
317
|
+
</Box>
|
|
279
318
|
);
|
|
280
319
|
}
|
|
281
320
|
|
|
@@ -288,17 +327,18 @@ export function TMDataGridFooter({
|
|
|
288
327
|
w={260}
|
|
289
328
|
position="top"
|
|
290
329
|
>
|
|
291
|
-
<
|
|
330
|
+
<Box
|
|
292
331
|
data-dg-part="footer"
|
|
293
|
-
className={
|
|
332
|
+
className={footerClassName}
|
|
294
333
|
data-paging-suspended={!paging}
|
|
334
|
+
{...others}
|
|
295
335
|
>
|
|
296
336
|
{/* The default render is the controls in order - which is what makes
|
|
297
337
|
`renderPagination` a rearrangement rather than a rebuild. */}
|
|
298
338
|
<Controls.PageSize />
|
|
299
339
|
<Controls.Range />
|
|
300
340
|
<Controls.Pager />
|
|
301
|
-
</
|
|
341
|
+
</Box>
|
|
302
342
|
</Tooltip>
|
|
303
343
|
);
|
|
304
344
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { UnstyledButton } from "@mantine/core";
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
useBodyControlTabIndex,
|
|
4
4
|
useTMDataGridContext,
|
|
5
5
|
} from "../TMDataGridContext";
|
|
6
6
|
import type { ColumnDef, Row, RowData } from "@tanstack/react-table";
|
|
@@ -52,9 +52,8 @@ function GroupCell<TData extends RowData>({
|
|
|
52
52
|
row: Row<TMDataGridFeatures, TData>;
|
|
53
53
|
}) {
|
|
54
54
|
const { labels } = useTMDataGridContext();
|
|
55
|
+
const tabIndex = useBodyControlTabIndex();
|
|
55
56
|
const expanded = useSelector(row.table.store, () => row.getIsExpanded());
|
|
56
|
-
// See useCellControlTabIndex: reached by stepping into the cell, not by Tab.
|
|
57
|
-
const tabIndex = useCellControlTabIndex();
|
|
58
57
|
|
|
59
58
|
// Leaf rows keep the lane empty: their values are in the data columns, and
|
|
60
59
|
// the indent alone is what places them under their group.
|
|
@@ -67,6 +66,8 @@ function GroupCell<TData extends RowData>({
|
|
|
67
66
|
className={classes.groupToggle}
|
|
68
67
|
// Padding rather than margin, so the whole indented width stays clickable.
|
|
69
68
|
style={{ paddingInlineStart: row.depth * INDENT_STEP }}
|
|
69
|
+
// See useBodyControlTabIndex: under cell selection, reached by stepping
|
|
70
|
+
// into the cell or by the Tab walk within the row, not by the page.
|
|
70
71
|
tabIndex={tabIndex}
|
|
71
72
|
aria-expanded={expanded}
|
|
72
73
|
aria-label={
|
|
@@ -149,6 +149,16 @@
|
|
|
149
149
|
z-index: 1;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
/* The last column's divider straddles inward instead. Half of a 10px box
|
|
153
|
+
hanging past the last track is 5px of scrollable overflow, which is a
|
|
154
|
+
permanent horizontal scrollbar under a grid whose columns fit exactly. The
|
|
155
|
+
grab area keeps its width, wholly inside the cell, and flex-end keeps the
|
|
156
|
+
visible line on the column edge where every other divider draws it. */
|
|
157
|
+
.headerCell:last-child .columnSeparator {
|
|
158
|
+
transform: none;
|
|
159
|
+
justify-content: flex-end;
|
|
160
|
+
}
|
|
161
|
+
|
|
152
162
|
.columnSeparatorResizable {
|
|
153
163
|
cursor: col-resize;
|
|
154
164
|
touch-action: none;
|
|
@@ -12,12 +12,14 @@ import {
|
|
|
12
12
|
import type { TMDataGridColumnLayout } from "./TMDataGridTable";
|
|
13
13
|
import { getColumnCapabilities, getGridCapabilities } from "../core/capabilities";
|
|
14
14
|
import { autosizeColumn } from "../core/autosize";
|
|
15
|
+
import { isNode } from "../core/dom";
|
|
15
16
|
import {
|
|
16
17
|
getColumnRegion,
|
|
17
18
|
getStepTargetColumn,
|
|
18
19
|
keepGeneratedColumnsOutermost,
|
|
19
20
|
moveColumn,
|
|
20
21
|
moveColumnByStep,
|
|
22
|
+
pinningPositionOfSide,
|
|
21
23
|
type TMDataGridDropSide,
|
|
22
24
|
} from "../core/columnOrdering";
|
|
23
25
|
import {
|
|
@@ -46,6 +48,7 @@ import {
|
|
|
46
48
|
UngroupIcon,
|
|
47
49
|
} from "./icons";
|
|
48
50
|
import { GROUP_COLUMN_ID } from "./TMDataGridGroupColumn";
|
|
51
|
+
import { TMDataGridMenuColumns } from "./TMDataGridMenu";
|
|
49
52
|
import { type TMDataGridFeatures, openColumnFilter } from "../useTMDataGrid";
|
|
50
53
|
|
|
51
54
|
/** The table as the header cell sees it, after the context type erasure. */
|
|
@@ -89,7 +92,7 @@ export function TMDataGridHeaderCell({
|
|
|
89
92
|
renderColumnMenuItems?: TMDataGridColumnMenuItemsRenderer;
|
|
90
93
|
}) {
|
|
91
94
|
const api = useTMDataGridContext();
|
|
92
|
-
const { table, features, labels } = api;
|
|
95
|
+
const { table, features, filters, labels } = api;
|
|
93
96
|
const column = header.column;
|
|
94
97
|
const [menuOpened, setMenuOpened] = useState(false);
|
|
95
98
|
const [contextMenuOpened, setContextMenuOpened] = useState(false);
|
|
@@ -133,6 +136,9 @@ export function TMDataGridHeaderCell({
|
|
|
133
136
|
(filter) => filter.id === column.id && isFilterActive(filter.value),
|
|
134
137
|
);
|
|
135
138
|
const isResizing = resizingColumnId === column.id;
|
|
139
|
+
// Any column, not just this one: a resize elsewhere must not leave a
|
|
140
|
+
// neighbouring header ready to start a native drag under the pointer.
|
|
141
|
+
const isAnyResizing = resizingColumnId !== false;
|
|
136
142
|
const pinnedAt = layout.pinnedAt;
|
|
137
143
|
|
|
138
144
|
// Every affordance below mirrors a TanStack capability check, so switching a
|
|
@@ -188,7 +194,7 @@ export function TMDataGridHeaderCell({
|
|
|
188
194
|
function handleDragLeave(event: DragEvent<HTMLDivElement>) {
|
|
189
195
|
// dragleave also fires when the pointer crosses into a child element.
|
|
190
196
|
const next = event.relatedTarget;
|
|
191
|
-
if (next
|
|
197
|
+
if (isNode(next) && event.currentTarget.contains(next)) return;
|
|
192
198
|
setDropSide(null);
|
|
193
199
|
}
|
|
194
200
|
|
|
@@ -222,6 +228,43 @@ export function TMDataGridHeaderCell({
|
|
|
222
228
|
if (container) autosizeColumn({ table, columnId: column.id, container });
|
|
223
229
|
}
|
|
224
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Writes the width this header's columns are rendered with into
|
|
233
|
+
* `columnSizing`, for the ones that have no width there yet.
|
|
234
|
+
*
|
|
235
|
+
* An unresized column is a fluid (`fr`) track: it renders at whatever share
|
|
236
|
+
* of the grid it is given, while `column.getSize()` keeps reporting the
|
|
237
|
+
* declared `size`. Anything that switches a column to exact pixels has to
|
|
238
|
+
* close that gap first, or the column jumps from the width on screen to the
|
|
239
|
+
* declared one.
|
|
240
|
+
*
|
|
241
|
+
* Under a held pointer that jump is more than cosmetic: it slides the
|
|
242
|
+
* divider out from under the cursor onto the neighbouring header, where the
|
|
243
|
+
* browser starts a native column-move drag instead. That drag swallows the
|
|
244
|
+
* `mouseup` the resize listens for, so the resize keeps running after the
|
|
245
|
+
* button is released.
|
|
246
|
+
*/
|
|
247
|
+
function freezeRenderedWidths() {
|
|
248
|
+
const container = cellRef.current?.closest<HTMLElement>(
|
|
249
|
+
"[data-dg-scroll-container]",
|
|
250
|
+
);
|
|
251
|
+
if (!container) return;
|
|
252
|
+
const sizing = table.store.state.columnSizing;
|
|
253
|
+
const frozen: Record<string, number> = {};
|
|
254
|
+
for (const leaf of header.getLeafHeaders()) {
|
|
255
|
+
const id = leaf.column.id;
|
|
256
|
+
if (id in sizing) continue;
|
|
257
|
+
const width = container
|
|
258
|
+
.querySelector<HTMLElement>(
|
|
259
|
+
`[role="columnheader"][data-column-id="${CSS.escape(id)}"]`,
|
|
260
|
+
)
|
|
261
|
+
?.getBoundingClientRect().width;
|
|
262
|
+
if (width) frozen[id] = Math.round(width);
|
|
263
|
+
}
|
|
264
|
+
if (Object.keys(frozen).length === 0) return;
|
|
265
|
+
table.setColumnSizing((previous) => ({ ...previous, ...frozen }));
|
|
266
|
+
}
|
|
267
|
+
|
|
225
268
|
/**
|
|
226
269
|
* Pinned columns are positioned with `getStart()` / `getAfter()`, which sum
|
|
227
270
|
* `column.getSize()`. Fluid (`fr`) columns don't report their rendered width
|
|
@@ -229,17 +272,9 @@ export function TMDataGridHeaderCell({
|
|
|
229
272
|
* keeps the column looking the same and keeps the offsets exact.
|
|
230
273
|
*/
|
|
231
274
|
function setPinned(position: "left" | "right" | false) {
|
|
232
|
-
if (position
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
table.setColumnSizing((prev) => ({
|
|
236
|
-
...prev,
|
|
237
|
-
[column.id]: Math.round(width),
|
|
238
|
-
}));
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
column.pin(position);
|
|
242
|
-
// `pin("right")` appends, which would leave the column outside the edit
|
|
275
|
+
if (position) freezeRenderedWidths();
|
|
276
|
+
column.pin(pinningPositionOfSide(position));
|
|
277
|
+
// `pin("end")` appends, which would leave the column outside the edit
|
|
243
278
|
// lane. Normalising after the pin rather than writing the lane by hand
|
|
244
279
|
// keeps TanStack's own handling of a header group's leaves.
|
|
245
280
|
table.setColumnPinning(keepGeneratedColumnsOutermost);
|
|
@@ -265,7 +300,9 @@ export function TMDataGridHeaderCell({
|
|
|
265
300
|
<Menu.Divider key="sort-divider" />,
|
|
266
301
|
);
|
|
267
302
|
}
|
|
268
|
-
|
|
303
|
+
// Not under header filters: the item's only job is to reveal a control that
|
|
304
|
+
// is already on screen, one row below this menu.
|
|
305
|
+
if (canFilter && !filters.inHeader) {
|
|
269
306
|
menuItems.push(
|
|
270
307
|
<Menu.Item
|
|
271
308
|
key="filter"
|
|
@@ -444,13 +481,19 @@ export function TMDataGridHeaderCell({
|
|
|
444
481
|
}
|
|
445
482
|
if (canManageColumns) {
|
|
446
483
|
menuItems.push(
|
|
447
|
-
<Menu.
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
484
|
+
<Menu.Sub key="manage">
|
|
485
|
+
<Menu.Sub.Target>
|
|
486
|
+
<Menu.Sub.Item leftSection={<ColumnsIcon size={16} stroke={1.6} />}>
|
|
487
|
+
{labels.manageColumns}
|
|
488
|
+
</Menu.Sub.Item>
|
|
489
|
+
</Menu.Sub.Target>
|
|
490
|
+
<Menu.Sub.Dropdown>
|
|
491
|
+
{/* No search box: `Menu.Search` registers on the root menu context,
|
|
492
|
+
which switches off type-ahead and the arrow-key handling of every
|
|
493
|
+
dropdown of this menu - the column menu's included. */}
|
|
494
|
+
<TMDataGridMenuColumns searchable={false} />
|
|
495
|
+
</Menu.Sub.Dropdown>
|
|
496
|
+
</Menu.Sub>,
|
|
454
497
|
);
|
|
455
498
|
}
|
|
456
499
|
// A trailing divider reads as a dropped item - drop the divider instead.
|
|
@@ -514,16 +557,23 @@ export function TMDataGridHeaderCell({
|
|
|
514
557
|
: undefined
|
|
515
558
|
}
|
|
516
559
|
className={cellClass}
|
|
517
|
-
// Dropped while
|
|
518
|
-
// resizes the column instead of starting to move it
|
|
519
|
-
//
|
|
520
|
-
|
|
560
|
+
// Dropped while any column is resizing, so that dragging the separator
|
|
561
|
+
// resizes the column instead of starting to move it - and so that a
|
|
562
|
+
// header the divider passes under cannot start a move of its own. The
|
|
563
|
+
// resize handler records the column on mousedown, before the first
|
|
564
|
+
// pointer move.
|
|
565
|
+
draggable={canReorder && !isAnyResizing}
|
|
521
566
|
onDragStart={canReorder ? handleDragStart : undefined}
|
|
522
567
|
onDragEnd={canReorder ? handleDragEnd : undefined}
|
|
523
568
|
onDragOver={isDropTarget ? handleDragOver : undefined}
|
|
524
569
|
onDragLeave={isDropTarget ? handleDragLeave : undefined}
|
|
525
570
|
onDrop={isDropTarget ? handleDrop : undefined}
|
|
526
571
|
style={{
|
|
572
|
+
// A group header covers the tracks of the leaves under it. Without the
|
|
573
|
+
// span it would sit in one track and leave the rest of its width with
|
|
574
|
+
// no cell at all - a gap the body rows scroll up through, since the
|
|
575
|
+
// header rows above the body are what hides them.
|
|
576
|
+
gridColumn: header.colSpan > 1 ? `span ${header.colSpan}` : undefined,
|
|
527
577
|
left: pinnedAt === "left" ? layout.offset : undefined,
|
|
528
578
|
right: pinnedAt === "right" ? layout.offset : undefined,
|
|
529
579
|
position: pinnedAt ? "sticky" : undefined,
|
|
@@ -565,7 +615,9 @@ export function TMDataGridHeaderCell({
|
|
|
565
615
|
|
|
566
616
|
{isLeaf && (
|
|
567
617
|
<div className={classes.headerActions}>
|
|
568
|
-
{
|
|
618
|
+
{/* Dropped under header filters, where the filled-in control below
|
|
619
|
+
is both the indicator and the way in. */}
|
|
620
|
+
{isFiltered && !filters.inHeader && (
|
|
569
621
|
<ActionIcon
|
|
570
622
|
className={classes.headerAction}
|
|
571
623
|
data-pinned-visible="true"
|
|
@@ -671,15 +723,35 @@ export function TMDataGridHeaderCell({
|
|
|
671
723
|
// Keeps a drag that starts on the separator from being picked up by
|
|
672
724
|
// the draggable header around it.
|
|
673
725
|
draggable={false}
|
|
726
|
+
// The drag starts from `column.getSize()`, so the rendered width has
|
|
727
|
+
// to be in `columnSizing` before the handler reads it - otherwise
|
|
728
|
+
// the first pointer move snaps a fluid column to its declared size.
|
|
729
|
+
//
|
|
730
|
+
// The handler listens for the moves and the release on the document
|
|
731
|
+
// it is given - the separator's own, not the global one, which is
|
|
732
|
+
// the opener's when the grid is rendered through a portal into a
|
|
733
|
+
// window opened with `window.open`.
|
|
674
734
|
onMouseDown={
|
|
675
735
|
canResize
|
|
676
736
|
? (event) => {
|
|
677
737
|
suppressSortRef.current = true;
|
|
678
|
-
|
|
738
|
+
freezeRenderedWidths();
|
|
739
|
+
header.getResizeHandler(event.currentTarget.ownerDocument)(
|
|
740
|
+
event,
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
: undefined
|
|
744
|
+
}
|
|
745
|
+
onTouchStart={
|
|
746
|
+
canResize
|
|
747
|
+
? (event) => {
|
|
748
|
+
freezeRenderedWidths();
|
|
749
|
+
header.getResizeHandler(event.currentTarget.ownerDocument)(
|
|
750
|
+
event,
|
|
751
|
+
);
|
|
679
752
|
}
|
|
680
753
|
: undefined
|
|
681
754
|
}
|
|
682
|
-
onTouchStart={canResize ? header.getResizeHandler() : undefined}
|
|
683
755
|
onClick={(event) => event.stopPropagation()}
|
|
684
756
|
// The spreadsheet gesture: double-click the divider, the column
|
|
685
757
|
// fits its content. Same measurement as the menu item.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/* The second header row, under the group rows and above the body. Same column
|
|
2
|
+
tracks as every other row, through subgrid, so it follows resizing,
|
|
3
|
+
reordering and pinning without knowing about any of them.
|
|
4
|
+
|
|
5
|
+
Sticky like the header rows above it, and stacked under them by the same
|
|
6
|
+
measurement: the table writes every header row's `top` inline once it knows
|
|
7
|
+
how tall the rows before it are. This is the pre-measurement fallback, and
|
|
8
|
+
it is right for the common case of one header row. */
|
|
9
|
+
.headerFilterRow {
|
|
10
|
+
display: grid;
|
|
11
|
+
grid-column: 1 / -1;
|
|
12
|
+
grid-template-columns: subgrid;
|
|
13
|
+
position: sticky;
|
|
14
|
+
top: var(--dg-header-height);
|
|
15
|
+
z-index: var(--dg-z-header, 6);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.headerFilterCell {
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
gap: 2px;
|
|
22
|
+
min-width: 0;
|
|
23
|
+
/* A 60px column has room for the input and nothing beside it. Clipping is
|
|
24
|
+
what keeps the control from spilling over its neighbour; the operator
|
|
25
|
+
button is the first thing to go. */
|
|
26
|
+
overflow: hidden;
|
|
27
|
+
padding: calc(var(--dg-padding) / 2);
|
|
28
|
+
/* Opaque: the body scrolls underneath it. */
|
|
29
|
+
background-color: var(--mantine-color-body);
|
|
30
|
+
border-bottom: 1px solid var(--mantine-color-default-border);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* The generated lanes - checkbox, details, row number - hold no filter, so
|
|
34
|
+
they keep the header's own padding rather than a control's. */
|
|
35
|
+
.headerFilterCell[data-control-column="true"] {
|
|
36
|
+
padding: 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.headerFilterValue {
|
|
40
|
+
flex: 1;
|
|
41
|
+
min-width: 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.headerFilterOperator {
|
|
45
|
+
flex: none;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.operatorItemActive {
|
|
49
|
+
font-weight: 600;
|
|
50
|
+
color: var(--mantine-primary-color-filled);
|
|
51
|
+
}
|