@jielga/tmdatagrid 2.0.0-beta.9 → 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.
Files changed (154) hide show
  1. package/README.md +5 -212
  2. package/dist/index.d.ts +1281 -768
  3. package/dist/index.js +4607 -3250
  4. package/dist/index.js.map +1 -1
  5. package/dist/styles.css +1 -1
  6. package/docs/adding-rows.md +132 -0
  7. package/docs/anatomy.md +119 -0
  8. package/docs/card-view.md +108 -0
  9. package/docs/cell-selection.md +194 -0
  10. package/docs/column-layout.md +182 -0
  11. package/docs/column-menu.md +66 -0
  12. package/docs/columns.md +268 -0
  13. package/docs/components.md +311 -0
  14. package/docs/draft-store.md +242 -0
  15. package/docs/editing.md +303 -0
  16. package/docs/editors.md +250 -0
  17. package/docs/export.md +319 -0
  18. package/docs/filtering.md +362 -0
  19. package/docs/getting-started.md +123 -0
  20. package/docs/grouping.md +165 -0
  21. package/docs/loading-and-empty.md +92 -0
  22. package/docs/localization.md +79 -0
  23. package/docs/menu.md +143 -0
  24. package/docs/migrating-to-2.md +163 -0
  25. package/docs/pagination.md +144 -0
  26. package/docs/persistence.md +114 -0
  27. package/docs/portfolio-rebalancer.md +94 -0
  28. package/docs/query-builder.md +179 -0
  29. package/docs/quick-search.md +84 -0
  30. package/docs/row-details.md +115 -0
  31. package/docs/row-interaction.md +149 -0
  32. package/docs/row-pinning.md +132 -0
  33. package/docs/row-selection.md +136 -0
  34. package/docs/row-styling.md +133 -0
  35. package/docs/scrolling.md +112 -0
  36. package/docs/server-query.md +246 -0
  37. package/docs/server-side.md +206 -0
  38. package/docs/sorting.md +101 -0
  39. package/docs/styling.md +126 -0
  40. package/docs/summary-row.md +76 -0
  41. package/docs/testing.md +744 -0
  42. package/docs/toolbar.md +161 -0
  43. package/docs/use-tm-data-grid.md +361 -0
  44. package/package.json +22 -46
  45. package/skills/appearance/SKILL.md +72 -19
  46. package/skills/cell-selection/SKILL.md +46 -47
  47. package/skills/columns/SKILL.md +90 -34
  48. package/skills/data/SKILL.md +86 -16
  49. package/skills/editing/SKILL.md +67 -40
  50. package/skills/editing/references/common-mistakes.md +77 -69
  51. package/skills/editing/references/editing-api.md +22 -19
  52. package/skills/editing/references/editors-and-validation.md +24 -17
  53. package/skills/filtering/SKILL.md +148 -40
  54. package/skills/getting-started/SKILL.md +17 -15
  55. package/skills/grouping/SKILL.md +31 -16
  56. package/skills/options/SKILL.md +7 -7
  57. package/skills/rows/SKILL.md +22 -18
  58. package/skills/server-side/SKILL.md +170 -17
  59. package/skills/testing/SKILL.md +150 -32
  60. package/skills/testing-components/SKILL.md +230 -0
  61. package/skills/testing-editing/SKILL.md +240 -0
  62. package/src/{tmdatagrid/components → components}/TMDataGrid.tsx +38 -21
  63. package/src/{tmdatagrid/components → components}/TMDataGridCellEditor.tsx +54 -8
  64. package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.module.css +5 -1
  65. package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.tsx +47 -55
  66. package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.tsx +5 -51
  67. package/src/{tmdatagrid/components → components}/TMDataGridDraftActions.tsx +41 -24
  68. package/src/{tmdatagrid/components → components}/TMDataGridEditColumn.tsx +12 -59
  69. package/src/{tmdatagrid/components → components}/TMDataGridEntryRows.tsx +164 -92
  70. package/src/components/TMDataGridExportPicker.module.css +77 -0
  71. package/src/components/TMDataGridExportPicker.tsx +234 -0
  72. package/src/components/TMDataGridFilterPanel.module.css +54 -0
  73. package/src/components/TMDataGridFilterPanel.tsx +348 -0
  74. package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.tsx +15 -8
  75. package/src/components/TMDataGridFilterSurface.module.css +54 -0
  76. package/src/components/TMDataGridFilterSurface.tsx +167 -0
  77. package/src/{tmdatagrid/components → components}/TMDataGridFooter.tsx +53 -90
  78. package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.tsx +5 -69
  79. package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.module.css +12 -2
  80. package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.tsx +58 -21
  81. package/src/components/TMDataGridHeaderFilterRow.module.css +51 -0
  82. package/src/components/TMDataGridHeaderFilterRow.tsx +301 -0
  83. package/src/components/TMDataGridMenu.tsx +357 -0
  84. package/src/{tmdatagrid/components → components}/TMDataGridSelectColumn.tsx +11 -48
  85. package/src/{tmdatagrid/components → components}/TMDataGridTable.module.css +69 -56
  86. package/src/{tmdatagrid/components → components}/TMDataGridTable.tsx +240 -138
  87. package/src/{tmdatagrid/components → components}/TMDataGridToolbar.module.css +5 -0
  88. package/src/components/TMDataGridToolbar.tsx +181 -0
  89. package/src/{tmdatagrid/components → components}/editors/TMDataGridBooleanEditor.tsx +3 -3
  90. package/src/{tmdatagrid/components → components}/editors/TMDataGridDateEditor.tsx +3 -3
  91. package/src/{tmdatagrid/components → components}/editors/TMDataGridMultiSelectEditor.tsx +3 -3
  92. package/src/{tmdatagrid/components → components}/editors/TMDataGridNumberEditor.tsx +3 -3
  93. package/src/{tmdatagrid/components → components}/editors/TMDataGridSelectEditor.tsx +3 -3
  94. package/src/{tmdatagrid/components → components}/editors/TMDataGridStringEditor.tsx +3 -3
  95. package/src/{tmdatagrid/components → components}/editors/editorShared.ts +16 -2
  96. package/src/{tmdatagrid/components → components}/filters/DgAutocompleteFilter.tsx +5 -4
  97. package/src/{tmdatagrid/components → components}/filters/DgDateRangeFilter.tsx +22 -5
  98. package/src/{tmdatagrid/components → components}/filters/DgRangeSliderFilter.tsx +5 -1
  99. package/src/{tmdatagrid/components → components}/filters/DgTriStateFilter.tsx +5 -1
  100. package/src/{tmdatagrid/components → components}/filters/TMDataGridFilterValueInput.tsx +44 -28
  101. package/src/components/filters/controlLayout.ts +32 -0
  102. package/src/components/filters/filterControlFor.ts +65 -0
  103. package/src/components/generatedColumns.tsx +187 -0
  104. package/src/{tmdatagrid/components → components}/icons.ts +1 -0
  105. package/src/{tmdatagrid/components → components}/sticky.module.css +44 -0
  106. package/src/components/useHideableColumns.ts +52 -0
  107. package/src/{tmdatagrid/core → core}/autosize.ts +5 -2
  108. package/src/{tmdatagrid/core → core}/columnOptions.ts +60 -8
  109. package/src/{tmdatagrid/core → core}/columnOrdering.ts +33 -14
  110. package/src/{tmdatagrid/core → core}/columnUtils.ts +44 -5
  111. package/src/core/controlledStateSync.ts +108 -0
  112. package/src/core/deletedRows.ts +34 -0
  113. package/src/core/dom.ts +74 -0
  114. package/src/{tmdatagrid/core → core}/editEngine.ts +1107 -460
  115. package/src/core/export.ts +704 -0
  116. package/src/{tmdatagrid/core → core}/filterControls.ts +38 -1
  117. package/src/{tmdatagrid/core → core}/filterOperators.ts +64 -1
  118. package/src/core/filterSurface.ts +99 -0
  119. package/src/{tmdatagrid/core → core}/grouping.ts +21 -0
  120. package/src/{tmdatagrid/core → core}/labels.ts +51 -6
  121. package/src/{tmdatagrid/core → core}/labelsSv.ts +27 -6
  122. package/src/core/pageReset.ts +120 -0
  123. package/src/core/pagination.ts +81 -0
  124. package/src/{tmdatagrid/core → core}/persistence.ts +22 -5
  125. package/src/{tmdatagrid/core → core}/summary.ts +20 -4
  126. package/src/{tmdatagrid/index.ts → index.ts} +69 -35
  127. package/src/{tmdatagrid/useTMDataGrid.tsx → useTMDataGrid.tsx} +428 -109
  128. package/src/useTMDataGridExport.ts +78 -0
  129. package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +0 -35
  130. package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +0 -354
  131. package/src/tmdatagrid/components/TMDataGridToolbar.tsx +0 -161
  132. package/src/tmdatagrid/core/cellExport.ts +0 -320
  133. /package/src/{tmdatagrid/TMDataGridContext.ts → TMDataGridContext.ts} +0 -0
  134. /package/src/{tmdatagrid/components → components}/TMDataGrid.module.css +0 -0
  135. /package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.module.css +0 -0
  136. /package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.module.css +0 -0
  137. /package/src/{tmdatagrid/components → components}/TMDataGridFooter.module.css +0 -0
  138. /package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.module.css +0 -0
  139. /package/src/{tmdatagrid/components → components}/TMDataGridRowNumberColumn.tsx +0 -0
  140. /package/src/{tmdatagrid/components → components}/TMDataGridSearch.tsx +0 -0
  141. /package/src/{tmdatagrid/core → core}/capabilities.ts +0 -0
  142. /package/src/{tmdatagrid/core → core}/cellNavigation.ts +0 -0
  143. /package/src/{tmdatagrid/core → core}/cellRange.ts +0 -0
  144. /package/src/{tmdatagrid/core → core}/controlledState.ts +0 -0
  145. /package/src/{tmdatagrid/core → core}/draftCellContext.ts +0 -0
  146. /package/src/{tmdatagrid/core → core}/editorFocus.ts +0 -0
  147. /package/src/{tmdatagrid/core → core}/expanding.ts +0 -0
  148. /package/src/{tmdatagrid/core → core}/matchHighlight.ts +0 -0
  149. /package/src/{tmdatagrid/core → core}/quickSearch.ts +0 -0
  150. /package/src/{tmdatagrid/core → core}/resizePreview.ts +0 -0
  151. /package/src/{tmdatagrid/core → core}/rowPinning.ts +0 -0
  152. /package/src/{tmdatagrid/core → core}/rowSelection.ts +0 -0
  153. /package/src/{tmdatagrid/core → core}/sizes.ts +0 -0
  154. /package/src/{tmdatagrid/core → core}/useSettledTableState.ts +0 -0
