compote-ui 0.84.0 → 0.86.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/dist/components/data-table-v9/toolbar/data-table-export.svelte +146 -146
- package/dist/components/data-table-v9/xlsx/index.d.ts +22 -0
- package/dist/components/data-table-v9/xlsx/index.js +39 -5
- package/dist/components/data-table-v9/xlsx/xlsx-rows.d.ts +31 -0
- package/dist/components/data-table-v9/xlsx/xlsx-rows.js +60 -0
- package/dist/icons/PhDownloadSimple.svelte +18 -18
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +129 -129
- package/skills/data-table/SKILL.md +401 -400
- package/skills/data-table/references/column-options.md +181 -181
- package/skills/data-table/references/export.md +209 -158
|
@@ -1,158 +1,209 @@
|
|
|
1
|
-
# Export
|
|
2
|
-
|
|
3
|
-
`DataTable.Export` downloads the table as a file. It sits in the toolbar like `ColumnVisibility`
|
|
4
|
-
and takes the `table`.
|
|
5
|
-
|
|
6
|
-
```svelte
|
|
7
|
-
<script lang="ts">
|
|
8
|
-
import * as DataTable from 'compote-ui/data-table';
|
|
9
|
-
</script>
|
|
10
|
-
|
|
11
|
-
<DataTable.Toolbar>
|
|
12
|
-
{#snippet right()}
|
|
13
|
-
<DataTable.Export {table} filename="invoices" />
|
|
14
|
-
<DataTable.ColumnVisibility {table} />
|
|
15
|
-
{/snippet}
|
|
16
|
-
</DataTable.Toolbar>
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Props:
|
|
20
|
-
|
|
21
|
-
- `table: DataTableInstance<T>`
|
|
22
|
-
- `exporters?: DataTableExporter[]` — defaults to `[csvExporter]`.
|
|
23
|
-
- `filename?: string` — without extension, defaults to `'export'`.
|
|
24
|
-
- `triggerLabel?: string` — icon-only with a tooltip when omitted.
|
|
25
|
-
|
|
26
|
-
One exporter and no selected rows renders a plain button. More exporters, or any selected row,
|
|
27
|
-
renders a menu. When rows are selected the menu offers **All rows (n)** / **Selected rows (k)**.
|
|
28
|
-
`k` counts the selected rows the current filter keeps, exactly what gets exported.
|
|
29
|
-
|
|
30
|
-
## What gets exported
|
|
31
|
-
|
|
32
|
-
- **Columns:** the visible ones, in the order the table shows them (pinned left, center, pinned
|
|
33
|
-
right). Columns with `enableExport: false` are skipped, and so are `type: 'action'` columns by
|
|
34
|
-
default. A grouped column's header is `Group / Column`.
|
|
35
|
-
- **Rows:** the filtered and sorted rows (`table.getRowModel()`). The table has no pagination,
|
|
36
|
-
so this is every matching row, not one page.
|
|
37
|
-
- **Cells:** the text the table shows (`cell`, else type formatting; `boolean`, `url` and
|
|
38
|
-
`phone` as the table draws them, which ignores `cell`). Empty cells are blank, not `-`. A
|
|
39
|
-
column with `exportValue` and no `cell` formats `exportValue` output by its `type`.
|
|
40
|
-
|
|
41
|
-
## CSV
|
|
42
|
-
|
|
43
|
-
`csvExporter` writes a UTF-8 BOM (so Excel reads č/ć/š/đ/ž correctly), `;` as the separator
|
|
44
|
-
(what Excel expects under most European locales), and CRLF line endings. Fields starting with
|
|
45
|
-
`= + - @`, tab or CR get a `'` prefix so spreadsheets don't run them as formulas. Exempt, so
|
|
46
|
-
negative numbers and phone numbers stay as they are: text made only of digits, spaces and number
|
|
47
|
-
punctuation (`-5`, `+381 60 123`), and number, currency and percent columns without their own
|
|
48
|
-
`cell`.
|
|
49
|
-
|
|
50
|
-
```ts
|
|
51
|
-
import { createCsvExporter } from 'compote-ui/data-table';
|
|
52
|
-
|
|
53
|
-
const commaCsv = createCsvExporter({ id: 'csv-comma', label: 'CSV (comma)', separator: ',' });
|
|
54
|
-
// Also: bom?: boolean (default true)
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
CSV holds the formatted text (`1,234.56 RSD`), so a spreadsheet can't sum it.
|
|
58
|
-
|
|
59
|
-
## Excel (.xlsx)
|
|
60
|
-
|
|
61
|
-
`xlsxExporter` lives in its own subpath and needs the optional peer `write-excel-file`
|
|
62
|
-
(about 19 KB gzip, loaded only when an export runs, so importing the subpath is SSR-safe):
|
|
63
|
-
|
|
64
|
-
```sh
|
|
65
|
-
bun add write-excel-file
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
```svelte
|
|
69
|
-
<script lang="ts">
|
|
70
|
-
import * as DataTable from 'compote-ui/data-table';
|
|
71
|
-
import { xlsxExporter } from 'compote-ui/data-table/xlsx';
|
|
72
|
-
</script>
|
|
73
|
-
|
|
74
|
-
<DataTable.Export {table} filename="invoices" exporters={[DataTable.csvExporter, xlsxExporter]} />
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Cells stay computable in Excel:
|
|
78
|
-
|
|
79
|
-
- `number`, `currency`, `percent` → numbers, also from numeric strings (`"1234.56"`), with an
|
|
80
|
-
Excel format built from the column's Intl options: grouping, the decimals Intl would show,
|
|
81
|
-
`%`, and the currency code (`#,##0.00 "USD"`). Options Excel can't express (`notation`,
|
|
82
|
-
significant digits, `signDisplay`, `currencyDisplay: 'name'`, …) fall back to the text.
|
|
83
|
-
- `date`, `time`, `date-time` → Excel dates, on the day and time the table shows in the column's
|
|
84
|
-
zone (`formatOptions.timeZone`, else local). A `date` column drops the time; a `time` column
|
|
85
|
-
keeps only the time of day, so it sorts by time. Formats default to `dd.mm.yyyy`, `hh:mm` and
|
|
86
|
-
`dd.mm.yyyy hh:mm`.
|
|
87
|
-
- `boolean` → TRUE/FALSE for real `true`/`false`; other values are blank, as the table shows
|
|
88
|
-
`-` for them.
|
|
89
|
-
- Everything else, and any column with its own `cell` but no `exportValue`, is the text the
|
|
90
|
-
table shows. Typed cells can't hold formulas, so there is no `'` prefix.
|
|
91
|
-
|
|
92
|
-
The header row is bold and frozen
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
1
|
+
# Export
|
|
2
|
+
|
|
3
|
+
`DataTable.Export` downloads the table as a file. It sits in the toolbar like `ColumnVisibility`
|
|
4
|
+
and takes the `table`.
|
|
5
|
+
|
|
6
|
+
```svelte
|
|
7
|
+
<script lang="ts">
|
|
8
|
+
import * as DataTable from 'compote-ui/data-table';
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<DataTable.Toolbar>
|
|
12
|
+
{#snippet right()}
|
|
13
|
+
<DataTable.Export {table} filename="invoices" />
|
|
14
|
+
<DataTable.ColumnVisibility {table} />
|
|
15
|
+
{/snippet}
|
|
16
|
+
</DataTable.Toolbar>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Props:
|
|
20
|
+
|
|
21
|
+
- `table: DataTableInstance<T>`
|
|
22
|
+
- `exporters?: DataTableExporter[]` — defaults to `[csvExporter]`.
|
|
23
|
+
- `filename?: string` — without extension, defaults to `'export'`.
|
|
24
|
+
- `triggerLabel?: string` — icon-only with a tooltip when omitted.
|
|
25
|
+
|
|
26
|
+
One exporter and no selected rows renders a plain button. More exporters, or any selected row,
|
|
27
|
+
renders a menu. When rows are selected the menu offers **All rows (n)** / **Selected rows (k)**.
|
|
28
|
+
`k` counts the selected rows the current filter keeps, exactly what gets exported.
|
|
29
|
+
|
|
30
|
+
## What gets exported
|
|
31
|
+
|
|
32
|
+
- **Columns:** the visible ones, in the order the table shows them (pinned left, center, pinned
|
|
33
|
+
right). Columns with `enableExport: false` are skipped, and so are `type: 'action'` columns by
|
|
34
|
+
default. A grouped column's header is `Group / Column`.
|
|
35
|
+
- **Rows:** the filtered and sorted rows (`table.getRowModel()`). The table has no pagination,
|
|
36
|
+
so this is every matching row, not one page.
|
|
37
|
+
- **Cells:** the text the table shows (`cell`, else type formatting; `boolean`, `url` and
|
|
38
|
+
`phone` as the table draws them, which ignores `cell`). Empty cells are blank, not `-`. A
|
|
39
|
+
column with `exportValue` and no `cell` formats `exportValue` output by its `type`.
|
|
40
|
+
|
|
41
|
+
## CSV
|
|
42
|
+
|
|
43
|
+
`csvExporter` writes a UTF-8 BOM (so Excel reads č/ć/š/đ/ž correctly), `;` as the separator
|
|
44
|
+
(what Excel expects under most European locales), and CRLF line endings. Fields starting with
|
|
45
|
+
`= + - @`, tab or CR get a `'` prefix so spreadsheets don't run them as formulas. Exempt, so
|
|
46
|
+
negative numbers and phone numbers stay as they are: text made only of digits, spaces and number
|
|
47
|
+
punctuation (`-5`, `+381 60 123`), and number, currency and percent columns without their own
|
|
48
|
+
`cell`.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { createCsvExporter } from 'compote-ui/data-table';
|
|
52
|
+
|
|
53
|
+
const commaCsv = createCsvExporter({ id: 'csv-comma', label: 'CSV (comma)', separator: ',' });
|
|
54
|
+
// Also: bom?: boolean (default true)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
CSV holds the formatted text (`1,234.56 RSD`), so a spreadsheet can't sum it.
|
|
58
|
+
|
|
59
|
+
## Excel (.xlsx)
|
|
60
|
+
|
|
61
|
+
`xlsxExporter` lives in its own subpath and needs the optional peer `write-excel-file`
|
|
62
|
+
(about 19 KB gzip, loaded only when an export runs, so importing the subpath is SSR-safe):
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
bun add write-excel-file
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```svelte
|
|
69
|
+
<script lang="ts">
|
|
70
|
+
import * as DataTable from 'compote-ui/data-table';
|
|
71
|
+
import { xlsxExporter } from 'compote-ui/data-table/xlsx';
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<DataTable.Export {table} filename="invoices" exporters={[DataTable.csvExporter, xlsxExporter]} />
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Cells stay computable in Excel:
|
|
78
|
+
|
|
79
|
+
- `number`, `currency`, `percent` → numbers, also from numeric strings (`"1234.56"`), with an
|
|
80
|
+
Excel format built from the column's Intl options: grouping, the decimals Intl would show,
|
|
81
|
+
`%`, and the currency code (`#,##0.00 "USD"`). Options Excel can't express (`notation`,
|
|
82
|
+
significant digits, `signDisplay`, `currencyDisplay: 'name'`, …) fall back to the text.
|
|
83
|
+
- `date`, `time`, `date-time` → Excel dates, on the day and time the table shows in the column's
|
|
84
|
+
zone (`formatOptions.timeZone`, else local). A `date` column drops the time; a `time` column
|
|
85
|
+
keeps only the time of day, so it sorts by time. Formats default to `dd.mm.yyyy`, `hh:mm` and
|
|
86
|
+
`dd.mm.yyyy hh:mm`.
|
|
87
|
+
- `boolean` → TRUE/FALSE for real `true`/`false`; other values are blank, as the table shows
|
|
88
|
+
`-` for them.
|
|
89
|
+
- Everything else, and any column with its own `cell` but no `exportValue`, is the text the
|
|
90
|
+
table shows. Typed cells can't hold formulas, so there is no `'` prefix.
|
|
91
|
+
|
|
92
|
+
The header row is bold and frozen. Each column is as wide as its header or its longest cell
|
|
93
|
+
text (longest line for multi-line text) plus 1 character, between 8 and 60
|
|
94
|
+
(`xlsxColumnWidths(data)` computes them).
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { createXlsxExporter } from 'compote-ui/data-table/xlsx';
|
|
98
|
+
|
|
99
|
+
const excel = createXlsxExporter({
|
|
100
|
+
sheet: 'Invoices',
|
|
101
|
+
date: 'yyyy-mm-dd',
|
|
102
|
+
dateTime: 'yyyy-mm-dd hh:mm'
|
|
103
|
+
// also: time, id, label
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`exportDataToXlsxBlob(data, options)` returns the file as a `Blob` (e.g. to upload instead of
|
|
108
|
+
download), and `toXlsxCell` maps one cell.
|
|
109
|
+
|
|
110
|
+
### Plain rows (an export that isn't a table)
|
|
111
|
+
|
|
112
|
+
`exportRowsToXlsxBlob(rows, columns, options)` writes row objects with the same typed cells, bold
|
|
113
|
+
frozen header and content-fitted widths. `columns` are `{ key, header?, type?, formatOptions? }`;
|
|
114
|
+
`options` are the exporter options plus `locale` (default `en-US`), the locale values are
|
|
115
|
+
formatted in: it sets the text column widths are measured on, the text of cells written as text
|
|
116
|
+
(a number format Excel can't express, such as `notation: 'compact'`), and the decimals of a
|
|
117
|
+
number format.
|
|
118
|
+
`downloadBlob` (exported from `compote-ui`) hands the file to the browser. Browser-only: call it
|
|
119
|
+
from an event handler.
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import { downloadBlob } from 'compote-ui';
|
|
123
|
+
import { exportRowsToXlsxBlob } from 'compote-ui/data-table/xlsx';
|
|
124
|
+
|
|
125
|
+
const blob = await exportRowsToXlsxBlob(
|
|
126
|
+
rows, // Record<string, unknown>[]
|
|
127
|
+
[
|
|
128
|
+
{ key: 'sku', header: 'SKU' },
|
|
129
|
+
{
|
|
130
|
+
key: 'qty',
|
|
131
|
+
header: 'Qty',
|
|
132
|
+
type: 'number',
|
|
133
|
+
formatOptions: { useGrouping: false, maximumFractionDigits: 0 }
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
key: 'price',
|
|
137
|
+
header: 'Price',
|
|
138
|
+
type: 'number',
|
|
139
|
+
formatOptions: { minimumFractionDigits: 2, maximumFractionDigits: 2 }
|
|
140
|
+
}
|
|
141
|
+
],
|
|
142
|
+
{ sheet: 'Cart' }
|
|
143
|
+
);
|
|
144
|
+
downloadBlob(blob, 'cart.xlsx');
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
- A column without a `type` (or `text`) is written as text, so a numeric column needs
|
|
148
|
+
`type: 'number'` to stay computable in Excel; a text SKU such as `00123` keeps its zeros.
|
|
149
|
+
- A `number` column with no `formatOptions` shows as many decimals as each value has (up to 3):
|
|
150
|
+
give prices `minimumFractionDigits`/`maximumFractionDigits` for a fixed `#,##0.00`, and
|
|
151
|
+
`{ useGrouping: false, maximumFractionDigits: 0 }` for a plain `0` (quantities, ids);
|
|
152
|
+
`useGrouping: false` alone only drops the thousands separator, so `1.5` would be `0.0`.
|
|
153
|
+
- Values are read like the table reads them: a numeric string in a `number` column is a number,
|
|
154
|
+
a `date` column takes a `Date`, an ISO string or an epoch number, and `NaN` or text that isn't
|
|
155
|
+
a number or date is an empty cell.
|
|
156
|
+
- `null`, `undefined` and `''` are empty cells. `rowsToExportData(rows, columns, locale)` is the
|
|
157
|
+
conversion alone, if you want to pass the result to `exportDataToXlsxBlob` yourself.
|
|
158
|
+
|
|
159
|
+
## Custom exporter
|
|
160
|
+
|
|
161
|
+
An exporter is `{ id, label, icon?, export(context) }`. `context` holds `table`, `scope`
|
|
162
|
+
(`'filtered' | 'selected'`), `filename` and `getData()`, which returns `{ columns, rows }`. Each
|
|
163
|
+
cell is `{ value, text }`. If `export` returns a promise, the trigger stays busy until it
|
|
164
|
+
settles. Errors are not caught.
|
|
165
|
+
|
|
166
|
+
Export on the server, from the table state:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import type { DataTableExporter } from 'compote-ui/data-table';
|
|
170
|
+
|
|
171
|
+
const serverExcel: DataTableExporter = {
|
|
172
|
+
id: 'server-xlsx',
|
|
173
|
+
label: 'Excel',
|
|
174
|
+
export: async ({ table }) => {
|
|
175
|
+
const url = await requestExport({
|
|
176
|
+
sorting: table.atoms.sorting.get(),
|
|
177
|
+
columnFilters: table.atoms.columnFilters.get(),
|
|
178
|
+
globalFilter: table.atoms.globalFilter.get(),
|
|
179
|
+
columns: table.getVisibleLeafColumns().map((column) => column.id)
|
|
180
|
+
});
|
|
181
|
+
window.location.assign(url);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Use a library the app already has instead of `write-excel-file` (SheetJS here):
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
import { utils, writeFileXLSX } from 'xlsx';
|
|
190
|
+
import type { DataTableExporter } from 'compote-ui/data-table';
|
|
191
|
+
|
|
192
|
+
const sheetJs: DataTableExporter = {
|
|
193
|
+
id: 'xlsx',
|
|
194
|
+
label: 'Excel',
|
|
195
|
+
export: ({ filename, getData }) => {
|
|
196
|
+
const { columns, rows } = getData();
|
|
197
|
+
const sheet = utils.aoa_to_sheet([
|
|
198
|
+
columns.map((column) => column.header),
|
|
199
|
+
...rows.map((cells) => cells.map((cell) => cell.text))
|
|
200
|
+
]);
|
|
201
|
+
const book = utils.book_new();
|
|
202
|
+
utils.book_append_sheet(book, sheet, 'Sheet1');
|
|
203
|
+
writeFileXLSX(book, `${filename}.xlsx`);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`getExportData(table, scope)`, `getExportRows(table, scope)` and `getExportColumns(table)` are
|
|
209
|
+
exported for building export UI outside `DataTable.Export`.
|