compote-ui 0.83.0 → 0.84.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/create-table.svelte.js +55 -5
- package/dist/components/data-table-v9/export/csv-exporter.d.ts +14 -0
- package/dist/components/data-table-v9/export/csv-exporter.js +43 -0
- package/dist/components/data-table-v9/export/export-data.d.ts +98 -0
- package/dist/components/data-table-v9/export/export-data.js +55 -0
- package/dist/components/data-table-v9/export/types.d.ts +23 -0
- package/dist/components/data-table-v9/export/types.js +1 -0
- package/dist/components/data-table-v9/index.d.ts +6 -0
- package/dist/components/data-table-v9/index.js +4 -0
- package/dist/components/data-table-v9/toolbar/data-table-export.svelte +146 -0
- package/dist/components/data-table-v9/toolbar/data-table-export.svelte.d.ts +35 -0
- package/dist/components/data-table-v9/types.d.ts +23 -0
- package/dist/components/data-table-v9/virtual/index.d.ts +2 -2
- package/dist/components/data-table-v9/virtual/index.js +1 -1
- package/dist/components/data-table-v9/xlsx/index.d.ts +24 -0
- package/dist/components/data-table-v9/xlsx/index.js +60 -0
- package/dist/components/data-table-v9/xlsx/xlsx-cell.d.ts +51 -0
- package/dist/components/data-table-v9/xlsx/xlsx-cell.js +123 -0
- package/dist/icons/PhDownloadSimple.svelte +18 -0
- package/dist/icons/PhDownloadSimple.svelte.d.ts +5 -0
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/utils/csv.d.ts +13 -0
- package/dist/utils/csv.js +18 -0
- package/dist/utils/download.d.ts +5 -0
- package/dist/utils/download.js +17 -0
- package/dist/utils/select-filter.js +6 -1
- package/package.json +129 -119
- package/skills/data-table/SKILL.md +400 -362
- package/skills/data-table/references/column-options.md +181 -177
- package/skills/data-table/references/export.md +158 -0
|
@@ -3,7 +3,8 @@ import { useLocaleContext } from '@ark-ui/svelte/locale';
|
|
|
3
3
|
import { onDestroy } from 'svelte';
|
|
4
4
|
import { renderComponent, renderSnippet } from '@tanstack/svelte-table';
|
|
5
5
|
import { dataTableFeatures } from './features';
|
|
6
|
-
import { TYPE_NUMBER_FORMAT_DEFAULTS } from './data-table-utils';
|
|
6
|
+
import { TYPE_NUMBER_FORMAT_DEFAULTS, getBooleanCellValue, getPhoneCellValue, getUrlCellValue } from './data-table-utils';
|
|
7
|
+
import { formatPhoneCellValue } from './phone-format';
|
|
7
8
|
import { filterKindFor } from './column-filter-kind';
|
|
8
9
|
import { dateCellTimeZone, formatDateCell } from './date-cell';
|
|
9
10
|
import { isEmptyDateRange, matchesDateRange, resolveDateRange } from '../../utils/date-filter';
|
|
@@ -168,7 +169,8 @@ function createColumns(columns, localeCtx) {
|
|
|
168
169
|
formatLocale: column.formatLocale,
|
|
169
170
|
grow: column.grow,
|
|
170
171
|
sum: column.sum,
|
|
171
|
-
footer: column.footer
|
|
172
|
+
footer: column.footer,
|
|
173
|
+
export: createExportMeta(column, localeCtx)
|
|
172
174
|
},
|
|
173
175
|
// Select cells may hold an array (multi-value / tag columns): facet each item
|
|
174
176
|
// separately so the Funnel lists options, not value combinations.
|
|
@@ -265,14 +267,62 @@ function formatCellValue(column, value, row, localeCtx) {
|
|
|
265
267
|
if (column.cellSnippet) {
|
|
266
268
|
return renderSnippet(column.cellSnippet, { value, row });
|
|
267
269
|
}
|
|
270
|
+
return formatCellText(column, value, row, localeCtx) ?? '-';
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* A cell's text as the table shows it — `cell`, else type formatting — or undefined when
|
|
274
|
+
* empty. Shared by the cell renderer (which shows `-` for empty) and exports.
|
|
275
|
+
*/
|
|
276
|
+
function formatCellText(column, value, row, localeCtx) {
|
|
268
277
|
const rendered = column.cell
|
|
269
278
|
? column.cell(value, row)
|
|
270
279
|
: applyTypeFormat(column, value, localeCtx);
|
|
271
|
-
if (rendered === null || rendered === undefined || rendered === '')
|
|
272
|
-
return
|
|
273
|
-
}
|
|
280
|
+
if (rendered === null || rendered === undefined || rendered === '')
|
|
281
|
+
return undefined;
|
|
274
282
|
return String(rendered);
|
|
275
283
|
}
|
|
284
|
+
// DataTableCellContent draws these types itself, ignoring `cell`/`cellComponent`.
|
|
285
|
+
const BUILT_IN_CELL_TYPES = new Set(['boolean', 'url', 'phone']);
|
|
286
|
+
/**
|
|
287
|
+
* The text DataTableCellContent shows for a type it draws itself (`{ text: undefined }` for its
|
|
288
|
+
* `-`), or undefined for any other type. Keep in step with data-table-cell-content.svelte.
|
|
289
|
+
*/
|
|
290
|
+
function formatBuiltInCellText(type, value) {
|
|
291
|
+
switch (type) {
|
|
292
|
+
case 'boolean': {
|
|
293
|
+
const checked = getBooleanCellValue(value);
|
|
294
|
+
return { text: checked === undefined ? undefined : checked ? 'Yes' : 'No' };
|
|
295
|
+
}
|
|
296
|
+
case 'url':
|
|
297
|
+
return { text: getUrlCellValue(value) };
|
|
298
|
+
case 'phone': {
|
|
299
|
+
const phone = getPhoneCellValue(value);
|
|
300
|
+
return { text: phone === undefined ? undefined : formatPhoneCellValue(phone) };
|
|
301
|
+
}
|
|
302
|
+
default:
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function createExportMeta(column, localeCtx) {
|
|
307
|
+
const builtIn = column.type !== undefined && BUILT_IN_CELL_TYPES.has(column.type);
|
|
308
|
+
return {
|
|
309
|
+
enabled: column.enableExport ?? column.type !== 'action',
|
|
310
|
+
hasCustomCell: typeof column.cell === 'function' && !builtIn,
|
|
311
|
+
exportValue: column.exportValue,
|
|
312
|
+
formatText: (value, row) => builtIn
|
|
313
|
+
? formatBuiltInCellText(column.type, value)?.text
|
|
314
|
+
: formatCellText(column, value, row, localeCtx),
|
|
315
|
+
formatTypedText: (value) => {
|
|
316
|
+
if (builtIn)
|
|
317
|
+
return formatBuiltInCellText(column.type, value)?.text;
|
|
318
|
+
const rendered = applyTypeFormat(column, value, localeCtx);
|
|
319
|
+
if (rendered === null || rendered === undefined || rendered === '')
|
|
320
|
+
return undefined;
|
|
321
|
+
return String(rendered);
|
|
322
|
+
},
|
|
323
|
+
resolveLocale: () => column.formatLocale ?? localeCtx().locale
|
|
324
|
+
};
|
|
325
|
+
}
|
|
276
326
|
function getCellComponentProps(column, value, row) {
|
|
277
327
|
return column.cellProps ? column.cellProps(value, row) : { value, row };
|
|
278
328
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type CsvOptions } from '../../../utils/csv';
|
|
2
|
+
import type { DataTableExportData } from './export-data';
|
|
3
|
+
import type { DataTableExporter } from './types';
|
|
4
|
+
export type CsvExporterOptions = CsvOptions & {
|
|
5
|
+
id?: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
};
|
|
8
|
+
/** Neutralizes a field a spreadsheet would run as a formula, by prefixing `'`. */
|
|
9
|
+
export declare function guardCsvFormula(text: string): string;
|
|
10
|
+
/** The export data as rows of CSV fields, header first, guarded against formula injection. */
|
|
11
|
+
export declare function exportDataToCsvRows(data: DataTableExportData): string[][];
|
|
12
|
+
/** A CSV exporter: the text the table shows, BOM-prefixed and `;`-separated by default. */
|
|
13
|
+
export declare function createCsvExporter(options?: CsvExporterOptions): DataTableExporter;
|
|
14
|
+
export declare const csvExporter: DataTableExporter;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { toCsv } from '../../../utils/csv';
|
|
2
|
+
import { downloadBlob } from '../../../utils/download';
|
|
3
|
+
import { PhFileText } from '../../../icons';
|
|
4
|
+
const NUMBER_TYPES = new Set(['number', 'currency', 'percent']);
|
|
5
|
+
// Spreadsheet apps run a field starting with one of these as a formula (CSV injection).
|
|
6
|
+
const FORMULA_START = /^[=+\-@\t\r]/;
|
|
7
|
+
// Digits, spaces and number punctuation only (`+381 60 123`, `-5`, `(011) 22-33`): at worst
|
|
8
|
+
// arithmetic, it can't call a function, so it is left as is. Spaces include the no-break ones
|
|
9
|
+
// Intl groups digits with; tab and CR stay guarded.
|
|
10
|
+
const NUMERIC_LIKE = new RegExp('^[+-]?[\\d \\u00a0\\u202f().,/+-]*$');
|
|
11
|
+
/** Neutralizes a field a spreadsheet would run as a formula, by prefixing `'`. */
|
|
12
|
+
export function guardCsvFormula(text) {
|
|
13
|
+
return FORMULA_START.test(text) && !NUMERIC_LIKE.test(text) ? `'${text}` : text;
|
|
14
|
+
}
|
|
15
|
+
// Only a number column without its own `cell` is known to hold Intl-formatted text, so a
|
|
16
|
+
// leading `-` there is a sign even next to a currency name (`-US$ 5`). Any `cell` may return
|
|
17
|
+
// arbitrary text.
|
|
18
|
+
function isFormattedNumber(column) {
|
|
19
|
+
return column.type !== undefined && NUMBER_TYPES.has(column.type) && !column.hasCustomCell;
|
|
20
|
+
}
|
|
21
|
+
/** The export data as rows of CSV fields, header first, guarded against formula injection. */
|
|
22
|
+
export function exportDataToCsvRows(data) {
|
|
23
|
+
const header = data.columns.map((column) => guardCsvFormula(column.header));
|
|
24
|
+
const rows = data.rows.map((cells) => cells.map((cell, index) => {
|
|
25
|
+
const column = data.columns[index];
|
|
26
|
+
return column && isFormattedNumber(column) ? cell.text : guardCsvFormula(cell.text);
|
|
27
|
+
}));
|
|
28
|
+
return [header, ...rows];
|
|
29
|
+
}
|
|
30
|
+
/** A CSV exporter: the text the table shows, BOM-prefixed and `;`-separated by default. */
|
|
31
|
+
export function createCsvExporter(options = {}) {
|
|
32
|
+
const { id = 'csv', label = 'CSV', ...csvOptions } = options;
|
|
33
|
+
return {
|
|
34
|
+
id,
|
|
35
|
+
label,
|
|
36
|
+
icon: PhFileText,
|
|
37
|
+
export: ({ filename, getData }) => {
|
|
38
|
+
const csv = toCsv(exportDataToCsvRows(getData()), csvOptions);
|
|
39
|
+
downloadBlob(new Blob([csv], { type: 'text/csv;charset=utf-8' }), `${filename}.csv`);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export const csvExporter = createCsvExporter();
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { Column, RowData } from '@tanstack/svelte-table';
|
|
2
|
+
import { type DataTableInstance } from '../data-table-utils';
|
|
3
|
+
import type { DataTableColumnType } from '../types';
|
|
4
|
+
/** Which rows an export writes. Both keep the table's filtering and sorting. */
|
|
5
|
+
export type DataTableExportScope = 'filtered' | 'selected';
|
|
6
|
+
export type DataTableExportColumn = {
|
|
7
|
+
id: string;
|
|
8
|
+
/** The column header; a grouped column's reads `Group / Column`. */
|
|
9
|
+
header: string;
|
|
10
|
+
type?: DataTableColumnType;
|
|
11
|
+
formatOptions?: Intl.NumberFormatOptions | Intl.DateTimeFormatOptions;
|
|
12
|
+
/** The column has its own `cell` renderer: its text is not derived from `type`. */
|
|
13
|
+
hasCustomCell: boolean;
|
|
14
|
+
/** The column sets `exportValue`, so `value` is that, not the accessor value. */
|
|
15
|
+
hasExportValue: boolean;
|
|
16
|
+
locale: string;
|
|
17
|
+
/** Current width in px. */
|
|
18
|
+
size: number;
|
|
19
|
+
};
|
|
20
|
+
export type DataTableExportCell = {
|
|
21
|
+
/** `exportValue` output when the column sets one, else the accessor value. */
|
|
22
|
+
value: unknown;
|
|
23
|
+
/** The text the table shows for this cell; `''` when empty. */
|
|
24
|
+
text: string;
|
|
25
|
+
};
|
|
26
|
+
export type DataTableExportData = {
|
|
27
|
+
columns: DataTableExportColumn[];
|
|
28
|
+
rows: DataTableExportCell[][];
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* The rows an export writes, in table order: filtered and sorted, and for `selected` only the
|
|
32
|
+
* selected ones among them (a selected row the filter hides is not exported).
|
|
33
|
+
*/
|
|
34
|
+
export declare function getExportRows<T extends RowData>(table: DataTableInstance<T>, scope: DataTableExportScope): import("@tanstack/table-core").Row<{
|
|
35
|
+
rowSortingFeature: import("@tanstack/table-core").TableFeature;
|
|
36
|
+
rowSelectionFeature: import("@tanstack/table-core").TableFeature;
|
|
37
|
+
columnFilteringFeature: import("@tanstack/table-core").TableFeature;
|
|
38
|
+
columnFacetingFeature: import("@tanstack/table-core").TableFeature;
|
|
39
|
+
globalFilteringFeature: import("@tanstack/table-core").TableFeature;
|
|
40
|
+
columnVisibilityFeature: import("@tanstack/table-core").TableFeature;
|
|
41
|
+
columnPinningFeature: import("@tanstack/table-core").TableFeature;
|
|
42
|
+
columnSizingFeature: import("@tanstack/table-core").TableFeature;
|
|
43
|
+
columnResizingFeature: import("@tanstack/table-core").TableFeature;
|
|
44
|
+
sortedRowModel: (table: import("@tanstack/table-core").Table<any, any>) => () => import("@tanstack/table-core").RowModel<any, any>;
|
|
45
|
+
filteredRowModel: (table: import("@tanstack/table-core").Table<any, any>) => () => import("@tanstack/table-core").RowModel<any, any>;
|
|
46
|
+
facetedRowModel: (table: import("@tanstack/table-core").Table<any, any>, columnId: string) => () => import("@tanstack/table-core").RowModel<any, any>;
|
|
47
|
+
facetedUniqueValues: (table: import("@tanstack/table-core").Table<import("@tanstack/table-core").TableFeatures, any>, columnId: string) => () => Map<any, number>;
|
|
48
|
+
facetedMinMaxValues: (table: import("@tanstack/table-core").Table<import("@tanstack/table-core").TableFeatures, any>, columnId: string) => () => undefined | [number, number];
|
|
49
|
+
sortFns: {
|
|
50
|
+
alphanumeric: import("@tanstack/table-core").CreatedSortFn<any, any>;
|
|
51
|
+
basic: import("@tanstack/table-core").CreatedSortFn<any, any>;
|
|
52
|
+
datetime: import("@tanstack/table-core").CreatedSortFn<any, any>;
|
|
53
|
+
text: import("@tanstack/table-core").CreatedSortFn<any, any>;
|
|
54
|
+
};
|
|
55
|
+
filterFns: {
|
|
56
|
+
arrIncludes: import("@tanstack/table-core").CreatedFilterFn<any, any>;
|
|
57
|
+
equals: import("@tanstack/table-core").CreatedFilterFn<any, any>;
|
|
58
|
+
inDateRange: import("@tanstack/table-core").CreatedFilterFn<any, any>;
|
|
59
|
+
inNumberRange: import("@tanstack/table-core").CreatedFilterFn<any, any>;
|
|
60
|
+
includesString: import("@tanstack/table-core").CreatedFilterFn<any, any>;
|
|
61
|
+
weakEquals: import("@tanstack/table-core").CreatedFilterFn<any, any>;
|
|
62
|
+
};
|
|
63
|
+
columnMeta: import("../types").DataTableColumnMeta;
|
|
64
|
+
}, T>[];
|
|
65
|
+
/** Visible columns that exports include, in the order the table shows them (pinning). */
|
|
66
|
+
export declare function getExportColumns<T extends RowData>(table: DataTableInstance<T>): Column<{
|
|
67
|
+
rowSortingFeature: import("@tanstack/table-core").TableFeature;
|
|
68
|
+
rowSelectionFeature: import("@tanstack/table-core").TableFeature;
|
|
69
|
+
columnFilteringFeature: import("@tanstack/table-core").TableFeature;
|
|
70
|
+
columnFacetingFeature: import("@tanstack/table-core").TableFeature;
|
|
71
|
+
globalFilteringFeature: import("@tanstack/table-core").TableFeature;
|
|
72
|
+
columnVisibilityFeature: import("@tanstack/table-core").TableFeature;
|
|
73
|
+
columnPinningFeature: import("@tanstack/table-core").TableFeature;
|
|
74
|
+
columnSizingFeature: import("@tanstack/table-core").TableFeature;
|
|
75
|
+
columnResizingFeature: import("@tanstack/table-core").TableFeature;
|
|
76
|
+
sortedRowModel: (table: import("@tanstack/table-core").Table<any, any>) => () => import("@tanstack/table-core").RowModel<any, any>;
|
|
77
|
+
filteredRowModel: (table: import("@tanstack/table-core").Table<any, any>) => () => import("@tanstack/table-core").RowModel<any, any>;
|
|
78
|
+
facetedRowModel: (table: import("@tanstack/table-core").Table<any, any>, columnId: string) => () => import("@tanstack/table-core").RowModel<any, any>;
|
|
79
|
+
facetedUniqueValues: (table: import("@tanstack/table-core").Table<import("@tanstack/table-core").TableFeatures, any>, columnId: string) => () => Map<any, number>;
|
|
80
|
+
facetedMinMaxValues: (table: import("@tanstack/table-core").Table<import("@tanstack/table-core").TableFeatures, any>, columnId: string) => () => undefined | [number, number];
|
|
81
|
+
sortFns: {
|
|
82
|
+
alphanumeric: import("@tanstack/table-core").CreatedSortFn<any, any>;
|
|
83
|
+
basic: import("@tanstack/table-core").CreatedSortFn<any, any>;
|
|
84
|
+
datetime: import("@tanstack/table-core").CreatedSortFn<any, any>;
|
|
85
|
+
text: import("@tanstack/table-core").CreatedSortFn<any, any>;
|
|
86
|
+
};
|
|
87
|
+
filterFns: {
|
|
88
|
+
arrIncludes: import("@tanstack/table-core").CreatedFilterFn<any, any>;
|
|
89
|
+
equals: import("@tanstack/table-core").CreatedFilterFn<any, any>;
|
|
90
|
+
inDateRange: import("@tanstack/table-core").CreatedFilterFn<any, any>;
|
|
91
|
+
inNumberRange: import("@tanstack/table-core").CreatedFilterFn<any, any>;
|
|
92
|
+
includesString: import("@tanstack/table-core").CreatedFilterFn<any, any>;
|
|
93
|
+
weakEquals: import("@tanstack/table-core").CreatedFilterFn<any, any>;
|
|
94
|
+
};
|
|
95
|
+
columnMeta: import("../types").DataTableColumnMeta;
|
|
96
|
+
}, T, unknown>[];
|
|
97
|
+
/** The columns and cells an export writes. */
|
|
98
|
+
export declare function getExportData<T extends RowData>(table: DataTableInstance<T>, scope: DataTableExportScope): DataTableExportData;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { getColumnLabel, getColumnMeta } from '../data-table-utils';
|
|
2
|
+
/**
|
|
3
|
+
* The rows an export writes, in table order: filtered and sorted, and for `selected` only the
|
|
4
|
+
* selected ones among them (a selected row the filter hides is not exported).
|
|
5
|
+
*/
|
|
6
|
+
export function getExportRows(table, scope) {
|
|
7
|
+
const rows = table.getRowModel().rows;
|
|
8
|
+
return scope === 'selected' ? rows.filter((row) => row.getIsSelected()) : rows;
|
|
9
|
+
}
|
|
10
|
+
/** Visible columns that exports include, in the order the table shows them (pinning). */
|
|
11
|
+
export function getExportColumns(table) {
|
|
12
|
+
return [
|
|
13
|
+
...table.getStartVisibleLeafColumns(),
|
|
14
|
+
...table.getCenterVisibleLeafColumns(),
|
|
15
|
+
...table.getEndVisibleLeafColumns()
|
|
16
|
+
].filter((column) => getColumnMeta(column.columnDef)?.export?.enabled === true);
|
|
17
|
+
}
|
|
18
|
+
function exportHeader(column) {
|
|
19
|
+
const labels = [];
|
|
20
|
+
for (let current = column; current; current = current.parent) {
|
|
21
|
+
labels.unshift(getColumnLabel(current));
|
|
22
|
+
}
|
|
23
|
+
return labels.join(' / ');
|
|
24
|
+
}
|
|
25
|
+
/** The columns and cells an export writes. */
|
|
26
|
+
export function getExportData(table, scope) {
|
|
27
|
+
const columns = getExportColumns(table).flatMap((column) => {
|
|
28
|
+
const meta = getColumnMeta(column.columnDef);
|
|
29
|
+
const exportMeta = meta?.export;
|
|
30
|
+
return exportMeta ? [{ column, meta, exportMeta }] : [];
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
columns: columns.map(({ column, meta, exportMeta }) => ({
|
|
34
|
+
id: column.id,
|
|
35
|
+
header: exportHeader(column),
|
|
36
|
+
type: meta.type,
|
|
37
|
+
formatOptions: meta.formatOptions,
|
|
38
|
+
hasCustomCell: exportMeta.hasCustomCell,
|
|
39
|
+
hasExportValue: exportMeta.exportValue !== undefined,
|
|
40
|
+
locale: exportMeta.resolveLocale(),
|
|
41
|
+
size: column.getSize()
|
|
42
|
+
})),
|
|
43
|
+
rows: getExportRows(table, scope).map((row) => columns.map(({ column, exportMeta }) => {
|
|
44
|
+
const original = row.original;
|
|
45
|
+
const raw = row.getValue(column.id);
|
|
46
|
+
const { exportValue, formatText, formatTypedText } = exportMeta;
|
|
47
|
+
const value = exportValue ? exportValue(raw, original) : raw;
|
|
48
|
+
// `cell` always gets the accessor value — the text matches what the table shows.
|
|
49
|
+
const text = exportValue && !exportMeta.hasCustomCell
|
|
50
|
+
? formatTypedText(value)
|
|
51
|
+
: formatText(raw, original);
|
|
52
|
+
return { value, text: text ?? '' };
|
|
53
|
+
}))
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { RowData } from '@tanstack/svelte-table';
|
|
2
|
+
import type { Component } from 'svelte';
|
|
3
|
+
import type { DataTableInstance } from '../data-table-utils';
|
|
4
|
+
import type { DataTableExportData, DataTableExportScope } from './export-data';
|
|
5
|
+
export type DataTableExportContext<T extends RowData> = {
|
|
6
|
+
table: DataTableInstance<T>;
|
|
7
|
+
scope: DataTableExportScope;
|
|
8
|
+
/** File name without extension. */
|
|
9
|
+
filename: string;
|
|
10
|
+
/**
|
|
11
|
+
* The columns and cells to write. Lazy: an exporter that exports on the server only needs
|
|
12
|
+
* `table` (its sorting, filters and visible columns) and need not call it.
|
|
13
|
+
*/
|
|
14
|
+
getData: () => DataTableExportData;
|
|
15
|
+
};
|
|
16
|
+
/** One file format `DataTable.Export` offers. */
|
|
17
|
+
export type DataTableExporter = {
|
|
18
|
+
id: string;
|
|
19
|
+
label: string;
|
|
20
|
+
icon?: Component;
|
|
21
|
+
/** If it returns a promise, the Export trigger stays busy until it settles. */
|
|
22
|
+
export: <T extends RowData>(context: DataTableExportContext<T>) => void | Promise<unknown>;
|
|
23
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -8,6 +8,9 @@ export { default as ColumnFilter } from './toolbar/data-table-column-filter.svel
|
|
|
8
8
|
export { default as ColumnVisibility } from './toolbar/data-table-column-visibility.svelte';
|
|
9
9
|
export { default as Search } from './toolbar/data-table-search.svelte';
|
|
10
10
|
export { default as Refresh } from './toolbar/data-table-refresh.svelte';
|
|
11
|
+
export { default as Export } from './toolbar/data-table-export.svelte';
|
|
12
|
+
export { csvExporter, createCsvExporter } from './export/csv-exporter';
|
|
13
|
+
export { getExportColumns, getExportData, getExportRows } from './export/export-data';
|
|
11
14
|
export { default as ColumnFilterEditor } from './filter-editors/column-filter-editor.svelte';
|
|
12
15
|
export { ColumnFilterEditing } from './column-filter-editing.svelte';
|
|
13
16
|
export { filterKindFor, getColumnFilterKind } from './column-filter-kind';
|
|
@@ -16,5 +19,8 @@ export { EMPTY_SELECT_VALUE, selectFilterOptions } from '../../utils/select-filt
|
|
|
16
19
|
export type { CreateDataTableOptions, DataTableInstance } from './create-table.svelte';
|
|
17
20
|
export type { ColumnFilterEditingOptions, FilterableColumn } from './column-filter-editing.svelte';
|
|
18
21
|
export type { ColumnFilterKind } from './column-filter-kind';
|
|
22
|
+
export type { CsvExporterOptions } from './export/csv-exporter';
|
|
23
|
+
export type { DataTableExportCell, DataTableExportColumn, DataTableExportData, DataTableExportScope } from './export/export-data';
|
|
24
|
+
export type { DataTableExportContext, DataTableExporter } from './export/types';
|
|
19
25
|
export type { SelectFilterOption, SelectFilterValue } from '../../utils/select-filter';
|
|
20
26
|
export type { DataTableAlign, DataTableAccessorFnColumn, DataTableAccessorKeyColumn, DataTableColumn, DataTableColumnBase, DataTableColumnInstance, DataTableColumnOptions, DataTableColumnType, DataTableCellPropsResolver, DataTableCellRenderProps, DataTableGroupColumn, DataTableLeafColumnBase, DataTableLeafColumn } from './types';
|
|
@@ -8,6 +8,10 @@ export { default as ColumnFilter } from './toolbar/data-table-column-filter.svel
|
|
|
8
8
|
export { default as ColumnVisibility } from './toolbar/data-table-column-visibility.svelte';
|
|
9
9
|
export { default as Search } from './toolbar/data-table-search.svelte';
|
|
10
10
|
export { default as Refresh } from './toolbar/data-table-refresh.svelte';
|
|
11
|
+
export { default as Export } from './toolbar/data-table-export.svelte';
|
|
12
|
+
// Export formats for DataTable.Export, and the data they write (for a custom exporter).
|
|
13
|
+
export { csvExporter, createCsvExporter } from './export/csv-exporter';
|
|
14
|
+
export { getExportColumns, getExportData, getExportRows } from './export/export-data';
|
|
11
15
|
// Building blocks for filter UI outside the toolbar and header popups (e.g. a facet panel):
|
|
12
16
|
// the same editors, pending-edit handling and select options the Funnel uses.
|
|
13
17
|
export { default as ColumnFilterEditor } from './filter-editors/column-filter-editor.svelte';
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<script lang="ts" generics="T extends RowData">
|
|
2
|
+
import type { RowData } from '@tanstack/svelte-table';
|
|
3
|
+
import { Menu as ArkMenu } from '@ark-ui/svelte/menu';
|
|
4
|
+
import * as Menu from '../../menu';
|
|
5
|
+
import * as Tooltip from '../../tooltip';
|
|
6
|
+
import { button } from '../../button/button.variants';
|
|
7
|
+
import { PhDownloadSimple } from '../../../icons';
|
|
8
|
+
import type { DataTableInstance } from '../data-table-utils';
|
|
9
|
+
import { csvExporter } from '../export/csv-exporter';
|
|
10
|
+
import { getExportData, getExportRows, type DataTableExportScope } from '../export/export-data';
|
|
11
|
+
import type { DataTableExporter } from '../export/types';
|
|
12
|
+
|
|
13
|
+
type Props = {
|
|
14
|
+
table: DataTableInstance<T>;
|
|
15
|
+
/** File formats to offer. One format and no selected rows: a plain button; otherwise a menu. */
|
|
16
|
+
exporters?: DataTableExporter[];
|
|
17
|
+
/** File name without extension. */
|
|
18
|
+
filename?: string;
|
|
19
|
+
/** Text label for the trigger. When omitted, a download icon is shown instead. */
|
|
20
|
+
triggerLabel?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
let { table, exporters = [csvExporter], filename = 'export', triggerLabel }: Props = $props();
|
|
24
|
+
|
|
25
|
+
// Menu and tooltip share one trigger element, so both must agree on its id.
|
|
26
|
+
const triggerId = $props.id();
|
|
27
|
+
|
|
28
|
+
let pending = $state(false);
|
|
29
|
+
|
|
30
|
+
// Row model reads aren't reactive on their own: read the slices they depend on first.
|
|
31
|
+
const rowSlices = $derived.by(() => [
|
|
32
|
+
table.options.data,
|
|
33
|
+
table.atoms.columnFilters.get(),
|
|
34
|
+
table.atoms.globalFilter.get(),
|
|
35
|
+
table.atoms.sorting.get(),
|
|
36
|
+
table.atoms.rowSelection.get()
|
|
37
|
+
]);
|
|
38
|
+
const rowCount = $derived.by(() => {
|
|
39
|
+
void rowSlices;
|
|
40
|
+
return getExportRows(table, 'filtered').length;
|
|
41
|
+
});
|
|
42
|
+
// Counted exactly as the export picks them: selected rows the filter keeps.
|
|
43
|
+
const selectedCount = $derived.by(() => {
|
|
44
|
+
void rowSlices;
|
|
45
|
+
return getExportRows(table, 'selected').length;
|
|
46
|
+
});
|
|
47
|
+
const hasSelection = $derived(selectedCount > 0);
|
|
48
|
+
// The menu's choice, reset to all rows whenever the selection empties or starts again, so a
|
|
49
|
+
// "Selected rows" pick from an earlier selection never carries over to a new one.
|
|
50
|
+
let chosenScope = $derived.by<DataTableExportScope>(() => {
|
|
51
|
+
void hasSelection;
|
|
52
|
+
return 'filtered';
|
|
53
|
+
});
|
|
54
|
+
// `selected` with nothing selected would write an empty file: fall back to all rows.
|
|
55
|
+
const scope = $derived<DataTableExportScope>(hasSelection ? chosenScope : 'filtered');
|
|
56
|
+
const showMenu = $derived(exporters.length > 1 || hasSelection);
|
|
57
|
+
|
|
58
|
+
async function runExport(exporter: DataTableExporter) {
|
|
59
|
+
if (pending) return;
|
|
60
|
+
pending = true;
|
|
61
|
+
const exportScope = scope;
|
|
62
|
+
try {
|
|
63
|
+
await exporter.export({
|
|
64
|
+
table,
|
|
65
|
+
scope: exportScope,
|
|
66
|
+
filename,
|
|
67
|
+
getData: () => getExportData(table, exportScope)
|
|
68
|
+
});
|
|
69
|
+
} finally {
|
|
70
|
+
pending = false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
{#snippet triggerContent()}
|
|
76
|
+
<PhDownloadSimple />
|
|
77
|
+
{triggerLabel}
|
|
78
|
+
{/snippet}
|
|
79
|
+
|
|
80
|
+
{#if showMenu}
|
|
81
|
+
<Menu.Root ids={{ trigger: triggerId }} positioning={{ placement: 'bottom-end' }}>
|
|
82
|
+
<Tooltip.Root ids={{ trigger: triggerId }} disabled={!!triggerLabel}>
|
|
83
|
+
<Tooltip.Trigger>
|
|
84
|
+
{#snippet asChild(tooltipProps)}
|
|
85
|
+
<ArkMenu.Trigger
|
|
86
|
+
{...tooltipProps()}
|
|
87
|
+
aria-label={triggerLabel ? undefined : 'Export'}
|
|
88
|
+
aria-busy={pending}
|
|
89
|
+
class={button({ variant: 'outline', size: triggerLabel ? 'default' : 'icon' })}
|
|
90
|
+
>
|
|
91
|
+
{@render triggerContent()}
|
|
92
|
+
</ArkMenu.Trigger>
|
|
93
|
+
{/snippet}
|
|
94
|
+
</Tooltip.Trigger>
|
|
95
|
+
<Tooltip.Content>Export</Tooltip.Content>
|
|
96
|
+
</Tooltip.Root>
|
|
97
|
+
|
|
98
|
+
<Menu.Content class="min-w-44">
|
|
99
|
+
{#if hasSelection}
|
|
100
|
+
<Menu.RadioItemGroup
|
|
101
|
+
value={scope}
|
|
102
|
+
onValueChange={(details) => (chosenScope = details.value as DataTableExportScope)}
|
|
103
|
+
>
|
|
104
|
+
<Menu.RadioItem value="filtered" closeOnSelect={false}>
|
|
105
|
+
All rows ({rowCount})
|
|
106
|
+
</Menu.RadioItem>
|
|
107
|
+
<Menu.RadioItem value="selected" closeOnSelect={false}>
|
|
108
|
+
Selected rows ({selectedCount})
|
|
109
|
+
</Menu.RadioItem>
|
|
110
|
+
</Menu.RadioItemGroup>
|
|
111
|
+
<Menu.Separator />
|
|
112
|
+
{/if}
|
|
113
|
+
{#each exporters as exporter (exporter.id)}
|
|
114
|
+
{@const Icon = exporter.icon}
|
|
115
|
+
<Menu.Item
|
|
116
|
+
value={`export:${exporter.id}`}
|
|
117
|
+
disabled={pending}
|
|
118
|
+
onSelect={() => runExport(exporter)}
|
|
119
|
+
>
|
|
120
|
+
{#if Icon}<Icon class="text-ink-dim" />{/if}
|
|
121
|
+
{exporter.label}
|
|
122
|
+
</Menu.Item>
|
|
123
|
+
{/each}
|
|
124
|
+
</Menu.Content>
|
|
125
|
+
</Menu.Root>
|
|
126
|
+
{:else}
|
|
127
|
+
{@const exporter = exporters[0]}
|
|
128
|
+
{#if exporter}
|
|
129
|
+
<Tooltip.Root disabled={!!triggerLabel}>
|
|
130
|
+
<!-- aria-disabled instead of disabled so keyboard focus isn't lost mid-export -->
|
|
131
|
+
<Tooltip.Trigger
|
|
132
|
+
type="button"
|
|
133
|
+
variant="outline"
|
|
134
|
+
size={triggerLabel ? 'default' : 'icon'}
|
|
135
|
+
class="aria-disabled:cursor-default"
|
|
136
|
+
aria-label={triggerLabel ? undefined : `Export ${exporter.label}`}
|
|
137
|
+
aria-busy={pending}
|
|
138
|
+
aria-disabled={pending}
|
|
139
|
+
onclick={() => runExport(exporter)}
|
|
140
|
+
>
|
|
141
|
+
{@render triggerContent()}
|
|
142
|
+
</Tooltip.Trigger>
|
|
143
|
+
<Tooltip.Content>Export {exporter.label}</Tooltip.Content>
|
|
144
|
+
</Tooltip.Root>
|
|
145
|
+
{/if}
|
|
146
|
+
{/if}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { RowData } from '@tanstack/svelte-table';
|
|
2
|
+
import type { DataTableInstance } from '../data-table-utils';
|
|
3
|
+
import type { DataTableExporter } from '../export/types';
|
|
4
|
+
declare function $$render<T extends RowData>(): {
|
|
5
|
+
props: {
|
|
6
|
+
table: DataTableInstance<T>;
|
|
7
|
+
/** File formats to offer. One format and no selected rows: a plain button; otherwise a menu. */
|
|
8
|
+
exporters?: DataTableExporter[];
|
|
9
|
+
/** File name without extension. */
|
|
10
|
+
filename?: string;
|
|
11
|
+
/** Text label for the trigger. When omitted, a download icon is shown instead. */
|
|
12
|
+
triggerLabel?: string;
|
|
13
|
+
};
|
|
14
|
+
exports: {};
|
|
15
|
+
bindings: "";
|
|
16
|
+
slots: {};
|
|
17
|
+
events: {};
|
|
18
|
+
};
|
|
19
|
+
declare class __sveltets_Render<T extends RowData> {
|
|
20
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
21
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
22
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
23
|
+
bindings(): "";
|
|
24
|
+
exports(): {};
|
|
25
|
+
}
|
|
26
|
+
interface $$IsomorphicComponent {
|
|
27
|
+
new <T extends RowData>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
28
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
29
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
30
|
+
<T extends RowData>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
31
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
32
|
+
}
|
|
33
|
+
declare const DataTableExport: $$IsomorphicComponent;
|
|
34
|
+
type DataTableExport<T extends RowData> = InstanceType<typeof DataTableExport<T>>;
|
|
35
|
+
export default DataTableExport;
|
|
@@ -36,6 +36,15 @@ export type DataTableLeafColumnBase<T extends RowData> = DataTableColumnOptions<
|
|
|
36
36
|
grow?: boolean;
|
|
37
37
|
sum?: boolean;
|
|
38
38
|
footer?: (values: unknown[]) => string | number | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* The value an export writes for this column, in place of the accessor value. Use it
|
|
41
|
+
* when the cell holds something a file can't show (an object rendered by
|
|
42
|
+
* `cellComponent`). Without `cell`, its text is formatted by the column `type`; it is
|
|
43
|
+
* never passed to `cell`, which keeps receiving the accessor value.
|
|
44
|
+
*/
|
|
45
|
+
exportValue?: (value: unknown, row: T) => unknown;
|
|
46
|
+
/** Whether exports include this column. Defaults to `true`, `false` for `type: 'action'`. */
|
|
47
|
+
enableExport?: boolean;
|
|
39
48
|
columns?: never;
|
|
40
49
|
};
|
|
41
50
|
export type DataTableAccessorKeyColumn<T extends RowData> = DataTableLeafColumnBase<T> & {
|
|
@@ -73,4 +82,18 @@ export type DataTableColumnMeta = {
|
|
|
73
82
|
grow?: boolean;
|
|
74
83
|
sum?: boolean;
|
|
75
84
|
footer?: (values: unknown[]) => string | number | undefined;
|
|
85
|
+
/** Leaf columns only: what exports read. Set by `createTable`, not by consumers. */
|
|
86
|
+
export?: DataTableColumnExportMeta;
|
|
87
|
+
};
|
|
88
|
+
export type DataTableColumnExportMeta = {
|
|
89
|
+
enabled: boolean;
|
|
90
|
+
/** The column has its own `cell` renderer, so its text is not derived from `type`. */
|
|
91
|
+
hasCustomCell: boolean;
|
|
92
|
+
exportValue?: (value: unknown, row: never) => unknown;
|
|
93
|
+
/** The cell text the table shows (`cell`, else type formatting), without the `-` placeholder. */
|
|
94
|
+
formatText: (value: unknown, row: never) => string | undefined;
|
|
95
|
+
/** Type formatting only, never `cell` — for `exportValue` output. */
|
|
96
|
+
formatTypedText: (value: unknown) => string | undefined;
|
|
97
|
+
/** `formatLocale`, else the current locale context. */
|
|
98
|
+
resolveLocale: () => string;
|
|
76
99
|
};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { default as Root } from './data-table-virtualized.svelte';
|
|
2
|
-
export { ColumnFilter, ColumnFilterEditing, ColumnFilterEditor, ColumnVisibility, EMPTY_SELECT_VALUE, FlexRender, Refresh, Search, Title, Toolbar, createDataTableColumnHelper, createTable, filterKindFor, getColumnFilterKind, getColumnLabel, renderComponent, renderSnippet, selectFilterOptions } from '../index';
|
|
3
|
-
export type { ColumnFilterEditingOptions, ColumnFilterKind, CreateDataTableOptions, DataTableAccessorFnColumn, DataTableAccessorKeyColumn, DataTableAlign, DataTableCellPropsResolver, DataTableCellRenderProps, DataTableColumn, DataTableColumnBase, DataTableColumnInstance, DataTableColumnOptions, DataTableColumnType, DataTableGroupColumn, DataTableInstance, DataTableLeafColumn, DataTableLeafColumnBase, FilterableColumn, SelectFilterOption, SelectFilterValue } from '../index';
|
|
2
|
+
export { ColumnFilter, ColumnFilterEditing, ColumnFilterEditor, ColumnVisibility, EMPTY_SELECT_VALUE, Export, FlexRender, Refresh, Search, Title, Toolbar, createCsvExporter, createDataTableColumnHelper, createTable, csvExporter, filterKindFor, getColumnFilterKind, getColumnLabel, getExportColumns, getExportData, getExportRows, renderComponent, renderSnippet, selectFilterOptions } from '../index';
|
|
3
|
+
export type { ColumnFilterEditingOptions, ColumnFilterKind, CreateDataTableOptions, CsvExporterOptions, DataTableAccessorFnColumn, DataTableAccessorKeyColumn, DataTableAlign, DataTableCellPropsResolver, DataTableCellRenderProps, DataTableColumn, DataTableColumnBase, DataTableColumnInstance, DataTableColumnOptions, DataTableColumnType, DataTableExportCell, DataTableExportColumn, DataTableExportContext, DataTableExportData, DataTableExporter, DataTableExportScope, DataTableGroupColumn, DataTableInstance, DataTableLeafColumn, DataTableLeafColumnBase, FilterableColumn, SelectFilterOption, SelectFilterValue } from '../index';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { default as Root } from './data-table-virtualized.svelte';
|
|
2
|
-
export { ColumnFilter, ColumnFilterEditing, ColumnFilterEditor, ColumnVisibility, EMPTY_SELECT_VALUE, FlexRender, Refresh, Search, Title, Toolbar, createDataTableColumnHelper, createTable, filterKindFor, getColumnFilterKind, getColumnLabel, renderComponent, renderSnippet, selectFilterOptions } from '../index';
|
|
2
|
+
export { ColumnFilter, ColumnFilterEditing, ColumnFilterEditor, ColumnVisibility, EMPTY_SELECT_VALUE, Export, FlexRender, Refresh, Search, Title, Toolbar, createCsvExporter, createDataTableColumnHelper, createTable, csvExporter, filterKindFor, getColumnFilterKind, getColumnLabel, getExportColumns, getExportData, getExportRows, renderComponent, renderSnippet, selectFilterOptions } from '../index';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { DataTableExportData } from '../export/export-data';
|
|
2
|
+
import type { DataTableExporter } from '../export/types';
|
|
3
|
+
import { type XlsxCell, type XlsxDateFormats } from './xlsx-cell';
|
|
4
|
+
export { DEFAULT_XLSX_DATE_FORMATS, toXlsxCell, xlsxNumberFormat } from './xlsx-cell';
|
|
5
|
+
export type { XlsxCell, XlsxDateFormats, XlsxDatePart } from './xlsx-cell';
|
|
6
|
+
export type XlsxExporterOptions = Partial<XlsxDateFormats> & {
|
|
7
|
+
id?: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
/** Sheet name (Excel allows 31 characters, without `[ ] : * ? / \`). */
|
|
10
|
+
sheet?: string;
|
|
11
|
+
};
|
|
12
|
+
/** The export data as sheet rows: a bold header row, then typed cells. */
|
|
13
|
+
export declare function exportDataToSheet(data: DataTableExportData, formats?: XlsxDateFormats): XlsxCell[][];
|
|
14
|
+
/**
|
|
15
|
+
* The export data as an .xlsx file. Loads `write-excel-file` (an optional peer dependency) on
|
|
16
|
+
* first call, so importing this module costs nothing until an export runs — and stays SSR-safe.
|
|
17
|
+
*/
|
|
18
|
+
export declare function exportDataToXlsxBlob(data: DataTableExportData, options?: XlsxExporterOptions): Promise<Blob>;
|
|
19
|
+
/**
|
|
20
|
+
* An Excel exporter: numbers, dates and booleans as typed cells, formatted like the table.
|
|
21
|
+
* Needs `write-excel-file` installed.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createXlsxExporter(options?: XlsxExporterOptions): DataTableExporter;
|
|
24
|
+
export declare const xlsxExporter: DataTableExporter;
|