@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,4 +1,4 @@
|
|
|
1
|
-
import { ActionIcon } from "@mantine/core";
|
|
1
|
+
import { ActionIcon, Tooltip } from "@mantine/core";
|
|
2
2
|
import { FieldApi } from "@tanstack/react-form";
|
|
3
3
|
import type { Cell, Row } from "@tanstack/react-table";
|
|
4
4
|
import {
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "react";
|
|
12
12
|
import classes from "./TMDataGridTable.module.css";
|
|
13
13
|
import { type TMDataGridRowData, useTMDataGridContext } from "../TMDataGridContext";
|
|
14
|
+
import { isButtonElement, isInputElement, isNode } from "../core/dom";
|
|
14
15
|
import {
|
|
15
16
|
getEditFieldName,
|
|
16
17
|
normalizeFieldValidate,
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
type TMDataGridEditValueMap,
|
|
20
21
|
} from "../core/editEngine";
|
|
21
22
|
import type { TMDataGridFeatures } from "../useTMDataGrid";
|
|
23
|
+
import { FOCUSABLE_IN_CELL } from "../core/editorFocus";
|
|
22
24
|
import { CheckIcon, CloseIcon } from "./icons";
|
|
23
25
|
import { TMDataGridBooleanEditor } from "./editors/TMDataGridBooleanEditor";
|
|
24
26
|
import { TMDataGridDateEditor } from "./editors/TMDataGridDateEditor";
|
|
@@ -26,6 +28,7 @@ import { TMDataGridMultiSelectEditor } from "./editors/TMDataGridMultiSelectEdit
|
|
|
26
28
|
import { TMDataGridNumberEditor } from "./editors/TMDataGridNumberEditor";
|
|
27
29
|
import { TMDataGridSelectEditor } from "./editors/TMDataGridSelectEditor";
|
|
28
30
|
import { TMDataGridStringEditor } from "./editors/TMDataGridStringEditor";
|
|
31
|
+
import { useFieldError } from "./editors/editorShared";
|
|
29
32
|
|
|
30
33
|
const BUILT_IN_EDITORS: Record<
|
|
31
34
|
string,
|
|
@@ -43,8 +46,8 @@ const BUILT_IN_EDITORS: Record<
|
|
|
43
46
|
export type TMDataGridCellEditorClose =
|
|
44
47
|
| { committed: true; via: "enter" | "tab" | "shift-tab" | "pick" }
|
|
45
48
|
| { committed: false; via: "escape" }
|
|
46
|
-
/**
|
|
47
|
-
| { committed: false; via: "defer
|
|
49
|
+
/** `cellConfirm`: the editor closed, the draft stays, waiting for its ✓. */
|
|
50
|
+
| { committed: false; via: "defer-tab" | "defer-shift-tab" };
|
|
48
51
|
|
|
49
52
|
/**
|
|
50
53
|
* The host an editing cell mounts - it owns the field, the keys and the blur
|
|
@@ -68,10 +71,10 @@ export function TMDataGridCellEditor({
|
|
|
68
71
|
/** After the editor closed itself - the table moves the focus. */
|
|
69
72
|
onClose: (args: TMDataGridCellEditorClose) => void;
|
|
70
73
|
/**
|
|
71
|
-
* Hosted by the entry block rather than a body cell.
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
74
|
+
* Hosted by the entry block rather than a body cell. An entry row is
|
|
75
|
+
* row-shaped in every mode: Enter enters the whole row, Tab stays the
|
|
76
|
+
* browser's - walking the row's own inputs the way row mode's do - and
|
|
77
|
+
* focus leaving decides nothing, because the ✓ is the decision.
|
|
75
78
|
*/
|
|
76
79
|
inEntryBlock?: boolean;
|
|
77
80
|
}) {
|
|
@@ -79,8 +82,9 @@ export function TMDataGridCellEditor({
|
|
|
79
82
|
// Row mode mounts one host per editable cell of the row; every key that
|
|
80
83
|
// commits or cancels acts on the whole row's form either way, but Tab must
|
|
81
84
|
// stay the browser's (it walks the row's inputs) and blur must do nothing
|
|
82
|
-
// (focus moves between this row's own editors).
|
|
83
|
-
|
|
85
|
+
// (focus moves between this row's own editors). The entry block is built
|
|
86
|
+
// the same way, in every mode, so it follows the same two rules.
|
|
87
|
+
const isRowShaped = features.editMode === "row" || inEntryBlock;
|
|
84
88
|
const column = cell.column;
|
|
85
89
|
const form = edit.getForm(row.id);
|
|
86
90
|
const fieldName = getEditFieldName(column);
|
|
@@ -136,6 +140,17 @@ export function TMDataGridCellEditor({
|
|
|
136
140
|
});
|
|
137
141
|
useEffect(() => field.mount(), [field]);
|
|
138
142
|
|
|
143
|
+
// The field's message, read here rather than in each editor: the tooltip
|
|
144
|
+
// below is the host's, so a column's own `meta.edit.editor` gets it too.
|
|
145
|
+
const error = useFieldError(field);
|
|
146
|
+
// When that tooltip is up, held here rather than left to Tooltip's own
|
|
147
|
+
// `events`: those bring floating-ui's dismiss interaction with them, which
|
|
148
|
+
// puts an `onKeyDown` on the anchor and stops Escape inside the editor
|
|
149
|
+
// before the host's handler below can cancel on it. A controlled `opened`
|
|
150
|
+
// turns the interaction off, so hover and focus are tracked here instead.
|
|
151
|
+
const [messageHovered, setMessageHovered] = useState(false);
|
|
152
|
+
const [messageFocused, setMessageFocused] = useState(false);
|
|
153
|
+
|
|
139
154
|
// Type-to-edit: the seed replaces the value, as it does in a spreadsheet.
|
|
140
155
|
// Only for the types where keystrokes are the input.
|
|
141
156
|
useEffect(() => {
|
|
@@ -172,8 +187,12 @@ export function TMDataGridCellEditor({
|
|
|
172
187
|
onClose({ committed: false, via: "escape" });
|
|
173
188
|
};
|
|
174
189
|
|
|
175
|
-
/**
|
|
176
|
-
|
|
190
|
+
/**
|
|
191
|
+
* `cellConfirm`: the key closes the editor and leaves the draft where it
|
|
192
|
+
* is, waiting for the ✓. Moving along the row is not a decision about it;
|
|
193
|
+
* Enter is, and commits instead.
|
|
194
|
+
*/
|
|
195
|
+
const deferAndClose = (via: "defer-tab" | "defer-shift-tab") => {
|
|
177
196
|
if (closingRef.current) return;
|
|
178
197
|
closingRef.current = true;
|
|
179
198
|
edit.deactivate();
|
|
@@ -181,27 +200,42 @@ export function TMDataGridCellEditor({
|
|
|
181
200
|
};
|
|
182
201
|
|
|
183
202
|
const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
184
|
-
|
|
203
|
+
// Enter on the ✓ or ✕ is the button's own press, and does what a click on
|
|
204
|
+
// it does: commit and stay, or cancel. Left to the branch below it would
|
|
205
|
+
// commit and move down, and the same button would mean two things.
|
|
206
|
+
if (event.key === "Enter" && isButtonElement(event.target)) {
|
|
207
|
+
event.stopPropagation();
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
185
210
|
if (event.key === "Enter") {
|
|
186
211
|
event.preventDefault();
|
|
187
212
|
event.stopPropagation();
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
if (isDraft && !inEntryBlock) deferAndClose("defer");
|
|
193
|
-
else void commitAndClose("enter");
|
|
213
|
+
// Enter is OK, in every mode: the row's form submits and has to pass
|
|
214
|
+
// validation. Where a commit *goes* is `editing.draft`'s business, not
|
|
215
|
+
// this key's - out to the consumer, or into the draft store.
|
|
216
|
+
void commitAndClose("enter");
|
|
194
217
|
} else if (event.key === "Escape") {
|
|
195
218
|
event.preventDefault();
|
|
196
219
|
event.stopPropagation();
|
|
197
220
|
cancelAndClose();
|
|
198
|
-
} else if (event.key === "Tab" && !
|
|
199
|
-
// In the draft entry block Tab stays the browser's: every editable
|
|
200
|
-
// cell's editor is mounted, so it walks the row's inputs as row mode's
|
|
201
|
-
// does.
|
|
221
|
+
} else if (event.key === "Tab" && !isRowShaped) {
|
|
202
222
|
event.preventDefault();
|
|
203
223
|
event.stopPropagation();
|
|
204
|
-
|
|
224
|
+
// `cellConfirm` is the one mode where leaving a cell decides nothing:
|
|
225
|
+
// the caret moves on and the draft waits for its ✓. The ✓ and ✕ beside
|
|
226
|
+
// the input are the cell's own form, so Tab reaches them first - input,
|
|
227
|
+
// ✓, ✕ - and only past the last of them moves on.
|
|
228
|
+
if (features.editMode === "cellConfirm") {
|
|
229
|
+
const tabbables = Array.from(
|
|
230
|
+
event.currentTarget.querySelectorAll<HTMLElement>(FOCUSABLE_IN_CELL),
|
|
231
|
+
).filter((element) => element.tabIndex >= 0);
|
|
232
|
+
const index = tabbables.indexOf(event.target as HTMLElement);
|
|
233
|
+
const next = tabbables[index + (event.shiftKey ? -1 : 1)];
|
|
234
|
+
if (index >= 0 && next !== undefined) {
|
|
235
|
+
next.focus();
|
|
236
|
+
if (isInputElement(next)) next.select();
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
205
239
|
deferAndClose(event.shiftKey ? "defer-shift-tab" : "defer-tab");
|
|
206
240
|
} else {
|
|
207
241
|
void commitAndClose(event.shiftKey ? "shift-tab" : "tab");
|
|
@@ -211,27 +245,33 @@ export function TMDataGridCellEditor({
|
|
|
211
245
|
|
|
212
246
|
/**
|
|
213
247
|
* Focus left the editor entirely - a click somewhere else. Under `"cell"`
|
|
214
|
-
* that commits (Sheets)
|
|
215
|
-
*
|
|
216
|
-
*
|
|
248
|
+
* that commits (Sheets): a row the user has walked away from is a row they
|
|
249
|
+
* are done typing into, and where the commit lands is `editing.draft`'s
|
|
250
|
+
* business. A refused commit - validation, or `onCommit` rejecting - leaves
|
|
251
|
+
* the editor where it is, invalid, with the value still in it: closed, the
|
|
252
|
+
* cell would render the refused value as if it had landed, and the message
|
|
253
|
+
* would go with the editor. The engine holds the same line when another
|
|
254
|
+
* row is opened over a refused one (see `begin`). The user fixes the value,
|
|
255
|
+
* or Escapes.
|
|
217
256
|
*/
|
|
218
257
|
const handleBlur = (event: FocusEvent<HTMLDivElement>) => {
|
|
219
258
|
if (closingRef.current) return;
|
|
220
|
-
// Row mode: the row's other editors are outside this
|
|
221
|
-
// moving between them is not leaving the edit. Blur means
|
|
222
|
-
// the lane's
|
|
223
|
-
if (
|
|
259
|
+
// Row mode and the entry block: the row's other editors are outside this
|
|
260
|
+
// host, and focus moving between them is not leaving the edit. Blur means
|
|
261
|
+
// nothing there; the lane's buttons and the keys end the edit.
|
|
262
|
+
if (isRowShaped) return;
|
|
224
263
|
const next = event.relatedTarget;
|
|
225
|
-
if (next
|
|
264
|
+
if (isNode(next) && event.currentTarget.contains(next)) return;
|
|
226
265
|
closingRef.current = true;
|
|
227
|
-
if (features.editMode === "
|
|
228
|
-
|
|
229
|
-
if (!ok) edit.deactivate();
|
|
230
|
-
});
|
|
231
|
-
} else {
|
|
232
|
-
// cellConfirm: the draft stays, dirty-marked, waiting for its ✓.
|
|
266
|
+
if (features.editMode === "cellConfirm") {
|
|
267
|
+
// The draft stays, dirty-marked, waiting for its ✓.
|
|
233
268
|
edit.deactivate();
|
|
269
|
+
return;
|
|
234
270
|
}
|
|
271
|
+
void edit.commit(row.id).then((ok) => {
|
|
272
|
+
// Open for another try: the next blur or key has to be able to commit.
|
|
273
|
+
if (!ok) closingRef.current = false;
|
|
274
|
+
});
|
|
235
275
|
};
|
|
236
276
|
|
|
237
277
|
if (form === undefined || fieldName === null) return null;
|
|
@@ -267,7 +307,35 @@ export function TMDataGridCellEditor({
|
|
|
267
307
|
onMouseDown={(event) => event.stopPropagation()}
|
|
268
308
|
onDoubleClick={(event) => event.stopPropagation()}
|
|
269
309
|
>
|
|
270
|
-
|
|
310
|
+
{/* The validation message, on the editor rather than under it: inside a
|
|
311
|
+
narrow column Mantine's inline error wraps, grows the row and is
|
|
312
|
+
clipped, so the input keeps the invalid border and the text comes up
|
|
313
|
+
beside it. The editor gets a box of its own because Tooltip needs a
|
|
314
|
+
child that takes a ref, which a `meta.edit.editor` need not forward. */}
|
|
315
|
+
<Tooltip
|
|
316
|
+
label={error}
|
|
317
|
+
opened={error !== undefined && (messageHovered || messageFocused)}
|
|
318
|
+
withArrow
|
|
319
|
+
position="bottom-start"
|
|
320
|
+
>
|
|
321
|
+
<div
|
|
322
|
+
className={classes.cellEditorField}
|
|
323
|
+
onMouseEnter={() => setMessageHovered(true)}
|
|
324
|
+
onMouseLeave={() => setMessageHovered(false)}
|
|
325
|
+
onFocus={() => setMessageFocused(true)}
|
|
326
|
+
onBlur={(event) => {
|
|
327
|
+
// Focus moving inside the editor - into an open dropdown, say - is
|
|
328
|
+
// not focus leaving it.
|
|
329
|
+
const next = event.relatedTarget;
|
|
330
|
+
if (isNode(next) && event.currentTarget.contains(next)) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
setMessageFocused(false);
|
|
334
|
+
}}
|
|
335
|
+
>
|
|
336
|
+
<Editor {...args} />
|
|
337
|
+
</div>
|
|
338
|
+
</Tooltip>
|
|
271
339
|
{/* cellConfirm's chrome: the draft only commits through the ✓ (or
|
|
272
340
|
Enter), so the pair sits right beside the input. */}
|
|
273
341
|
{features.editMode === "cellConfirm" && (
|
|
@@ -12,7 +12,11 @@
|
|
|
12
12
|
display: flex;
|
|
13
13
|
flex-direction: column;
|
|
14
14
|
gap: var(--mantine-spacing-sm);
|
|
15
|
-
padding:
|
|
15
|
+
padding: var(--mantine-spacing-sm);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.columnsPanelSearch + * .columnsPanelList {
|
|
19
|
+
padding-top: 0;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
.columnsPanelFooter {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
+
Box,
|
|
3
|
+
type BoxProps,
|
|
2
4
|
Button,
|
|
3
5
|
Checkbox,
|
|
4
6
|
ScrollArea,
|
|
@@ -6,36 +8,42 @@ import {
|
|
|
6
8
|
TextInput,
|
|
7
9
|
Tooltip,
|
|
8
10
|
} from "@mantine/core";
|
|
9
|
-
import { useSelector } from "@tanstack/react-store";
|
|
10
11
|
import { useState } from "react";
|
|
11
12
|
import classes from "./TMDataGridColumnsPanel.module.css";
|
|
12
13
|
import { useTMDataGridContext } from "../TMDataGridContext";
|
|
13
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
getColumnLabel,
|
|
16
|
+
showColumnSearch,
|
|
17
|
+
type TMDataGridColumnSearchable,
|
|
18
|
+
} from "../core/columnUtils";
|
|
19
|
+
import { useHideableColumns } from "./useHideableColumns";
|
|
14
20
|
import { SearchIcon } from "./icons";
|
|
15
21
|
|
|
16
22
|
/**
|
|
17
|
-
* The "Manage columns" surface
|
|
18
|
-
*
|
|
19
|
-
*
|
|
23
|
+
* The "Manage columns" surface as plain controls, for a host that is not a
|
|
24
|
+
* menu - a Popover, a Drawer, an inline layout. `TMDataGrid.Menu.Columns` is
|
|
25
|
+
* the same chooser as menu items.
|
|
20
26
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
27
|
+
/** Mantine's style props (`w={320}`, `p="sm"`) are set on the panel block. */
|
|
28
|
+
export type TMDataGridColumnsPanelProps = BoxProps & {
|
|
29
|
+
/**
|
|
30
|
+
* The search box over the list: `"auto"` (the default) from six hideable
|
|
31
|
+
* columns, `true` always, `false` never.
|
|
32
|
+
*/
|
|
33
|
+
searchable?: TMDataGridColumnSearchable;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export function TMDataGridColumnsPanel({
|
|
37
|
+
className,
|
|
38
|
+
searchable = "auto",
|
|
39
|
+
...others
|
|
40
|
+
}: TMDataGridColumnsPanelProps = {}) {
|
|
41
|
+
const { labels, controlSize, resetSettings } = useTMDataGridContext();
|
|
23
42
|
const [search, setSearch] = useState("");
|
|
24
43
|
|
|
25
|
-
const columnVisibility =
|
|
26
|
-
|
|
27
|
-
(state) => state.columnVisibility,
|
|
28
|
-
);
|
|
44
|
+
const { columns, columnVisibility, shownCount, setAllVisible } =
|
|
45
|
+
useHideableColumns();
|
|
29
46
|
|
|
30
|
-
// Only what can actually be hidden. A column with `enableHiding: false` is
|
|
31
|
-
// left out rather than listed and disabled: a box that cannot be ticked only
|
|
32
|
-
// invites the question, and every generated lane is one - the checkbox and
|
|
33
|
-
// edit lanes hold the controls the grid needs, the tree column follows the
|
|
34
|
-
// grouping state, the row-number gutter follows `enableRowNumbers`. None of
|
|
35
|
-
// them is a setting.
|
|
36
|
-
const columns = table
|
|
37
|
-
.getAllLeafColumns()
|
|
38
|
-
.filter((column) => column.getCanHide());
|
|
39
47
|
const needle = search.trim().toLowerCase();
|
|
40
48
|
const visibleInPanel = needle
|
|
41
49
|
? columns.filter((column) =>
|
|
@@ -43,41 +51,25 @@ export function TMDataGridColumnsPanel() {
|
|
|
43
51
|
)
|
|
44
52
|
: columns;
|
|
45
53
|
|
|
46
|
-
const shownCount = columns.filter(
|
|
47
|
-
(column) => columnVisibility[column.id] !== false,
|
|
48
|
-
).length;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Show or hide every listed column.
|
|
52
|
-
*
|
|
53
|
-
* Not `table.toggleAllColumnsVisible`, which writes a visibility entry for
|
|
54
|
-
* *every* leaf column: showing all would publish the tree column - hidden
|
|
55
|
-
* because nothing is grouped, not because the user hid it - and hiding all
|
|
56
|
-
* would force the same column visible, since it writes `!getCanHide()` for
|
|
57
|
-
* the columns it will not touch. Either way a lane the panel never listed
|
|
58
|
-
* changes state, and persistence then keeps it that way.
|
|
59
|
-
*/
|
|
60
|
-
const setAllVisible = (visible: boolean) => {
|
|
61
|
-
table.setColumnVisibility((previous) => {
|
|
62
|
-
const next = { ...previous };
|
|
63
|
-
for (const column of columns) next[column.id] = visible;
|
|
64
|
-
return next;
|
|
65
|
-
});
|
|
66
|
-
};
|
|
67
|
-
|
|
68
54
|
return (
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
55
|
+
<Box
|
|
56
|
+
data-dg-part="columns-panel"
|
|
57
|
+
className={[classes.columnsPanel, className].filter(Boolean).join(" ")}
|
|
58
|
+
{...others}
|
|
59
|
+
>
|
|
60
|
+
{showColumnSearch(searchable, columns.length) && (
|
|
61
|
+
<div className={classes.columnsPanelSearch}>
|
|
62
|
+
<TextInput
|
|
63
|
+
value={search}
|
|
64
|
+
onChange={(event) => setSearch(event.currentTarget.value)}
|
|
65
|
+
placeholder={labels.columnsSearchPlaceholder}
|
|
66
|
+
leftSection={<SearchIcon size={16} stroke={1.6} />}
|
|
67
|
+
size={controlSize}
|
|
68
|
+
data-dg-part="columns-search"
|
|
69
|
+
data-autofocus
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
72
|
+
)}
|
|
81
73
|
|
|
82
74
|
<ScrollArea.Autosize mah={260} type="auto">
|
|
83
75
|
<div className={classes.columnsPanelList}>
|
|
@@ -126,6 +118,6 @@ export function TMDataGridColumnsPanel() {
|
|
|
126
118
|
</Button>
|
|
127
119
|
</Tooltip>
|
|
128
120
|
</div>
|
|
129
|
-
</
|
|
121
|
+
</Box>
|
|
130
122
|
);
|
|
131
123
|
}
|
|
@@ -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";
|
|
@@ -26,10 +26,8 @@ function DetailsCell<TData extends RowData>({
|
|
|
26
26
|
row: Row<TMDataGridFeatures, TData>;
|
|
27
27
|
}) {
|
|
28
28
|
const { labels } = useTMDataGridContext();
|
|
29
|
+
const tabIndex = useBodyControlTabIndex();
|
|
29
30
|
const expanded = useSelector(row.table.store, () => row.getIsExpanded());
|
|
30
|
-
// See useCellControlTabIndex: a body control is reached by stepping into its
|
|
31
|
-
// cell, not by Tab, or the grid would have one tab stop per mounted row.
|
|
32
|
-
const tabIndex = useCellControlTabIndex();
|
|
33
31
|
|
|
34
32
|
// Group rows open into their children, not into a panel - see the body's
|
|
35
33
|
// `showsDetails`. Their cells are aggregated, so this lane is already blank
|
|
@@ -39,6 +37,8 @@ function DetailsCell<TData extends RowData>({
|
|
|
39
37
|
return (
|
|
40
38
|
<UnstyledButton
|
|
41
39
|
className={classes.detailsToggle}
|
|
40
|
+
// See useBodyControlTabIndex: under cell selection, reached by stepping
|
|
41
|
+
// into the cell or by the Tab walk within the row, not by the page.
|
|
42
42
|
tabIndex={tabIndex}
|
|
43
43
|
aria-expanded={expanded}
|
|
44
44
|
aria-label={expanded ? labels.hideDetails : labels.showDetails}
|