runeforge 0.0.52 → 0.0.54

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.
Files changed (46) hide show
  1. package/README.md +128 -26
  2. package/dist/components/crud/GenericCRUD.svelte +18 -23
  3. package/dist/components/crud/GenericCRUD.svelte.d.ts +6 -9
  4. package/dist/components/crud/utils/embedded.d.ts +1 -0
  5. package/dist/components/crud/utils/embedded.js +14 -0
  6. package/dist/components/crud/utils/formatters.d.ts +0 -1
  7. package/dist/components/crud/utils/formatters.js +0 -6
  8. package/dist/components/crud/utils/reorder.d.ts +4 -0
  9. package/dist/components/crud/utils/reorder.js +13 -0
  10. package/dist/components/crud/utils/resolution.d.ts +1 -0
  11. package/dist/components/crud/utils/resolution.js +11 -0
  12. package/dist/components/crud/views/Create.svelte +2 -1
  13. package/dist/components/crud/views/Update.svelte +2 -2
  14. package/dist/components/crud/views/list/List.svelte +291 -0
  15. package/dist/components/crud/views/{List.svelte.d.ts → list/List.svelte.d.ts} +4 -14
  16. package/dist/components/crud/views/list/Modals.svelte +56 -0
  17. package/dist/components/crud/views/list/Modals.svelte.d.ts +36 -0
  18. package/dist/components/crud/views/list/Table.svelte +176 -0
  19. package/dist/components/crud/views/list/Table.svelte.d.ts +59 -0
  20. package/dist/components/crud/views/list/Toolbar.svelte +341 -0
  21. package/dist/components/crud/views/list/Toolbar.svelte.d.ts +46 -0
  22. package/dist/components/table/PaginatedTable.svelte +361 -16
  23. package/dist/components/table/PaginatedTable.svelte.d.ts +11 -2
  24. package/dist/components/table/TableBody.svelte +83 -3
  25. package/dist/components/table/TableBody.svelte.d.ts +14 -1
  26. package/dist/components/table/TableHeader.svelte +9 -4
  27. package/dist/components/table/TableHeader.svelte.d.ts +1 -0
  28. package/dist/components/table/sortable.d.ts +27 -0
  29. package/dist/components/table/sortable.js +55 -0
  30. package/dist/components/table/utils.d.ts +19 -1
  31. package/dist/components/table/utils.js +40 -0
  32. package/dist/i18n/en.js +2 -0
  33. package/dist/i18n/es.js +2 -0
  34. package/dist/i18n/types.d.ts +2 -0
  35. package/dist/icons/defaults/Grip.svelte +7 -0
  36. package/dist/icons/defaults/Grip.svelte.d.ts +7 -0
  37. package/dist/icons/sets/bootstrap.js +2 -1
  38. package/dist/icons/sets/default.js +2 -0
  39. package/dist/icons/types.d.ts +3 -0
  40. package/dist/index.d.ts +7 -6
  41. package/dist/index.js +5 -4
  42. package/dist/types/attribute.d.ts +8 -0
  43. package/dist/types/crud.d.ts +79 -2
  44. package/dist/types/table.d.ts +56 -0
  45. package/package.json +3 -1
  46. package/dist/components/crud/views/List.svelte +0 -584
