@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
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ActionIcon,
|
|
3
|
+
Checkbox,
|
|
4
|
+
Menu,
|
|
5
|
+
ScrollArea,
|
|
6
|
+
Tooltip,
|
|
7
|
+
type MenuProps,
|
|
8
|
+
} from "@mantine/core";
|
|
9
|
+
import {
|
|
10
|
+
useState,
|
|
11
|
+
type ComponentPropsWithoutRef,
|
|
12
|
+
type KeyboardEvent,
|
|
13
|
+
type ReactNode,
|
|
14
|
+
} from "react";
|
|
15
|
+
import { useTMDataGridContext } from "../TMDataGridContext";
|
|
16
|
+
import {
|
|
17
|
+
getColumnLabel,
|
|
18
|
+
showColumnSearch,
|
|
19
|
+
type TMDataGridColumnSearchable,
|
|
20
|
+
} from "../core/columnUtils";
|
|
21
|
+
import type {
|
|
22
|
+
TMDataGridExportColumns,
|
|
23
|
+
TMDataGridExportOptions,
|
|
24
|
+
} from "../core/export";
|
|
25
|
+
import { useTMDataGridExport } from "../useTMDataGridExport";
|
|
26
|
+
import { useHideableColumns } from "./useHideableColumns";
|
|
27
|
+
import { BurgerIcon, DownloadIcon, RestoreIcon } from "./icons";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The column toggles are `Menu.Item`s with a `Checkbox.Indicator` in front,
|
|
31
|
+
* not `Menu.CheckboxItem`: that one draws its tick only when checked, so an
|
|
32
|
+
* unchecked column next to the consumer's own items reads as a plain action.
|
|
33
|
+
* A box that is visibly empty says "off, click to turn on".
|
|
34
|
+
*
|
|
35
|
+
* `Menu.Item` writes `role="menuitem"` after the props it is given, so the
|
|
36
|
+
* checkbox role goes on through `renderRoot`, the polymorphic escape hatch.
|
|
37
|
+
*/
|
|
38
|
+
const asCheckboxItem = (props: ComponentPropsWithoutRef<"button">) => (
|
|
39
|
+
<button {...props} role="menuitemcheckbox" />
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
export type TMDataGridMenuProps = Omit<MenuProps, "children"> & {
|
|
43
|
+
/** The dropdown's content: Mantine `Menu.Item`s and the `TMDataGrid.Menu.*` items. */
|
|
44
|
+
children: ReactNode;
|
|
45
|
+
/** Replaces the burger icon on the trigger. */
|
|
46
|
+
icon?: ReactNode;
|
|
47
|
+
/** Tooltip and `aria-label` of the trigger. Default `labels.menuButton`. */
|
|
48
|
+
label?: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type TMDataGridMenuColumnsProps = {
|
|
52
|
+
/**
|
|
53
|
+
* Renders a `Menu.Search` above the toggles: `"auto"` (the default) from
|
|
54
|
+
* six hideable columns, `true` always, `false` never. Use `false` inside a
|
|
55
|
+
* `Menu.Sub`.
|
|
56
|
+
*/
|
|
57
|
+
searchable?: TMDataGridColumnSearchable;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Documented on the `TMDataGridMenu` export below.
|
|
61
|
+
function TMDataGridMenuRoot({
|
|
62
|
+
children,
|
|
63
|
+
icon,
|
|
64
|
+
label,
|
|
65
|
+
...menuProps
|
|
66
|
+
}: TMDataGridMenuProps) {
|
|
67
|
+
const { labels, controlSize } = useTMDataGridContext();
|
|
68
|
+
const triggerLabel = label ?? labels.menuButton;
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<Menu
|
|
72
|
+
position="bottom-end"
|
|
73
|
+
shadow="md"
|
|
74
|
+
width={260}
|
|
75
|
+
withinPortal
|
|
76
|
+
{...menuProps}
|
|
77
|
+
>
|
|
78
|
+
{/* The Tooltip goes outside the Target. Inside it, the Target's
|
|
79
|
+
`aria-expanded` / `aria-controls` reach the Tooltip, which hands
|
|
80
|
+
unknown props to its own floating label, not to the button. */}
|
|
81
|
+
<Tooltip label={triggerLabel} openDelay={400}>
|
|
82
|
+
<Menu.Target>
|
|
83
|
+
<ActionIcon
|
|
84
|
+
variant="subtle"
|
|
85
|
+
color="gray"
|
|
86
|
+
size={controlSize}
|
|
87
|
+
aria-label={triggerLabel}
|
|
88
|
+
data-dg-part="menu-button"
|
|
89
|
+
>
|
|
90
|
+
{icon ?? <BurgerIcon size={18} stroke={1.6} />}
|
|
91
|
+
</ActionIcon>
|
|
92
|
+
</Menu.Target>
|
|
93
|
+
</Tooltip>
|
|
94
|
+
<Menu.Dropdown>{children}</Menu.Dropdown>
|
|
95
|
+
</Menu>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The whole column chooser as menu items: a search box, one checkbox item per
|
|
101
|
+
* hideable column, show/hide all, and Reset layout. Renders nothing when no
|
|
102
|
+
* column can be hidden.
|
|
103
|
+
*
|
|
104
|
+
* `searchable` is only for a block at the top level of a dropdown.
|
|
105
|
+
* `Menu.Search` registers on the root menu context (`hasSearch`), which
|
|
106
|
+
* switches off type-ahead and the arrow-key handling of every dropdown of that
|
|
107
|
+
* menu, so a search inside a `Menu.Sub` breaks the parent menu's keyboard
|
|
108
|
+
* behaviour.
|
|
109
|
+
*/
|
|
110
|
+
export function TMDataGridMenuColumns({
|
|
111
|
+
searchable = "auto",
|
|
112
|
+
}: TMDataGridMenuColumnsProps) {
|
|
113
|
+
const { labels, controlSize } = useTMDataGridContext();
|
|
114
|
+
const { columns } = useHideableColumns();
|
|
115
|
+
const [search, setSearch] = useState("");
|
|
116
|
+
|
|
117
|
+
if (columns.length === 0) return null;
|
|
118
|
+
|
|
119
|
+
const withSearch = showColumnSearch(searchable, columns.length);
|
|
120
|
+
|
|
121
|
+
// `Menu.Search` walks every item of the dropdown from the top, so with items
|
|
122
|
+
// placed above this block ArrowDown from the search lands on the first of
|
|
123
|
+
// those - and Enter runs it. When something precedes the block, ArrowDown
|
|
124
|
+
// moves focus to the first listed column instead; when nothing does,
|
|
125
|
+
// Mantine's own walk already starts there and is left alone.
|
|
126
|
+
const handleSearchKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
|
127
|
+
if (event.key !== "ArrowDown") return;
|
|
128
|
+
const dropdown = event.currentTarget.closest('[role="menu"]');
|
|
129
|
+
const first = dropdown?.querySelector('[role^="menuitem"]');
|
|
130
|
+
const firstToggle = dropdown?.querySelector<HTMLElement>(
|
|
131
|
+
'[data-dg-part="columns-toggle"]',
|
|
132
|
+
);
|
|
133
|
+
if (!first || !firstToggle || first === firstToggle) return;
|
|
134
|
+
event.preventDefault();
|
|
135
|
+
firstToggle.focus();
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<>
|
|
140
|
+
{withSearch && (
|
|
141
|
+
<Menu.Search
|
|
142
|
+
value={search}
|
|
143
|
+
onChange={(event) => setSearch(event.currentTarget.value)}
|
|
144
|
+
onKeyDown={handleSearchKeyDown}
|
|
145
|
+
placeholder={labels.columnsSearchPlaceholder}
|
|
146
|
+
size={controlSize}
|
|
147
|
+
data-dg-part="columns-search"
|
|
148
|
+
/>
|
|
149
|
+
)}
|
|
150
|
+
<TMDataGridMenuColumnToggles search={search} />
|
|
151
|
+
<Menu.Divider />
|
|
152
|
+
<TMDataGridMenuShowHideAll />
|
|
153
|
+
<TMDataGridMenuResetLayout />
|
|
154
|
+
</>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* One checkbox item per hideable column, narrowed to `search` when one is
|
|
160
|
+
* given. Renders nothing when no column can be hidden.
|
|
161
|
+
*/
|
|
162
|
+
export function TMDataGridMenuColumnToggles({ search }: { search?: string }) {
|
|
163
|
+
const { labels, controlSize } = useTMDataGridContext();
|
|
164
|
+
const { columns, columnVisibility } = useHideableColumns();
|
|
165
|
+
|
|
166
|
+
if (columns.length === 0) return null;
|
|
167
|
+
|
|
168
|
+
const needle = (search ?? "").trim().toLowerCase();
|
|
169
|
+
const listed = needle
|
|
170
|
+
? columns.filter((column) =>
|
|
171
|
+
getColumnLabel(column).toLowerCase().includes(needle),
|
|
172
|
+
)
|
|
173
|
+
: columns;
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<ScrollArea.Autosize mah={260} type="auto">
|
|
177
|
+
{listed.length === 0 ? (
|
|
178
|
+
<Menu.Label>{labels.columnsNoMatch(search ?? "")}</Menu.Label>
|
|
179
|
+
) : (
|
|
180
|
+
listed.map((column) => {
|
|
181
|
+
const visible = columnVisibility[column.id] !== false;
|
|
182
|
+
return (
|
|
183
|
+
<Menu.Item
|
|
184
|
+
key={column.id}
|
|
185
|
+
renderRoot={asCheckboxItem}
|
|
186
|
+
aria-checked={visible}
|
|
187
|
+
closeMenuOnClick={false}
|
|
188
|
+
leftSection={
|
|
189
|
+
<Checkbox.Indicator checked={visible} size={controlSize} />
|
|
190
|
+
}
|
|
191
|
+
onClick={() => column.toggleVisibility(!visible)}
|
|
192
|
+
data-dg-part="columns-toggle"
|
|
193
|
+
data-column-id={column.id}
|
|
194
|
+
>
|
|
195
|
+
{getColumnLabel(column)}
|
|
196
|
+
</Menu.Item>
|
|
197
|
+
);
|
|
198
|
+
})
|
|
199
|
+
)}
|
|
200
|
+
</ScrollArea.Autosize>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Shows or hides every listed column at once. Renders nothing when no column
|
|
206
|
+
* can be hidden.
|
|
207
|
+
*/
|
|
208
|
+
export function TMDataGridMenuShowHideAll() {
|
|
209
|
+
const { labels, controlSize } = useTMDataGridContext();
|
|
210
|
+
const { columns, shownCount, setAllVisible } = useHideableColumns();
|
|
211
|
+
|
|
212
|
+
if (columns.length === 0) return null;
|
|
213
|
+
|
|
214
|
+
const all = shownCount === columns.length;
|
|
215
|
+
// Some shown but not all: an indeterminate box, and a click shows all - the
|
|
216
|
+
// same reading as the panel's checkbox.
|
|
217
|
+
const some = shownCount > 0 && !all;
|
|
218
|
+
|
|
219
|
+
return (
|
|
220
|
+
<Menu.Item
|
|
221
|
+
renderRoot={asCheckboxItem}
|
|
222
|
+
aria-checked={some ? "mixed" : all}
|
|
223
|
+
closeMenuOnClick={false}
|
|
224
|
+
leftSection={
|
|
225
|
+
<Checkbox.Indicator
|
|
226
|
+
checked={all}
|
|
227
|
+
indeterminate={some}
|
|
228
|
+
size={controlSize}
|
|
229
|
+
/>
|
|
230
|
+
}
|
|
231
|
+
onClick={() => setAllVisible(!all)}
|
|
232
|
+
data-dg-part="columns-toggle-all"
|
|
233
|
+
>
|
|
234
|
+
{labels.columnsShowHideAll}
|
|
235
|
+
</Menu.Item>
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Resets the whole layout, not only visibility: order, widths, pinning and
|
|
241
|
+
* grouping go back too. Not `table.resetColumnVisibility()`, which resets to
|
|
242
|
+
* `initialState` - and under persistence the mount built that *from* the
|
|
243
|
+
* persisted payload.
|
|
244
|
+
*/
|
|
245
|
+
export function TMDataGridMenuResetLayout() {
|
|
246
|
+
const { labels, resetSettings } = useTMDataGridContext();
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<Menu.Item
|
|
250
|
+
leftSection={<RestoreIcon size={16} stroke={1.6} />}
|
|
251
|
+
onClick={() => resetSettings()}
|
|
252
|
+
data-dg-part="columns-reset"
|
|
253
|
+
>
|
|
254
|
+
{labels.columnsReset}
|
|
255
|
+
</Menu.Item>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Per-item overrides of the grid's `exportOptions`, and the item's text. */
|
|
260
|
+
export type TMDataGridMenuExportProps = Omit<
|
|
261
|
+
TMDataGridExportOptions,
|
|
262
|
+
"columns"
|
|
263
|
+
> & {
|
|
264
|
+
/**
|
|
265
|
+
* Which columns the item writes: `"visible"`, `"all"`, a list of ids, or
|
|
266
|
+
* `"custom"` - a picker listing every exportable column with the visible
|
|
267
|
+
* ones ticked, and the download on its Export button. Defaults to the grid's
|
|
268
|
+
* `exportOptions.columns`.
|
|
269
|
+
*/
|
|
270
|
+
columns?: TMDataGridExportColumns | "custom";
|
|
271
|
+
/**
|
|
272
|
+
* The item's text. Defaults to `labels.exportAll`, or for the selected-rows
|
|
273
|
+
* item `labels.exportSelected(count)`. Two items offering two formats need
|
|
274
|
+
* two texts, which is what this is for.
|
|
275
|
+
*/
|
|
276
|
+
label?: ReactNode;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
/** The item's props as export options, with `"custom"` set aside for the picker. */
|
|
280
|
+
function splitExportProps({ label, columns, ...rest }: TMDataGridMenuExportProps) {
|
|
281
|
+
const custom = columns === "custom";
|
|
282
|
+
const options: TMDataGridExportOptions = custom
|
|
283
|
+
? rest
|
|
284
|
+
: { ...rest, columns };
|
|
285
|
+
return { label, custom, options };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Downloads every filtered and sorted row, all pages, in the grid's export
|
|
290
|
+
* format. Props override `exportOptions` for this item alone, which is how one
|
|
291
|
+
* menu offers two formats.
|
|
292
|
+
*/
|
|
293
|
+
export function TMDataGridMenuExport(props: TMDataGridMenuExportProps) {
|
|
294
|
+
const { label, custom, options } = splitExportProps(props);
|
|
295
|
+
const { ui, labels } = useTMDataGridContext();
|
|
296
|
+
const { exportAll } = useTMDataGridExport(options);
|
|
297
|
+
|
|
298
|
+
return (
|
|
299
|
+
<Menu.Item
|
|
300
|
+
leftSection={<DownloadIcon size={16} stroke={1.6} />}
|
|
301
|
+
onClick={() => {
|
|
302
|
+
if (custom) ui.actions.openExportPicker({ rows: "all", options });
|
|
303
|
+
else void exportAll();
|
|
304
|
+
}}
|
|
305
|
+
data-dg-part="menu-export"
|
|
306
|
+
>
|
|
307
|
+
{label ?? labels.exportAll}
|
|
308
|
+
</Menu.Item>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Downloads the selected rows, in grid order. Disabled while nothing is
|
|
314
|
+
* selected; renders nothing when row selection is off, since then there is
|
|
315
|
+
* never anything for it to do.
|
|
316
|
+
*/
|
|
317
|
+
export function TMDataGridMenuExportSelected(props: TMDataGridMenuExportProps) {
|
|
318
|
+
const { label, custom, options } = splitExportProps(props);
|
|
319
|
+
const { ui, labels } = useTMDataGridContext();
|
|
320
|
+
const { exportSelected, selectedCount, canExportSelected } =
|
|
321
|
+
useTMDataGridExport(options);
|
|
322
|
+
|
|
323
|
+
if (!canExportSelected) return null;
|
|
324
|
+
|
|
325
|
+
return (
|
|
326
|
+
<Menu.Item
|
|
327
|
+
leftSection={<DownloadIcon size={16} stroke={1.6} />}
|
|
328
|
+
disabled={selectedCount === 0}
|
|
329
|
+
onClick={() => {
|
|
330
|
+
if (custom) ui.actions.openExportPicker({ rows: "selected", options });
|
|
331
|
+
else void exportSelected();
|
|
332
|
+
}}
|
|
333
|
+
data-dg-part="menu-export-selected"
|
|
334
|
+
>
|
|
335
|
+
{label ?? labels.exportSelected(selectedCount)}
|
|
336
|
+
</Menu.Item>
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Burger menu in the grid's top-right corner, holding whatever the consumer
|
|
342
|
+
* puts in it. Every Mantine `Menu` prop is accepted and wins over the defaults
|
|
343
|
+
* below.
|
|
344
|
+
*
|
|
345
|
+
* It always renders. It cannot know whether its children render anything, so
|
|
346
|
+
* a menu holding only `TMDataGrid.Menu.Columns` on a grid where nothing can be
|
|
347
|
+
* hidden still shows its button over an empty dropdown - leave the menu out of
|
|
348
|
+
* the toolbar in that case.
|
|
349
|
+
*/
|
|
350
|
+
export const TMDataGridMenu = Object.assign(TMDataGridMenuRoot, {
|
|
351
|
+
Columns: TMDataGridMenuColumns,
|
|
352
|
+
ColumnToggles: TMDataGridMenuColumnToggles,
|
|
353
|
+
ShowHideAll: TMDataGridMenuShowHideAll,
|
|
354
|
+
ResetLayout: TMDataGridMenuResetLayout,
|
|
355
|
+
Export: TMDataGridMenuExport,
|
|
356
|
+
ExportSelected: TMDataGridMenuExportSelected,
|
|
357
|
+
});
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Checkbox } from "@mantine/core";
|
|
2
2
|
import { useSelector } from "@tanstack/react-store";
|
|
3
|
-
import type {
|
|
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,12 +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
|
-
|
|
85
|
-
// useCellControlTabIndex. Enter on the lane still steps in, and Space on any
|
|
86
|
-
// cell of the row ticks it.
|
|
87
|
-
const tabIndex = useCellControlTabIndex();
|
|
84
|
+
const { ui, features, labels, edit } = useTMDataGridContext();
|
|
85
|
+
const tabIndex = useBodyControlTabIndex();
|
|
88
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));
|
|
89
92
|
|
|
90
93
|
// A group row is never selected by id: `rowSelection` only holds the leaves,
|
|
91
94
|
// and TanStack's `getIsSelected()` is a plain lookup in that map. A group
|
|
@@ -109,6 +112,8 @@ function SelectRowCheckbox<TData extends RowData>({
|
|
|
109
112
|
return (
|
|
110
113
|
<Checkbox
|
|
111
114
|
size="xs"
|
|
115
|
+
// See useBodyControlTabIndex: under cell selection Enter steps into the
|
|
116
|
+
// lane, the row's Tab walk reaches it, and Space on any cell ticks it.
|
|
112
117
|
tabIndex={tabIndex}
|
|
113
118
|
aria-label={isGroupRow ? labels.selectGroup : labels.selectRow}
|
|
114
119
|
data-dg-part="select-row"
|
|
@@ -127,7 +132,7 @@ function SelectRowCheckbox<TData extends RowData>({
|
|
|
127
132
|
// silently loses the clicked row, which `onChange` then toggles back off.
|
|
128
133
|
onChange={(event) => {
|
|
129
134
|
const native = event.nativeEvent;
|
|
130
|
-
const isMouse = native
|
|
135
|
+
const isMouse = isMouseEvent(native);
|
|
131
136
|
const resolved = resolveRowSelectionClick({
|
|
132
137
|
rows: getDisplayedRows(row.table, features),
|
|
133
138
|
rowId: row.id,
|
|
@@ -149,46 +154,3 @@ function SelectRowCheckbox<TData extends RowData>({
|
|
|
149
154
|
/>
|
|
150
155
|
);
|
|
151
156
|
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* The generated checkbox column, prepended under
|
|
155
|
-
* `selectionMode: "checkbox"` (the default) or `"checkboxAndHighlight"`.
|
|
156
|
-
*/
|
|
157
|
-
export function createSelectColumn<TData extends RowData>(
|
|
158
|
-
label = "Checkbox selection",
|
|
159
|
-
): ColumnDef<TMDataGridFeatures, TData, unknown> {
|
|
160
|
-
return {
|
|
161
|
-
id: SELECT_COLUMN_ID,
|
|
162
|
-
meta: {
|
|
163
|
-
label,
|
|
164
|
-
align: "center",
|
|
165
|
-
// Structurally the first column; it also anchors the left pinned lane, so
|
|
166
|
-
// no other column can be moved in front of it.
|
|
167
|
-
enableOrdering: false,
|
|
168
|
-
},
|
|
169
|
-
// A system lane: as wide as the control it holds and no wider. Fixed at
|
|
170
|
-
// every scale - the control does not grow with the font size, so neither
|
|
171
|
-
// should its track.
|
|
172
|
-
size: 36,
|
|
173
|
-
minSize: 36,
|
|
174
|
-
maxSize: 36,
|
|
175
|
-
enableResizing: false,
|
|
176
|
-
enableSorting: false,
|
|
177
|
-
enableColumnFilter: false,
|
|
178
|
-
enableGlobalFilter: false,
|
|
179
|
-
// Not a column the user chose, so not one they can switch off: hiding the
|
|
180
|
-
// lane would take the grid's only way to select a row with it, with the
|
|
181
|
-
// row-selection state left behind and no way back to it. Keeping it out of
|
|
182
|
-
// "Manage columns" follows from this - the panel lists what can be hidden.
|
|
183
|
-
enableHiding: false,
|
|
184
|
-
// Structurally pinned to the left; users shouldn't be able to move it.
|
|
185
|
-
enablePinning: false,
|
|
186
|
-
header: ({ table }) => <SelectAllHeader table={table} />,
|
|
187
|
-
cell: ({ row }) => <SelectRowCheckbox row={row} />,
|
|
188
|
-
// Every cell on a group row that is not the grouped column counts as
|
|
189
|
-
// aggregated, this lane included, and an aggregated cell with nothing
|
|
190
|
-
// declared renders blank. Without this the checkbox would disappear from
|
|
191
|
-
// exactly the rows that select a whole group. See renderCellContent.
|
|
192
|
-
aggregatedCell: ({ row }) => <SelectRowCheckbox row={row} />,
|
|
193
|
-
};
|
|
194
|
-
}
|