compote-ui 0.82.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/data-table.svelte +6 -1
- package/dist/components/data-table-v9/data-table.svelte.d.ts +3 -0
- 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/data-table-virtual-rows.svelte +13 -4
- package/dist/components/data-table-v9/virtual/data-table-virtual-rows.svelte.d.ts +2 -0
- package/dist/components/data-table-v9/virtual/data-table-virtualized.svelte +4 -0
- package/dist/components/data-table-v9/virtual/data-table-virtualized.svelte.d.ts +2 -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 -175
- package/skills/data-table/references/export.md +158 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { RowData } from '@tanstack/svelte-table';
|
|
2
|
+
import { type ClassValue } from 'tailwind-variants';
|
|
2
3
|
import type { DataTableInstance } from '../data-table-utils';
|
|
3
4
|
import type { DataTableViewState } from '../table-view-state.svelte';
|
|
4
5
|
declare function $$render<T extends RowData>(): {
|
|
@@ -15,6 +16,7 @@ declare function $$render<T extends RowData>(): {
|
|
|
15
16
|
row: T;
|
|
16
17
|
event: MouseEvent;
|
|
17
18
|
}) => void;
|
|
19
|
+
rowClass?: (row: T) => ClassValue;
|
|
18
20
|
};
|
|
19
21
|
exports: {};
|
|
20
22
|
bindings: "";
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
headerFilters?: boolean;
|
|
19
19
|
onRowClick?: (details: { row: T; event: MouseEvent }) => void;
|
|
20
20
|
onRowDoubleClick?: (details: { row: T; event: MouseEvent }) => void;
|
|
21
|
+
/** Extra classes for a row's `<tr>`; `row` is `row.original`. */
|
|
22
|
+
rowClass?: (row: T) => ClassValue;
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
let {
|
|
@@ -28,6 +30,7 @@
|
|
|
28
30
|
headerFilters = false,
|
|
29
31
|
onRowClick,
|
|
30
32
|
onRowDoubleClick,
|
|
33
|
+
rowClass,
|
|
31
34
|
...rest
|
|
32
35
|
}: Props = $props();
|
|
33
36
|
|
|
@@ -64,6 +67,7 @@
|
|
|
64
67
|
{emptyMessage}
|
|
65
68
|
{onRowClick}
|
|
66
69
|
{onRowDoubleClick}
|
|
70
|
+
{rowClass}
|
|
67
71
|
/>
|
|
68
72
|
{/if}
|
|
69
73
|
{#if view.hasFooter}
|
|
@@ -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;
|
|
@@ -0,0 +1,60 @@
|
|
|
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
|
+
// Roughly how many px a character of Excel's default font takes.
|
|
6
|
+
const PX_PER_CHAR = 7;
|
|
7
|
+
/** The export data as sheet rows: a bold header row, then typed cells. */
|
|
8
|
+
export function exportDataToSheet(data, formats = DEFAULT_XLSX_DATE_FORMATS) {
|
|
9
|
+
const header = data.columns.map((column) => ({
|
|
10
|
+
type: String,
|
|
11
|
+
value: column.header,
|
|
12
|
+
fontWeight: 'bold'
|
|
13
|
+
}));
|
|
14
|
+
const rows = data.rows.map((cells) => data.columns.map((column, index) => {
|
|
15
|
+
const cell = cells[index];
|
|
16
|
+
return cell ? toXlsxCell(column, cell, formats) : null;
|
|
17
|
+
}));
|
|
18
|
+
return [header, ...rows];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The export data as an .xlsx file. Loads `write-excel-file` (an optional peer dependency) on
|
|
22
|
+
* first call, so importing this module costs nothing until an export runs — and stays SSR-safe.
|
|
23
|
+
*/
|
|
24
|
+
export async function exportDataToXlsxBlob(data, options = {}) {
|
|
25
|
+
const { default: writeXlsxFile } = await import('write-excel-file/browser');
|
|
26
|
+
const formats = { ...DEFAULT_XLSX_DATE_FORMATS, ...pickFormats(options) };
|
|
27
|
+
const sheetData = exportDataToSheet(data, formats);
|
|
28
|
+
// XlsxCell is a subset of the package's cell shape; cast via its own (type-only) type.
|
|
29
|
+
return writeXlsxFile(sheetData, {
|
|
30
|
+
sheet: options.sheet,
|
|
31
|
+
stickyRowsCount: 1,
|
|
32
|
+
columns: data.columns.map((column) => ({
|
|
33
|
+
width: Math.max(8, Math.round(column.size / PX_PER_CHAR))
|
|
34
|
+
}))
|
|
35
|
+
}).toBlob();
|
|
36
|
+
}
|
|
37
|
+
function pickFormats({ date, time, dateTime }) {
|
|
38
|
+
return {
|
|
39
|
+
...(date !== undefined ? { date } : {}),
|
|
40
|
+
...(time !== undefined ? { time } : {}),
|
|
41
|
+
...(dateTime !== undefined ? { dateTime } : {})
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* An Excel exporter: numbers, dates and booleans as typed cells, formatted like the table.
|
|
46
|
+
* Needs `write-excel-file` installed.
|
|
47
|
+
*/
|
|
48
|
+
export function createXlsxExporter(options = {}) {
|
|
49
|
+
const { id = 'xlsx', label = 'Excel' } = options;
|
|
50
|
+
return {
|
|
51
|
+
id,
|
|
52
|
+
label,
|
|
53
|
+
icon: PhMicrosoftExcelLogo,
|
|
54
|
+
export: async ({ filename, getData }) => {
|
|
55
|
+
const blob = await exportDataToXlsxBlob(getData(), options);
|
|
56
|
+
downloadBlob(blob, `${filename}.xlsx`);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
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
|
}
|