@@ -0,0 +1,291 @@
1
+ <script lang="ts" generics="T extends object = Record<string, unknown>">
2
+ import { SvelteSet } from 'svelte/reactivity';
3
+ import { invalidateAll } from '$app/navigation';
4
+ import Toolbar from './Toolbar.svelte';
5
+ import Table from './Table.svelte';
6
+ import Modals from './Modals.svelte';
7
+ import { computeReorderChanges } from '../../utils/reorder.js';
8
+ import type {
9
+ ActionConfiguration,
10
+ ColumnDefinition,
11
+ CustomAction,
12
+ CustomBulkAction,
13
+ ListActions,
14
+ ListConfig,
15
+ ViewBasedCustomBulkAction
16
+ } from '../../../../types/crud.js';
17
+ import type {
18
+ FilterSnapshot,
19
+ ServerPagination,
20
+ SortDirection,
21
+ TableQuery
22
+ } from '../../../../types/table.js';
23
+ import { getStrings } from '../../../../i18n/context.js';
24
+
25
+ const strings = getStrings();
26
+
27
+ function isViewBasedBulkAction<U extends object>(
28
+ action: CustomBulkAction<U>
29
+ ): action is ViewBasedCustomBulkAction<U> {
30
+ return action.kind === 'view';
31
+ }
32
+
33
+ let {
34
+ data = [] as T[],
35
+ labelOne = '',
36
+ labelMany = '',
37
+ icon,
38
+ pageSize = 10,
39
+ idKey = '_id',
40
+ creation = {} as ActionConfiguration<T>,
41
+ update = {} as ActionConfiguration<T>,
42
+ read = {} as ActionConfiguration<T>,
43
+ deletion = {} as ActionConfiguration<T>,
44
+ actions = {} as ListActions<T>,
45
+ config = {} as ListConfig<T>,
46
+ columns = [] as ColumnDefinition<T>[],
47
+ pagination = undefined as ServerPagination | undefined,
48
+ initialSort = undefined as { column: string; direction: SortDirection } | undefined,
49
+ initialFilters = undefined as Partial<FilterSnapshot> | undefined,
50
+ onPaginationChange = undefined as ((query: TableQuery) => void) | undefined,
51
+ onCreate,
52
+ onEdit,
53
+ onView,
54
+ onAction,
55
+ onBulkAction
56
+ }: {
57
+ data?: T[];
58
+ labelOne?: string;
59
+ labelMany?: string;
60
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
+ icon?: any;
62
+ pageSize?: number;
63
+ idKey?: string;
64
+ creation?: ActionConfiguration<T>;
65
+ update?: ActionConfiguration<T>;
66
+ read?: ActionConfiguration<T>;
67
+ deletion?: ActionConfiguration<T>;
68
+ actions?: ListActions<T>;
69
+ config?: ListConfig<T>;
70
+ columns?: ColumnDefinition<T>[];
71
+ pagination?: ServerPagination;
72
+ initialSort?: { column: string; direction: SortDirection };
73
+ initialFilters?: Partial<FilterSnapshot>;
74
+ onPaginationChange?: (query: TableQuery) => void;
75
+ onCreate?: () => void;
76
+ onEdit?: (item: T) => void;
77
+ onView?: (item: T) => void;
78
+ onAction?: (action: CustomAction<T>, item: T) => void;
79
+ onBulkAction?: (action: ViewBasedCustomBulkAction<T>, items: T[]) => void;
80
+ } = $props();
81
+
82
+ const customActions = $derived(actions.custom ?? []);
83
+ const bulkActions = $derived(actions.bulk ?? []);
84
+ const searchConfig = $derived(config.search);
85
+ const exportConfig = $derived(config.export);
86
+ const reorderConfig = $derived(config.reorder);
87
+
88
+ const allowRead = $derived(read.enabled ?? true);
89
+ const allowUpdate = $derived(update.enabled ?? true);
90
+ const allowDelete = $derived(deletion.enabled ?? true);
91
+ const allowCreate = $derived(creation.enabled ?? true);
92
+ const deleteLabel = $derived(deletion.label ?? strings.delete);
93
+ const updateLabel = $derived(update.label ?? strings.edit);
94
+ const readLabel = $derived(read.label ?? strings.view);
95
+ const allowSelection = $derived(
96
+ allowDelete || bulkActions.some((action) => !isViewBasedBulkAction(action))
97
+ );
98
+
99
+ // SvelteSet is already reactive on its own; passing it through Table's
100
+ // (and PaginatedTable's) own `bind:selected` makes svelte-check's
101
+ // non_reactive_update warning fire here too — a false positive, per the
102
+ // same reasoning as the "intentional one-time hydration" notes elsewhere
103
+ // in this codebase.
104
+ let selected = new SvelteSet<number>();
105
+ let pendingDeletion = $state<T[] | null>(null);
106
+ let pendingBulkAction = $state<{ action: CustomBulkAction<T>; items: T[] } | null>(null);
107
+ const selectedItems = $derived([...selected].map((i) => data[i]));
108
+
109
+ let visibleRows = $state<T[]>([]);
110
+ let exportQuery = $state<TableQuery | undefined>(undefined);
111
+
112
+ // `selected` holds indices into `data`; if `data` is swapped for a different
113
+ // slice (page/sort/filter change in server mode) stale indices could point
114
+ // at unrelated rows, so clear on any data reference change.
115
+ let lastData: T[] | undefined;
116
+ $effect(() => {
117
+ if (data !== lastData) {
118
+ selected.clear();
119
+ lastData = data;
120
+ }
121
+ });
122
+
123
+ async function runEndpointAction(
124
+ endpoint: string,
125
+ items: T[],
126
+ extraFields?: (item: T) => Record<string, string>
127
+ ) {
128
+ await Promise.all(
129
+ items.map((item) => {
130
+ const fd = new FormData();
131
+ fd.set('id', String((item as Record<string, unknown>)[idKey] ?? ''));
132
+ for (const [k, v] of Object.entries(extraFields?.(item) ?? {})) fd.set(k, v);
133
+ return fetch(endpoint, { method: 'POST', body: fd });
134
+ })
135
+ );
136
+ await invalidateAll();
137
+ }
138
+
139
+ /** One POST for every row that moved, instead of `runEndpointAction`'s one
140
+ * request per row — a drag that shifts N rows only settles once (on
141
+ * release), but N rows changing means N *parallel* requests fire at that
142
+ * moment, which reads a lot like "one per row crossed" from the network
143
+ * tab. FormData field `changes` carries a JSON array of `{id, value}`
144
+ * pairs, one per row whose `attribute` changed. */
145
+ async function runBatchEndpointAction(endpoint: string, items: T[], attribute: keyof T & string) {
146
+ const fd = new FormData();
147
+ fd.set(
148
+ 'changes',
149
+ JSON.stringify(
150
+ items.map((item) => ({
151
+ id: String((item as Record<string, unknown>)[idKey] ?? ''),
152
+ value: (item as Record<string, unknown>)[attribute]
153
+ }))
154
+ )
155
+ );
156
+ await fetch(endpoint, { method: 'POST', body: fd });
157
+ await invalidateAll();
158
+ }
159
+
160
+ async function runDeletion(items: T[]) {
161
+ if (deletion.endpoint) {
162
+ await runEndpointAction(deletion.endpoint, items);
163
+ } else {
164
+ await deletion.callback?.(items);
165
+ }
166
+ }
167
+
168
+ function requestDeletion(items: T[]) {
169
+ if (deletion.confirm) {
170
+ pendingDeletion = items;
171
+ } else {
172
+ runDeletion(items);
173
+ }
174
+ }
175
+
176
+ function handleDeleteSelected() {
177
+ const items = [...selected].map((i) => data[i]);
178
+ selected.clear();
179
+ requestDeletion(items);
180
+ }
181
+
182
+ async function confirmDeletion() {
183
+ if (pendingDeletion) {
184
+ await runDeletion(pendingDeletion);
185
+ pendingDeletion = null;
186
+ }
187
+ }
188
+
189
+ async function runBulkAction(action: CustomBulkAction<T>, items: T[]) {
190
+ if (isViewBasedBulkAction(action)) return;
191
+ await runEndpointAction(action.endpoint, items);
192
+ }
193
+
194
+ function requestBulkAction(action: CustomBulkAction<T>, items: T[]) {
195
+ if (!isViewBasedBulkAction(action) && action.confirm) {
196
+ pendingBulkAction = { action, items };
197
+ } else {
198
+ runBulkAction(action, items);
199
+ }
200
+ }
201
+
202
+ function handleBulkAction(action: CustomBulkAction<T>) {
203
+ const items = selectedItems;
204
+ if (isViewBasedBulkAction(action)) {
205
+ onBulkAction?.(action, items);
206
+ return;
207
+ }
208
+ selected.clear();
209
+ requestBulkAction(action, items);
210
+ }
211
+
212
+ async function confirmBulkAction() {
213
+ if (pendingBulkAction) {
214
+ await runBulkAction(pendingBulkAction.action, pendingBulkAction.items);
215
+ pendingBulkAction = null;
216
+ }
217
+ }
218
+
219
+ async function handleReorder(rows: T[]) {
220
+ const cfg = reorderConfig;
221
+ if (!cfg) return;
222
+ const changed = computeReorderChanges(rows, cfg.attribute);
223
+ if (changed.length === 0) return;
224
+ if (cfg.endpoint) {
225
+ await runBatchEndpointAction(cfg.endpoint, changed, cfg.attribute);
226
+ } else {
227
+ await cfg.callback?.(changed);
228
+ }
229
+ }
230
+ </script>
231
+
232
+ <div class="flex flex-col gap-6">
233
+ <Toolbar
234
+ {labelOne}
235
+ {labelMany}
236
+ {icon}
237
+ {creation}
238
+ {allowCreate}
239
+ {allowDelete}
240
+ {deleteLabel}
241
+ search={searchConfig}
242
+ {exportConfig}
243
+ {bulkActions}
244
+ {columns}
245
+ {pagination}
246
+ {visibleRows}
247
+ {exportQuery}
248
+ selectedCount={selected.size}
249
+ {selectedItems}
250
+ {onCreate}
251
+ onDeleteSelected={handleDeleteSelected}
252
+ onBulkAction={handleBulkAction}
253
+ />
254
+
255
+ <Table
256
+ {data}
257
+ {columns}
258
+ {pageSize}
259
+ selectable={allowSelection}
260
+ bind:selected
261
+ {pagination}
262
+ {initialSort}
263
+ {initialFilters}
264
+ {onPaginationChange}
265
+ bind:visibleRows
266
+ bind:query={exportQuery}
267
+ {customActions}
268
+ {allowRead}
269
+ {allowUpdate}
270
+ {allowDelete}
271
+ {readLabel}
272
+ {updateLabel}
273
+ {deleteLabel}
274
+ reorder={reorderConfig}
275
+ {onView}
276
+ {onEdit}
277
+ {onAction}
278
+ onRequestDeletion={(item) => requestDeletion([item])}
279
+ onReorder={handleReorder}
280
+ />
281
+ </div>
282
+
283
+ <Modals
284
+ {deleteLabel}
285
+ {pendingDeletion}
286
+ {pendingBulkAction}
287
+ onCancelDeletion={() => (pendingDeletion = null)}
288
+ onConfirmDeletion={confirmDeletion}
289
+ onCancelBulkAction={() => (pendingBulkAction = null)}
290
+ onConfirmBulkAction={confirmBulkAction}
291
+ />
@@ -1,6 +1,5 @@
1
- import { type XlsxModule } from '../../table/export.js';
2
- import type { ActionConfiguration, ColumnDefinition, CustomAction, CustomBulkAction, SearchConfiguration, ViewBasedCustomBulkAction } from '../../../types/crud.js';
3
- import type { FilterSnapshot, ServerPagination, SortDirection, TableQuery } from '../../../types/table.js';
1
+ import type { ActionConfiguration, ColumnDefinition, CustomAction, ListActions, ListConfig, ViewBasedCustomBulkAction } from '../../../../types/crud.js';
2
+ import type { FilterSnapshot, ServerPagination, SortDirection, TableQuery } from '../../../../types/table.js';
4
3
  declare function $$render<T extends object = Record<string, unknown>>(): {
5
4
  props: {
6
5
  data?: T[];
@@ -13,9 +12,8 @@ declare function $$render<T extends object = Record<string, unknown>>(): {
13
12
  update?: ActionConfiguration<T>;
14
13
  read?: ActionConfiguration<T>;
15
14
  deletion?: ActionConfiguration<T>;
16
- actions?: CustomAction<T>[];
17
- customBulkActions?: CustomBulkAction<T>[];
18
- search?: SearchConfiguration;
15
+ actions?: ListActions<T>;
16
+ config?: ListConfig<T>;
19
17
  columns?: ColumnDefinition<T>[];
20
18
  pagination?: ServerPagination;
21
19
  initialSort?: {
@@ -24,14 +22,6 @@ declare function $$render<T extends object = Record<string, unknown>>(): {
24
22
  };
25
23
  initialFilters?: Partial<FilterSnapshot>;
26
24
  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;
35
25
  onCreate?: () => void;
36
26
  onEdit?: (item: T) => void;
37
27
  onView?: (item: T) => void;
@@ -0,0 +1,56 @@
1
+ <script lang="ts" generics="T extends object = Record<string, unknown>">
2
+ import Button from '../../../form/Button.svelte';
3
+ import Modal from '../../../Modal.svelte';
4
+ import type { CustomBulkAction } from '../../../../types/crud.js';
5
+ import { getStrings } from '../../../../i18n/context.js';
6
+
7
+ const strings = getStrings();
8
+
9
+ let {
10
+ deleteLabel = '',
11
+ pendingDeletion = null as T[] | null,
12
+ pendingBulkAction = null as { action: CustomBulkAction<T>; items: T[] } | null,
13
+ onCancelDeletion,
14
+ onConfirmDeletion,
15
+ onCancelBulkAction,
16
+ onConfirmBulkAction
17
+ }: {
18
+ deleteLabel?: string;
19
+ pendingDeletion?: T[] | null;
20
+ pendingBulkAction?: { action: CustomBulkAction<T>; items: T[] } | null;
21
+ onCancelDeletion?: () => void;
22
+ onConfirmDeletion?: () => void;
23
+ onCancelBulkAction?: () => void;
24
+ onConfirmBulkAction?: () => void;
25
+ } = $props();
26
+ </script>
27
+
28
+ {#if pendingDeletion !== null}
29
+ <Modal title={deleteLabel} onClose={onCancelDeletion}>
30
+ <p>{strings.deleteConfirm(pendingDeletion.length, deleteLabel)}</p>
31
+ <div class="flex justify-end gap-2 mt-4">
32
+ <Button variant="ghost" onclick={onCancelDeletion}>
33
+ {strings.cancel}
34
+ </Button>
35
+ <Button variant="error" onclick={onConfirmDeletion}>
36
+ {strings.confirm}
37
+ </Button>
38
+ </div>
39
+ </Modal>
40
+ {/if}
41
+
42
+ {#if pendingBulkAction !== null}
43
+ {@const pendingActionLabel =
44
+ pendingBulkAction.action.label ?? pendingBulkAction.action.tooltip ?? strings.actions}
45
+ <Modal title={pendingActionLabel} onClose={onCancelBulkAction}>
46
+ <p>{strings.deleteConfirm(pendingBulkAction.items.length, pendingActionLabel)}</p>
47
+ <div class="flex justify-end gap-2 mt-4">
48
+ <Button variant="ghost" onclick={onCancelBulkAction}>
49
+ {strings.cancel}
50
+ </Button>
51
+ <Button variant={pendingBulkAction.action.variant ?? 'primary'} onclick={onConfirmBulkAction}>
52
+ {strings.confirm}
53
+ </Button>
54
+ </div>
55
+ </Modal>
56
+ {/if}
@@ -0,0 +1,36 @@
1
+ import type { CustomBulkAction } from '../../../../types/crud.js';
2
+ declare function $$render<T extends object = Record<string, unknown>>(): {
3
+ props: {
4
+ deleteLabel?: string;
5
+ pendingDeletion?: T[] | null;
6
+ pendingBulkAction?: {
7
+ action: CustomBulkAction<T>;
8
+ items: T[];
9
+ } | null;
10
+ onCancelDeletion?: () => void;
11
+ onConfirmDeletion?: () => void;
12
+ onCancelBulkAction?: () => void;
13
+ onConfirmBulkAction?: () => void;
14
+ };
15
+ exports: {};
16
+ bindings: "";
17
+ slots: {};
18
+ events: {};
19
+ };
20
+ declare class __sveltets_Render<T extends object = Record<string, unknown>> {
21
+ props(): ReturnType<typeof $$render<T>>['props'];
22
+ events(): ReturnType<typeof $$render<T>>['events'];
23
+ slots(): ReturnType<typeof $$render<T>>['slots'];
24
+ bindings(): "";
25
+ exports(): {};
26
+ }
27
+ interface $$IsomorphicComponent {
28
+ new <T extends object = Record<string, unknown>>(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']>> & {
29
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
30
+ } & ReturnType<__sveltets_Render<T>['exports']>;
31
+ <T extends object = Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
32
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
33
+ }
34
+ declare const Modals: $$IsomorphicComponent;
35
+ type Modals<T extends object = Record<string, unknown>> = InstanceType<typeof Modals<T>>;
36
+ export default Modals;
@@ -0,0 +1,176 @@
1
+ <script lang="ts" generics="T extends object = Record<string, unknown>">
2
+ import { SvelteSet } from 'svelte/reactivity';
3
+ import Button from '../../../form/Button.svelte';
4
+ import PaginatedTable from '../../../table/PaginatedTable.svelte';
5
+ import { getIconSet } from '../../../../icons/context.js';
6
+ import { defaultIconSet } from '../../../../icons/sets/default.js';
7
+ import type {
8
+ ColumnDefinition,
9
+ CustomAction,
10
+ ReorderConfiguration,
11
+ RowAction
12
+ } from '../../../../types/crud.js';
13
+ import type {
14
+ FilterSnapshot,
15
+ ServerPagination,
16
+ SortDirection,
17
+ TableQuery
18
+ } from '../../../../types/table.js';
19
+
20
+ const icons = $derived(getIconSet() ?? defaultIconSet);
21
+
22
+ let {
23
+ data = [] as T[],
24
+ columns = [] as ColumnDefinition<T>[],
25
+ pageSize = 10,
26
+ selectable = true,
27
+ selected = $bindable(new SvelteSet<number>()),
28
+ pagination = undefined as ServerPagination | undefined,
29
+ initialSort = undefined as { column: string; direction: SortDirection } | undefined,
30
+ initialFilters = undefined as Partial<FilterSnapshot> | undefined,
31
+ onPaginationChange = undefined as ((query: TableQuery) => void) | undefined,
32
+ visibleRows = $bindable<T[]>([]),
33
+ query = $bindable<TableQuery | undefined>(undefined),
34
+ customActions = [] as CustomAction<T>[],
35
+ allowRead = true,
36
+ allowUpdate = true,
37
+ allowDelete = true,
38
+ readLabel = '',
39
+ updateLabel = '',
40
+ deleteLabel = '',
41
+ reorder = undefined as ReorderConfiguration<T> | undefined,
42
+ onView,
43
+ onEdit,
44
+ onAction,
45
+ onRequestDeletion,
46
+ onReorder
47
+ }: {
48
+ data?: T[];
49
+ columns?: ColumnDefinition<T>[];
50
+ pageSize?: number;
51
+ selectable?: boolean;
52
+ selected?: SvelteSet<number>;
53
+ pagination?: ServerPagination;
54
+ initialSort?: { column: string; direction: SortDirection };
55
+ initialFilters?: Partial<FilterSnapshot>;
56
+ onPaginationChange?: (query: TableQuery) => void;
57
+ visibleRows?: T[];
58
+ query?: TableQuery;
59
+ customActions?: CustomAction<T>[];
60
+ allowRead?: boolean;
61
+ allowUpdate?: boolean;
62
+ allowDelete?: boolean;
63
+ readLabel?: string;
64
+ updateLabel?: string;
65
+ deleteLabel?: string;
66
+ /** Drag-to-reorder configuration — see `ReorderConfiguration`. Ignored
67
+ * in server-pagination mode (`pagination` set). */
68
+ reorder?: ReorderConfiguration<T>;
69
+ onView?: (item: T) => void;
70
+ onEdit?: (item: T) => void;
71
+ onAction?: (action: CustomAction<T>, item: T) => void;
72
+ onRequestDeletion?: (item: T) => void;
73
+ /** Fires with the complete reordered row list after a drag; the caller
74
+ * (the list orchestrator) owns persisting it. */
75
+ onReorder?: (rows: T[]) => void;
76
+ } = $props();
77
+
78
+ const reorderActive = $derived(!!reorder && reorder.enabled !== false && !pagination);
79
+
80
+ const showRowActions = $derived(
81
+ allowRead || allowUpdate || allowDelete || customActions.length > 0
82
+ );
83
+
84
+ const rowActions = $derived<RowAction<T>[]>([
85
+ ...customActions.map((action) => ({
86
+ label: action.label,
87
+ icon: action.icon,
88
+ toggle: action.toggle,
89
+ class: action.class,
90
+ condition: action.condition,
91
+ run: (item: T) => onAction?.(action, item)
92
+ })),
93
+ ...(allowRead
94
+ ? [{ label: readLabel, icon: icons.view, run: (item: T) => onView?.(item) }]
95
+ : []),
96
+ ...(allowUpdate
97
+ ? [{ label: updateLabel, icon: icons.edit, run: (item: T) => onEdit?.(item) }]
98
+ : []),
99
+ ...(allowDelete
100
+ ? [
101
+ {
102
+ label: deleteLabel,
103
+ icon: icons.delete,
104
+ class: 'text-error',
105
+ run: (item: T) => onRequestDeletion?.(item)
106
+ }
107
+ ]
108
+ : [])
109
+ ]);
110
+ </script>
111
+
112
+ {#snippet actionsCell(item: T)}
113
+ <div class="flex justify-end items-center gap-1">
114
+ {#each rowActions as action (action.label)}
115
+ {#if action.condition?.(item) ?? true}
116
+ {@const resolvedClass =
117
+ typeof action.class === 'function' ? action.class(item) : action.class}
118
+ {#if action.toggle}
119
+ <input
120
+ type="checkbox"
121
+ class={['toggle toggle-sm', resolvedClass]}
122
+ title={action.label}
123
+ aria-label={action.label}
124
+ checked={action.toggle(item)}
125
+ onclick={(e) => {
126
+ e.preventDefault();
127
+ e.stopPropagation();
128
+ action.run(item);
129
+ }}
130
+ />
131
+ {:else}
132
+ {@const Icon = action.icon}
133
+ <Button
134
+ variant="ghost"
135
+ class={['btn-xs', resolvedClass]}
136
+ title={action.label}
137
+ aria-label={action.label}
138
+ onclick={(e) => {
139
+ e.stopPropagation();
140
+ action.run(item);
141
+ }}
142
+ >
143
+ <Icon class="size-4" />
144
+ </Button>
145
+ {/if}
146
+ {/if}
147
+ {/each}
148
+ </div>
149
+ {/snippet}
150
+
151
+ <div class="table-wrapper">
152
+ <PaginatedTable
153
+ {data}
154
+ {columns}
155
+ {pageSize}
156
+ {selectable}
157
+ bind:selected
158
+ rowActions={showRowActions ? actionsCell : undefined}
159
+ {pagination}
160
+ {initialSort}
161
+ {initialFilters}
162
+ {onPaginationChange}
163
+ bind:visibleRows
164
+ bind:query
165
+ reorder={reorderActive ? reorder : undefined}
166
+ {onReorder}
167
+ />
168
+ </div>
169
+
170
+ <style>
171
+ .table-wrapper {
172
+ width: 100%;
173
+ max-width: var(--runeforge-crud-max-width);
174
+ margin-inline: auto;
175
+ }
176
+ </style>
@@ -0,0 +1,59 @@
1
+ import { SvelteSet } from 'svelte/reactivity';
2
+ import type { ColumnDefinition, CustomAction, ReorderConfiguration } from '../../../../types/crud.js';
3
+ import type { FilterSnapshot, ServerPagination, SortDirection, TableQuery } from '../../../../types/table.js';
4
+ declare function $$render<T extends object = Record<string, unknown>>(): {
5
+ props: {
6
+ data?: T[];
7
+ columns?: ColumnDefinition<T>[];
8
+ pageSize?: number;
9
+ selectable?: boolean;
10
+ selected?: SvelteSet<number>;
11
+ pagination?: ServerPagination;
12
+ initialSort?: {
13
+ column: string;
14
+ direction: SortDirection;
15
+ };
16
+ initialFilters?: Partial<FilterSnapshot>;
17
+ onPaginationChange?: (query: TableQuery) => void;
18
+ visibleRows?: T[];
19
+ query?: TableQuery;
20
+ customActions?: CustomAction<T>[];
21
+ allowRead?: boolean;
22
+ allowUpdate?: boolean;
23
+ allowDelete?: boolean;
24
+ readLabel?: string;
25
+ updateLabel?: string;
26
+ deleteLabel?: string;
27
+ /** Drag-to-reorder configuration — see `ReorderConfiguration`. Ignored
28
+ * in server-pagination mode (`pagination` set). */
29
+ reorder?: ReorderConfiguration<T>;
30
+ onView?: (item: T) => void;
31
+ onEdit?: (item: T) => void;
32
+ onAction?: (action: CustomAction<T>, item: T) => void;
33
+ onRequestDeletion?: (item: T) => void;
34
+ /** Fires with the complete reordered row list after a drag; the caller
35
+ * (the list orchestrator) owns persisting it. */
36
+ onReorder?: (rows: T[]) => void;
37
+ };
38
+ exports: {};
39
+ bindings: "selected" | "visibleRows" | "query";
40
+ slots: {};
41
+ events: {};
42
+ };
43
+ declare class __sveltets_Render<T extends object = Record<string, unknown>> {
44
+ props(): ReturnType<typeof $$render<T>>['props'];
45
+ events(): ReturnType<typeof $$render<T>>['events'];
46
+ slots(): ReturnType<typeof $$render<T>>['slots'];
47
+ bindings(): "selected" | "visibleRows" | "query";
48
+ exports(): {};
49
+ }
50
+ interface $$IsomorphicComponent {
51
+ new <T extends object = Record<string, unknown>>(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']>> & {
52
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
53
+ } & ReturnType<__sveltets_Render<T>['exports']>;
54
+ <T extends object = Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
55
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
56
+ }
57
+ declare const Table: $$IsomorphicComponent;
58
+ type Table<T extends object = Record<string, unknown>> = InstanceType<typeof Table<T>>;
59
+ export default Table;