@jielga/tmdatagrid 1.0.0 → 1.0.1
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 +8 -8
- package/dist/index.d.ts +225 -225
- package/dist/index.js +58 -50
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +3 -2
- package/skills/columns/SKILL.md +4 -4
- package/skills/features/SKILL.md +30 -30
- package/skills/getting-started/SKILL.md +16 -16
- package/skills/options/SKILL.md +8 -8
- package/skills/server-side/SKILL.md +7 -7
- package/skills/testing/SKILL.md +12 -12
- package/src/tmdatagrid/TMDataGridContext.ts +2 -2
- package/src/tmdatagrid/components/TMDataGrid.module.css +2 -2
- package/src/tmdatagrid/components/TMDataGrid.tsx +5 -5
- package/src/tmdatagrid/components/TMDataGridCellEditor.tsx +4 -4
- package/src/tmdatagrid/components/TMDataGridColumnsPanel.tsx +2 -2
- package/src/tmdatagrid/components/TMDataGridDetailsColumn.tsx +6 -6
- package/src/tmdatagrid/components/TMDataGridEditActions.tsx +2 -2
- package/src/tmdatagrid/components/TMDataGridEditColumn.tsx +4 -4
- package/src/tmdatagrid/components/TMDataGridEntryRows.tsx +6 -6
- package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +6 -6
- package/src/tmdatagrid/components/TMDataGridFilterPills.module.css +2 -2
- package/src/tmdatagrid/components/TMDataGridFilterPills.tsx +1 -1
- package/src/tmdatagrid/components/TMDataGridFooter.module.css +1 -1
- package/src/tmdatagrid/components/TMDataGridFooter.tsx +12 -6
- package/src/tmdatagrid/components/TMDataGridGroupColumn.module.css +1 -1
- package/src/tmdatagrid/components/TMDataGridGroupColumn.tsx +5 -5
- package/src/tmdatagrid/components/TMDataGridHeaderCell.module.css +8 -8
- package/src/tmdatagrid/components/TMDataGridHeaderCell.tsx +12 -12
- package/src/tmdatagrid/components/TMDataGridRowNumberColumn.tsx +4 -4
- package/src/tmdatagrid/components/TMDataGridSearch.tsx +5 -5
- package/src/tmdatagrid/components/TMDataGridSelectColumn.tsx +8 -8
- package/src/tmdatagrid/components/TMDataGridTable.module.css +18 -18
- package/src/tmdatagrid/components/TMDataGridTable.tsx +116 -116
- package/src/tmdatagrid/components/TMDataGridToolbar.tsx +5 -5
- package/src/tmdatagrid/components/editors/TMDataGridBooleanEditor.tsx +1 -1
- package/src/tmdatagrid/components/editors/TMDataGridDateEditor.tsx +2 -2
- package/src/tmdatagrid/components/editors/TMDataGridMultiSelectEditor.tsx +1 -1
- package/src/tmdatagrid/components/editors/TMDataGridNumberEditor.tsx +1 -1
- package/src/tmdatagrid/components/editors/TMDataGridSelectEditor.tsx +2 -2
- package/src/tmdatagrid/components/editors/editorShared.ts +3 -3
- package/src/tmdatagrid/components/filters/DgDateRangeFilter.tsx +1 -1
- package/src/tmdatagrid/components/filters/DgRangeSliderFilter.tsx +1 -1
- package/src/tmdatagrid/components/filters/DgTriStateFilter.tsx +1 -1
- package/src/tmdatagrid/components/filters/TMDataGridFilterValueInput.tsx +2 -2
- package/src/tmdatagrid/components/sticky.module.css +8 -8
- package/src/tmdatagrid/core/autosize.ts +4 -4
- package/src/tmdatagrid/core/capabilities.ts +17 -17
- package/src/tmdatagrid/core/cellExport.ts +13 -13
- package/src/tmdatagrid/core/cellNavigation.ts +6 -6
- package/src/tmdatagrid/core/cellRange.ts +4 -4
- package/src/tmdatagrid/core/columnOptions.ts +2 -2
- package/src/tmdatagrid/core/columnOrdering.ts +2 -2
- package/src/tmdatagrid/core/columnUtils.ts +3 -3
- package/src/tmdatagrid/core/editEngine.ts +49 -49
- package/src/tmdatagrid/core/expanding.ts +5 -5
- package/src/tmdatagrid/core/filterControls.ts +5 -5
- package/src/tmdatagrid/core/filterOperators.ts +14 -14
- package/src/tmdatagrid/core/labels.ts +8 -8
- package/src/tmdatagrid/core/matchHighlight.ts +4 -4
- package/src/tmdatagrid/core/persistence.ts +8 -8
- package/src/tmdatagrid/core/quickSearch.ts +8 -8
- package/src/tmdatagrid/core/rowPinning.ts +3 -3
- package/src/tmdatagrid/core/rowSelection.ts +12 -12
- package/src/tmdatagrid/core/sizes.ts +1 -1
- package/src/tmdatagrid/core/summary.ts +3 -3
- package/src/tmdatagrid/useTMDataGrid.tsx +79 -79
|
@@ -26,7 +26,7 @@ export function TMDataGridToolbarSpacer() {
|
|
|
26
26
|
/**
|
|
27
27
|
* A small spinner shown while `meta.loading` is true, and nothing otherwise.
|
|
28
28
|
*
|
|
29
|
-
* The body only shows its loading state while the grid is *empty*
|
|
29
|
+
* The body only shows its loading state while the grid is *empty* - a
|
|
30
30
|
* server-driven grid refetching with rows on screen keeps showing them, which
|
|
31
31
|
* is right, but leaves nothing saying a fetch is running. This is that signal,
|
|
32
32
|
* and the consumer decides where it sits by placing it in the toolbar:
|
|
@@ -48,7 +48,7 @@ export function TMDataGridLoadingIndicator() {
|
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* Visible rows over total rows. On a server-driven grid, set
|
|
51
|
-
* `meta.totalRowCount` for the denominator
|
|
51
|
+
* `meta.totalRowCount` for the denominator - the client never sees every row.
|
|
52
52
|
*/
|
|
53
53
|
export function TMDataGridSummaryCount({ children }: { children?: ReactNode }) {
|
|
54
54
|
const { table, controlSize } = useTMDataGridContext();
|
|
@@ -56,7 +56,7 @@ export function TMDataGridSummaryCount({ children }: { children?: ReactNode }) {
|
|
|
56
56
|
|
|
57
57
|
if (children !== undefined) {
|
|
58
58
|
return (
|
|
59
|
-
<Text size={controlSize} c="dimmed" data-dg-part="summary-count">
|
|
59
|
+
<Text span size={controlSize} c="dimmed" data-dg-part="summary-count">
|
|
60
60
|
{children}
|
|
61
61
|
</Text>
|
|
62
62
|
);
|
|
@@ -68,14 +68,14 @@ export function TMDataGridSummaryCount({ children }: { children?: ReactNode }) {
|
|
|
68
68
|
table.getPreFilteredRowModel().rows.length;
|
|
69
69
|
|
|
70
70
|
return (
|
|
71
|
-
<Text size={controlSize} c="dimmed" data-dg-part="summary-count">
|
|
71
|
+
<Text span size={controlSize} c="dimmed" data-dg-part="summary-count">
|
|
72
72
|
{shown} / {total}
|
|
73
73
|
</Text>
|
|
74
74
|
);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
|
-
* Burger menu in the grid's top-right corner
|
|
78
|
+
* Burger menu in the grid's top-right corner - opens "Manage columns".
|
|
79
79
|
* Renders nothing when no column can be hidden (`enableHiding: false`).
|
|
80
80
|
*/
|
|
81
81
|
export function TMDataGridColumnsButton() {
|
|
@@ -4,7 +4,7 @@ import { getColumnLabel } from "../../core/columnUtils";
|
|
|
4
4
|
import { useTMDataGridContext } from "../../TMDataGridContext";
|
|
5
5
|
import { useFieldError, useFieldValue } from "./editorShared";
|
|
6
6
|
|
|
7
|
-
/** The built-in editor for `meta.type: "boolean"`
|
|
7
|
+
/** The built-in editor for `meta.type: "boolean"` - a focused checkbox. */
|
|
8
8
|
export function TMDataGridBooleanEditor({
|
|
9
9
|
field,
|
|
10
10
|
autoFocus,
|
|
@@ -6,7 +6,7 @@ import { useTMDataGridContext } from "../../TMDataGridContext";
|
|
|
6
6
|
import { useFieldError, useFieldValue } from "./editorShared";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* The built-in editor for `meta.type: "date"`
|
|
9
|
+
* The built-in editor for `meta.type: "date"` - the native
|
|
10
10
|
* `<input type="date">` styled by Mantine, no `@mantine/dates` involved. A
|
|
11
11
|
* consumer wanting a real picker plugs one in through `meta.editor`.
|
|
12
12
|
*/
|
|
@@ -20,7 +20,7 @@ export function TMDataGridDateEditor({
|
|
|
20
20
|
const value = useFieldValue(field);
|
|
21
21
|
const error = useFieldError(field);
|
|
22
22
|
// The cell's original shape decides what the editor writes back: a `Date`
|
|
23
|
-
// cell keeps getting Dates, an ISO-string cell keeps getting strings
|
|
23
|
+
// cell keeps getting Dates, an ISO-string cell keeps getting strings -
|
|
24
24
|
// decided once at open, so clearing and retyping cannot flip the type.
|
|
25
25
|
const writesDates = useRef(value instanceof Date);
|
|
26
26
|
|
|
@@ -9,7 +9,7 @@ import { useTMDataGridContext } from "../../TMDataGridContext";
|
|
|
9
9
|
import { useFieldError, useFieldValue } from "./editorShared";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* The built-in editor for `meta.type: "multiSelect"`
|
|
12
|
+
* The built-in editor for `meta.type: "multiSelect"` - cells holding string
|
|
13
13
|
* arrays. Never commits on pick: building a set takes several, so the commit
|
|
14
14
|
* stays on Enter/blur like a text edit.
|
|
15
15
|
*/
|
|
@@ -26,7 +26,7 @@ export function TMDataGridNumberEditor({
|
|
|
26
26
|
value={typeof value === "number" ? value : ""}
|
|
27
27
|
onChange={(next) =>
|
|
28
28
|
// Mantine reports a number once one can be parsed, and a string while
|
|
29
|
-
// it cannot. An empty cell is `null`
|
|
29
|
+
// it cannot. An empty cell is `null` - the honest "no value".
|
|
30
30
|
field.handleChange(
|
|
31
31
|
typeof next === "number" ? next : next === "" ? null : Number(next),
|
|
32
32
|
)
|
|
@@ -10,7 +10,7 @@ import { useFieldError, useFieldValue } from "./editorShared";
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* The built-in editor for `meta.type: "select"`. Options come from the same
|
|
13
|
-
* `meta.options` the filter panel reads
|
|
13
|
+
* `meta.options` the filter panel reads - with the row in hand, so a
|
|
14
14
|
* function source can depend on the record being edited.
|
|
15
15
|
*/
|
|
16
16
|
export function TMDataGridSelectEditor({
|
|
@@ -31,7 +31,7 @@ export function TMDataGridSelectEditor({
|
|
|
31
31
|
w="100%"
|
|
32
32
|
autoFocus={autoFocus}
|
|
33
33
|
searchable
|
|
34
|
-
// Inside the cell, so picking an option is not a click-away
|
|
34
|
+
// Inside the cell, so picking an option is not a click-away - the same
|
|
35
35
|
// reason the filter panel keeps its dropdowns unportalled.
|
|
36
36
|
comboboxProps={{ withinPortal: false }}
|
|
37
37
|
aria-label={labels.editCell(getColumnLabel(column))}
|
|
@@ -2,7 +2,7 @@ import { useStore } from "@tanstack/react-form";
|
|
|
2
2
|
import type { TMDataGridEditField } from "../../core/editEngine";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* The first error as text, whatever shape the validator produced
|
|
5
|
+
* The first error as text, whatever shape the validator produced - TanStack
|
|
6
6
|
* Form keeps errors verbatim, so a Zod issue arrives as `{ message }`, a
|
|
7
7
|
* plain function's return arrives as it was, and a form-level schema's
|
|
8
8
|
* pathless issues arrive keyed under an empty path (`{ "": [issues] }`).
|
|
@@ -29,7 +29,7 @@ export function firstErrorText(error: unknown): string | undefined {
|
|
|
29
29
|
return undefined;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
/** The field's live value
|
|
32
|
+
/** The field's live value - one subscription, on the field's own store. */
|
|
33
33
|
export function useFieldValue(field: TMDataGridEditField): unknown {
|
|
34
34
|
return useStore(field.store, (state) => state.value as unknown);
|
|
35
35
|
}
|
|
@@ -41,7 +41,7 @@ export function useFieldError(field: TMDataGridEditField): string | undefined {
|
|
|
41
41
|
);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
/** Select-all on focus
|
|
44
|
+
/** Select-all on focus - F2/Enter open with the value ready to replace. */
|
|
45
45
|
export function selectAllOnFocus(event: { currentTarget: unknown }): void {
|
|
46
46
|
const target = event.currentTarget;
|
|
47
47
|
if (target instanceof HTMLInputElement) target.select();
|
|
@@ -5,7 +5,7 @@ import { TMDataGridFilterValueInput } from "./TMDataGridFilterValueInput";
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* A built-in `meta.filterControl` for `date` columns: a From/To pair of
|
|
8
|
-
* native date inputs writing the `between` pair
|
|
8
|
+
* native date inputs writing the `between` pair - no `@mantine/dates`
|
|
9
9
|
* involved, like the built-in date editor. Pair it with
|
|
10
10
|
* `meta.defaultFilterOperator: "between"`; any non-range operator falls back
|
|
11
11
|
* to the standard input.
|
|
@@ -16,7 +16,7 @@ export function DgRangeSliderFilter(args: TMDataGridFilterControlArgs) {
|
|
|
16
16
|
return <TMDataGridFilterValueInput {...args} />;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
// Bounds from the faceted index
|
|
19
|
+
// Bounds from the faceted index - the min and max actually in the data.
|
|
20
20
|
const numbers: number[] = [];
|
|
21
21
|
for (const key of column.getFacetedUniqueValues().keys()) {
|
|
22
22
|
const numeric = Number(key);
|
|
@@ -9,7 +9,7 @@ import { TMDataGridFilterValueInput } from "./TMDataGridFilterValueInput";
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* A built-in `meta.filterControl` for `boolean` columns: an All / Yes / No
|
|
12
|
-
* segmented control
|
|
12
|
+
* segmented control - All clears the value, which deactivates the filter.
|
|
13
13
|
* Set or range operators fall back to the standard input.
|
|
14
14
|
*/
|
|
15
15
|
export function DgTriStateFilter(args: TMDataGridFilterControlArgs) {
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "../../core/filterOperators";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* The built-in value control of a filter-panel row
|
|
12
|
+
* The built-in value control of a filter-panel row - what renders when a
|
|
13
13
|
* column declares no `meta.filterControl`. Shaped by the operator: a
|
|
14
14
|
* multi-select for the set operators, a From/To pair for `between`, a Yes/No
|
|
15
15
|
* dropdown for booleans, a typed input otherwise.
|
|
@@ -37,7 +37,7 @@ export function TMDataGridFilterValueInput({
|
|
|
37
37
|
? [String(value[0] ?? ""), String(value[1] ?? "")]
|
|
38
38
|
: ["", ""];
|
|
39
39
|
return (
|
|
40
|
-
// The interval's two ends. Either may stay empty
|
|
40
|
+
// The interval's two ends. Either may stay empty (an open end), and
|
|
41
41
|
// each writes its slot of the `[min, max]` pair.
|
|
42
42
|
<Group gap={4} wrap="nowrap" align="flex-start">
|
|
43
43
|
<TextInput
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/* Boundary of a pinned column group. Shared, because the edge is cast by both
|
|
2
|
-
the header cell and every body cell in the same column
|
|
2
|
+
the header cell and every body cell in the same column - the two have to draw
|
|
3
3
|
an identical band or the seam shows at the header.
|
|
4
4
|
|
|
5
5
|
A box-shadow is drawn per cell, and its blur fades in at each cell's own top
|
|
6
|
-
and bottom edge
|
|
6
|
+
and bottom edge - stacked down a column that reads as banding at every row
|
|
7
7
|
seam. Instead: a gradient pseudo element pinned to `top: 0; bottom: 0` so
|
|
8
8
|
consecutive cells butt together into one unbroken band. Mantine sets
|
|
9
9
|
`color-scheme` on the root, so light-dark() resolves without a second
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
No border. A 1px line the colour of the grid's own borders drew a hard rule
|
|
13
13
|
between the pinned lanes and the data, so the pinned columns read as a
|
|
14
|
-
separate table rather than as part of this one
|
|
14
|
+
separate table rather than as part of this one - and at the system lanes,
|
|
15
15
|
which are only a control wide, it framed them like a sidebar. */
|
|
16
16
|
.stickyLeft::after,
|
|
17
17
|
.stickyRight::after {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/* Three stops rather than two. A straight linear ramp to `transparent` fades
|
|
27
|
-
fastest at the start, so its own outer edge stays visible as a faint line
|
|
27
|
+
fastest at the start, so its own outer edge stays visible as a faint line -
|
|
28
28
|
the sharpness a hard border was blamed for. Front-loading the falloff puts
|
|
29
29
|
the density against the pinned cell and lets the tail reach zero unnoticed. */
|
|
30
30
|
.stickyLeft::after {
|
|
@@ -53,12 +53,12 @@
|
|
|
53
53
|
|
|
54
54
|
Driven by the scroll position itself rather than by a listener writing a
|
|
55
55
|
class. A scroll-driven animation is evaluated on the compositor, so the band
|
|
56
|
-
tracks the scroll frame for frame without a single React render
|
|
56
|
+
tracks the scroll frame for frame without a single React render - and the
|
|
57
57
|
body re-renders on every scroll frame already, from virtualization.
|
|
58
58
|
|
|
59
59
|
`scroll(nearest inline)` resolves to `.scrollContainer`, the one horizontal
|
|
60
|
-
scroller above these cells. An inactive timeline
|
|
61
|
-
with nothing to scroll gives
|
|
60
|
+
scroller above these cells. An inactive timeline - which is what a container
|
|
61
|
+
with nothing to scroll gives - leaves the base style in place, so `opacity:
|
|
62
62
|
0` inside this block is also the answer for a grid that does not scroll. */
|
|
63
63
|
@supports (animation-timeline: scroll()) {
|
|
64
64
|
.stickyLeft::after,
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
|
|
80
80
|
/* The mirror: on at rest, gone as the last column arrives. The range is
|
|
81
81
|
measured from the end, and `both` holds the first value everywhere before
|
|
82
|
-
it
|
|
82
|
+
it - so the band stays fully on until the scroller is within 20px of home.
|
|
83
83
|
*/
|
|
84
84
|
.stickyRight::after {
|
|
85
85
|
animation-name: stickyEdgeOut;
|
|
@@ -18,7 +18,7 @@ const CONTENT_ALLOWANCE = 2;
|
|
|
18
18
|
* A Range over the children rather than the span's `scrollWidth`, because the
|
|
19
19
|
* two disagree in opposite directions: a left-aligned cell stretches its
|
|
20
20
|
* content span to fill the cell (`flex: 1 1 auto`), so `scrollWidth` reports
|
|
21
|
-
* the stretched box and autosize could never *shrink* a column
|
|
21
|
+
* the stretched box and autosize could never *shrink* a column - while for
|
|
22
22
|
* clipped content both see past the `overflow: hidden`. The Range measures
|
|
23
23
|
* the laid-out text and elements themselves, whatever box they sit in.
|
|
24
24
|
*
|
|
@@ -41,14 +41,14 @@ function contentSpanWidth(content: HTMLElement): number {
|
|
|
41
41
|
* Widest rendered content of one column, in px, measured from the DOM.
|
|
42
42
|
*
|
|
43
43
|
* Mounted cells only: under virtualization the unmounted rows do not exist to
|
|
44
|
-
* be measured, so this reads the visible window plus overscan
|
|
44
|
+
* be measured, so this reads the visible window plus overscan - the same
|
|
45
45
|
* trade AG Grid's autosize makes by default.
|
|
46
46
|
*/
|
|
47
47
|
export function measureColumnContentWidth({
|
|
48
48
|
container,
|
|
49
49
|
columnId,
|
|
50
50
|
}: {
|
|
51
|
-
/** The grid's scroll container
|
|
51
|
+
/** The grid's scroll container - anything enclosing the column's cells. */
|
|
52
52
|
container: HTMLElement;
|
|
53
53
|
columnId: string;
|
|
54
54
|
}): number {
|
|
@@ -91,7 +91,7 @@ export function measureColumnContentWidth({
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
|
-
* Sizes a column to its widest mounted content
|
|
94
|
+
* Sizes a column to its widest mounted content - what double-clicking the
|
|
95
95
|
* resize divider does, exported so a menu item or consumer code can trigger
|
|
96
96
|
* the same thing.
|
|
97
97
|
*
|
|
@@ -21,10 +21,10 @@ import type {
|
|
|
21
21
|
* what applies the per-column overrides.
|
|
22
22
|
*/
|
|
23
23
|
/**
|
|
24
|
-
* How rows are selected
|
|
24
|
+
* How rows are selected - see `selectionMode` on {@link UseTMDataGridOptions}.
|
|
25
25
|
*
|
|
26
|
-
* One axis, because the two things a config could set independently
|
|
27
|
-
* chrome selects, and what a bare row click does
|
|
26
|
+
* One axis, because the two things a config could set independently - which
|
|
27
|
+
* chrome selects, and what a bare row click does - cannot both be free: a click
|
|
28
28
|
* can either toggle a multi-selection or move the highlight, never both. Folding
|
|
29
29
|
* them into one value makes that conflict unrepresentable rather than something
|
|
30
30
|
* to warn about.
|
|
@@ -36,11 +36,11 @@ export type TMDataGridSelectionMode =
|
|
|
36
36
|
| "row"
|
|
37
37
|
/** Checkbox column multi-selects, and a row click highlights one row. */
|
|
38
38
|
| "checkboxAndHighlight"
|
|
39
|
-
/** No selection at all
|
|
39
|
+
/** No selection at all - a row click only highlights. Master-detail. */
|
|
40
40
|
| "highlight";
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
* How cells are selected
|
|
43
|
+
* How cells are selected - see `cellSelection` on {@link UseTMDataGridOptions}.
|
|
44
44
|
*
|
|
45
45
|
* A mode rather than a pair of booleans, for the reason
|
|
46
46
|
* {@link TMDataGridSelectionMode} is one: "cells can be selected" and "more
|
|
@@ -58,7 +58,7 @@ export type TMDataGridCellSelectionMode =
|
|
|
58
58
|
export type TMDataGridFeatureFlags = {
|
|
59
59
|
sorting: boolean;
|
|
60
60
|
filtering: boolean;
|
|
61
|
-
/** Whether the quick search may filter
|
|
61
|
+
/** Whether the quick search may filter - `enableGlobalFilter`. */
|
|
62
62
|
globalFiltering: boolean;
|
|
63
63
|
hiding: boolean;
|
|
64
64
|
pinning: boolean;
|
|
@@ -73,7 +73,7 @@ export type TMDataGridFeatureFlags = {
|
|
|
73
73
|
/** Whether a bare row click toggles the selection. Only under `"row"`. */
|
|
74
74
|
rowClickSelects: boolean;
|
|
75
75
|
/**
|
|
76
|
-
* Whether more than one row can be selected at once
|
|
76
|
+
* Whether more than one row can be selected at once - TanStack's
|
|
77
77
|
* `enableMultiRowSelection`. Off, the grid drops the select-all header
|
|
78
78
|
* checkbox: TanStack's `toggleAllRowsSelected` only consults `getCanSelect`,
|
|
79
79
|
* so that control would select every row and walk straight past the limit.
|
|
@@ -84,14 +84,14 @@ export type TMDataGridFeatureFlags = {
|
|
|
84
84
|
/** Whether a selected row takes the highlight colour. Follows the mode. */
|
|
85
85
|
showSelectedBackground: boolean;
|
|
86
86
|
/**
|
|
87
|
-
* The single highlighted row
|
|
87
|
+
* The single highlighted row - clicking a row highlights it, for a detail
|
|
88
88
|
* panel to follow. State of its own, not a slice of `rowSelection`, which is
|
|
89
89
|
* what lets it coexist with a checkbox multi-selection.
|
|
90
90
|
*/
|
|
91
91
|
highlightRow: boolean;
|
|
92
92
|
/**
|
|
93
93
|
* Whether cells can be selected at all. Off unless `cellSelection` asks for
|
|
94
|
-
* it
|
|
94
|
+
* it - it takes the body's tab stop off the row and puts it on a cell, and
|
|
95
95
|
* reports the grid to assistive technology as a `grid` rather than a `table`,
|
|
96
96
|
* which is a different promise about what the keyboard does.
|
|
97
97
|
*/
|
|
@@ -109,7 +109,7 @@ export type TMDataGridFeatureFlags = {
|
|
|
109
109
|
/**
|
|
110
110
|
* Row grouping: the "Group by" items in the header menu and the generated
|
|
111
111
|
* tree column. On unless `enableGrouping: false`, since an ungrouped grid
|
|
112
|
-
* looks and behaves exactly as it did before
|
|
112
|
+
* looks and behaves exactly as it did before - `grouping` starts empty, and
|
|
113
113
|
* an empty grouping state passes straight through the row model.
|
|
114
114
|
*
|
|
115
115
|
* The exception is `manualPagination`, where the client holds one page rather
|
|
@@ -118,14 +118,14 @@ export type TMDataGridFeatureFlags = {
|
|
|
118
118
|
* its own grouping can still say `enableGrouping: true` to override.
|
|
119
119
|
*/
|
|
120
120
|
grouping: boolean;
|
|
121
|
-
/** Whether cells can be edited at all
|
|
121
|
+
/** Whether cells can be edited at all - `editMode` was set. */
|
|
122
122
|
editing: boolean;
|
|
123
123
|
/** The commit policy, or `null` while editing is off. */
|
|
124
124
|
editMode: TMDataGridEditMode | null;
|
|
125
|
-
/** The generated row-number gutter
|
|
125
|
+
/** The generated row-number gutter - `enableRowNumbers`. Off by default. */
|
|
126
126
|
rowNumbers: boolean;
|
|
127
127
|
/**
|
|
128
|
-
* Whether rows can be pinned to the top or bottom edge
|
|
128
|
+
* Whether rows can be pinned to the top or bottom edge - `enableRowPinning`,
|
|
129
129
|
* boolean or per-row predicate. Off by default: pinning has no built-in
|
|
130
130
|
* gesture, so a grid that never calls `row.pin()` has nothing to gain from
|
|
131
131
|
* the body watching the state.
|
|
@@ -133,7 +133,7 @@ export type TMDataGridFeatureFlags = {
|
|
|
133
133
|
rowPinning: boolean;
|
|
134
134
|
/**
|
|
135
135
|
* Whether cells highlight the matched text while a contains-family filter
|
|
136
|
-
* or the quick search is active
|
|
136
|
+
* or the quick search is active - `enableMatchHighlighting`. Off by
|
|
137
137
|
* default, so a grid that has not asked pays nothing per cell.
|
|
138
138
|
*/
|
|
139
139
|
matchHighlighting: boolean;
|
|
@@ -205,7 +205,7 @@ export function readFeatureFlags<TData extends RowData>(
|
|
|
205
205
|
editing: options.editMode !== undefined,
|
|
206
206
|
editMode: options.editMode ?? null,
|
|
207
207
|
rowNumbers: options.enableRowNumbers === true,
|
|
208
|
-
// A predicate counts as on
|
|
208
|
+
// A predicate counts as on - some rows may still pin.
|
|
209
209
|
rowPinning:
|
|
210
210
|
options.enableRowPinning === true ||
|
|
211
211
|
typeof options.enableRowPinning === "function",
|
|
@@ -246,7 +246,7 @@ export function getColumnCapabilities(
|
|
|
246
246
|
canHide: features.hiding && column.getCanHide(),
|
|
247
247
|
canPin: features.pinning && column.getCanPin(),
|
|
248
248
|
canResize: features.resizing && column.getCanResize(),
|
|
249
|
-
// Ordering has no TanStack capability method
|
|
249
|
+
// Ordering has no TanStack capability method - see isColumnReorderable.
|
|
250
250
|
canReorder: features.ordering && isColumnReorderable(column),
|
|
251
251
|
// `getCanGroup()` also insists on an accessor, which is what keeps the
|
|
252
252
|
// generated checkbox and tree columns from offering to group on themselves.
|
|
@@ -254,7 +254,7 @@ export function getColumnCapabilities(
|
|
|
254
254
|
};
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
-
/** The same questions, asked of the whole grid
|
|
257
|
+
/** The same questions, asked of the whole grid - used to hide toolbar buttons. */
|
|
258
258
|
export type TMDataGridCapabilities = {
|
|
259
259
|
canSortAny: boolean;
|
|
260
260
|
canFilterAny: boolean;
|
|
@@ -71,7 +71,7 @@ export function formatExportValue(
|
|
|
71
71
|
.map((entry) => formatExportValue(entry, { decimalComma }))
|
|
72
72
|
.join(", ");
|
|
73
73
|
}
|
|
74
|
-
// `sv-SE` is ISO-shaped
|
|
74
|
+
// `sv-SE` is ISO-shaped (2026-07-31, 2026-07-31 14:05:00), which is both the
|
|
75
75
|
// Nordic form and the one Excel parses as a date rather than as text.
|
|
76
76
|
if (value instanceof Date) {
|
|
77
77
|
return Number.isNaN(value.getTime()) ? "" : value.toLocaleString("sv-SE");
|
|
@@ -90,7 +90,7 @@ export function formatExportValue(
|
|
|
90
90
|
|
|
91
91
|
export type BuildCellMatrixArgs = {
|
|
92
92
|
rows: ReadonlyArray<Row<TMDataGridFeatures, TMDataGridRowData>>;
|
|
93
|
-
/** Every visible column, in render order
|
|
93
|
+
/** Every visible column, in render order - the same list the bounds index into. */
|
|
94
94
|
columns: ReadonlyArray<Column<TMDataGridFeatures, TMDataGridRowData, unknown>>;
|
|
95
95
|
bounds: TMDataGridRangeBounds;
|
|
96
96
|
includeHeaders: boolean;
|
|
@@ -100,7 +100,7 @@ export type BuildCellMatrixArgs = {
|
|
|
100
100
|
/**
|
|
101
101
|
* The selected rectangle as rows of text.
|
|
102
102
|
*
|
|
103
|
-
* The generated lanes
|
|
103
|
+
* The generated lanes - the checkbox, the tree chevron, the details chevron -
|
|
104
104
|
* are dropped even when the rectangle covers them. They hold controls rather
|
|
105
105
|
* than data, so a column of empty strings is all they could contribute, and
|
|
106
106
|
* pasting one into a spreadsheet only shifts everything to its right.
|
|
@@ -135,13 +135,13 @@ export function buildCellMatrix({
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
/**
|
|
138
|
-
* The whole grid as rows of text: every filtered and sorted data row
|
|
139
|
-
* pages, not the one on screen
|
|
138
|
+
* The whole grid as rows of text: every filtered and sorted data row - all
|
|
139
|
+
* pages, not the one on screen - by every visible non-control column, in
|
|
140
140
|
* render order.
|
|
141
141
|
*
|
|
142
142
|
* The values are the same ones a cell-range export writes; only the bounds
|
|
143
143
|
* differ. Group rows are left out (their records are the rows themselves),
|
|
144
|
-
* and hidden columns are not exported
|
|
144
|
+
* and hidden columns are not exported - what you see is what you get, minus
|
|
145
145
|
* paging.
|
|
146
146
|
*/
|
|
147
147
|
export function buildGridCellMatrix<TData extends RowData>({
|
|
@@ -162,7 +162,7 @@ export function buildGridCellMatrix<TData extends RowData>({
|
|
|
162
162
|
...erased.getRightVisibleLeafColumns(),
|
|
163
163
|
];
|
|
164
164
|
// The sorted model: after filtering, grouping and sorting, before expansion
|
|
165
|
-
// and paging
|
|
165
|
+
// and paging - so a paged grid exports the whole filtered set, and a grouped
|
|
166
166
|
// grid exports the records under every group whether or not it is open.
|
|
167
167
|
//
|
|
168
168
|
// Walked by hand rather than taken from `flatRows`: in table-core
|
|
@@ -195,7 +195,7 @@ export function buildGridCellMatrix<TData extends RowData>({
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
/**
|
|
198
|
-
* Downloads the whole grid as a CSV for Excel
|
|
198
|
+
* Downloads the whole grid as a CSV for Excel - {@link buildGridCellMatrix}
|
|
199
199
|
* through {@link toExcelCsv}, with the same Nordic defaults and overrides as
|
|
200
200
|
* the cell-range export.
|
|
201
201
|
*
|
|
@@ -243,7 +243,7 @@ function escapeField(value: string, separator: string): string {
|
|
|
243
243
|
* The clipboard format spreadsheets read: tab between cells, CRLF between rows.
|
|
244
244
|
*
|
|
245
245
|
* Tabs rather than commas because that is what Excel, Sheets and Numbers all
|
|
246
|
-
* put on the clipboard themselves
|
|
246
|
+
* put on the clipboard themselves - paste it and the cells land in cells. A
|
|
247
247
|
* comma-separated string pastes into a single column, which is the thing this
|
|
248
248
|
* exists to avoid.
|
|
249
249
|
*/
|
|
@@ -260,12 +260,12 @@ export function toClipboardText(matrix: TMDataGridCellMatrix): string {
|
|
|
260
260
|
*
|
|
261
261
|
* | Part | Why |
|
|
262
262
|
* | ---- | --- |
|
|
263
|
-
* | `sep=;` first line | Excel's own directive
|
|
263
|
+
* | `sep=;` first line | Excel's own directive - it stops guessing and uses this |
|
|
264
264
|
* | UTF-8 BOM | without it Excel reads the file as ANSI, and å ä ö arrive broken |
|
|
265
265
|
* | CRLF line endings | what Excel writes, and what its importer is happiest with |
|
|
266
266
|
*
|
|
267
267
|
* The `sep=` line is Excel's alone; other readers show it as a first row. That
|
|
268
|
-
* is the trade this makes
|
|
268
|
+
* is the trade this makes - the file is for Excel, and "it just opens" is worth
|
|
269
269
|
* more than being a well-behaved CSV nobody was going to feed to a parser.
|
|
270
270
|
*/
|
|
271
271
|
export function toExcelCsv(
|
|
@@ -282,7 +282,7 @@ export function toExcelCsv(
|
|
|
282
282
|
* Puts text on the clipboard, reporting whether it landed.
|
|
283
283
|
*
|
|
284
284
|
* The async clipboard API only resolves for a document that has the focus and a
|
|
285
|
-
* user gesture behind it
|
|
285
|
+
* user gesture behind it - both true when this runs off Ctrl+C or a menu item.
|
|
286
286
|
* It is still allowed to reject (a permissions policy, a page that lost focus
|
|
287
287
|
* mid-copy), so the result is a boolean rather than a promise nobody checks.
|
|
288
288
|
*/
|
|
@@ -297,7 +297,7 @@ export async function writeClipboardText(text: string): Promise<boolean> {
|
|
|
297
297
|
|
|
298
298
|
/**
|
|
299
299
|
* Downloads text as a file, through the one mechanism a library can use: an
|
|
300
|
-
* anchor with an object URL behind it, clicked. Revoked on the next frame
|
|
300
|
+
* anchor with an object URL behind it, clicked. Revoked on the next frame -
|
|
301
301
|
* immediately would race the browser's own read of it.
|
|
302
302
|
*/
|
|
303
303
|
export function downloadTextFile({
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Ids, because every other thing the grid does moves cells around: sorting
|
|
5
5
|
* reorders rows, filtering removes them, dragging a header reorders columns. A
|
|
6
6
|
* coordinate pair would silently come to mean a different cell after any of
|
|
7
|
-
* them, while a pair of ids either still resolves or does not resolve at all
|
|
7
|
+
* them, while a pair of ids either still resolves or does not resolve at all -
|
|
8
8
|
* and "does not resolve" is a state the grid can handle honestly.
|
|
9
9
|
*
|
|
10
10
|
* Indices are what navigation is actually computed in, so they are resolved
|
|
@@ -24,7 +24,7 @@ export type TMDataGridCellCoords = {
|
|
|
24
24
|
export type ResolveCellMoveArgs = {
|
|
25
25
|
/** `KeyboardEvent.key`. */
|
|
26
26
|
key: string;
|
|
27
|
-
/** Ctrl or Cmd
|
|
27
|
+
/** Ctrl or Cmd - Home and End jump to the corner rather than the row end. */
|
|
28
28
|
ctrlKey: boolean;
|
|
29
29
|
from: TMDataGridCellCoords;
|
|
30
30
|
rowCount: number;
|
|
@@ -45,7 +45,7 @@ const clamp = (value: number, max: number) =>
|
|
|
45
45
|
* Moves clamp at the edges instead of wrapping. Wrapping suits a menu, where
|
|
46
46
|
* the items are a short ring; a grid is a coordinate space, and a Right at the
|
|
47
47
|
* last column that lands on the first column of the next row loses the one
|
|
48
|
-
* thing arrow keys are for
|
|
48
|
+
* thing arrow keys are for - knowing where you will end up without looking.
|
|
49
49
|
*
|
|
50
50
|
* A clamped move returns the position it started from rather than `null`. The
|
|
51
51
|
* caller still needs to know the key was handled: an arrow at the last row must
|
|
@@ -82,7 +82,7 @@ export function resolveCellMove({
|
|
|
82
82
|
return at(from.rowIndex + Math.max(pageRows, 1), from.columnIndex);
|
|
83
83
|
case "PageUp":
|
|
84
84
|
return at(from.rowIndex - Math.max(pageRows, 1), from.columnIndex);
|
|
85
|
-
// Ctrl+Home / Ctrl+End address the grid, plain Home / End the row
|
|
85
|
+
// Ctrl+Home / Ctrl+End address the grid, plain Home / End the row - the
|
|
86
86
|
// convention every spreadsheet and APG's own grid pattern share.
|
|
87
87
|
case "Home":
|
|
88
88
|
return ctrlKey ? at(0, 0) : at(from.rowIndex, 0);
|
|
@@ -104,13 +104,13 @@ export type NextEditableCellArgs = {
|
|
|
104
104
|
};
|
|
105
105
|
|
|
106
106
|
/**
|
|
107
|
-
* The next editable cell in reading order, wrapping to the next row
|
|
107
|
+
* The next editable cell in reading order, wrapping to the next row - and
|
|
108
108
|
* from the last cell of the grid back to the first. `null` when no other
|
|
109
109
|
* cell is editable.
|
|
110
110
|
*
|
|
111
111
|
* Deliberately wrapping where {@link resolveCellMove} deliberately clamps:
|
|
112
112
|
* arrows are a coordinate space and clamp so the destination is predictable,
|
|
113
|
-
* but Tab mid-edit is the spreadsheet's "next field"
|
|
113
|
+
* but Tab mid-edit is the spreadsheet's "next field" - at the end of a row
|
|
114
114
|
* the next field is on the next row, which is a convention, not a surprise.
|
|
115
115
|
*/
|
|
116
116
|
export function getNextEditableCell({
|
|
@@ -4,13 +4,13 @@ import type { TMDataGridCellPosition } from "./cellNavigation";
|
|
|
4
4
|
* A selected rectangle of cells, held as the two cells that span it.
|
|
5
5
|
*
|
|
6
6
|
* Two corners rather than a list of cells, because a range is a gesture, not a
|
|
7
|
-
* set: dragging over 40 columns of a 100k-row grid would otherwise build
|
|
8
|
-
* rebuild, on every mouse move
|
|
7
|
+
* set: dragging over 40 columns of a 100k-row grid would otherwise build - and
|
|
8
|
+
* rebuild, on every mouse move - a list with millions of entries in it. Two
|
|
9
9
|
* positions describe the same thing and cost nothing to extend.
|
|
10
10
|
*
|
|
11
11
|
* `anchor` is where the gesture started and stays put; `focus` is the end that
|
|
12
12
|
* moves. Which of the two is top-left depends on the direction it was dragged,
|
|
13
|
-
* so nothing here assumes an order
|
|
13
|
+
* so nothing here assumes an order - see {@link resolveRangeBounds}.
|
|
14
14
|
*/
|
|
15
15
|
export type TMDataGridCellRange = {
|
|
16
16
|
anchor: TMDataGridCellPosition;
|
|
@@ -37,7 +37,7 @@ export type ResolveRangeBoundsArgs = {
|
|
|
37
37
|
* corner no longer exists.
|
|
38
38
|
*
|
|
39
39
|
* A corner goes missing whenever a filter drops its row or a column is hidden,
|
|
40
|
-
* and the honest answer then is that there is no rectangle
|
|
40
|
+
* and the honest answer then is that there is no rectangle - better than
|
|
41
41
|
* guessing at a replacement corner and quietly copying cells the user never
|
|
42
42
|
* selected. The range itself is left alone: clearing it here would throw away
|
|
43
43
|
* a selection that comes straight back when the filter is lifted.
|
|
@@ -31,14 +31,14 @@ export type TMDataGridOptionsArgs = {
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* Where a column's options come from
|
|
34
|
+
* Where a column's options come from - `meta.options`. One declaration feeds
|
|
35
35
|
* both the filter panel's value control and the cell editor.
|
|
36
36
|
*
|
|
37
37
|
* | Form | For |
|
|
38
38
|
* | --- | --- |
|
|
39
39
|
* | array | a known, fixed set |
|
|
40
40
|
* | `"faceted"` | low-cardinality data columns: the distinct values present, via `getFacetedUniqueValues` |
|
|
41
|
-
* | function | large or contextual sets
|
|
41
|
+
* | function | large or contextual sets - `row` is set when an editor asks, so options can depend on the record (city given country) |
|
|
42
42
|
*/
|
|
43
43
|
export type TMDataGridOptionsSource =
|
|
44
44
|
| ReadonlyArray<TMDataGridOption | string>
|
|
@@ -12,7 +12,7 @@ type GridTable = TMDataGridTable<TMDataGridRowData>;
|
|
|
12
12
|
* Pinning splits the grid into three, and TanStack sequences them from two
|
|
13
13
|
* different state slices: `columnPinning.left` / `.right` order the pinned
|
|
14
14
|
* lanes, `columnOrder` orders the centre. A move therefore always stays inside
|
|
15
|
-
* one lane
|
|
15
|
+
* one lane - moving a column into another one would be a pin, not a reorder.
|
|
16
16
|
*/
|
|
17
17
|
export type TMDataGridColumnRegion = "left" | "center" | "right";
|
|
18
18
|
|
|
@@ -104,7 +104,7 @@ export type ColumnStepArgs = {
|
|
|
104
104
|
* The column a single step would swap with, or `null` when there is none.
|
|
105
105
|
*
|
|
106
106
|
* A neighbour that cannot be reordered acts as a wall rather than being skipped
|
|
107
|
-
*
|
|
107
|
+
* - that is what keeps the checkbox column anchored at the start of the left
|
|
108
108
|
* lane. Use it to enable or disable a "move" control.
|
|
109
109
|
*/
|
|
110
110
|
export function getStepTargetColumn({
|
|
@@ -34,7 +34,7 @@ export function getColumnType(column: ColumnLike): TMDataGridColumnType {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
|
-
* The operator a fresh filter on this column starts with
|
|
37
|
+
* The operator a fresh filter on this column starts with -
|
|
38
38
|
* `meta.defaultFilterOperator`, else the type's default.
|
|
39
39
|
*/
|
|
40
40
|
export function getColumnDefaultOperator(
|
|
@@ -51,13 +51,13 @@ export function getColumnAlign(column: ColumnLike): "left" | "right" | "center"
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
|
-
* A generated lane holding one fixed-width thing
|
|
54
|
+
* A generated lane holding one fixed-width thing - the checkbox, the details
|
|
55
55
|
* chevron, the row number.
|
|
56
56
|
*
|
|
57
57
|
* They are laid out differently from every other column: cell padding is sized
|
|
58
58
|
* for text and grows with the scale, which at `xl` squeezed a 16px checkbox out
|
|
59
59
|
* of its track, so these lanes centre their content in an unpadded cell.
|
|
60
|
-
* Their headers carry no column menu, and none of them is ever exported
|
|
60
|
+
* Their headers carry no column menu, and none of them is ever exported -
|
|
61
61
|
* they hold chrome, not values.
|
|
62
62
|
*
|
|
63
63
|
* The tree column is deliberately not one of them: it holds a label as well as a
|