@@ -0,0 +1,77 @@
1
+ .title {
2
+ font-weight: 500;
3
+ }
4
+
5
+ .list {
6
+ display: flex;
7
+ flex-direction: column;
8
+ border: 1px solid var(--mantine-color-default-border);
9
+ border-radius: var(--mantine-radius-default);
10
+ overflow: hidden;
11
+ }
12
+
13
+ .listHeader {
14
+ display: flex;
15
+ align-items: center;
16
+ justify-content: space-between;
17
+ gap: var(--mantine-spacing-sm);
18
+ padding: var(--mantine-spacing-xs) var(--mantine-spacing-sm);
19
+ background-color: var(--mantine-color-default-hover);
20
+ border-bottom: 1px solid var(--mantine-color-default-border);
21
+ }
22
+
23
+ .headerLabel {
24
+ font-weight: 500;
25
+ cursor: pointer;
26
+ }
27
+
28
+ .rows {
29
+ display: flex;
30
+ flex-direction: column;
31
+ padding: calc(var(--mantine-spacing-xs) / 2) 0;
32
+ }
33
+
34
+ /* Each column is one full-width row: the label stretches over the free space
35
+ so the whole row toggles the box, and the row highlights on hover like a
36
+ menu item. */
37
+ .row {
38
+ padding: 0 var(--mantine-spacing-sm);
39
+ }
40
+
41
+ .row:hover {
42
+ background-color: var(--mantine-color-default-hover);
43
+ }
44
+
45
+ .rowBody {
46
+ align-items: center;
47
+ }
48
+
49
+ .rowLabelWrapper {
50
+ flex: 1;
51
+ min-width: 0;
52
+ }
53
+
54
+ .rowLabel {
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: space-between;
58
+ gap: var(--mantine-spacing-sm);
59
+ padding-block: calc(var(--mantine-spacing-xs) * 0.75);
60
+ cursor: pointer;
61
+ }
62
+
63
+ .rowText {
64
+ overflow: hidden;
65
+ text-overflow: ellipsis;
66
+ white-space: nowrap;
67
+ }
68
+
69
+ .rowHint {
70
+ flex: none;
71
+ font-size: var(--mantine-font-size-xs);
72
+ color: var(--mantine-color-dimmed);
73
+ }
74
+
75
+ .empty {
76
+ padding: var(--mantine-spacing-xs) var(--mantine-spacing-sm);
77
+ }
@@ -0,0 +1,234 @@
1
+ import {
2
+ Button,
3
+ Checkbox,
4
+ Group,
5
+ type MantineSize,
6
+ Modal,
7
+ ScrollArea,
8
+ Stack,
9
+ Text,
10
+ TextInput,
11
+ } from "@mantine/core";
12
+ import { useSelector } from "@tanstack/react-store";
13
+ import { useState } from "react";
14
+ import classes from "./TMDataGridExportPicker.module.css";
15
+ import { useTMDataGridContext } from "../TMDataGridContext";
16
+ import { getColumnLabel, showColumnSearch } from "../core/columnUtils";
17
+ import {
18
+ countSelectedExportRows,
19
+ getExportableColumns,
20
+ resolveExportOptions,
21
+ type TMDataGridExportPickerRequest,
22
+ } from "../core/export";
23
+ import type { TMDataGridSize } from "../core/sizes";
24
+ import { useTMDataGridExport } from "../useTMDataGridExport";
25
+ import { SearchIcon } from "./icons";
26
+
27
+ /**
28
+ * The column picker behind `columns="custom"` on the export menu items: a
29
+ * list of every exportable column, the visible ones ticked and the hidden
30
+ * ones marked, a select-all row over the list, a search box once the list is
31
+ * long, and an Export button that downloads the ticked ones.
32
+ *
33
+ * Mounted once by the root and driven by `ui.state.exportPicker`, because the
34
+ * menu item that opens it unmounts with the dropdown on the click. The form
35
+ * inside is keyed on the request, so each opening starts from the visible
36
+ * columns again rather than from the last choice.
37
+ */
38
+ export function TMDataGridExportPicker() {
39
+ const { ui, labels, size, controlSize, exportOptions } =
40
+ useTMDataGridContext();
41
+ const request = useSelector(ui, (state) => state.exportPicker);
42
+ const format = request
43
+ ? resolveExportOptions(exportOptions, request.options).format
44
+ : null;
45
+
46
+ return (
47
+ <Modal
48
+ opened={request !== null}
49
+ onClose={() => ui.actions.closeExportPicker()}
50
+ title={
51
+ format && labels.exportPickerTitle(format.extension.toUpperCase())
52
+ }
53
+ size={MODAL_WIDTH[size]}
54
+ closeButtonProps={{ size: CLOSE_BUTTON_SIZE[controlSize] }}
55
+ classNames={{ title: classes.title }}
56
+ styles={{ title: { fontSize: `var(--mantine-font-size-${controlSize})` } }}
57
+ >
58
+ {request && <PickerForm request={request} />}
59
+ </Modal>
60
+ );
61
+ }
62
+
63
+ /** Dialog width per grid size: the same three steps as the controls. */
64
+ const MODAL_WIDTH: Record<TMDataGridSize, MantineSize> = {
65
+ xs: "xs",
66
+ sm: "xs",
67
+ md: "sm",
68
+ lg: "sm",
69
+ xl: "md",
70
+ };
71
+
72
+ /** The close button one step above the controls, which is Mantine's own ratio. */
73
+ const CLOSE_BUTTON_SIZE: Record<string, MantineSize> = {
74
+ xs: "sm",
75
+ sm: "md",
76
+ md: "lg",
77
+ };
78
+
79
+ function PickerForm({ request }: { request: TMDataGridExportPickerRequest }) {
80
+ const { table, ui, labels, controlSize } = useTMDataGridContext();
81
+ const { exportAll, exportSelected } = useTMDataGridExport(request.options);
82
+ const columns = getExportableColumns(table);
83
+ const [search, setSearch] = useState("");
84
+ const [checked, setChecked] = useState<ReadonlySet<string>>(
85
+ () =>
86
+ new Set(
87
+ columns
88
+ .filter((column) => column.getIsVisible())
89
+ .map((column) => column.id),
90
+ ),
91
+ );
92
+
93
+ const searchable = showColumnSearch("auto", columns.length);
94
+ const needle = search.trim().toLowerCase();
95
+ const listed = needle
96
+ ? columns.filter((column) =>
97
+ getColumnLabel(column).toLowerCase().includes(needle),
98
+ )
99
+ : columns;
100
+
101
+ // Select all works on the listed columns: with a search active it ticks the
102
+ // matches and leaves the rest as they are, the way a filtered list reads.
103
+ const listedChecked = listed.filter((column) => checked.has(column.id));
104
+ const allListed = listed.length > 0 && listedChecked.length === listed.length;
105
+ const someListed = listedChecked.length > 0 && !allListed;
106
+
107
+ const toggle = (columnId: string, on: boolean) =>
108
+ setChecked((previous) => {
109
+ const next = new Set(previous);
110
+ if (on) next.add(columnId);
111
+ else next.delete(columnId);
112
+ return next;
113
+ });
114
+
115
+ const toggleListed = (on: boolean) =>
116
+ setChecked((previous) => {
117
+ const next = new Set(previous);
118
+ for (const column of listed) {
119
+ if (on) next.add(column.id);
120
+ else next.delete(column.id);
121
+ }
122
+ return next;
123
+ });
124
+
125
+ const confirm = async () => {
126
+ // Render order, whatever order the boxes were ticked in.
127
+ const ids = columns
128
+ .filter((column) => checked.has(column.id))
129
+ .map((column) => column.id);
130
+ const run = request.rows === "selected" ? exportSelected : exportAll;
131
+ await run({ columns: ids });
132
+ ui.actions.closeExportPicker();
133
+ };
134
+
135
+ const hint = labels.exportPickerHint(
136
+ request.rows === "selected" ? countSelectedExportRows(table) : null,
137
+ );
138
+
139
+ return (
140
+ <Stack gap="sm" data-dg-part="export-picker">
141
+ <Text size={controlSize} c="dimmed" data-dg-part="export-picker-hint">
142
+ {hint}
143
+ </Text>
144
+
145
+ {searchable && (
146
+ <TextInput
147
+ value={search}
148
+ onChange={(event) => setSearch(event.currentTarget.value)}
149
+ placeholder={labels.columnsSearchPlaceholder}
150
+ leftSection={<SearchIcon size={16} stroke={1.6} />}
151
+ size={controlSize}
152
+ data-dg-part="export-picker-search"
153
+ data-autofocus
154
+ />
155
+ )}
156
+
157
+ <div className={classes.list}>
158
+ <div className={classes.listHeader}>
159
+ <Checkbox
160
+ size={controlSize}
161
+ label={labels.exportPickerSelectAll}
162
+ checked={allListed}
163
+ indeterminate={someListed}
164
+ onChange={(event) => toggleListed(event.currentTarget.checked)}
165
+ classNames={{ label: classes.headerLabel }}
166
+ data-dg-part="export-column-all"
167
+ />
168
+ <Text size="xs" c="dimmed" data-dg-part="export-picker-count">
169
+ {labels.exportPickerCount(checked.size, columns.length)}
170
+ </Text>
171
+ </div>
172
+
173
+ <ScrollArea.Autosize mah="50vh" type="auto">
174
+ <div className={classes.rows}>
175
+ {listed.map((column) => (
176
+ <Checkbox
177
+ key={column.id}
178
+ size={controlSize}
179
+ label={
180
+ <>
181
+ <span className={classes.rowText}>
182
+ {getColumnLabel(column)}
183
+ </span>
184
+ {!column.getIsVisible() && (
185
+ <span className={classes.rowHint}>
186
+ {labels.exportPickerHidden}
187
+ </span>
188
+ )}
189
+ </>
190
+ }
191
+ checked={checked.has(column.id)}
192
+ onChange={(event) =>
193
+ toggle(column.id, event.currentTarget.checked)
194
+ }
195
+ classNames={{
196
+ root: classes.row,
197
+ body: classes.rowBody,
198
+ labelWrapper: classes.rowLabelWrapper,
199
+ label: classes.rowLabel,
200
+ }}
201
+ data-dg-part="export-column"
202
+ data-column-id={column.id}
203
+ />
204
+ ))}
205
+ {listed.length === 0 && (
206
+ <Text size={controlSize} c="dimmed" className={classes.empty}>
207
+ {labels.columnsNoMatch(search)}
208
+ </Text>
209
+ )}
210
+ </div>
211
+ </ScrollArea.Autosize>
212
+ </div>
213
+
214
+ <Group justify="flex-end" gap="xs">
215
+ <Button
216
+ variant="default"
217
+ size={controlSize}
218
+ onClick={() => ui.actions.closeExportPicker()}
219
+ data-dg-part="export-picker-cancel"
220
+ >
221
+ {labels.exportPickerCancel}
222
+ </Button>
223
+ <Button
224
+ size={controlSize}
225
+ disabled={checked.size === 0}
226
+ onClick={() => void confirm()}
227
+ data-dg-part="export-picker-confirm"
228
+ >
229
+ {labels.exportPickerConfirm}
230
+ </Button>
231
+ </Group>
232
+ </Stack>
233
+ );
234
+ }
@@ -0,0 +1,54 @@
1
+ /* A plain block of controls. Nothing here positions it - the popup and the
2
+ sidebar own their own placement, and a consumer placing the panel by hand
3
+ places it the way any other block. */
4
+ .filterPanel {
5
+ display: flex;
6
+ flex-direction: column;
7
+ gap: var(--mantine-spacing-xs);
8
+ }
9
+
10
+ /* Column, operator and value side by side, bottom-aligned so their fields line
11
+ up under labels of differing heights. */
12
+ .filterRow {
13
+ display: flex;
14
+ align-items: flex-end;
15
+ gap: var(--mantine-spacing-xs);
16
+ }
17
+
18
+ /* The narrow-host layout: the same three fields one under the other, each
19
+ filling the width, with the ✕ beside the column it removes. A card rather
20
+ than a bare stack, because without the side-by-side alignment there is
21
+ nothing else saying where one filter ends and the next begins. */
22
+ .filterRow[data-layout="stacked"] {
23
+ display: grid;
24
+ grid-template-columns: 1fr auto;
25
+ align-items: end;
26
+ gap: 6px var(--mantine-spacing-xs);
27
+ padding: var(--mantine-spacing-xs);
28
+ border: 1px solid var(--mantine-color-default-border);
29
+ border-radius: var(--mantine-radius-sm);
30
+ }
31
+
32
+ .filterRow[data-layout="stacked"] .filterRemove {
33
+ grid-column: 2;
34
+ grid-row: 1;
35
+ }
36
+
37
+ .filterRow[data-layout="stacked"] .filterColumnSlot {
38
+ grid-column: 1;
39
+ grid-row: 1;
40
+ min-width: 0;
41
+ }
42
+
43
+ .filterRow[data-layout="stacked"] .filterOperatorSlot,
44
+ .filterRow[data-layout="stacked"] .filterValueSlot {
45
+ grid-column: 1 / -1;
46
+ min-width: 0;
47
+ }
48
+
49
+ .filterPanelFooter {
50
+ display: flex;
51
+ align-items: center;
52
+ justify-content: space-between;
53
+ gap: var(--mantine-spacing-md);
54
+ }