runeforge 0.0.18 → 0.0.20
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/crud/Field.svelte +13 -2
- package/dist/components/crud/GenericCRUD.svelte +29 -4
- package/dist/components/crud/GenericCRUD.svelte.d.ts +5 -0
- package/dist/components/crud/views/List.svelte +71 -0
- package/dist/components/crud/views/List.svelte.d.ts +9 -0
- package/dist/components/crud/views/Update.svelte +2 -1
- package/dist/components/table/PaginatedTable.svelte +16 -0
- package/dist/components/table/PaginatedTable.svelte.d.ts +8 -2
- package/dist/components/table/export.d.ts +17 -0
- package/dist/components/table/export.js +31 -0
- package/dist/i18n/en.js +3 -0
- package/dist/i18n/es.js +3 -0
- package/dist/i18n/types.d.ts +3 -0
- package/dist/icons/defaults/Download.svelte +7 -0
- package/dist/icons/defaults/Download.svelte.d.ts +7 -0
- package/dist/icons/sets/bootstrap.js +2 -1
- package/dist/icons/sets/default.js +2 -0
- package/dist/icons/types.d.ts +1 -0
- package/dist/types/attribute.d.ts +6 -0
- package/dist/types/crud.d.ts +6 -0
- package/package.json +1 -1
|
@@ -43,6 +43,16 @@
|
|
|
43
43
|
const preview = $derived(filePreview ?? (typeof saved === 'string' && saved ? saved : null));
|
|
44
44
|
const avatarInitials = $derived(initials(record.firstName as string, record.lastName as string));
|
|
45
45
|
const displayValue = $derived(saved == null ? '' : String(saved));
|
|
46
|
+
const selectOptions = $derived(field.dependentOptions ? field.dependentOptions(record) : (field.options ?? []));
|
|
47
|
+
const selectDisabled = $derived(field.disabled ? field.disabled(record) : false);
|
|
48
|
+
|
|
49
|
+
$effect(() => {
|
|
50
|
+
if (!field.dependentOptions) return;
|
|
51
|
+
const current = record[field.attribute];
|
|
52
|
+
if (current && !selectOptions.some((o) => o.value === String(current))) {
|
|
53
|
+
record[field.attribute] = '';
|
|
54
|
+
}
|
|
55
|
+
});
|
|
46
56
|
</script>
|
|
47
57
|
|
|
48
58
|
<div class="flex flex-col gap-1">
|
|
@@ -86,15 +96,16 @@
|
|
|
86
96
|
type="text"
|
|
87
97
|
id={field.attribute}
|
|
88
98
|
class="input input-bordered w-full"
|
|
89
|
-
value={
|
|
99
|
+
value={selectOptions.find((o) => o.value === String(saved))?.label ?? displayValue}
|
|
90
100
|
disabled
|
|
91
101
|
/>
|
|
92
102
|
{:else}
|
|
93
103
|
<Select
|
|
94
104
|
name={field.attribute}
|
|
95
105
|
bind:value={record[field.attribute] as string}
|
|
96
|
-
options={
|
|
106
|
+
options={selectOptions}
|
|
97
107
|
placeholder={field.placeholder}
|
|
108
|
+
disabled={selectDisabled}
|
|
98
109
|
{error}
|
|
99
110
|
/>
|
|
100
111
|
{/if}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
inferType
|
|
13
13
|
} from './utils/resolution.js';
|
|
14
14
|
import { isFilterable } from '../table/utils.js';
|
|
15
|
+
import type { XlsxModule } from '../table/export.js';
|
|
15
16
|
import type { AttributeMetadata } from '../../types/attribute.js';
|
|
16
17
|
import type {
|
|
17
18
|
ActionConfiguration,
|
|
@@ -47,7 +48,10 @@
|
|
|
47
48
|
columns = undefined as ColumnDefinition<T>[] | undefined,
|
|
48
49
|
fields = undefined as FieldDefinition<T>[] | undefined,
|
|
49
50
|
meta = undefined as Partial<Record<string, AttributeMetadata>> | undefined,
|
|
50
|
-
form = null as { error?: string } | null
|
|
51
|
+
form = null as { error?: string } | null,
|
|
52
|
+
enableExport = false,
|
|
53
|
+
onExport = undefined as ((query: TableQuery) => Promise<T[]>) | undefined,
|
|
54
|
+
xlsx = undefined as XlsxModule | undefined
|
|
51
55
|
}: {
|
|
52
56
|
data?: Record<string, unknown>;
|
|
53
57
|
dataKey?: string;
|
|
@@ -68,6 +72,9 @@
|
|
|
68
72
|
fields?: FieldDefinition<T>[];
|
|
69
73
|
meta?: Partial<Record<string, AttributeMetadata>>;
|
|
70
74
|
form?: { error?: string } | null;
|
|
75
|
+
enableExport?: boolean;
|
|
76
|
+
onExport?: (query: TableQuery) => Promise<T[]>;
|
|
77
|
+
xlsx?: XlsxModule;
|
|
71
78
|
} = $props();
|
|
72
79
|
|
|
73
80
|
function isEnvelope(value: unknown): value is PaginatedEnvelope<T> {
|
|
@@ -229,7 +236,12 @@
|
|
|
229
236
|
autocomplete: m.autocomplete,
|
|
230
237
|
placeholder: m.placeholder,
|
|
231
238
|
default: m.default,
|
|
232
|
-
options: resolveOptions(m, data)
|
|
239
|
+
options: resolveOptions(m, data),
|
|
240
|
+
dependentOptions: m.dependentOptions
|
|
241
|
+
? (record: Record<string, unknown>) => m.dependentOptions!(data, record)
|
|
242
|
+
: undefined,
|
|
243
|
+
disabled: m.disabled,
|
|
244
|
+
seed: m.seed
|
|
233
245
|
}))
|
|
234
246
|
: entityData.length > 0
|
|
235
247
|
? (Object.entries(entityData[0]) as [string, unknown][])
|
|
@@ -251,7 +263,12 @@
|
|
|
251
263
|
autocomplete: m.autocomplete,
|
|
252
264
|
placeholder: m.placeholder,
|
|
253
265
|
default: m.default,
|
|
254
|
-
options: resolveOptions(m, data)
|
|
266
|
+
options: resolveOptions(m, data),
|
|
267
|
+
dependentOptions: m.dependentOptions
|
|
268
|
+
? (record: Record<string, unknown>) => m.dependentOptions!(data, record)
|
|
269
|
+
: undefined,
|
|
270
|
+
disabled: m.disabled,
|
|
271
|
+
seed: m.seed
|
|
255
272
|
}))
|
|
256
273
|
: entityData.length > 0
|
|
257
274
|
? (Object.entries(entityData[0]) as [string, unknown][])
|
|
@@ -273,7 +290,12 @@
|
|
|
273
290
|
autocomplete: m.autocomplete,
|
|
274
291
|
placeholder: m.placeholder,
|
|
275
292
|
default: m.default,
|
|
276
|
-
options: resolveOptions(m, data)
|
|
293
|
+
options: resolveOptions(m, data),
|
|
294
|
+
dependentOptions: m.dependentOptions
|
|
295
|
+
? (record: Record<string, unknown>) => m.dependentOptions!(data, record)
|
|
296
|
+
: undefined,
|
|
297
|
+
disabled: m.disabled,
|
|
298
|
+
seed: m.seed
|
|
277
299
|
}))
|
|
278
300
|
: entityData.length > 0
|
|
279
301
|
? (Object.entries(entityData[0]) as [string, unknown][])
|
|
@@ -342,6 +364,9 @@
|
|
|
342
364
|
{initialSort}
|
|
343
365
|
{initialFilters}
|
|
344
366
|
onPaginationChange={handlePaginationChange}
|
|
367
|
+
{enableExport}
|
|
368
|
+
{onExport}
|
|
369
|
+
{xlsx}
|
|
345
370
|
onCreate={navCreate}
|
|
346
371
|
onEdit={navEdit}
|
|
347
372
|
onView={navRead}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { XlsxModule } from '../table/export.js';
|
|
1
2
|
import type { AttributeMetadata } from '../../types/attribute.js';
|
|
2
3
|
import type { ActionConfiguration, ColumnDefinition, CustomAction, CustomBulkAction, FieldDefinition, SearchConfiguration } from '../../types/crud.js';
|
|
4
|
+
import type { TableQuery } from '../../types/table.js';
|
|
3
5
|
declare function $$render<T extends object = Record<string, unknown>>(): {
|
|
4
6
|
props: {
|
|
5
7
|
data?: Record<string, unknown>;
|
|
@@ -22,6 +24,9 @@ declare function $$render<T extends object = Record<string, unknown>>(): {
|
|
|
22
24
|
form?: {
|
|
23
25
|
error?: string;
|
|
24
26
|
} | null;
|
|
27
|
+
enableExport?: boolean;
|
|
28
|
+
onExport?: (query: TableQuery) => Promise<T[]>;
|
|
29
|
+
xlsx?: XlsxModule;
|
|
25
30
|
};
|
|
26
31
|
exports: {};
|
|
27
32
|
bindings: "";
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import Modal from '../../Modal.svelte';
|
|
7
7
|
import PaginatedTable from '../../table/PaginatedTable.svelte';
|
|
8
8
|
import SearchInput from '../SearchInput.svelte';
|
|
9
|
+
import { downloadCsv, downloadXlsx, type XlsxModule } from '../../table/export.js';
|
|
9
10
|
import { getIconSet } from '../../../icons/context.js';
|
|
10
11
|
import { defaultIconSet } from '../../../icons/sets/default.js';
|
|
11
12
|
import type {
|
|
@@ -45,6 +46,9 @@
|
|
|
45
46
|
initialSort = undefined as { column: string; direction: SortDirection } | undefined,
|
|
46
47
|
initialFilters = undefined as Partial<FilterSnapshot> | undefined,
|
|
47
48
|
onPaginationChange = undefined as ((query: TableQuery) => void) | undefined,
|
|
49
|
+
enableExport = false,
|
|
50
|
+
onExport = undefined as ((query: TableQuery) => Promise<T[]>) | undefined,
|
|
51
|
+
xlsx = undefined as XlsxModule | undefined,
|
|
48
52
|
onCreate,
|
|
49
53
|
onEdit,
|
|
50
54
|
onView,
|
|
@@ -69,6 +73,14 @@
|
|
|
69
73
|
initialSort?: { column: string; direction: SortDirection };
|
|
70
74
|
initialFilters?: Partial<FilterSnapshot>;
|
|
71
75
|
onPaginationChange?: (query: TableQuery) => void;
|
|
76
|
+
/** Shows an icon-only export button (CSV, and Excel if `xlsx` is provided). */
|
|
77
|
+
enableExport?: boolean;
|
|
78
|
+
/** Server-pagination mode only: fetch all rows matching the current query
|
|
79
|
+
* (unpaginated) for export. Without it, export falls back to the loaded page. */
|
|
80
|
+
onExport?: (query: TableQuery) => Promise<T[]>;
|
|
81
|
+
/** Resolved `xlsx` (SheetJS) module, e.g. `import * as xlsx from 'xlsx'`.
|
|
82
|
+
* Enables the "Export as Excel" option; omit to only offer CSV. */
|
|
83
|
+
xlsx?: XlsxModule;
|
|
72
84
|
onCreate?: () => void;
|
|
73
85
|
onEdit?: (item: T) => void;
|
|
74
86
|
onView?: (item: T) => void;
|
|
@@ -92,6 +104,25 @@
|
|
|
92
104
|
let pendingBulkAction = $state<{ action: CustomBulkAction<T>; items: T[] } | null>(null);
|
|
93
105
|
const selectedItems = $derived([...selected].map((i) => data[i]));
|
|
94
106
|
|
|
107
|
+
let visibleRows = $state<T[]>([]);
|
|
108
|
+
let exportQuery = $state<TableQuery | undefined>(undefined);
|
|
109
|
+
let exportPopoverEl: HTMLElement | undefined = $state();
|
|
110
|
+
const exportId = $props.id();
|
|
111
|
+
|
|
112
|
+
async function resolveExportRows(): Promise<T[]> {
|
|
113
|
+
if (!pagination) return visibleRows;
|
|
114
|
+
if (!onExport || !exportQuery) return data;
|
|
115
|
+
return onExport(exportQuery);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function handleExport(format: 'csv' | 'xlsx') {
|
|
119
|
+
exportPopoverEl?.hidePopover();
|
|
120
|
+
const rows = await resolveExportRows();
|
|
121
|
+
const filename = `${labelMany || 'export'}-${new Date().toISOString().slice(0, 10)}`;
|
|
122
|
+
if (format === 'csv') downloadCsv(rows, columns, filename);
|
|
123
|
+
else if (xlsx) downloadXlsx(rows, columns, filename, xlsx);
|
|
124
|
+
}
|
|
125
|
+
|
|
95
126
|
// `selected` holds indices into `data`; if `data` is swapped for a different
|
|
96
127
|
// slice (page/sort/filter change in server mode) stale indices could point
|
|
97
128
|
// at unrelated rows, so clear on any data reference change.
|
|
@@ -230,6 +261,44 @@
|
|
|
230
261
|
<SearchInput config={search} />
|
|
231
262
|
{/if}
|
|
232
263
|
|
|
264
|
+
{#if enableExport}
|
|
265
|
+
{@const ExportIcon = icons.download}
|
|
266
|
+
<Button
|
|
267
|
+
variant="ghost"
|
|
268
|
+
class="btn-square"
|
|
269
|
+
popovertarget="export-menu-{exportId}"
|
|
270
|
+
style="anchor-name:--export-anchor-{exportId}"
|
|
271
|
+
aria-label={strings.export}
|
|
272
|
+
title={strings.export}
|
|
273
|
+
>
|
|
274
|
+
<ExportIcon class="size-4" />
|
|
275
|
+
</Button>
|
|
276
|
+
<div
|
|
277
|
+
popover="auto"
|
|
278
|
+
id="export-menu-{exportId}"
|
|
279
|
+
style="position-anchor:--export-anchor-{exportId}"
|
|
280
|
+
class="dropdown dropdown-end w-40 rounded-box border border-base-content/10 bg-base-100 p-1 shadow-lg"
|
|
281
|
+
bind:this={exportPopoverEl}
|
|
282
|
+
>
|
|
283
|
+
<Button
|
|
284
|
+
variant="ghost"
|
|
285
|
+
class="btn-sm w-full justify-start"
|
|
286
|
+
onclick={() => handleExport('csv')}
|
|
287
|
+
>
|
|
288
|
+
{strings.exportCsv}
|
|
289
|
+
</Button>
|
|
290
|
+
{#if xlsx}
|
|
291
|
+
<Button
|
|
292
|
+
variant="ghost"
|
|
293
|
+
class="btn-sm w-full justify-start"
|
|
294
|
+
onclick={() => handleExport('xlsx')}
|
|
295
|
+
>
|
|
296
|
+
{strings.exportExcel}
|
|
297
|
+
</Button>
|
|
298
|
+
{/if}
|
|
299
|
+
</div>
|
|
300
|
+
{/if}
|
|
301
|
+
|
|
233
302
|
{#each customBulkActions as bulkAction (bulkAction.label)}
|
|
234
303
|
{#if bulkAction.condition?.(selectedItems) ?? true}
|
|
235
304
|
{@const BulkIcon = bulkAction.icon}
|
|
@@ -282,6 +351,8 @@
|
|
|
282
351
|
{initialSort}
|
|
283
352
|
{initialFilters}
|
|
284
353
|
{onPaginationChange}
|
|
354
|
+
bind:visibleRows
|
|
355
|
+
bind:query={exportQuery}
|
|
285
356
|
/>
|
|
286
357
|
</div>
|
|
287
358
|
</div>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type XlsxModule } from '../../table/export.js';
|
|
1
2
|
import type { ActionConfiguration, ColumnDefinition, CustomAction, CustomBulkAction, SearchConfiguration } from '../../../types/crud.js';
|
|
2
3
|
import type { FilterSnapshot, ServerPagination, SortDirection, TableQuery } from '../../../types/table.js';
|
|
3
4
|
declare function $$render<T extends object = Record<string, unknown>>(): {
|
|
@@ -23,6 +24,14 @@ declare function $$render<T extends object = Record<string, unknown>>(): {
|
|
|
23
24
|
};
|
|
24
25
|
initialFilters?: Partial<FilterSnapshot>;
|
|
25
26
|
onPaginationChange?: (query: TableQuery) => void;
|
|
27
|
+
/** Shows an icon-only export button (CSV, and Excel if `xlsx` is provided). */
|
|
28
|
+
enableExport?: boolean;
|
|
29
|
+
/** Server-pagination mode only: fetch all rows matching the current query
|
|
30
|
+
* (unpaginated) for export. Without it, export falls back to the loaded page. */
|
|
31
|
+
onExport?: (query: TableQuery) => Promise<T[]>;
|
|
32
|
+
/** Resolved `xlsx` (SheetJS) module, e.g. `import * as xlsx from 'xlsx'`.
|
|
33
|
+
* Enables the "Export as Excel" option; omit to only offer CSV. */
|
|
34
|
+
xlsx?: XlsxModule;
|
|
26
35
|
onCreate?: () => void;
|
|
27
36
|
onEdit?: (item: T) => void;
|
|
28
37
|
onView?: (item: T) => void;
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
const seeded: Record<string, unknown> = { ...inst };
|
|
48
48
|
for (const f of fields) {
|
|
49
49
|
if (f.type !== 'boolean' && f.type !== 'file') {
|
|
50
|
-
|
|
50
|
+
const raw = f.seed ? f.seed(inst) : inst[f.attribute];
|
|
51
|
+
seeded[f.attribute] = String(raw ?? '');
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
return seeded;
|
|
@@ -30,6 +30,8 @@
|
|
|
30
30
|
initialSort = undefined as { column: string; direction: SortDirection } | undefined,
|
|
31
31
|
initialFilters = undefined as Partial<FilterSnapshot> | undefined,
|
|
32
32
|
onPaginationChange = undefined as ((query: TableQuery) => void) | undefined,
|
|
33
|
+
visibleRows = $bindable<T[]>([]),
|
|
34
|
+
query = $bindable<TableQuery | undefined>(undefined),
|
|
33
35
|
}: {
|
|
34
36
|
data?: T[];
|
|
35
37
|
columns?: ColumnDefinition<T>[];
|
|
@@ -45,6 +47,12 @@
|
|
|
45
47
|
initialSort?: { column: string; direction: SortDirection };
|
|
46
48
|
initialFilters?: Partial<FilterSnapshot>;
|
|
47
49
|
onPaginationChange?: (query: TableQuery) => void;
|
|
50
|
+
/** Filtered + sorted rows before page slicing (client mode), or the
|
|
51
|
+
* current page's rows as-is (server mode). Read-only for callers. */
|
|
52
|
+
visibleRows?: T[];
|
|
53
|
+
/** Current ordering + filters snapshot, kept in sync for callers that
|
|
54
|
+
* need to replicate the active query (e.g. exporting server-side). */
|
|
55
|
+
query?: TableQuery;
|
|
48
56
|
} = $props();
|
|
49
57
|
|
|
50
58
|
// Intentional one-time hydration of local state from the initial prop
|
|
@@ -107,6 +115,14 @@
|
|
|
107
115
|
}
|
|
108
116
|
});
|
|
109
117
|
|
|
118
|
+
// Surface the filtered+sorted rows and current query for callers (e.g. export).
|
|
119
|
+
$effect(() => {
|
|
120
|
+
visibleRows = sorted.map((e) => e.row);
|
|
121
|
+
});
|
|
122
|
+
$effect(() => {
|
|
123
|
+
query = currentQuery(displayPage);
|
|
124
|
+
});
|
|
125
|
+
|
|
110
126
|
function currentQuery(page: number): TableQuery {
|
|
111
127
|
return {
|
|
112
128
|
page,
|
|
@@ -21,9 +21,15 @@ declare function $$render<T extends object = Record<string, unknown>>(): {
|
|
|
21
21
|
};
|
|
22
22
|
initialFilters?: Partial<FilterSnapshot>;
|
|
23
23
|
onPaginationChange?: (query: TableQuery) => void;
|
|
24
|
+
/** Filtered + sorted rows before page slicing (client mode), or the
|
|
25
|
+
* current page's rows as-is (server mode). Read-only for callers. */
|
|
26
|
+
visibleRows?: T[];
|
|
27
|
+
/** Current ordering + filters snapshot, kept in sync for callers that
|
|
28
|
+
* need to replicate the active query (e.g. exporting server-side). */
|
|
29
|
+
query?: TableQuery;
|
|
24
30
|
};
|
|
25
31
|
exports: {};
|
|
26
|
-
bindings: "selected";
|
|
32
|
+
bindings: "selected" | "visibleRows" | "query";
|
|
27
33
|
slots: {};
|
|
28
34
|
events: {};
|
|
29
35
|
};
|
|
@@ -31,7 +37,7 @@ declare class __sveltets_Render<T extends object = Record<string, unknown>> {
|
|
|
31
37
|
props(): ReturnType<typeof $$render<T>>['props'];
|
|
32
38
|
events(): ReturnType<typeof $$render<T>>['events'];
|
|
33
39
|
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
34
|
-
bindings(): "selected";
|
|
40
|
+
bindings(): "selected" | "visibleRows" | "query";
|
|
35
41
|
exports(): {};
|
|
36
42
|
}
|
|
37
43
|
interface $$IsomorphicComponent {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ColumnDefinition } from '../../types/crud.js';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal duck-typed subset of the SheetJS (`xlsx`) module API used for
|
|
4
|
+
* building a workbook. Runeforge never imports `xlsx` itself — the consumer
|
|
5
|
+
* installs it and passes the resolved module in, so the dependency stays
|
|
6
|
+
* fully optional and never affects consumers who don't use Excel export.
|
|
7
|
+
*/
|
|
8
|
+
export interface XlsxModule {
|
|
9
|
+
utils: {
|
|
10
|
+
aoa_to_sheet: (data: unknown[][]) => unknown;
|
|
11
|
+
book_new: () => unknown;
|
|
12
|
+
book_append_sheet: (workbook: unknown, worksheet: unknown, name?: string) => void;
|
|
13
|
+
};
|
|
14
|
+
writeFile: (workbook: unknown, filename: string) => void;
|
|
15
|
+
}
|
|
16
|
+
export declare function downloadCsv<T extends object>(rows: T[], columns: ColumnDefinition<T>[], filename: string): void;
|
|
17
|
+
export declare function downloadXlsx<T extends object>(rows: T[], columns: ColumnDefinition<T>[], filename: string, xlsx: XlsxModule): void;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { cellRenderedText } from './utils.js';
|
|
2
|
+
function buildTable(rows, columns) {
|
|
3
|
+
const headers = columns.map((col) => col.title ?? col.attribute);
|
|
4
|
+
const body = rows.map((row) => columns.map((col) => cellRenderedText(row, col)));
|
|
5
|
+
return { headers, body };
|
|
6
|
+
}
|
|
7
|
+
function escapeCsvCell(value) {
|
|
8
|
+
return /[",\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
|
|
9
|
+
}
|
|
10
|
+
function triggerDownload(blob, filename) {
|
|
11
|
+
const url = URL.createObjectURL(blob);
|
|
12
|
+
const link = document.createElement('a');
|
|
13
|
+
link.href = url;
|
|
14
|
+
link.download = filename;
|
|
15
|
+
link.click();
|
|
16
|
+
URL.revokeObjectURL(url);
|
|
17
|
+
}
|
|
18
|
+
export function downloadCsv(rows, columns, filename) {
|
|
19
|
+
const { headers, body } = buildTable(rows, columns);
|
|
20
|
+
const csv = [headers, ...body]
|
|
21
|
+
.map((line) => line.map(escapeCsvCell).join(','))
|
|
22
|
+
.join('\r\n');
|
|
23
|
+
triggerDownload(new Blob([csv], { type: 'text/csv;charset=utf-8;' }), `${filename}.csv`);
|
|
24
|
+
}
|
|
25
|
+
export function downloadXlsx(rows, columns, filename, xlsx) {
|
|
26
|
+
const { headers, body } = buildTable(rows, columns);
|
|
27
|
+
const worksheet = xlsx.utils.aoa_to_sheet([headers, ...body]);
|
|
28
|
+
const workbook = xlsx.utils.book_new();
|
|
29
|
+
xlsx.utils.book_append_sheet(workbook, worksheet);
|
|
30
|
+
xlsx.writeFile(workbook, `${filename}.xlsx`);
|
|
31
|
+
}
|
package/dist/i18n/en.js
CHANGED
|
@@ -16,6 +16,9 @@ export const en = {
|
|
|
16
16
|
delete: 'Delete',
|
|
17
17
|
create: 'Create',
|
|
18
18
|
searchPlaceholder: 'Search...',
|
|
19
|
+
export: 'Export',
|
|
20
|
+
exportCsv: 'Export as CSV',
|
|
21
|
+
exportExcel: 'Export as Excel',
|
|
19
22
|
save: 'Save',
|
|
20
23
|
saveAndContinue: 'Save and continue',
|
|
21
24
|
cancel: 'Cancel',
|
package/dist/i18n/es.js
CHANGED
|
@@ -16,6 +16,9 @@ export const es = {
|
|
|
16
16
|
delete: 'Eliminar',
|
|
17
17
|
create: 'Crear',
|
|
18
18
|
searchPlaceholder: 'Buscar...',
|
|
19
|
+
export: 'Exportar',
|
|
20
|
+
exportCsv: 'Exportar a CSV',
|
|
21
|
+
exportExcel: 'Exportar a Excel',
|
|
19
22
|
save: 'Guardar',
|
|
20
23
|
saveAndContinue: 'Guardar y continuar',
|
|
21
24
|
cancel: 'Cancelar',
|
package/dist/i18n/types.d.ts
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let { size = '1em', class: cls = '' }: { size?: string | number; class?: string } = $props();
|
|
3
|
+
</script>
|
|
4
|
+
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 16 16" fill="currentColor" class={cls}>
|
|
5
|
+
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5"/>
|
|
6
|
+
<path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708z"/>
|
|
7
|
+
</svg>
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* setIconSet(bootstrapIconSet);
|
|
11
11
|
*/
|
|
12
12
|
import * as Icons from 'svelte-bootstrap-icons';
|
|
13
|
-
const { ChevronExpand, CaretUpFill, CaretDownFill, Funnel, FunnelFill, Plus, Eye, PencilSquare, Trash3, HouseDoor, Folder, EyeSlash, X, } = Icons;
|
|
13
|
+
const { ChevronExpand, CaretUpFill, CaretDownFill, Funnel, FunnelFill, Plus, Eye, PencilSquare, Trash3, HouseDoor, Folder, EyeSlash, X, Download, } = Icons;
|
|
14
14
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
15
|
function asIcon(c) { return c; }
|
|
16
16
|
export const bootstrapIconSet = {
|
|
@@ -28,6 +28,7 @@ export const bootstrapIconSet = {
|
|
|
28
28
|
folder: asIcon(Folder),
|
|
29
29
|
passwordShow: asIcon(Eye),
|
|
30
30
|
passwordHide: asIcon(EyeSlash),
|
|
31
|
+
download: asIcon(Download),
|
|
31
32
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
33
|
getByName: (name) => asIcon(Icons[name]) ?? null,
|
|
33
34
|
};
|
|
@@ -12,6 +12,7 @@ import Home from '../defaults/Home.svelte';
|
|
|
12
12
|
import Folder from '../defaults/Folder.svelte';
|
|
13
13
|
import PasswordShow from '../defaults/PasswordShow.svelte';
|
|
14
14
|
import PasswordHide from '../defaults/PasswordHide.svelte';
|
|
15
|
+
import Download from '../defaults/Download.svelte';
|
|
15
16
|
export const defaultIconSet = {
|
|
16
17
|
sortNone: SortNone,
|
|
17
18
|
sortAsc: SortAsc,
|
|
@@ -27,4 +28,5 @@ export const defaultIconSet = {
|
|
|
27
28
|
folder: Folder,
|
|
28
29
|
passwordShow: PasswordShow,
|
|
29
30
|
passwordHide: PasswordHide,
|
|
31
|
+
download: Download,
|
|
30
32
|
};
|
package/dist/icons/types.d.ts
CHANGED
|
@@ -19,10 +19,16 @@ export type SelectOption = {
|
|
|
19
19
|
};
|
|
20
20
|
export type OptionsResolver = SelectOption[] | ((data: any) => SelectOption[]);
|
|
21
21
|
export type FormatterResolver = (data?: any) => CellFormatter<any, any>;
|
|
22
|
+
export type DependentOptionsResolver = (data: any, record: Record<string, unknown>) => SelectOption[];
|
|
23
|
+
export type DisabledResolver = (record: Record<string, unknown>) => boolean;
|
|
24
|
+
export type SeedResolver = (instance: any) => unknown;
|
|
22
25
|
export type AttributeMetadata = {
|
|
23
26
|
label?: string;
|
|
24
27
|
type?: AttributeType;
|
|
25
28
|
options?: OptionsResolver;
|
|
29
|
+
dependentOptions?: DependentOptionsResolver;
|
|
30
|
+
disabled?: DisabledResolver;
|
|
31
|
+
seed?: SeedResolver;
|
|
26
32
|
component?: CellComponent<any, any>;
|
|
27
33
|
formatter?: FormatterResolver;
|
|
28
34
|
required?: boolean;
|
package/dist/types/crud.d.ts
CHANGED
|
@@ -25,6 +25,12 @@ export interface FieldDefinition<T extends object = Record<string, unknown>> {
|
|
|
25
25
|
value: string;
|
|
26
26
|
label: string;
|
|
27
27
|
}[];
|
|
28
|
+
dependentOptions?: (record: Record<string, unknown>) => {
|
|
29
|
+
value: string;
|
|
30
|
+
label: string;
|
|
31
|
+
}[];
|
|
32
|
+
disabled?: (record: Record<string, unknown>) => boolean;
|
|
33
|
+
seed?: (instance: any) => unknown;
|
|
28
34
|
}
|
|
29
35
|
export interface ActionConfiguration<T extends object = Record<string, unknown>> {
|
|
30
36
|
enabled?: boolean;
|