@jielga/tmdatagrid 2.0.0-beta.8 → 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.
- package/README.md +5 -212
- package/dist/index.d.ts +1323 -796
- package/dist/index.js +4719 -3193
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/docs/adding-rows.md +132 -0
- package/docs/anatomy.md +119 -0
- package/docs/card-view.md +108 -0
- package/docs/cell-selection.md +194 -0
- package/docs/column-layout.md +182 -0
- package/docs/column-menu.md +66 -0
- package/docs/columns.md +268 -0
- package/docs/components.md +311 -0
- package/docs/draft-store.md +242 -0
- package/docs/editing.md +303 -0
- package/docs/editors.md +250 -0
- package/docs/export.md +319 -0
- package/docs/filtering.md +362 -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/migrating-to-2.md +163 -0
- package/docs/pagination.md +144 -0
- package/docs/persistence.md +114 -0
- package/docs/portfolio-rebalancer.md +94 -0
- package/docs/query-builder.md +179 -0
- package/docs/quick-search.md +84 -0
- package/docs/row-details.md +115 -0
- package/docs/row-interaction.md +149 -0
- package/docs/row-pinning.md +132 -0
- package/docs/row-selection.md +136 -0
- package/docs/row-styling.md +133 -0
- package/docs/scrolling.md +112 -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 +744 -0
- package/docs/toolbar.md +161 -0
- package/docs/use-tm-data-grid.md +361 -0
- package/package.json +22 -46
- package/skills/appearance/SKILL.md +72 -19
- package/skills/cell-selection/SKILL.md +69 -78
- package/skills/columns/SKILL.md +90 -34
- package/skills/data/SKILL.md +86 -16
- package/skills/editing/SKILL.md +83 -50
- package/skills/editing/references/common-mistakes.md +77 -69
- package/skills/editing/references/editing-api.md +31 -23
- package/skills/editing/references/editors-and-validation.md +24 -17
- package/skills/filtering/SKILL.md +148 -40
- package/skills/getting-started/SKILL.md +17 -15
- package/skills/grouping/SKILL.md +31 -16
- package/skills/options/SKILL.md +8 -8
- package/skills/rows/SKILL.md +22 -18
- package/skills/server-side/SKILL.md +170 -17
- package/skills/testing/SKILL.md +150 -32
- package/skills/testing-components/SKILL.md +230 -0
- package/skills/testing-editing/SKILL.md +240 -0
- 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 +38 -21
- package/src/{tmdatagrid/components → components}/TMDataGridCellEditor.tsx +73 -7
- 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 +9 -55
- package/src/{tmdatagrid/components → components}/TMDataGridDraftActions.tsx +41 -24
- package/src/{tmdatagrid/components → components}/TMDataGridEditColumn.tsx +23 -63
- package/src/components/TMDataGridEntryRows.tsx +354 -0
- 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 +15 -8
- package/src/components/TMDataGridFilterSurface.module.css +54 -0
- package/src/components/TMDataGridFilterSurface.tsx +167 -0
- package/src/{tmdatagrid/components → components}/TMDataGridFooter.tsx +53 -90
- package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.tsx +9 -72
- package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.module.css +12 -2
- package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.tsx +58 -21
- package/src/components/TMDataGridHeaderFilterRow.module.css +51 -0
- package/src/components/TMDataGridHeaderFilterRow.tsx +301 -0
- package/src/components/TMDataGridMenu.tsx +357 -0
- package/src/{tmdatagrid/components → components}/TMDataGridSelectColumn.tsx +15 -53
- package/src/{tmdatagrid/components → components}/TMDataGridTable.module.css +88 -65
- package/src/{tmdatagrid/components → components}/TMDataGridTable.tsx +579 -165
- package/src/{tmdatagrid/components → components}/TMDataGridToolbar.module.css +5 -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/{tmdatagrid/components → components}/editors/TMDataGridNumberEditor.tsx +3 -3
- 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 +16 -2
- 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/components/generatedColumns.tsx +187 -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 +5 -5
- package/src/{tmdatagrid/core → core}/columnOptions.ts +60 -8
- package/src/{tmdatagrid/core → core}/columnOrdering.ts +33 -14
- package/src/{tmdatagrid/core → core}/columnUtils.ts +44 -5
- package/src/core/controlledStateSync.ts +108 -0
- package/src/core/deletedRows.ts +34 -0
- package/src/core/dom.ts +74 -0
- package/src/{tmdatagrid/core → core}/editEngine.ts +1172 -388
- package/src/{tmdatagrid/core → core}/editorFocus.ts +8 -4
- package/src/core/export.ts +704 -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}/grouping.ts +21 -0
- package/src/{tmdatagrid/core → core}/labels.ts +51 -6
- package/src/{tmdatagrid/core → core}/labelsSv.ts +27 -6
- package/src/core/pageReset.ts +120 -0
- package/src/core/pagination.ts +81 -0
- package/src/{tmdatagrid/core → core}/persistence.ts +22 -5
- package/src/{tmdatagrid/core → core}/summary.ts +20 -4
- package/src/{tmdatagrid/index.ts → index.ts} +70 -36
- package/src/{tmdatagrid/useTMDataGrid.tsx → useTMDataGrid.tsx} +534 -123
- package/src/useTMDataGridExport.ts +78 -0
- package/src/tmdatagrid/components/TMDataGridEntryRows.tsx +0 -298
- package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +0 -35
- package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +0 -354
- package/src/tmdatagrid/components/TMDataGridToolbar.tsx +0 -161
- package/src/tmdatagrid/core/cellExport.ts +0 -320
- /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}/controlledState.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}/matchHighlight.ts +0 -0
- /package/src/{tmdatagrid/core → core}/quickSearch.ts +0 -0
- /package/src/{tmdatagrid/core → core}/resizePreview.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
- /package/src/{tmdatagrid/core → core}/useSettledTableState.ts +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Button, Pill, UnstyledButton } from "@mantine/core";
|
|
1
|
+
import { Box, type BoxProps, Button, Pill, UnstyledButton } from "@mantine/core";
|
|
2
2
|
import type { RowData } from "@tanstack/react-table";
|
|
3
3
|
import { useSelector } from "@tanstack/react-store";
|
|
4
4
|
import classes from "./TMDataGridFilterPills.module.css";
|
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
import type { TMDataGridSize } from "../core/sizes";
|
|
12
12
|
import { openColumnFilter, type TMDataGridApi } from "../useTMDataGrid";
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
/** Mantine's style props (`mb="sm"`, `hiddenFrom="sm"`) are set on the wrapper. */
|
|
15
|
+
export type TMDataGridFilterPillsProps<TData extends RowData> = BoxProps & {
|
|
15
16
|
/** The object returned by `useTMDataGrid`. */
|
|
16
17
|
api: TMDataGridApi<TData>;
|
|
17
18
|
/** Mantine size of the pills. Defaults to `"sm"`. */
|
|
@@ -23,7 +24,6 @@ export type TMDataGridFilterPillsProps<TData extends RowData> = {
|
|
|
23
24
|
* filter panel on that column.
|
|
24
25
|
*/
|
|
25
26
|
onPillClick?: (columnId: string) => void;
|
|
26
|
-
className?: string;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
/**
|
|
@@ -52,6 +52,7 @@ export function TMDataGridFilterPills<TData extends RowData>({
|
|
|
52
52
|
showClearAll = true,
|
|
53
53
|
onPillClick,
|
|
54
54
|
className,
|
|
55
|
+
...others
|
|
55
56
|
}: TMDataGridFilterPillsProps<TData>) {
|
|
56
57
|
const { table, labels } = api;
|
|
57
58
|
const columnFilters = useSelector(
|
|
@@ -65,11 +66,12 @@ export function TMDataGridFilterPills<TData extends RowData>({
|
|
|
65
66
|
if (activeFilters.length === 0) return null;
|
|
66
67
|
|
|
67
68
|
return (
|
|
68
|
-
<
|
|
69
|
+
<Box
|
|
69
70
|
role="group"
|
|
70
71
|
aria-label={labels.activeFilters}
|
|
71
72
|
data-dg-part="filter-pills"
|
|
72
73
|
className={[classes.filterPills, className].filter(Boolean).join(" ")}
|
|
74
|
+
{...others}
|
|
73
75
|
>
|
|
74
76
|
<Pill.Group size={size}>
|
|
75
77
|
{activeFilters.map((filter) => {
|
|
@@ -78,6 +80,13 @@ export function TMDataGridFilterPills<TData extends RowData>({
|
|
|
78
80
|
const value = filter.value;
|
|
79
81
|
// isFilterActive already vouched for the shape; this narrows it.
|
|
80
82
|
if (!isTMDataGridFilterValue(value)) return null;
|
|
83
|
+
// Mantine types `removeButtonProps` as `CloseButtonProps`, which
|
|
84
|
+
// has no room for `data-*` in an object literal. Spread in from a
|
|
85
|
+
// typed value, they reach the button all the same.
|
|
86
|
+
const removeButtonParts: Record<`data-${string}`, string> = {
|
|
87
|
+
"data-dg-part": "filter-pill-remove",
|
|
88
|
+
"data-column-id": filter.id,
|
|
89
|
+
};
|
|
81
90
|
|
|
82
91
|
return (
|
|
83
92
|
<Pill
|
|
@@ -96,13 +105,11 @@ export function TMDataGridFilterPills<TData extends RowData>({
|
|
|
96
105
|
// the tab order, because inside a `PillsInput` Backspace is the
|
|
97
106
|
// way out. These pills stand on their own, so the ✕ has to be a
|
|
98
107
|
// real button.
|
|
99
|
-
// No test id of its own: Mantine types these props as
|
|
100
|
-
// `CloseButtonProps`, which has no room for `data-*`. The pill
|
|
101
|
-
// carries one, and the ✕ is the only button inside it.
|
|
102
108
|
removeButtonProps={{
|
|
103
109
|
"aria-label": labels.clearFilter(label),
|
|
104
110
|
"aria-hidden": false,
|
|
105
111
|
tabIndex: 0,
|
|
112
|
+
...removeButtonParts,
|
|
106
113
|
}}
|
|
107
114
|
>
|
|
108
115
|
<UnstyledButton
|
|
@@ -137,6 +144,6 @@ export function TMDataGridFilterPills<TData extends RowData>({
|
|
|
137
144
|
{labels.clearAllFilters}
|
|
138
145
|
</Button>
|
|
139
146
|
)}
|
|
140
|
-
</
|
|
147
|
+
</Box>
|
|
141
148
|
);
|
|
142
149
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/* Chrome shared by the two automatic surfaces. The panel inside them is a
|
|
2
|
+
plain block; everything that makes one float or sit beside the rows is
|
|
3
|
+
here. */
|
|
4
|
+
|
|
5
|
+
.surfaceHeader {
|
|
6
|
+
display: flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
justify-content: space-between;
|
|
9
|
+
gap: var(--mantine-spacing-md);
|
|
10
|
+
padding-bottom: var(--mantine-spacing-xs);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* Floats over the first body rows, anchored to `.tableWrapper`. Sits below the
|
|
14
|
+
whole header - group rows and the filter row included - which the table
|
|
15
|
+
measures onto the wrapper as `--dg-header-stack-height`. The size scale's
|
|
16
|
+
single-row value is the fallback for the frame before the first
|
|
17
|
+
measurement. */
|
|
18
|
+
.filterPopup {
|
|
19
|
+
position: absolute;
|
|
20
|
+
top: calc(var(--dg-header-stack-height, var(--dg-header-height)) + 2px);
|
|
21
|
+
left: var(--mantine-spacing-md);
|
|
22
|
+
z-index: var(--dg-z-filter-popup, 20);
|
|
23
|
+
padding: var(--mantine-spacing-sm);
|
|
24
|
+
border-radius: var(--mantine-radius-md);
|
|
25
|
+
border: 1px solid var(--mantine-color-default-border);
|
|
26
|
+
background-color: var(--mantine-color-body);
|
|
27
|
+
box-shadow: var(--mantine-shadow-md);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* A column of the table wrapper rather than a layer over it: the rows give up
|
|
31
|
+
the width instead of being covered by it. `flex: none` so the declared
|
|
32
|
+
width holds whatever the panel inside asks for. */
|
|
33
|
+
.filterSidebar {
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
flex: none;
|
|
37
|
+
min-width: 0;
|
|
38
|
+
padding: var(--mantine-spacing-sm);
|
|
39
|
+
background-color: var(--mantine-color-body);
|
|
40
|
+
border-left: 1px solid var(--mantine-color-default-border);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* On the left it is the same panel with its divider on the other edge, and
|
|
44
|
+
ahead of the scroll container in the flex order. */
|
|
45
|
+
.filterSidebar[data-side="left"] {
|
|
46
|
+
order: -1;
|
|
47
|
+
border-left: none;
|
|
48
|
+
border-right: 1px solid var(--mantine-color-default-border);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.filterSidebarBody {
|
|
52
|
+
flex: 1;
|
|
53
|
+
min-height: 0;
|
|
54
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { ActionIcon, ScrollArea, Text } from "@mantine/core";
|
|
2
|
+
import { useSelector } from "@tanstack/react-store";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import classes from "./TMDataGridFilterSurface.module.css";
|
|
5
|
+
import { useTMDataGridContext } from "../TMDataGridContext";
|
|
6
|
+
import { isElement } from "../core/dom";
|
|
7
|
+
import { CloseIcon } from "./icons";
|
|
8
|
+
import { TMDataGridFilterPanel } from "./TMDataGridFilterPanel";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The title row both automatic surfaces wear: the "Filters" heading and the
|
|
12
|
+
* close button that puts the surface away without touching the filters.
|
|
13
|
+
*/
|
|
14
|
+
function FilterSurfaceHeader() {
|
|
15
|
+
const { ui, labels, controlSize } = useTMDataGridContext();
|
|
16
|
+
return (
|
|
17
|
+
<div className={classes.surfaceHeader}>
|
|
18
|
+
<Text span size={controlSize} fw={600}>
|
|
19
|
+
{labels.filters}
|
|
20
|
+
</Text>
|
|
21
|
+
<ActionIcon
|
|
22
|
+
variant="subtle"
|
|
23
|
+
color="gray"
|
|
24
|
+
size="sm"
|
|
25
|
+
aria-label={labels.closeFilters}
|
|
26
|
+
data-dg-part="filter-panel-close"
|
|
27
|
+
onClick={ui.actions.closeFilterPanel}
|
|
28
|
+
>
|
|
29
|
+
<CloseIcon size={16} stroke={1.6} />
|
|
30
|
+
</ActionIcon>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Closes the popup once its last filter row goes, covering both ways that
|
|
37
|
+
* happens - removed by hand, or "Clear all" - without either of them having
|
|
38
|
+
* to know which surface is showing.
|
|
39
|
+
*
|
|
40
|
+
* On the transition to zero, not on being zero: a popup asked to start open
|
|
41
|
+
* with no filters yet would otherwise close itself before its first paint.
|
|
42
|
+
*/
|
|
43
|
+
function useCloseWhenEmptied(opened: boolean) {
|
|
44
|
+
const { table, ui } = useTMDataGridContext();
|
|
45
|
+
const filterCount = useSelector(
|
|
46
|
+
table.store,
|
|
47
|
+
(state) => state.columnFilters.length,
|
|
48
|
+
);
|
|
49
|
+
const previousCount = useRef(filterCount);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const emptied = previousCount.current > 0 && filterCount === 0;
|
|
52
|
+
previousCount.current = filterCount;
|
|
53
|
+
if (opened && emptied) ui.actions.closeFilterPanel();
|
|
54
|
+
}, [opened, filterCount, ui]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* `filters.surface: "popup"` - the panel floating over the first body rows,
|
|
59
|
+
* anchored under the header. The default surface, and the only one before
|
|
60
|
+
* `filters` existed.
|
|
61
|
+
*
|
|
62
|
+
* Everything that makes it floating lives here rather than in the panel: a
|
|
63
|
+
* pointerdown outside closes it, Escape closes it, and emptying it closes it.
|
|
64
|
+
*
|
|
65
|
+
* @internal Rendered by `TMDataGrid.Table`.
|
|
66
|
+
*/
|
|
67
|
+
export function TMDataGridFilterPopup() {
|
|
68
|
+
const { ui, labels } = useTMDataGridContext();
|
|
69
|
+
const opened = useSelector(ui, (state) => state.filterPanelOpen);
|
|
70
|
+
const popupRef = useRef<HTMLDivElement>(null);
|
|
71
|
+
useCloseWhenEmptied(opened);
|
|
72
|
+
|
|
73
|
+
// Clicking away hides the popup, the way any floating surface behaves. On
|
|
74
|
+
// pointerdown rather than click, so a press that starts outside dismisses it
|
|
75
|
+
// even when the pointer is released somewhere else.
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (!opened) return;
|
|
78
|
+
|
|
79
|
+
const handlePointerDown = (event: PointerEvent) => {
|
|
80
|
+
const target = event.target;
|
|
81
|
+
if (!isElement(target)) return;
|
|
82
|
+
if (popupRef.current?.contains(target)) return;
|
|
83
|
+
// The toolbar button toggles the popup itself. Closing from here first
|
|
84
|
+
// would leave its click reopening what the user meant to close.
|
|
85
|
+
if (target.closest('[data-dg-part="filter-button"]')) return;
|
|
86
|
+
// A portalled overlay - a header filter's dropdown, a custom control's
|
|
87
|
+
// portalled picker - is outside the popup in the DOM but not in meaning:
|
|
88
|
+
// a press in one is a press in a control, not a click away. Mantine
|
|
89
|
+
// stamps `data-portal` on every portal node it creates.
|
|
90
|
+
if (target.closest("[data-portal]")) return;
|
|
91
|
+
ui.actions.closeFilterPanel();
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// The popup's own document, not the global one: rendered through a
|
|
95
|
+
// portal into a window opened with `window.open`, the global `document`
|
|
96
|
+
// is the opener's and never sees a press in the grid's window.
|
|
97
|
+
const doc = popupRef.current?.ownerDocument ?? document;
|
|
98
|
+
doc.addEventListener("pointerdown", handlePointerDown);
|
|
99
|
+
return () => doc.removeEventListener("pointerdown", handlePointerDown);
|
|
100
|
+
}, [opened, ui]);
|
|
101
|
+
|
|
102
|
+
if (!opened) return null;
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div
|
|
106
|
+
ref={popupRef}
|
|
107
|
+
data-dg-part="filter-popup"
|
|
108
|
+
className={classes.filterPopup}
|
|
109
|
+
aria-label={labels.filters}
|
|
110
|
+
// Escape is what closes a floating surface, and every control that can
|
|
111
|
+
// hold focus in here sits inside this element.
|
|
112
|
+
onKeyDown={(event) => {
|
|
113
|
+
if (event.key !== "Escape") return;
|
|
114
|
+
event.stopPropagation();
|
|
115
|
+
ui.actions.closeFilterPanel();
|
|
116
|
+
}}
|
|
117
|
+
>
|
|
118
|
+
<FilterSurfaceHeader />
|
|
119
|
+
<TMDataGridFilterPanel />
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* `filters.surface: "sidebar"` - the panel beside the table, inside the grid
|
|
126
|
+
* frame and under the toolbar, taking width from the rows rather than covering
|
|
127
|
+
* them.
|
|
128
|
+
*
|
|
129
|
+
* Not a floating surface: a click in the table is a click on the rows the
|
|
130
|
+
* sidebar is filtering, so nothing about it dismisses the panel, and clearing
|
|
131
|
+
* the filters leaves it standing with its "Add filter" button - a place to
|
|
132
|
+
* work from rather than a thing that appears and goes away. Escape closes it,
|
|
133
|
+
* because a keyboard user inside it needs a way back out to the grid.
|
|
134
|
+
*
|
|
135
|
+
* @internal Rendered by `TMDataGrid.Table`.
|
|
136
|
+
*/
|
|
137
|
+
export function TMDataGridFilterSidebar() {
|
|
138
|
+
const { ui, filters, labels } = useTMDataGridContext();
|
|
139
|
+
const opened = useSelector(ui, (state) => state.filterPanelOpen);
|
|
140
|
+
|
|
141
|
+
if (!opened) return null;
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<div
|
|
145
|
+
data-dg-part="filter-sidebar"
|
|
146
|
+
data-side={filters.sidebarSide}
|
|
147
|
+
className={classes.filterSidebar}
|
|
148
|
+
style={{ width: filters.sidebarWidth }}
|
|
149
|
+
aria-label={labels.filters}
|
|
150
|
+
onKeyDown={(event) => {
|
|
151
|
+
if (event.key !== "Escape") return;
|
|
152
|
+
event.stopPropagation();
|
|
153
|
+
ui.actions.closeFilterPanel();
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
<FilterSurfaceHeader />
|
|
157
|
+
{/* The panel's rows are as tall as the filters put in them, and the
|
|
158
|
+
sidebar is as tall as the table beside it - so the overflow is the
|
|
159
|
+
sidebar's to scroll, not the page's. */}
|
|
160
|
+
<ScrollArea type="auto" className={classes.filterSidebarBody}>
|
|
161
|
+
{/* Stacked, not side by side: a filter row wants about 550px laid out
|
|
162
|
+
in a line, and the sidebar is 280. */}
|
|
163
|
+
<TMDataGridFilterPanel layout="stacked" />
|
|
164
|
+
</ScrollArea>
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
ActionIcon,
|
|
3
|
+
Box,
|
|
4
|
+
type BoxProps,
|
|
5
|
+
Group,
|
|
6
|
+
Select,
|
|
7
|
+
Text,
|
|
8
|
+
Tooltip,
|
|
9
|
+
} from "@mantine/core";
|
|
3
10
|
import { useMemo, type ReactNode } from "react";
|
|
4
11
|
import classes from "./TMDataGridFooter.module.css";
|
|
5
12
|
import { useTMDataGridContext } from "../TMDataGridContext";
|
|
@@ -7,49 +14,10 @@ import { getGridCapabilities } from "../core/capabilities";
|
|
|
7
14
|
import { useSettledTableState } from "../core/useSettledTableState";
|
|
8
15
|
import { isPagingActive } from "../core/rowSelection";
|
|
9
16
|
import { ChevronLeftIcon, ChevronRightIcon } from "./icons";
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
pageIndex: number;
|
|
15
|
-
pageSize: number;
|
|
16
|
-
/** `-1` when a manual grid declares `pageCount: -1` (unknown total). */
|
|
17
|
-
pageCount: number;
|
|
18
|
-
rowCount: number;
|
|
19
|
-
canPreviousPage: boolean;
|
|
20
|
-
canNextPage: boolean;
|
|
21
|
-
/**
|
|
22
|
-
* Whether the pager is slicing anything right now. `false` while a grouping
|
|
23
|
-
* is active, which suspends paging - see `isPagingActive`.
|
|
24
|
-
*/
|
|
25
|
-
isPagingActive: boolean;
|
|
26
|
-
/** First and last row number on this page, 1-based, for a range label. */
|
|
27
|
-
from: number;
|
|
28
|
-
to: number;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
/** What the pager can do. The write half of {@link TMDataGridPaginationApi}. */
|
|
32
|
-
export type TMDataGridPaginationActions = {
|
|
33
|
-
setPageIndex: (pageIndex: number) => void;
|
|
34
|
-
setPageSize: (pageSize: number) => void;
|
|
35
|
-
previousPage: () => void;
|
|
36
|
-
nextPage: () => void;
|
|
37
|
-
firstPage: () => void;
|
|
38
|
-
lastPage: () => void;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Pagination state and actions, split into the half you read and the half you
|
|
43
|
-
* call.
|
|
44
|
-
*
|
|
45
|
-
* The split is what makes a partial override possible: a consumer replacing
|
|
46
|
-
* only the range label reads `state` and never touches `actions`, and one
|
|
47
|
-
* replacing only the buttons does the opposite.
|
|
48
|
-
*/
|
|
49
|
-
export type TMDataGridPaginationApi = {
|
|
50
|
-
state: TMDataGridPaginationState;
|
|
51
|
-
actions: TMDataGridPaginationActions;
|
|
52
|
-
};
|
|
17
|
+
import {
|
|
18
|
+
getTMDataGridPaginationApi,
|
|
19
|
+
type TMDataGridPaginationApi,
|
|
20
|
+
} from "../core/pagination";
|
|
53
21
|
|
|
54
22
|
/**
|
|
55
23
|
* The pre-bound pieces of the built-in pager.
|
|
@@ -63,6 +31,12 @@ export type TMDataGridPaginationControls = {
|
|
|
63
31
|
PageSize: () => ReactNode;
|
|
64
32
|
/** The "1–25 of 300" range label. */
|
|
65
33
|
Range: () => ReactNode;
|
|
34
|
+
/**
|
|
35
|
+
* The "Page 3 of 200" label - what a server-paged grid usually shows in
|
|
36
|
+
* place of a row range. Not in the default footer; put it in a
|
|
37
|
+
* `renderPagination` layout.
|
|
38
|
+
*/
|
|
39
|
+
PageNumber: () => ReactNode;
|
|
66
40
|
/** The previous/next buttons. */
|
|
67
41
|
Pager: () => ReactNode;
|
|
68
42
|
};
|
|
@@ -72,43 +46,6 @@ export type TMDataGridPaginationSlotArgs = TMDataGridPaginationApi & {
|
|
|
72
46
|
Controls: TMDataGridPaginationControls;
|
|
73
47
|
};
|
|
74
48
|
|
|
75
|
-
/**
|
|
76
|
-
* Reads {@link TMDataGridPaginationApi} off a table. The Footer feeds it to the
|
|
77
|
-
* `renderPagination` slot; a pager living outside the Footer can call it with
|
|
78
|
-
* the table from `useTMDataGrid` directly.
|
|
79
|
-
*
|
|
80
|
-
* `Controls` are not here: they are components bound to the grid context, and
|
|
81
|
-
* this function takes only a table.
|
|
82
|
-
*/
|
|
83
|
-
export function getTMDataGridPaginationApi<TData extends RowData>(
|
|
84
|
-
table: TMDataGridTable<TData>,
|
|
85
|
-
isPaging = true,
|
|
86
|
-
): TMDataGridPaginationApi {
|
|
87
|
-
const { pageIndex, pageSize } = table.store.state.pagination;
|
|
88
|
-
const rowCount = table.getRowCount();
|
|
89
|
-
return {
|
|
90
|
-
state: {
|
|
91
|
-
pageIndex,
|
|
92
|
-
pageSize,
|
|
93
|
-
pageCount: table.getPageCount(),
|
|
94
|
-
rowCount,
|
|
95
|
-
canPreviousPage: table.getCanPreviousPage(),
|
|
96
|
-
canNextPage: table.getCanNextPage(),
|
|
97
|
-
isPagingActive: isPaging,
|
|
98
|
-
from: rowCount === 0 ? 0 : pageIndex * pageSize + 1,
|
|
99
|
-
to: Math.min(rowCount, (pageIndex + 1) * pageSize),
|
|
100
|
-
},
|
|
101
|
-
actions: {
|
|
102
|
-
setPageIndex: (index) => table.setPageIndex(index),
|
|
103
|
-
setPageSize: (size) => table.setPageSize(size),
|
|
104
|
-
previousPage: () => table.previousPage(),
|
|
105
|
-
nextPage: () => table.nextPage(),
|
|
106
|
-
firstPage: () => table.firstPage(),
|
|
107
|
-
lastPage: () => table.lastPage(),
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
49
|
/*
|
|
113
50
|
* The three controls are module-scope components on purpose. Defined inside
|
|
114
51
|
* the Footer they would be a new component type on every render, so React
|
|
@@ -182,6 +119,26 @@ function PaginationRange() {
|
|
|
182
119
|
);
|
|
183
120
|
}
|
|
184
121
|
|
|
122
|
+
function PaginationPageNumber() {
|
|
123
|
+
const { table, features, labels, controlSize } = useTMDataGridContext();
|
|
124
|
+
useSettledTableState(table.store);
|
|
125
|
+
const paging = isPagingActive(table, features);
|
|
126
|
+
const { pageIndex } = table.store.state.pagination;
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<Text span size={controlSize} c="dimmed" data-dg-part="page-number">
|
|
130
|
+
{paging
|
|
131
|
+
? labels.pageNumber({
|
|
132
|
+
page: pageIndex + 1,
|
|
133
|
+
pageCount: table.getPageCount(),
|
|
134
|
+
})
|
|
135
|
+
: // Same reasoning as the range label: nothing is being sliced, so a
|
|
136
|
+
// page number would be a lie.
|
|
137
|
+
labels.groupedAllRows(table.getFilteredRowModel().rows.length)}
|
|
138
|
+
</Text>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
185
142
|
function PaginationPager() {
|
|
186
143
|
const { table, features, labels } = useTMDataGridContext();
|
|
187
144
|
useSettledTableState(table.store);
|
|
@@ -213,7 +170,8 @@ function PaginationPager() {
|
|
|
213
170
|
);
|
|
214
171
|
}
|
|
215
172
|
|
|
216
|
-
|
|
173
|
+
/** Mantine's style props (`mt="sm"`, `px="md"`) are set on the footer bar. */
|
|
174
|
+
export type TMDataGridFooterProps = BoxProps & {
|
|
217
175
|
pageSizeOptions?: ReadonlyArray<number>;
|
|
218
176
|
/**
|
|
219
177
|
* Replaces the built-in pager, and is handed the pieces of it.
|
|
@@ -223,7 +181,7 @@ export type TMDataGridFooterProps = {
|
|
|
223
181
|
* renderPagination={({ state, actions, Controls }) => (
|
|
224
182
|
* <Group>
|
|
225
183
|
* <Controls.PageSize />
|
|
226
|
-
* <
|
|
184
|
+
* <Controls.PageNumber />
|
|
227
185
|
* <Controls.Pager />
|
|
228
186
|
* </Group>
|
|
229
187
|
* )}
|
|
@@ -248,7 +206,10 @@ const DEFAULT_PAGE_SIZE_OPTIONS = [10, 25, 50, 100];
|
|
|
248
206
|
export function TMDataGridFooter({
|
|
249
207
|
pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,
|
|
250
208
|
renderPagination,
|
|
209
|
+
className,
|
|
210
|
+
...others
|
|
251
211
|
}: TMDataGridFooterProps) {
|
|
212
|
+
const footerClassName = [classes.footer, className].filter(Boolean).join(" ");
|
|
252
213
|
const { table, features, labels } = useTMDataGridContext();
|
|
253
214
|
useSettledTableState(table.store);
|
|
254
215
|
|
|
@@ -258,6 +219,7 @@ export function TMDataGridFooter({
|
|
|
258
219
|
() => ({
|
|
259
220
|
PageSize: () => <PaginationPageSize pageSizeOptions={pageSizeOptions} />,
|
|
260
221
|
Range: PaginationRange,
|
|
222
|
+
PageNumber: PaginationPageNumber,
|
|
261
223
|
Pager: PaginationPager,
|
|
262
224
|
}),
|
|
263
225
|
[pageSizeOptions],
|
|
@@ -273,9 +235,9 @@ export function TMDataGridFooter({
|
|
|
273
235
|
if (renderPagination) {
|
|
274
236
|
const { state, actions } = getTMDataGridPaginationApi(table, paging);
|
|
275
237
|
return (
|
|
276
|
-
<
|
|
238
|
+
<Box data-dg-part="footer" className={footerClassName} {...others}>
|
|
277
239
|
{renderPagination({ state, actions, Controls })}
|
|
278
|
-
</
|
|
240
|
+
</Box>
|
|
279
241
|
);
|
|
280
242
|
}
|
|
281
243
|
|
|
@@ -288,17 +250,18 @@ export function TMDataGridFooter({
|
|
|
288
250
|
w={260}
|
|
289
251
|
position="top"
|
|
290
252
|
>
|
|
291
|
-
<
|
|
253
|
+
<Box
|
|
292
254
|
data-dg-part="footer"
|
|
293
|
-
className={
|
|
294
|
-
data-paging-suspended={!paging}
|
|
255
|
+
className={footerClassName}
|
|
256
|
+
data-paging-suspended={!paging || undefined}
|
|
257
|
+
{...others}
|
|
295
258
|
>
|
|
296
259
|
{/* The default render is the controls in order - which is what makes
|
|
297
260
|
`renderPagination` a rearrangement rather than a rebuild. */}
|
|
298
261
|
<Controls.PageSize />
|
|
299
262
|
<Controls.Range />
|
|
300
263
|
<Controls.Pager />
|
|
301
|
-
</
|
|
264
|
+
</Box>
|
|
302
265
|
</Tooltip>
|
|
303
266
|
);
|
|
304
267
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { UnstyledButton } from "@mantine/core";
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
useBodyControlTabIndex,
|
|
4
4
|
useTMDataGridContext,
|
|
5
5
|
} from "../TMDataGridContext";
|
|
6
|
-
import type {
|
|
6
|
+
import type { Row, RowData } from "@tanstack/react-table";
|
|
7
7
|
import { useSelector } from "@tanstack/react-store";
|
|
8
8
|
import { shallow } from "@tanstack/store";
|
|
9
9
|
import classes from "./TMDataGridGroupColumn.module.css";
|
|
10
10
|
import { ChevronRightIcon } from "./icons";
|
|
11
11
|
import { getColumnLabel } from "../core/columnUtils";
|
|
12
|
-
import { getGroupDataRows } from "../core/grouping";
|
|
12
|
+
import { formatGroupValue, getGroupDataRows } from "../core/grouping";
|
|
13
13
|
import type { TMDataGridFeatures, TMDataGridTable } from "../useTMDataGrid";
|
|
14
14
|
|
|
15
15
|
export const GROUP_COLUMN_ID = "__group__";
|
|
@@ -17,27 +17,6 @@ export const GROUP_COLUMN_ID = "__group__";
|
|
|
17
17
|
/** Indent added per level of nesting, in px. */
|
|
18
18
|
const INDENT_STEP = 16;
|
|
19
19
|
|
|
20
|
-
/** Shown for a group whose value is empty - `String(null)` would read as "null". */
|
|
21
|
-
const BLANK_GROUP_LABEL = "(Blank)";
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* How a grouping value is written into the tree cell.
|
|
25
|
-
*
|
|
26
|
-
* Deliberately not the grouped column's own `cell` renderer: that renderer is
|
|
27
|
-
* written for a data row and is free to reach into `row.original`, which on a
|
|
28
|
-
* group row is the first leaf's record rather than anything about the group.
|
|
29
|
-
*/
|
|
30
|
-
export function formatGroupValue(
|
|
31
|
-
value: unknown,
|
|
32
|
-
blankLabel = BLANK_GROUP_LABEL,
|
|
33
|
-
): string {
|
|
34
|
-
if (value === null || value === undefined || value === "") {
|
|
35
|
-
return blankLabel;
|
|
36
|
-
}
|
|
37
|
-
if (value instanceof Date) return value.toLocaleDateString();
|
|
38
|
-
return String(value);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
20
|
/**
|
|
42
21
|
* The tree cell: chevron, group value and leaf count, indented by depth.
|
|
43
22
|
*
|
|
@@ -46,15 +25,14 @@ export function formatGroupValue(
|
|
|
46
25
|
* `row` identity survives an expand, so the React Compiler would cache the call
|
|
47
26
|
* along with it and the chevron would never turn. See TMDataGridSelectColumn.
|
|
48
27
|
*/
|
|
49
|
-
function GroupCell<TData extends RowData>({
|
|
28
|
+
export function GroupCell<TData extends RowData>({
|
|
50
29
|
row,
|
|
51
30
|
}: {
|
|
52
31
|
row: Row<TMDataGridFeatures, TData>;
|
|
53
32
|
}) {
|
|
54
33
|
const { labels } = useTMDataGridContext();
|
|
34
|
+
const tabIndex = useBodyControlTabIndex();
|
|
55
35
|
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
36
|
|
|
59
37
|
// Leaf rows keep the lane empty: their values are in the data columns, and
|
|
60
38
|
// the indent alone is what places them under their group.
|
|
@@ -67,6 +45,8 @@ function GroupCell<TData extends RowData>({
|
|
|
67
45
|
className={classes.groupToggle}
|
|
68
46
|
// Padding rather than margin, so the whole indented width stays clickable.
|
|
69
47
|
style={{ paddingInlineStart: row.depth * INDENT_STEP }}
|
|
48
|
+
// See useBodyControlTabIndex: under cell selection, reached by stepping
|
|
49
|
+
// into the cell or by the Tab walk within the row, not by the page.
|
|
70
50
|
tabIndex={tabIndex}
|
|
71
51
|
aria-expanded={expanded}
|
|
72
52
|
aria-label={
|
|
@@ -81,7 +61,7 @@ function GroupCell<TData extends RowData>({
|
|
|
81
61
|
row.toggleExpanded();
|
|
82
62
|
}}
|
|
83
63
|
>
|
|
84
|
-
<span className={classes.chevron} data-expanded={expanded}>
|
|
64
|
+
<span className={classes.chevron} data-expanded={expanded || undefined}>
|
|
85
65
|
<ChevronRightIcon size={16} stroke={1.6} />
|
|
86
66
|
</span>
|
|
87
67
|
<span className={classes.groupLabel}>{label}</span>
|
|
@@ -98,7 +78,7 @@ function GroupCell<TData extends RowData>({
|
|
|
98
78
|
* Names the columns currently grouped on, so the lane says what it is showing
|
|
99
79
|
* rather than a static "Group".
|
|
100
80
|
*/
|
|
101
|
-
function GroupHeader<TData extends RowData>({
|
|
81
|
+
export function GroupHeader<TData extends RowData>({
|
|
102
82
|
table,
|
|
103
83
|
}: {
|
|
104
84
|
table: TMDataGridTable<TData>;
|
|
@@ -116,46 +96,3 @@ function GroupHeader<TData extends RowData>({
|
|
|
116
96
|
})
|
|
117
97
|
.join(" / ");
|
|
118
98
|
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* The generated tree column, prepended whenever grouping is enabled and hidden
|
|
122
|
-
* again while `grouping` is empty - see the visibility effect in
|
|
123
|
-
* `useTMDataGrid`.
|
|
124
|
-
*
|
|
125
|
-
* It exists because TanStack ships no auto group column: `groupedColumnMode:
|
|
126
|
-
* "remove"` takes the grouped column out of the grid, so something has to hold
|
|
127
|
-
* the tree. Modelled on the checkbox column, which is generated the same way.
|
|
128
|
-
*
|
|
129
|
-
* Not groupable itself, and nothing had to be written to make that true -
|
|
130
|
-
* `column.getCanGroup()` requires an `accessorFn`, which a display column has
|
|
131
|
-
* no reason to have.
|
|
132
|
-
*/
|
|
133
|
-
export function createGroupColumn<TData extends RowData>(
|
|
134
|
-
label = "Group",
|
|
135
|
-
): ColumnDef<TMDataGridFeatures, TData, unknown> {
|
|
136
|
-
return {
|
|
137
|
-
id: GROUP_COLUMN_ID,
|
|
138
|
-
meta: {
|
|
139
|
-
label,
|
|
140
|
-
// Structurally the first column after the checkbox lane.
|
|
141
|
-
enableOrdering: false,
|
|
142
|
-
},
|
|
143
|
-
size: 260,
|
|
144
|
-
minSize: 180,
|
|
145
|
-
enableSorting: false,
|
|
146
|
-
enableColumnFilter: false,
|
|
147
|
-
enableGlobalFilter: false,
|
|
148
|
-
// Keeps it out of the columns panel and out of the header menu: its
|
|
149
|
-
// visibility is not the user's to set, it follows the grouping state.
|
|
150
|
-
enableHiding: false,
|
|
151
|
-
// Structurally pinned to the left; users shouldn't be able to move it.
|
|
152
|
-
enablePinning: false,
|
|
153
|
-
cell: ({ row }) => <GroupCell row={row} />,
|
|
154
|
-
// A group row has subRows, so every cell on it that is not the grouped
|
|
155
|
-
// column reports `getIsAggregated()` - this lane included. Without an
|
|
156
|
-
// `aggregatedCell` the body would take that as "nothing to summarise" and
|
|
157
|
-
// render the tree lane blank on exactly the rows it exists for.
|
|
158
|
-
aggregatedCell: ({ row }) => <GroupCell row={row} />,
|
|
159
|
-
header: ({ table }) => <GroupHeader table={table} />,
|
|
160
|
-
};
|
|
161
|
-
}
|
|
@@ -90,11 +90,11 @@
|
|
|
90
90
|
|
|
91
91
|
/* See the sort ActionIcon: an unsorted column's arrow is an invitation, not a
|
|
92
92
|
state, so it sits back until the pointer is on it. */
|
|
93
|
-
.sortAction[data-sorted
|
|
93
|
+
.sortAction:not([data-sorted]) {
|
|
94
94
|
opacity: 0.55;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
.sortAction[data-sorted
|
|
97
|
+
.sortAction:not([data-sorted]):hover {
|
|
98
98
|
opacity: 1;
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -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;
|