compote-ui 0.83.0 → 0.85.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 +29 -0
- package/dist/components/data-table-v9/xlsx/index.js +84 -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 +13 -3
- package/skills/data-table/SKILL.md +42 -4
- package/skills/data-table/references/column-options.md +4 -0
- package/skills/data-table/references/export.md +160 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { PhMicrosoftExcelLogo } from '../../../icons';
|
|
2
|
+
import { downloadBlob } from '../../../utils/download';
|
|
3
|
+
import { DEFAULT_XLSX_DATE_FORMATS, toXlsxCell } from './xlsx-cell';
|
|
4
|
+
export { DEFAULT_XLSX_DATE_FORMATS, toXlsxCell, xlsxNumberFormat } from './xlsx-cell';
|
|
5
|
+
// Column widths in characters of Excel's default font.
|
|
6
|
+
const MIN_COLUMN_WIDTH = 8;
|
|
7
|
+
const MAX_COLUMN_WIDTH = 60;
|
|
8
|
+
// One character of slack: char counts already overshoot narrow lowercase text, and this keeps
|
|
9
|
+
// wide letters (W, M, capitals) from being cut off.
|
|
10
|
+
const COLUMN_WIDTH_PADDING = 1;
|
|
11
|
+
/** Characters in the longest line of `text` (code points, so č or 🙂 count once). */
|
|
12
|
+
function longestLine(text) {
|
|
13
|
+
let longest = 0;
|
|
14
|
+
for (const line of text.split(/\r\n|\r|\n/))
|
|
15
|
+
longest = Math.max(longest, Array.from(line).length);
|
|
16
|
+
return longest;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A width per column that fits its header and the longest cell text (the text the table
|
|
20
|
+
* shows, which is close to what Excel shows), plus padding, between 8 and 60 characters.
|
|
21
|
+
*/
|
|
22
|
+
export function xlsxColumnWidths(data) {
|
|
23
|
+
return data.columns.map((column, index) => {
|
|
24
|
+
let longest = longestLine(column.header);
|
|
25
|
+
for (const cells of data.rows) {
|
|
26
|
+
const text = cells[index]?.text;
|
|
27
|
+
if (text)
|
|
28
|
+
longest = Math.max(longest, longestLine(text));
|
|
29
|
+
}
|
|
30
|
+
return Math.min(MAX_COLUMN_WIDTH, Math.max(MIN_COLUMN_WIDTH, longest + COLUMN_WIDTH_PADDING));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/** The export data as sheet rows: a bold header row, then typed cells. */
|
|
34
|
+
export function exportDataToSheet(data, formats = DEFAULT_XLSX_DATE_FORMATS) {
|
|
35
|
+
const header = data.columns.map((column) => ({
|
|
36
|
+
type: String,
|
|
37
|
+
value: column.header,
|
|
38
|
+
fontWeight: 'bold'
|
|
39
|
+
}));
|
|
40
|
+
const rows = data.rows.map((cells) => data.columns.map((column, index) => {
|
|
41
|
+
const cell = cells[index];
|
|
42
|
+
return cell ? toXlsxCell(column, cell, formats) : null;
|
|
43
|
+
}));
|
|
44
|
+
return [header, ...rows];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The export data as an .xlsx file. Loads `write-excel-file` (an optional peer dependency) on
|
|
48
|
+
* first call, so importing this module costs nothing until an export runs — and stays SSR-safe.
|
|
49
|
+
*/
|
|
50
|
+
export async function exportDataToXlsxBlob(data, options = {}) {
|
|
51
|
+
const { default: writeXlsxFile } = await import('write-excel-file/browser');
|
|
52
|
+
const formats = { ...DEFAULT_XLSX_DATE_FORMATS, ...pickFormats(options) };
|
|
53
|
+
const sheetData = exportDataToSheet(data, formats);
|
|
54
|
+
// XlsxCell is a subset of the package's cell shape; cast via its own (type-only) type.
|
|
55
|
+
return writeXlsxFile(sheetData, {
|
|
56
|
+
sheet: options.sheet,
|
|
57
|
+
stickyRowsCount: 1,
|
|
58
|
+
columns: xlsxColumnWidths(data).map((width) => ({ width }))
|
|
59
|
+
}).toBlob();
|
|
60
|
+
}
|
|
61
|
+
function pickFormats({ date, time, dateTime }) {
|
|
62
|
+
return {
|
|
63
|
+
...(date !== undefined ? { date } : {}),
|
|
64
|
+
...(time !== undefined ? { time } : {}),
|
|
65
|
+
...(dateTime !== undefined ? { dateTime } : {})
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* An Excel exporter: numbers, dates and booleans as typed cells, formatted like the table.
|
|
70
|
+
* Needs `write-excel-file` installed.
|
|
71
|
+
*/
|
|
72
|
+
export function createXlsxExporter(options = {}) {
|
|
73
|
+
const { id = 'xlsx', label = 'Excel' } = options;
|
|
74
|
+
return {
|
|
75
|
+
id,
|
|
76
|
+
label,
|
|
77
|
+
icon: PhMicrosoftExcelLogo,
|
|
78
|
+
export: async ({ filename, getData }) => {
|
|
79
|
+
const blob = await exportDataToXlsxBlob(getData(), options);
|
|
80
|
+
downloadBlob(blob, `${filename}.xlsx`);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export const xlsxExporter = createXlsxExporter();
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type ZonedOrCalendarDate } from '../../../utils/date';
|
|
2
|
+
import type { DataTableExportCell, DataTableExportColumn } from '../export/export-data';
|
|
3
|
+
/** Excel number formats for date columns. */
|
|
4
|
+
export type XlsxDateFormats = {
|
|
5
|
+
date: string;
|
|
6
|
+
time: string;
|
|
7
|
+
dateTime: string;
|
|
8
|
+
};
|
|
9
|
+
export declare const DEFAULT_XLSX_DATE_FORMATS: XlsxDateFormats;
|
|
10
|
+
/**
|
|
11
|
+
* One sheet cell, in the shape `write-excel-file` takes. Declared here so the published types
|
|
12
|
+
* don't depend on the optional peer.
|
|
13
|
+
*/
|
|
14
|
+
export type XlsxCell = null | {
|
|
15
|
+
type: StringConstructor;
|
|
16
|
+
value: string;
|
|
17
|
+
fontWeight?: 'bold';
|
|
18
|
+
} | {
|
|
19
|
+
type: NumberConstructor;
|
|
20
|
+
value: number;
|
|
21
|
+
format: string;
|
|
22
|
+
} | {
|
|
23
|
+
type: DateConstructor;
|
|
24
|
+
value: Date;
|
|
25
|
+
format: string;
|
|
26
|
+
} | {
|
|
27
|
+
type: BooleanConstructor;
|
|
28
|
+
value: boolean;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* The Excel format string matching how the table shows `value`, or undefined when the
|
|
32
|
+
* column's Intl options have no faithful Excel equivalent (the cell is then written as text).
|
|
33
|
+
* Decimals are chosen per value, exactly as many as Intl shows (read from `formatToParts`, so
|
|
34
|
+
* Intl's own rounding decides): `#,##0.###` would leave a trailing `.` on whole numbers.
|
|
35
|
+
*/
|
|
36
|
+
export declare function xlsxNumberFormat(column: DataTableExportColumn, value: number): string | undefined;
|
|
37
|
+
/** Which part of a date a column shows, and so which part Excel gets. */
|
|
38
|
+
export type XlsxDatePart = 'date' | 'time' | 'date-time';
|
|
39
|
+
/**
|
|
40
|
+
* The date as Excel should show it. Excel dates have no zone, and `write-excel-file` reads a
|
|
41
|
+
* `Date` in UTC, so the wall clock of the zone the table shows is written as UTC. A `date`
|
|
42
|
+
* column drops the time (midnight); a `time` column keeps only the time of day, on Excel's day
|
|
43
|
+
* zero (1899-12-30), so times sort and compare by time alone.
|
|
44
|
+
*/
|
|
45
|
+
export declare function toXlsxDate(date: ZonedOrCalendarDate, part?: XlsxDatePart): Date;
|
|
46
|
+
/**
|
|
47
|
+
* One export cell as a typed sheet cell: numbers, dates and booleans stay computable in Excel;
|
|
48
|
+
* everything else — and any column with its own `cell` renderer but no `exportValue` — is the
|
|
49
|
+
* text the table shows. Typed cells can't hold formulas, so no `'` guard is needed.
|
|
50
|
+
*/
|
|
51
|
+
export declare function toXlsxCell(column: DataTableExportColumn, cell: DataTableExportCell, formats?: XlsxDateFormats): XlsxCell;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { GregorianCalendar, toCalendar } from '@internationalized/date';
|
|
2
|
+
import { readDateInZone } from '../../../utils/date';
|
|
3
|
+
import { dateCellTimeZone } from '../date-cell';
|
|
4
|
+
import { TYPE_NUMBER_FORMAT_DEFAULTS } from '../data-table-utils';
|
|
5
|
+
export const DEFAULT_XLSX_DATE_FORMATS = {
|
|
6
|
+
date: 'dd.mm.yyyy',
|
|
7
|
+
time: 'hh:mm',
|
|
8
|
+
dateTime: 'dd.mm.yyyy hh:mm'
|
|
9
|
+
};
|
|
10
|
+
const NUMBER_TYPES = new Set(['number', 'currency', 'percent']);
|
|
11
|
+
const DATE_TYPES = new Set(['date', 'time', 'date-time']);
|
|
12
|
+
const isEmpty = (value) => value === null || value === undefined || value === '';
|
|
13
|
+
function textCell(cell) {
|
|
14
|
+
return cell.text === '' ? null : { type: String, value: cell.text };
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The Excel format string matching how the table shows `value`, or undefined when the
|
|
18
|
+
* column's Intl options have no faithful Excel equivalent (the cell is then written as text).
|
|
19
|
+
* Decimals are chosen per value, exactly as many as Intl shows (read from `formatToParts`, so
|
|
20
|
+
* Intl's own rounding decides): `#,##0.###` would leave a trailing `.` on whole numbers.
|
|
21
|
+
*/
|
|
22
|
+
export function xlsxNumberFormat(column, value) {
|
|
23
|
+
const defaults = column.type ? TYPE_NUMBER_FORMAT_DEFAULTS[column.type] : undefined;
|
|
24
|
+
let formatter;
|
|
25
|
+
try {
|
|
26
|
+
formatter = new Intl.NumberFormat(column.locale, {
|
|
27
|
+
...defaults,
|
|
28
|
+
...column.formatOptions
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
const resolved = formatter.resolvedOptions();
|
|
35
|
+
const unsupported = resolved.notation !== 'standard' ||
|
|
36
|
+
resolved.minimumSignificantDigits !== undefined ||
|
|
37
|
+
resolved.maximumSignificantDigits !== undefined ||
|
|
38
|
+
resolved.signDisplay !== 'auto' ||
|
|
39
|
+
resolved.minimumIntegerDigits !== 1 ||
|
|
40
|
+
(resolved.roundingIncrement ?? 1) !== 1 ||
|
|
41
|
+
// Excel display rounds half away from zero, which is Intl's `halfExpand` only.
|
|
42
|
+
(resolved.roundingMode ?? 'halfExpand') !== 'halfExpand' ||
|
|
43
|
+
resolved.style === 'unit' ||
|
|
44
|
+
(resolved.style === 'currency' &&
|
|
45
|
+
(resolved.currencyDisplay === 'name' || resolved.currencySign === 'accounting'));
|
|
46
|
+
if (unsupported)
|
|
47
|
+
return undefined;
|
|
48
|
+
const fraction = formatter.formatToParts(value).find((part) => part.type === 'fraction');
|
|
49
|
+
const decimals = fraction?.value.length ?? 0;
|
|
50
|
+
const grouping = resolved.useGrouping === false ? '0' : '#,##0';
|
|
51
|
+
const format = decimals > 0 ? `${grouping}.${'0'.repeat(decimals)}` : grouping;
|
|
52
|
+
if (resolved.style === 'percent')
|
|
53
|
+
return `${format}%`;
|
|
54
|
+
if (resolved.style === 'currency')
|
|
55
|
+
return `${format} "${resolved.currency}"`;
|
|
56
|
+
return format;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The date as Excel should show it. Excel dates have no zone, and `write-excel-file` reads a
|
|
60
|
+
* `Date` in UTC, so the wall clock of the zone the table shows is written as UTC. A `date`
|
|
61
|
+
* column drops the time (midnight); a `time` column keeps only the time of day, on Excel's day
|
|
62
|
+
* zero (1899-12-30), so times sort and compare by time alone.
|
|
63
|
+
*/
|
|
64
|
+
export function toXlsxDate(date, part = 'date-time') {
|
|
65
|
+
const gregorian = toCalendar(date, new GregorianCalendar());
|
|
66
|
+
const year = gregorian.era === 'BC' ? 1 - gregorian.year : gregorian.year;
|
|
67
|
+
const result = new Date(0);
|
|
68
|
+
// Not Date.UTC: it reads years 0–99 as 1900–1999.
|
|
69
|
+
if (part === 'time')
|
|
70
|
+
result.setUTCFullYear(1899, 11, 30);
|
|
71
|
+
else
|
|
72
|
+
result.setUTCFullYear(year, gregorian.month - 1, gregorian.day);
|
|
73
|
+
if (part !== 'date' && 'hour' in gregorian) {
|
|
74
|
+
result.setUTCHours(gregorian.hour, gregorian.minute, gregorian.second, gregorian.millisecond);
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
function dateFormatFor(type, formats) {
|
|
79
|
+
if (type === 'time')
|
|
80
|
+
return formats.time;
|
|
81
|
+
if (type === 'date-time')
|
|
82
|
+
return formats.dateTime;
|
|
83
|
+
return formats.date;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* One export cell as a typed sheet cell: numbers, dates and booleans stay computable in Excel;
|
|
87
|
+
* everything else — and any column with its own `cell` renderer but no `exportValue` — is the
|
|
88
|
+
* text the table shows. Typed cells can't hold formulas, so no `'` guard is needed.
|
|
89
|
+
*/
|
|
90
|
+
export function toXlsxCell(column, cell, formats = DEFAULT_XLSX_DATE_FORMATS) {
|
|
91
|
+
const { type } = column;
|
|
92
|
+
if (column.hasCustomCell && !column.hasExportValue)
|
|
93
|
+
return textCell(cell);
|
|
94
|
+
if (type === undefined)
|
|
95
|
+
return textCell(cell);
|
|
96
|
+
if (NUMBER_TYPES.has(type)) {
|
|
97
|
+
if (isEmpty(cell.value))
|
|
98
|
+
return null;
|
|
99
|
+
const value = Number(cell.value);
|
|
100
|
+
if (!Number.isFinite(value))
|
|
101
|
+
return textCell(cell);
|
|
102
|
+
const format = xlsxNumberFormat(column, value);
|
|
103
|
+
return format === undefined ? textCell(cell) : { type: Number, value, format };
|
|
104
|
+
}
|
|
105
|
+
if (DATE_TYPES.has(type)) {
|
|
106
|
+
if (isEmpty(cell.value))
|
|
107
|
+
return null;
|
|
108
|
+
const formatOptions = column.formatOptions;
|
|
109
|
+
const date = readDateInZone(cell.value, dateCellTimeZone(formatOptions));
|
|
110
|
+
if (!date)
|
|
111
|
+
return textCell(cell);
|
|
112
|
+
return {
|
|
113
|
+
type: Date,
|
|
114
|
+
value: toXlsxDate(date, type),
|
|
115
|
+
format: dateFormatFor(type, formats)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (type === 'boolean') {
|
|
119
|
+
// Like the table, only a real true/false is a checkbox; anything else shows `-`.
|
|
120
|
+
return typeof cell.value === 'boolean' ? { type: Boolean, value: cell.value } : null;
|
|
121
|
+
}
|
|
122
|
+
return textCell(cell);
|
|
123
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let { class: className = '', ...restProps } = $props();
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<svg
|
|
6
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
+
width="1em"
|
|
8
|
+
height="1em"
|
|
9
|
+
viewBox="0 0 256 256"
|
|
10
|
+
class={className}
|
|
11
|
+
{...restProps}
|
|
12
|
+
>
|
|
13
|
+
<path
|
|
14
|
+
fill="currentColor"
|
|
15
|
+
d="M224 144v64a8 8 0 0 1-8 8H40a8 8 0 0 1-8-8v-64a8 8 0 0 1 16 0v56h160v-56a8 8 0 0 1 16 0m-101.66 5.66a8 8 0 0 0 11.32 0l40-40a8 8 0 0 0-11.32-11.32L136 124.69V32a8 8 0 0 0-16 0v92.69L93.66 98.34a8 8 0 0 0-11.32 11.32Z"
|
|
16
|
+
/>
|
|
17
|
+
</svg>
|
|
18
|
+
<!-- Icon from Phosphor by Phosphor Icons - https://github.com/phosphor-icons/core/blob/main/LICENSE -->
|
package/dist/icons/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export { default as PhCaretRight } from './PhCaretRight.svelte';
|
|
|
10
10
|
export { default as PhCaretUp } from './PhCaretUp.svelte';
|
|
11
11
|
export { default as PhCheck } from './PhCheck.svelte';
|
|
12
12
|
export { default as PhColumns } from './PhColumns.svelte';
|
|
13
|
+
export { default as PhDownloadSimple } from './PhDownloadSimple.svelte';
|
|
13
14
|
export { default as PhEye } from './PhEye.svelte';
|
|
14
15
|
export { default as PhEyeSlash } from './PhEyeSlash.svelte';
|
|
15
16
|
export { default as PhFile } from './PhFile.svelte';
|
package/dist/icons/index.js
CHANGED
|
@@ -10,6 +10,7 @@ export { default as PhCaretRight } from './PhCaretRight.svelte';
|
|
|
10
10
|
export { default as PhCaretUp } from './PhCaretUp.svelte';
|
|
11
11
|
export { default as PhCheck } from './PhCheck.svelte';
|
|
12
12
|
export { default as PhColumns } from './PhColumns.svelte';
|
|
13
|
+
export { default as PhDownloadSimple } from './PhDownloadSimple.svelte';
|
|
13
14
|
export { default as PhEye } from './PhEye.svelte';
|
|
14
15
|
export { default as PhEyeSlash } from './PhEyeSlash.svelte';
|
|
15
16
|
export { default as PhFile } from './PhFile.svelte';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type CsvOptions = {
|
|
2
|
+
/** Field separator. Defaults to `;`, which Excel expects under most European locales. */
|
|
3
|
+
separator?: string;
|
|
4
|
+
/**
|
|
5
|
+
* Prepend a UTF-8 BOM so Excel reads the file as UTF-8 (otherwise č/ć/š/đ/ž break).
|
|
6
|
+
* Defaults to `true`.
|
|
7
|
+
*/
|
|
8
|
+
bom?: boolean;
|
|
9
|
+
};
|
|
10
|
+
/** Quotes a field when it holds a quote, the separator or a line break (RFC 4180). */
|
|
11
|
+
export declare function escapeCsvField(value: string, separator: string): string;
|
|
12
|
+
/** Serializes rows of text as CSV, records separated by CRLF. */
|
|
13
|
+
export declare function toCsv(rows: string[][], { separator, bom }?: CsvOptions): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const BOM = '';
|
|
2
|
+
/** Quotes a field when it holds a quote, the separator or a line break (RFC 4180). */
|
|
3
|
+
export function escapeCsvField(value, separator) {
|
|
4
|
+
if (value.includes('"') || value.includes(separator) || /[\r\n]/.test(value)) {
|
|
5
|
+
return `"${value.replaceAll('"', '""')}"`;
|
|
6
|
+
}
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
/** Serializes rows of text as CSV, records separated by CRLF. */
|
|
10
|
+
export function toCsv(rows, { separator = ';', bom = true } = {}) {
|
|
11
|
+
if (separator.length === 0 || /["\r\n]/.test(separator)) {
|
|
12
|
+
throw new Error(`Invalid CSV separator: ${JSON.stringify(separator)}`);
|
|
13
|
+
}
|
|
14
|
+
const body = rows
|
|
15
|
+
.map((row) => row.map((field) => escapeCsvField(field, separator)).join(separator))
|
|
16
|
+
.join('\r\n');
|
|
17
|
+
return bom ? BOM + body : body;
|
|
18
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hands `blob` to the browser as a download named `filename`. Browser-only: call it from an
|
|
3
|
+
* event handler, never during SSR.
|
|
4
|
+
*/
|
|
5
|
+
export function downloadBlob(blob, filename) {
|
|
6
|
+
const url = URL.createObjectURL(blob);
|
|
7
|
+
const link = document.createElement('a');
|
|
8
|
+
link.href = url;
|
|
9
|
+
link.download = filename;
|
|
10
|
+
link.style.display = 'none';
|
|
11
|
+
document.body.append(link);
|
|
12
|
+
link.click();
|
|
13
|
+
link.remove();
|
|
14
|
+
// Revoking right away can cancel the download in some browsers (Safari); the URL only
|
|
15
|
+
// pins the blob in memory until then.
|
|
16
|
+
setTimeout(() => URL.revokeObjectURL(url), 10_000);
|
|
17
|
+
}
|
|
@@ -62,7 +62,12 @@ export function selectFilterOptions(facets, emptyLabel = '(empty)') {
|
|
|
62
62
|
label: value,
|
|
63
63
|
count
|
|
64
64
|
}));
|
|
65
|
-
|
|
65
|
+
// A column whose values are all numbers sorts by magnitude (so 2 < 10, -5 < 1.5); anything
|
|
66
|
+
// else sorts by label with digit runs compared numerically ("Item 2" < "Item 10").
|
|
67
|
+
const allNumbers = [...facets.keys()].every((key) => isEmpty(key) || Number.isFinite(key));
|
|
68
|
+
options.sort(allNumbers
|
|
69
|
+
? (a, b) => Number(a.value) - Number(b.value)
|
|
70
|
+
: (a, b) => a.label.localeCompare(b.label, undefined, { numeric: true }));
|
|
66
71
|
if (emptyCount > 0) {
|
|
67
72
|
options.push({ value: EMPTY_SELECT_VALUE, label: emptyLabel, count: emptyCount });
|
|
68
73
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "compote-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.85.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -53,15 +53,23 @@
|
|
|
53
53
|
"types": "./dist/components/data-table-v9/virtual/index.d.ts",
|
|
54
54
|
"svelte": "./dist/components/data-table-v9/virtual/index.js"
|
|
55
55
|
},
|
|
56
|
+
"./data-table/xlsx": {
|
|
57
|
+
"types": "./dist/components/data-table-v9/xlsx/index.d.ts",
|
|
58
|
+
"svelte": "./dist/components/data-table-v9/xlsx/index.js"
|
|
59
|
+
},
|
|
56
60
|
"./theme.css": "./dist/theme.css"
|
|
57
61
|
},
|
|
58
62
|
"peerDependencies": {
|
|
59
63
|
"@tanstack/svelte-table": ">=9.1.0 <10",
|
|
60
|
-
"svelte": "^5.0.0"
|
|
64
|
+
"svelte": "^5.0.0",
|
|
65
|
+
"write-excel-file": "^4.1.0"
|
|
61
66
|
},
|
|
62
67
|
"peerDependenciesMeta": {
|
|
63
68
|
"@tanstack/svelte-table": {
|
|
64
69
|
"optional": true
|
|
70
|
+
},
|
|
71
|
+
"write-excel-file": {
|
|
72
|
+
"optional": true
|
|
65
73
|
}
|
|
66
74
|
},
|
|
67
75
|
"devDependencies": {
|
|
@@ -80,6 +88,7 @@
|
|
|
80
88
|
"eslint": "^10.12.0",
|
|
81
89
|
"eslint-config-prettier": "^10.1.8",
|
|
82
90
|
"eslint-plugin-svelte": "^3.23.1",
|
|
91
|
+
"fflate": "^0.8.2",
|
|
83
92
|
"globals": "^17.13.0",
|
|
84
93
|
"prettier": "^3.9.10",
|
|
85
94
|
"prettier-plugin-svelte": "^4.1.1",
|
|
@@ -94,7 +103,8 @@
|
|
|
94
103
|
"unplugin-icons": "^24.0.0",
|
|
95
104
|
"vite": "^8.3.4",
|
|
96
105
|
"vitest": "^5.0.4",
|
|
97
|
-
"wrangler": "^4.151.0"
|
|
106
|
+
"wrangler": "^4.151.0",
|
|
107
|
+
"write-excel-file": "^4.1.1"
|
|
98
108
|
},
|
|
99
109
|
"keywords": [
|
|
100
110
|
"svelte",
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: data-table
|
|
3
3
|
description: >
|
|
4
|
-
Load when using compote-ui/data-table
|
|
5
|
-
Covers TanStack Table v9 wrapper usage, createTable,
|
|
4
|
+
Load when using compote-ui/data-table, compote-ui/data-table/virtual or
|
|
5
|
+
compote-ui/data-table/xlsx. Covers TanStack Table v9 wrapper usage, createTable,
|
|
6
6
|
createDataTableColumnHelper, table columns, row selection, filters,
|
|
7
|
-
formatting, toolbars, virtualized imports, and optional
|
|
7
|
+
formatting, toolbars, CSV/Excel export, virtualized imports, and optional
|
|
8
|
+
peer dependencies.
|
|
8
9
|
metadata:
|
|
9
10
|
type: composition
|
|
10
11
|
library: compote-ui
|
|
11
|
-
library_version: '0.
|
|
12
|
+
library_version: '0.84.0'
|
|
12
13
|
requires:
|
|
13
14
|
- component-usage
|
|
14
15
|
- theming
|
|
@@ -27,6 +28,12 @@ sources:
|
|
|
27
28
|
- src/lib/components/data-table-v9/column-filter-editing.svelte.ts
|
|
28
29
|
- src/lib/utils/select-filter.ts
|
|
29
30
|
- src/lib/utils/date-filter.ts
|
|
31
|
+
- src/lib/utils/csv.ts
|
|
32
|
+
- src/lib/components/data-table-v9/export/export-data.ts
|
|
33
|
+
- src/lib/components/data-table-v9/export/csv-exporter.ts
|
|
34
|
+
- src/lib/components/data-table-v9/toolbar/data-table-export.svelte
|
|
35
|
+
- src/lib/components/data-table-v9/xlsx/index.ts
|
|
36
|
+
- src/lib/components/data-table-v9/xlsx/xlsx-cell.ts
|
|
30
37
|
- src/lib/components/data-table-v9/virtual/data-table-virtualized.svelte
|
|
31
38
|
- src/lib/components/data-table-v9/virtual/data-table-virtual-rows.svelte
|
|
32
39
|
---
|
|
@@ -266,6 +273,25 @@ spins its icon and sets `aria-busy` until the promise settles. Clicks while busy
|
|
|
266
273
|
Props: `onRefresh: () => void | Promise<unknown>`, `loading?: boolean`, `triggerLabel?: string`
|
|
267
274
|
(icon-only with `aria-label="Refresh"` and a tooltip when omitted). Errors thrown by `onRefresh` are not caught.
|
|
268
275
|
|
|
276
|
+
### Export to a file
|
|
277
|
+
|
|
278
|
+
`DataTable.Export` downloads the visible columns and the filtered, sorted rows. It writes CSV by
|
|
279
|
+
default; pass `exporters` for more formats or your own (an exporter on the server, or a library
|
|
280
|
+
the app already has). With selected rows it offers all rows or only the selected ones. Excel
|
|
281
|
+
comes from `compote-ui/data-table/xlsx` and needs the optional peer `write-excel-file`.
|
|
282
|
+
|
|
283
|
+
```svelte
|
|
284
|
+
<script lang="ts">
|
|
285
|
+
import * as DataTable from 'compote-ui/data-table';
|
|
286
|
+
import { xlsxExporter } from 'compote-ui/data-table/xlsx';
|
|
287
|
+
</script>
|
|
288
|
+
|
|
289
|
+
<DataTable.Export {table} filename="invoices" exporters={[DataTable.csvExporter, xlsxExporter]} />
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Exclude a column with `enableExport: false` (action columns are excluded by default). Use
|
|
293
|
+
`exportValue` when a cell holds a value a file can't show. See [Export](references/export.md).
|
|
294
|
+
|
|
269
295
|
## Common Mistakes
|
|
270
296
|
|
|
271
297
|
### CRITICAL Missing id for accessorFn column
|
|
@@ -355,8 +381,20 @@ The virtual subpath imports `@tanstack/svelte-virtual`.
|
|
|
355
381
|
|
|
356
382
|
Source: `package.json`, `src/lib/components/data-table-v9/virtual/data-table-virtual-rows.svelte`
|
|
357
383
|
|
|
384
|
+
### HIGH Using the Excel exporter without its optional peer
|
|
385
|
+
|
|
386
|
+
`compote-ui/data-table/xlsx` imports `write-excel-file` (dynamically, on export). Without it the
|
|
387
|
+
app's build fails to resolve the import. Install it first:
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
bun add write-excel-file
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Source: `package.json`, `src/lib/components/data-table-v9/xlsx/index.ts`
|
|
394
|
+
|
|
358
395
|
## References
|
|
359
396
|
|
|
360
397
|
- [Column options](references/column-options.md)
|
|
361
398
|
- [Table state and reactivity](references/table-state-and-reactivity.md)
|
|
362
399
|
- [Virtual table](references/virtual-table.md)
|
|
400
|
+
- [Export](references/export.md)
|
|
@@ -40,6 +40,10 @@ Common options:
|
|
|
40
40
|
- `grow?: boolean`
|
|
41
41
|
- `sum?: boolean`
|
|
42
42
|
- `footer?: (values: unknown[]) => string | number | undefined`
|
|
43
|
+
- `exportValue?: (value, row) => unknown` — the value `DataTable.Export` writes instead of the
|
|
44
|
+
accessor value (e.g. a number out of an object a `cellComponent` renders). Without `cell`, its
|
|
45
|
+
text is formatted by `type`; it is never passed to `cell`. See [Export](export.md).
|
|
46
|
+
- `enableExport?: boolean` — defaults to `true`, `false` for `type: 'action'`.
|
|
43
47
|
|
|
44
48
|
Type defaults:
|
|
45
49
|
|