@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.
- package/README.md +5 -212
- package/dist/index.d.ts +1281 -768
- package/dist/index.js +4607 -3250
- 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 +46 -47
- package/skills/columns/SKILL.md +90 -34
- package/skills/data/SKILL.md +86 -16
- package/skills/editing/SKILL.md +67 -40
- package/skills/editing/references/common-mistakes.md +77 -69
- package/skills/editing/references/editing-api.md +22 -19
- 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 +7 -7
- 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/components → components}/TMDataGrid.tsx +38 -21
- package/src/{tmdatagrid/components → components}/TMDataGridCellEditor.tsx +54 -8
- 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 +5 -51
- package/src/{tmdatagrid/components → components}/TMDataGridDraftActions.tsx +41 -24
- package/src/{tmdatagrid/components → components}/TMDataGridEditColumn.tsx +12 -59
- package/src/{tmdatagrid/components → components}/TMDataGridEntryRows.tsx +164 -92
- 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 +5 -69
- 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 +11 -48
- package/src/{tmdatagrid/components → components}/TMDataGridTable.module.css +69 -56
- package/src/{tmdatagrid/components → components}/TMDataGridTable.tsx +240 -138
- 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}/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 +1107 -460
- 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} +69 -35
- package/src/{tmdatagrid/useTMDataGrid.tsx → useTMDataGrid.tsx} +428 -109
- package/src/useTMDataGridExport.ts +78 -0
- 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/TMDataGridContext.ts → TMDataGridContext.ts} +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGrid.module.css +0 -0
- /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}/capabilities.ts +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}/editorFocus.ts +0 -0
- /package/src/{tmdatagrid/core → core}/expanding.ts +0 -0
- /package/src/{tmdatagrid/core → core}/matchHighlight.ts +0 -0
- /package/src/{tmdatagrid/core → core}/quickSearch.ts +0 -0
- /package/src/{tmdatagrid/core → core}/resizePreview.ts +0 -0
- /package/src/{tmdatagrid/core → core}/rowPinning.ts +0 -0
- /package/src/{tmdatagrid/core → core}/rowSelection.ts +0 -0
- /package/src/{tmdatagrid/core → core}/sizes.ts +0 -0
- /package/src/{tmdatagrid/core → core}/useSettledTableState.ts +0 -0
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
import type { Column, Row, RowData } from "@tanstack/react-table";
|
|
2
|
-
import type { TMDataGridRowData } from "../TMDataGridContext";
|
|
3
|
-
import type { TMDataGridFeatures, TMDataGridTable } from "../useTMDataGrid";
|
|
4
|
-
import type { TMDataGridRangeBounds } from "./cellRange";
|
|
5
|
-
import { getColumnLabel, isControlColumn } from "./columnUtils";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* The byte order mark Excel looks for before it will read a file as UTF-8.
|
|
9
|
-
* Built from its code point rather than pasted in, because a literal BOM is
|
|
10
|
-
* invisible in a source file and easily deleted by accident.
|
|
11
|
-
*/
|
|
12
|
-
const UTF8_BOM = String.fromCharCode(0xfeff);
|
|
13
|
-
|
|
14
|
-
/** The selected block, flattened to text. One inner array per row. */
|
|
15
|
-
export type TMDataGridCellMatrix = Array<Array<string>>;
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* How values are written out.
|
|
19
|
-
*
|
|
20
|
-
* The defaults are the Nordic ones, because they are the ones that need
|
|
21
|
-
* choosing: an Excel running a Swedish, Norwegian, Danish or Finnish locale
|
|
22
|
-
* reads `;` as its list separator and `,` as its decimal mark, and a file
|
|
23
|
-
* written the other way opens as one column of text. Both are settable for a
|
|
24
|
-
* grid whose users run something else.
|
|
25
|
-
*/
|
|
26
|
-
export type TMDataGridCellExportOptions = {
|
|
27
|
-
/** CSV field separator. Defaults to `";"`. */
|
|
28
|
-
separator?: string;
|
|
29
|
-
/** Write numbers as `1,5` rather than `1.5`. Defaults to `true`. */
|
|
30
|
-
decimalComma?: boolean;
|
|
31
|
-
/** Column labels as the first row. Defaults to `true`. */
|
|
32
|
-
includeHeaders?: boolean;
|
|
33
|
-
/** Without extension. Defaults to `"export"`. */
|
|
34
|
-
fileName?: string;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
type ResolvedExportOptions = Required<TMDataGridCellExportOptions>;
|
|
38
|
-
|
|
39
|
-
export const DEFAULT_CELL_EXPORT_OPTIONS: ResolvedExportOptions = {
|
|
40
|
-
separator: ";",
|
|
41
|
-
decimalComma: true,
|
|
42
|
-
includeHeaders: true,
|
|
43
|
-
fileName: "export",
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* One value as text.
|
|
48
|
-
*
|
|
49
|
-
* Deliberately not the rendered cell: what a cell renders is React, and often a
|
|
50
|
-
* badge, a link or an icon rather than the value. The value is what a
|
|
51
|
-
* spreadsheet wants, and it is the one thing every column is guaranteed to
|
|
52
|
-
* have.
|
|
53
|
-
*/
|
|
54
|
-
export function formatExportValue(
|
|
55
|
-
value: unknown,
|
|
56
|
-
{ decimalComma }: { decimalComma: boolean },
|
|
57
|
-
): string {
|
|
58
|
-
if (value === null || value === undefined) return "";
|
|
59
|
-
if (typeof value === "number") {
|
|
60
|
-
if (!Number.isFinite(value)) return "";
|
|
61
|
-
const text = String(value);
|
|
62
|
-
return decimalComma ? text.replace(".", ",") : text;
|
|
63
|
-
}
|
|
64
|
-
if (typeof value === "string") return value;
|
|
65
|
-
if (typeof value === "boolean") return value ? "true" : "false";
|
|
66
|
-
// A multiSelect cell holds an array of values; a spreadsheet cell holds one
|
|
67
|
-
// string, so the elements are joined the way they read on screen.
|
|
68
|
-
if (Array.isArray(value)) {
|
|
69
|
-
return value
|
|
70
|
-
.map((entry) => formatExportValue(entry, { decimalComma }))
|
|
71
|
-
.join(", ");
|
|
72
|
-
}
|
|
73
|
-
// `sv-SE` is ISO-shaped (2026-07-31, 2026-07-31 14:05:00), which is both the
|
|
74
|
-
// Nordic form and the one Excel parses as a date rather than as text.
|
|
75
|
-
if (value instanceof Date) {
|
|
76
|
-
return Number.isNaN(value.getTime()) ? "" : value.toLocaleString("sv-SE");
|
|
77
|
-
}
|
|
78
|
-
// An object in a cell is a shape the grid cannot know. JSON at least keeps
|
|
79
|
-
// what was there, where `String(value)` would write "[object Object]".
|
|
80
|
-
if (typeof value === "object") {
|
|
81
|
-
try {
|
|
82
|
-
return JSON.stringify(value);
|
|
83
|
-
} catch {
|
|
84
|
-
return "";
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return String(value);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export type BuildCellMatrixArgs = {
|
|
91
|
-
rows: ReadonlyArray<Row<TMDataGridFeatures, TMDataGridRowData>>;
|
|
92
|
-
/** Every visible column, in render order - the same list the bounds index into. */
|
|
93
|
-
columns: ReadonlyArray<Column<TMDataGridFeatures, TMDataGridRowData, unknown>>;
|
|
94
|
-
bounds: TMDataGridRangeBounds;
|
|
95
|
-
includeHeaders: boolean;
|
|
96
|
-
decimalComma: boolean;
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* The selected rectangle as rows of text.
|
|
101
|
-
*
|
|
102
|
-
* The generated lanes - the checkbox, the tree chevron, the details chevron -
|
|
103
|
-
* are dropped even when the rectangle covers them. They hold controls rather
|
|
104
|
-
* than data, so a column of empty strings is all they could contribute, and
|
|
105
|
-
* pasting one into a spreadsheet only shifts everything to its right.
|
|
106
|
-
*/
|
|
107
|
-
export function buildCellMatrix({
|
|
108
|
-
rows,
|
|
109
|
-
columns,
|
|
110
|
-
bounds,
|
|
111
|
-
includeHeaders,
|
|
112
|
-
decimalComma,
|
|
113
|
-
}: BuildCellMatrixArgs): TMDataGridCellMatrix {
|
|
114
|
-
const selectedColumns = columns
|
|
115
|
-
.slice(bounds.left, bounds.right + 1)
|
|
116
|
-
.filter((column) => !isControlColumn(column.id));
|
|
117
|
-
if (selectedColumns.length === 0) return [];
|
|
118
|
-
|
|
119
|
-
const matrix: TMDataGridCellMatrix = [];
|
|
120
|
-
if (includeHeaders) {
|
|
121
|
-
matrix.push(selectedColumns.map((column) => getColumnLabel(column)));
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
for (let index = bounds.top; index <= bounds.bottom; index += 1) {
|
|
125
|
-
const row = rows[index];
|
|
126
|
-
if (row === undefined) continue;
|
|
127
|
-
matrix.push(
|
|
128
|
-
selectedColumns.map((column) =>
|
|
129
|
-
formatExportValue(row.getValue(column.id), { decimalComma }),
|
|
130
|
-
),
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
return matrix;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* The whole grid as rows of text: every filtered and sorted data row - all
|
|
138
|
-
* pages, not the one on screen - by every visible non-control column, in
|
|
139
|
-
* render order.
|
|
140
|
-
*
|
|
141
|
-
* The values are the same ones a cell-range export writes; only the bounds
|
|
142
|
-
* differ. Group rows are left out (their records are the rows themselves),
|
|
143
|
-
* and hidden columns are not exported - what you see is what you get, minus
|
|
144
|
-
* paging.
|
|
145
|
-
*/
|
|
146
|
-
export function buildGridCellMatrix<TData extends RowData>({
|
|
147
|
-
table,
|
|
148
|
-
includeHeaders = DEFAULT_CELL_EXPORT_OPTIONS.includeHeaders,
|
|
149
|
-
decimalComma = DEFAULT_CELL_EXPORT_OPTIONS.decimalComma,
|
|
150
|
-
}: {
|
|
151
|
-
table: TMDataGridTable<TData>;
|
|
152
|
-
includeHeaders?: boolean;
|
|
153
|
-
decimalComma?: boolean;
|
|
154
|
-
}): TMDataGridCellMatrix {
|
|
155
|
-
// The same erasure the context provider performs: the matrix only ever reads
|
|
156
|
-
// generic row and column APIs.
|
|
157
|
-
const erased = table as unknown as TMDataGridTable<TMDataGridRowData>;
|
|
158
|
-
const columns = [
|
|
159
|
-
...erased.getLeftVisibleLeafColumns(),
|
|
160
|
-
...erased.getCenterVisibleLeafColumns(),
|
|
161
|
-
...erased.getRightVisibleLeafColumns(),
|
|
162
|
-
];
|
|
163
|
-
// The sorted model: after filtering, grouping and sorting, before expansion
|
|
164
|
-
// and paging - so a paged grid exports the whole filtered set, and a grouped
|
|
165
|
-
// grid exports the records under every group whether or not it is open.
|
|
166
|
-
//
|
|
167
|
-
// Walked by hand rather than taken from `flatRows`: in table-core
|
|
168
|
-
// 9.0.0-beta.21 the grouped model lists every leaf twice there (once as a
|
|
169
|
-
// leaf row, once re-parented under its group), and its order puts the
|
|
170
|
-
// groups last. Depth-first over `rows` is exactly render order, once each.
|
|
171
|
-
const rows: Array<Row<TMDataGridFeatures, TMDataGridRowData>> = [];
|
|
172
|
-
const walk = (
|
|
173
|
-
list: ReadonlyArray<Row<TMDataGridFeatures, TMDataGridRowData>>,
|
|
174
|
-
) => {
|
|
175
|
-
for (const row of list) {
|
|
176
|
-
if (row.getIsGrouped()) walk(row.subRows);
|
|
177
|
-
else rows.push(row);
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
walk(erased.getSortedRowModel().rows);
|
|
181
|
-
|
|
182
|
-
return buildCellMatrix({
|
|
183
|
-
rows,
|
|
184
|
-
columns,
|
|
185
|
-
bounds: {
|
|
186
|
-
top: 0,
|
|
187
|
-
bottom: rows.length - 1,
|
|
188
|
-
left: 0,
|
|
189
|
-
right: columns.length - 1,
|
|
190
|
-
},
|
|
191
|
-
includeHeaders,
|
|
192
|
-
decimalComma,
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Downloads the whole grid as a CSV for Excel - {@link buildGridCellMatrix}
|
|
198
|
-
* through {@link toExcelCsv}, with the same Nordic defaults and overrides as
|
|
199
|
-
* the cell-range export.
|
|
200
|
-
*
|
|
201
|
-
* No built-in button: wire it to your own toolbar.
|
|
202
|
-
*
|
|
203
|
-
* ```tsx
|
|
204
|
-
* <Button onClick={() => exportGridToCsv({ table: grid.table })}>Export</Button>
|
|
205
|
-
* ```
|
|
206
|
-
*/
|
|
207
|
-
export function exportGridToCsv<TData extends RowData>({
|
|
208
|
-
table,
|
|
209
|
-
options,
|
|
210
|
-
}: {
|
|
211
|
-
table: TMDataGridTable<TData>;
|
|
212
|
-
options?: TMDataGridCellExportOptions;
|
|
213
|
-
}): void {
|
|
214
|
-
const resolved = { ...DEFAULT_CELL_EXPORT_OPTIONS, ...options };
|
|
215
|
-
const matrix = buildGridCellMatrix({
|
|
216
|
-
table,
|
|
217
|
-
includeHeaders: resolved.includeHeaders,
|
|
218
|
-
decimalComma: resolved.decimalComma,
|
|
219
|
-
});
|
|
220
|
-
if (matrix.length === 0) return;
|
|
221
|
-
downloadTextFile({
|
|
222
|
-
fileName: `${resolved.fileName}.csv`,
|
|
223
|
-
text: toExcelCsv(matrix, { separator: resolved.separator }),
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Quotes a field when it holds something that would otherwise end it early.
|
|
229
|
-
* Doubling the quote is how both CSV and Excel's own clipboard format escape
|
|
230
|
-
* one, so the same rule serves both.
|
|
231
|
-
*/
|
|
232
|
-
function escapeField(value: string, separator: string): string {
|
|
233
|
-
const needsQuotes =
|
|
234
|
-
value.includes(separator) ||
|
|
235
|
-
value.includes('"') ||
|
|
236
|
-
value.includes("\n") ||
|
|
237
|
-
value.includes("\r");
|
|
238
|
-
return needsQuotes ? `"${value.replaceAll('"', '""')}"` : value;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* The clipboard format spreadsheets read: tab between cells, CRLF between rows.
|
|
243
|
-
*
|
|
244
|
-
* Tabs rather than commas because that is what Excel, Sheets and Numbers all
|
|
245
|
-
* put on the clipboard themselves - paste it and the cells land in cells. A
|
|
246
|
-
* comma-separated string pastes into a single column, which is the thing this
|
|
247
|
-
* exists to avoid.
|
|
248
|
-
*/
|
|
249
|
-
export function toClipboardText(matrix: TMDataGridCellMatrix): string {
|
|
250
|
-
return matrix
|
|
251
|
-
.map((row) => row.map((value) => escapeField(value, "\t")).join("\t"))
|
|
252
|
-
.join("\r\n");
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* A CSV that opens straight into columns in Excel.
|
|
257
|
-
*
|
|
258
|
-
* Three things make that true, and all three are needed:
|
|
259
|
-
*
|
|
260
|
-
* | Part | Why |
|
|
261
|
-
* | ---- | --- |
|
|
262
|
-
* | `sep=;` first line | Excel's own directive - it stops guessing and uses this |
|
|
263
|
-
* | UTF-8 BOM | without it Excel reads the file as ANSI, and å ä ö arrive broken |
|
|
264
|
-
* | CRLF line endings | what Excel writes, and what its importer is happiest with |
|
|
265
|
-
*
|
|
266
|
-
* The `sep=` line is Excel's alone; other readers show it as a first row. This
|
|
267
|
-
* export targets Excel, so opening correctly there takes priority over strict
|
|
268
|
-
* CSV.
|
|
269
|
-
*/
|
|
270
|
-
export function toExcelCsv(
|
|
271
|
-
matrix: TMDataGridCellMatrix,
|
|
272
|
-
{ separator }: { separator: string },
|
|
273
|
-
): string {
|
|
274
|
-
const body = matrix
|
|
275
|
-
.map((row) => row.map((value) => escapeField(value, separator)).join(separator))
|
|
276
|
-
.join("\r\n");
|
|
277
|
-
return `${UTF8_BOM}sep=${separator}\r\n${body}\r\n`;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Puts text on the clipboard, reporting whether it landed.
|
|
282
|
-
*
|
|
283
|
-
* The async clipboard API only resolves for a document that has the focus and a
|
|
284
|
-
* user gesture behind it - both true when this runs off Ctrl+C or a menu item.
|
|
285
|
-
* It is still allowed to reject (a permissions policy, a page that lost focus
|
|
286
|
-
* mid-copy), so the result is a boolean the caller can act on.
|
|
287
|
-
*/
|
|
288
|
-
export async function writeClipboardText(text: string): Promise<boolean> {
|
|
289
|
-
try {
|
|
290
|
-
await navigator.clipboard.writeText(text);
|
|
291
|
-
return true;
|
|
292
|
-
} catch {
|
|
293
|
-
return false;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Downloads text as a file, through the one mechanism a library can use: an
|
|
299
|
-
* anchor with an object URL behind it, clicked. Revoked on the next frame -
|
|
300
|
-
* immediately would race the browser's own read of it.
|
|
301
|
-
*/
|
|
302
|
-
export function downloadTextFile({
|
|
303
|
-
fileName,
|
|
304
|
-
text,
|
|
305
|
-
mimeType = "text/csv;charset=utf-8",
|
|
306
|
-
}: {
|
|
307
|
-
fileName: string;
|
|
308
|
-
text: string;
|
|
309
|
-
mimeType?: string;
|
|
310
|
-
}): void {
|
|
311
|
-
const url = URL.createObjectURL(new Blob([text], { type: mimeType }));
|
|
312
|
-
const anchor = document.createElement("a");
|
|
313
|
-
anchor.href = url;
|
|
314
|
-
anchor.download = fileName;
|
|
315
|
-
anchor.style.display = "none";
|
|
316
|
-
document.body.append(anchor);
|
|
317
|
-
anchor.click();
|
|
318
|
-
anchor.remove();
|
|
319
|
-
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
320
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|