runeforge 0.0.16 → 0.0.18

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.
@@ -1,5 +1,5 @@
1
1
  import type { AttributeMetadata } from '../../types/attribute.js';
2
- import type { ActionConfiguration, ColumnDefinition, CustomAction, FieldDefinition } from '../../types/crud.js';
2
+ import type { ActionConfiguration, ColumnDefinition, CustomAction, CustomBulkAction, FieldDefinition, SearchConfiguration } from '../../types/crud.js';
3
3
  declare function $$render<T extends object = Record<string, unknown>>(): {
4
4
  props: {
5
5
  data?: Record<string, unknown>;
@@ -14,6 +14,8 @@ declare function $$render<T extends object = Record<string, unknown>>(): {
14
14
  read?: ActionConfiguration<T>;
15
15
  deletion?: ActionConfiguration<T>;
16
16
  actions?: CustomAction<T>[];
17
+ customBulkActions?: CustomBulkAction<T>[];
18
+ search?: SearchConfiguration;
17
19
  columns?: ColumnDefinition<T>[];
18
20
  fields?: FieldDefinition<T>[];
19
21
  meta?: Partial<Record<string, AttributeMetadata>>;
@@ -0,0 +1,53 @@
1
+ <script lang="ts">
2
+ import { page } from '$app/state';
3
+ import { goto } from '$app/navigation';
4
+ import { getStrings } from '../../i18n/context.js';
5
+ import type { SearchConfiguration } from '../../types/crud.js';
6
+
7
+ const strings = getStrings();
8
+
9
+ let {
10
+ config = {} as SearchConfiguration
11
+ }: {
12
+ config?: SearchConfiguration;
13
+ } = $props();
14
+
15
+ const param = $derived(config.param ?? 'search');
16
+ const debounceMs = $derived(config.debounceMs ?? 300);
17
+
18
+ // Intentional one-time hydration of local state from the initial `param`
19
+ // value (not a live binding) - svelte-check's state_referenced_locally
20
+ // warning is a false positive here, same as PaginatedTable's initialSort
21
+ // etc. After mount this is the source of truth for what the user is
22
+ // typing, so it doesn't fight with the URL updates this component itself
23
+ // triggers on every keystroke.
24
+ let value = $state(page.url.searchParams.get(param) ?? '');
25
+ let debounceTimer: ReturnType<typeof setTimeout>;
26
+
27
+ function onInput() {
28
+ clearTimeout(debounceTimer);
29
+ debounceTimer = setTimeout(() => {
30
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
31
+ const params = new URLSearchParams(page.url.searchParams);
32
+ const trimmed = value.trim();
33
+ if (trimmed) params.set(param, trimmed);
34
+ else params.delete(param);
35
+ // A new search term can leave the current page past the end of the
36
+ // narrowed result set, and any open create/read/edit view stops
37
+ // making sense once the underlying list changes - drop both.
38
+ params.delete('page');
39
+ params.delete('view');
40
+ params.delete('id');
41
+ const qs = params.toString();
42
+ goto(qs ? `?${qs}` : '?', { keepFocus: true, noScroll: true, replaceState: true });
43
+ }, debounceMs);
44
+ }
45
+ </script>
46
+
47
+ <input
48
+ type="search"
49
+ bind:value
50
+ oninput={onInput}
51
+ placeholder={config.placeholder ?? strings.searchPlaceholder}
52
+ class="input input-bordered"
53
+ />
@@ -0,0 +1,7 @@
1
+ import type { SearchConfiguration } from '../../types/crud.js';
2
+ type $$ComponentProps = {
3
+ config?: SearchConfiguration;
4
+ };
5
+ declare const SearchInput: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type SearchInput = ReturnType<typeof SearchInput>;
7
+ export default SearchInput;
@@ -1,236 +1,323 @@
1
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 Button from '../../form/Button.svelte';
5
- import Header from '../../common/Header.svelte';
6
- import Modal from '../../Modal.svelte';
7
- import PaginatedTable from '../../table/PaginatedTable.svelte';
8
- import { getIconSet } from '../../../icons/context.js';
9
- import { defaultIconSet } from '../../../icons/sets/default.js';
10
- import type {
11
- ActionConfiguration,
12
- ColumnDefinition,
13
- CustomAction,
14
- RowAction
15
- } from '../../../types/crud.js';
16
- import type { FilterSnapshot, ServerPagination, SortDirection, TableQuery } from '../../../types/table.js';
17
- import { getStrings } from '../../../i18n/context.js';
18
-
19
- const strings = getStrings();
20
-
21
- let {
22
- data = [] as T[],
23
- labelOne = '',
24
- labelMany = '',
25
- icon,
26
- pageSize = 10,
27
- idKey = '_id',
28
- creation = {} as ActionConfiguration<T>,
29
- update = {} as ActionConfiguration<T>,
30
- read = {} as ActionConfiguration<T>,
31
- deletion = {} as ActionConfiguration<T>,
32
- actions = [] as CustomAction<T>[],
33
- columns = [] as ColumnDefinition<T>[],
34
- pagination = undefined as ServerPagination | undefined,
35
- initialSort = undefined as { column: string; direction: SortDirection } | undefined,
36
- initialFilters = undefined as Partial<FilterSnapshot> | undefined,
37
- onPaginationChange = undefined as ((query: TableQuery) => void) | undefined,
38
- onCreate,
39
- onEdit,
40
- onView,
41
- onAction,
42
- }: {
43
- data?: T[];
44
- labelOne?: string;
45
- labelMany?: string;
46
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
- icon?: any;
48
- pageSize?: number;
49
- idKey?: string;
50
- creation?: ActionConfiguration<T>;
51
- update?: ActionConfiguration<T>;
52
- read?: ActionConfiguration<T>;
53
- deletion?: ActionConfiguration<T>;
54
- actions?: CustomAction<T>[];
55
- columns?: ColumnDefinition<T>[];
56
- pagination?: ServerPagination;
57
- initialSort?: { column: string; direction: SortDirection };
58
- initialFilters?: Partial<FilterSnapshot>;
59
- onPaginationChange?: (query: TableQuery) => void;
60
- onCreate?: () => void;
61
- onEdit?: (item: T) => void;
62
- onView?: (item: T) => void;
63
- onAction?: (action: CustomAction<T>, item: T) => void;
64
- } = $props();
65
-
66
- const icons = $derived(getIconSet() ?? defaultIconSet);
67
- const entityIcon = $derived(icon ?? icons.folder);
68
-
69
- const allowRead = $derived(read.enabled ?? true);
70
- const allowUpdate = $derived(update.enabled ?? true);
71
- const allowDelete = $derived(deletion.enabled ?? true);
72
- const deleteLabel = $derived(deletion.label ?? strings.delete);
73
- const updateLabel = $derived(update.label ?? strings.edit);
74
- const readLabel = $derived(read.label ?? strings.view);
75
- const showRowActions = $derived(allowRead || allowUpdate || allowDelete || actions.length > 0);
76
-
77
- let selected = new SvelteSet<number>();
78
- let pendingDeletion = $state<T[] | null>(null);
79
-
80
- // `selected` holds indices into `data`; if `data` is swapped for a different
81
- // slice (page/sort/filter change in server mode) stale indices could point
82
- // at unrelated rows, so clear on any data reference change.
83
- let lastData: T[] | undefined;
84
- $effect(() => {
85
- if (data !== lastData) {
86
- selected.clear();
87
- lastData = data;
88
- }
89
- });
90
-
91
- async function runEndpointAction(endpoint: string, items: T[]) {
92
- await Promise.all(items.map((item) => {
93
- const fd = new FormData();
94
- fd.set('id', String((item as Record<string, unknown>)[idKey] ?? ''));
95
- return fetch(endpoint, { method: 'POST', body: fd });
96
- }));
97
- await invalidateAll();
98
- }
99
-
100
- async function runDeletion(items: T[]) {
101
- if (deletion.endpoint) {
102
- await runEndpointAction(deletion.endpoint, items);
103
- } else {
104
- await deletion.callback?.(items);
105
- }
106
- }
107
-
108
- function requestDeletion(items: T[]) {
109
- if (deletion.confirm) {
110
- pendingDeletion = items;
111
- } else {
112
- runDeletion(items);
113
- }
114
- }
115
-
116
- function handleCreate() { onCreate?.(); }
117
-
118
- function handleDelete() {
119
- const items = [...selected].map(i => data[i]);
120
- selected.clear();
121
- requestDeletion(items);
122
- }
123
-
124
- async function confirmDeletion() {
125
- if (pendingDeletion) {
126
- await runDeletion(pendingDeletion);
127
- pendingDeletion = null;
128
- }
129
- }
130
-
131
- const rowActions = $derived<RowAction<T>[]>([
132
- ...actions.map((action) => ({
133
- label: action.label,
134
- icon: action.icon,
135
- condition: action.condition,
136
- run: (item: T) => onAction?.(action, item),
137
- })),
138
- ...(allowRead ? [{ label: readLabel, icon: icons.view, run: (item: T) => onView?.(item) }] : []),
139
- ...(allowUpdate ? [{ label: updateLabel, icon: icons.edit, run: (item: T) => onEdit?.(item) }] : []),
140
- ...(allowDelete ? [{ label: deleteLabel, icon: icons.delete, class: 'text-error', run: (item: T) => requestDeletion([item]) }] : []),
141
- ]);
2
+ import { SvelteSet } from 'svelte/reactivity';
3
+ import { invalidateAll } from '$app/navigation';
4
+ import Button from '../../form/Button.svelte';
5
+ import Header from '../../common/Header.svelte';
6
+ import Modal from '../../Modal.svelte';
7
+ import PaginatedTable from '../../table/PaginatedTable.svelte';
8
+ import SearchInput from '../SearchInput.svelte';
9
+ import { getIconSet } from '../../../icons/context.js';
10
+ import { defaultIconSet } from '../../../icons/sets/default.js';
11
+ import type {
12
+ ActionConfiguration,
13
+ ColumnDefinition,
14
+ CustomAction,
15
+ CustomBulkAction,
16
+ RowAction,
17
+ SearchConfiguration
18
+ } from '../../../types/crud.js';
19
+ import type {
20
+ FilterSnapshot,
21
+ ServerPagination,
22
+ SortDirection,
23
+ TableQuery
24
+ } from '../../../types/table.js';
25
+ import { getStrings } from '../../../i18n/context.js';
26
+
27
+ const strings = getStrings();
28
+
29
+ let {
30
+ data = [] as T[],
31
+ labelOne = '',
32
+ labelMany = '',
33
+ icon,
34
+ pageSize = 10,
35
+ idKey = '_id',
36
+ creation = {} as ActionConfiguration<T>,
37
+ update = {} as ActionConfiguration<T>,
38
+ read = {} as ActionConfiguration<T>,
39
+ deletion = {} as ActionConfiguration<T>,
40
+ actions = [] as CustomAction<T>[],
41
+ customBulkActions = [] as CustomBulkAction<T>[],
42
+ search = undefined as SearchConfiguration | undefined,
43
+ columns = [] as ColumnDefinition<T>[],
44
+ pagination = undefined as ServerPagination | undefined,
45
+ initialSort = undefined as { column: string; direction: SortDirection } | undefined,
46
+ initialFilters = undefined as Partial<FilterSnapshot> | undefined,
47
+ onPaginationChange = undefined as ((query: TableQuery) => void) | undefined,
48
+ onCreate,
49
+ onEdit,
50
+ onView,
51
+ onAction
52
+ }: {
53
+ data?: T[];
54
+ labelOne?: string;
55
+ labelMany?: string;
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
+ icon?: any;
58
+ pageSize?: number;
59
+ idKey?: string;
60
+ creation?: ActionConfiguration<T>;
61
+ update?: ActionConfiguration<T>;
62
+ read?: ActionConfiguration<T>;
63
+ deletion?: ActionConfiguration<T>;
64
+ actions?: CustomAction<T>[];
65
+ customBulkActions?: CustomBulkAction<T>[];
66
+ search?: SearchConfiguration;
67
+ columns?: ColumnDefinition<T>[];
68
+ pagination?: ServerPagination;
69
+ initialSort?: { column: string; direction: SortDirection };
70
+ initialFilters?: Partial<FilterSnapshot>;
71
+ onPaginationChange?: (query: TableQuery) => void;
72
+ onCreate?: () => void;
73
+ onEdit?: (item: T) => void;
74
+ onView?: (item: T) => void;
75
+ onAction?: (action: CustomAction<T>, item: T) => void;
76
+ } = $props();
77
+
78
+ const icons = $derived(getIconSet() ?? defaultIconSet);
79
+ const entityIcon = $derived(icon ?? icons.folder);
80
+
81
+ const allowRead = $derived(read.enabled ?? true);
82
+ const allowUpdate = $derived(update.enabled ?? true);
83
+ const allowDelete = $derived(deletion.enabled ?? true);
84
+ const deleteLabel = $derived(deletion.label ?? strings.delete);
85
+ const updateLabel = $derived(update.label ?? strings.edit);
86
+ const readLabel = $derived(read.label ?? strings.view);
87
+ const showRowActions = $derived(allowRead || allowUpdate || allowDelete || actions.length > 0);
88
+ const allowSelection = $derived(allowDelete || customBulkActions.length > 0);
89
+
90
+ let selected = new SvelteSet<number>();
91
+ let pendingDeletion = $state<T[] | null>(null);
92
+ let pendingBulkAction = $state<{ action: CustomBulkAction<T>; items: T[] } | null>(null);
93
+ const selectedItems = $derived([...selected].map((i) => data[i]));
94
+
95
+ // `selected` holds indices into `data`; if `data` is swapped for a different
96
+ // slice (page/sort/filter change in server mode) stale indices could point
97
+ // at unrelated rows, so clear on any data reference change.
98
+ let lastData: T[] | undefined;
99
+ $effect(() => {
100
+ if (data !== lastData) {
101
+ selected.clear();
102
+ lastData = data;
103
+ }
104
+ });
105
+
106
+ async function runEndpointAction(endpoint: string, items: T[]) {
107
+ await Promise.all(
108
+ items.map((item) => {
109
+ const fd = new FormData();
110
+ fd.set('id', String((item as Record<string, unknown>)[idKey] ?? ''));
111
+ return fetch(endpoint, { method: 'POST', body: fd });
112
+ })
113
+ );
114
+ await invalidateAll();
115
+ }
116
+
117
+ async function runDeletion(items: T[]) {
118
+ if (deletion.endpoint) {
119
+ await runEndpointAction(deletion.endpoint, items);
120
+ } else {
121
+ await deletion.callback?.(items);
122
+ }
123
+ }
124
+
125
+ function requestDeletion(items: T[]) {
126
+ if (deletion.confirm) {
127
+ pendingDeletion = items;
128
+ } else {
129
+ runDeletion(items);
130
+ }
131
+ }
132
+
133
+ function handleCreate() {
134
+ onCreate?.();
135
+ }
136
+
137
+ function handleDelete() {
138
+ const items = [...selected].map((i) => data[i]);
139
+ selected.clear();
140
+ requestDeletion(items);
141
+ }
142
+
143
+ async function confirmDeletion() {
144
+ if (pendingDeletion) {
145
+ await runDeletion(pendingDeletion);
146
+ pendingDeletion = null;
147
+ }
148
+ }
149
+
150
+ async function runBulkAction(action: CustomBulkAction<T>, items: T[]) {
151
+ await runEndpointAction(action.endpoint, items);
152
+ }
153
+
154
+ function requestBulkAction(action: CustomBulkAction<T>, items: T[]) {
155
+ if (action.confirm) {
156
+ pendingBulkAction = { action, items };
157
+ } else {
158
+ runBulkAction(action, items);
159
+ }
160
+ }
161
+
162
+ function handleBulkAction(action: CustomBulkAction<T>) {
163
+ const items = selectedItems;
164
+ selected.clear();
165
+ requestBulkAction(action, items);
166
+ }
167
+
168
+ async function confirmBulkAction() {
169
+ if (pendingBulkAction) {
170
+ await runBulkAction(pendingBulkAction.action, pendingBulkAction.items);
171
+ pendingBulkAction = null;
172
+ }
173
+ }
174
+
175
+ const rowActions = $derived<RowAction<T>[]>([
176
+ ...actions.map((action) => ({
177
+ label: action.label,
178
+ icon: action.icon,
179
+ condition: action.condition,
180
+ run: (item: T) => onAction?.(action, item)
181
+ })),
182
+ ...(allowRead
183
+ ? [{ label: readLabel, icon: icons.view, run: (item: T) => onView?.(item) }]
184
+ : []),
185
+ ...(allowUpdate
186
+ ? [{ label: updateLabel, icon: icons.edit, run: (item: T) => onEdit?.(item) }]
187
+ : []),
188
+ ...(allowDelete
189
+ ? [
190
+ {
191
+ label: deleteLabel,
192
+ icon: icons.delete,
193
+ class: 'text-error',
194
+ run: (item: T) => requestDeletion([item])
195
+ }
196
+ ]
197
+ : [])
198
+ ]);
142
199
  </script>
143
200
 
144
201
  {#snippet actionsCell(item: T)}
145
- <div class="flex justify-end gap-1">
146
- {#each rowActions as action (action.label)}
147
- {#if action.condition?.(item) ?? true}
148
- {@const Icon = action.icon}
149
- <Button
150
- variant="ghost"
151
- class={['btn-xs', action.class]}
152
- title={action.label}
153
- aria-label={action.label}
154
- onclick={(e) => { e.stopPropagation(); action.run(item); }}
155
- >
156
- <Icon class="size-4" />
157
- </Button>
158
- {/if}
159
- {/each}
160
- </div>
202
+ <div class="flex justify-end gap-1">
203
+ {#each rowActions as action (action.label)}
204
+ {#if action.condition?.(item) ?? true}
205
+ {@const Icon = action.icon}
206
+ <Button
207
+ variant="ghost"
208
+ class={['btn-xs', action.class]}
209
+ title={action.label}
210
+ aria-label={action.label}
211
+ onclick={(e) => {
212
+ e.stopPropagation();
213
+ action.run(item);
214
+ }}
215
+ >
216
+ <Icon class="size-4" />
217
+ </Button>
218
+ {/if}
219
+ {/each}
220
+ </div>
161
221
  {/snippet}
162
222
 
163
223
  <div class="flex flex-col gap-6">
224
+ <Header
225
+ title={labelMany}
226
+ breadcrumbs={[{ label: labelMany, icon: entityIcon, link: { href: '#' }, prominent: true }]}
227
+ >
228
+ {#snippet buttons()}
229
+ {#if search}
230
+ <SearchInput config={search} />
231
+ {/if}
164
232
 
165
- <Header
166
- title={labelMany}
167
- breadcrumbs={[
168
- { label: labelMany, icon: entityIcon, link: { href: '#' }, prominent: true },
169
- ]}
170
- >
171
- {#snippet buttons()}
172
- {#if allowDelete}
173
- {@const DeleteIcon = icons.delete}
174
- <Button
175
- variant="error"
176
- class="btn-outline"
177
- disabled={selected.size === 0}
178
- onclick={handleDelete}
179
- >
180
- <DeleteIcon class="size-4" />
181
- {deleteLabel} ({selected.size})
182
- </Button>
183
- {/if}
184
-
185
- {@const CreateIcon = icons.create}
186
- <Button
187
- variant="primary"
188
- onclick={handleCreate}
189
- >
190
- <CreateIcon class="size-5" />
191
- {#if creation.label}
192
- <span>{creation.label}</span>
193
- {:else}
194
- <span>{strings.create}<span class="hidden sm:inline">&nbsp;{labelOne}</span></span>
195
- {/if}
196
- </Button>
197
- {/snippet}
198
- </Header>
199
-
200
- <div class="table-wrapper">
201
- <PaginatedTable
202
- {data}
203
- {columns}
204
- {pageSize}
205
- selectable={allowDelete}
206
- {selected}
207
- rowActions={showRowActions ? actionsCell : undefined}
208
- {pagination}
209
- {initialSort}
210
- {initialFilters}
211
- {onPaginationChange}
212
- />
213
- </div>
233
+ {#each customBulkActions as bulkAction (bulkAction.label)}
234
+ {#if bulkAction.condition?.(selectedItems) ?? true}
235
+ {@const BulkIcon = bulkAction.icon}
236
+ <Button
237
+ variant={bulkAction.variant ?? 'ghost'}
238
+ class="btn-outline"
239
+ disabled={selected.size === 0}
240
+ onclick={() => handleBulkAction(bulkAction)}
241
+ >
242
+ <BulkIcon class="size-4" />
243
+ {bulkAction.label} ({selected.size})
244
+ </Button>
245
+ {/if}
246
+ {/each}
247
+
248
+ {#if allowDelete}
249
+ {@const DeleteIcon = icons.delete}
250
+ <Button
251
+ variant="error"
252
+ class="btn-outline"
253
+ disabled={selected.size === 0}
254
+ onclick={handleDelete}
255
+ >
256
+ <DeleteIcon class="size-4" />
257
+ {deleteLabel} ({selected.size})
258
+ </Button>
259
+ {/if}
260
+
261
+ {@const CreateIcon = icons.create}
262
+ <Button variant="primary" onclick={handleCreate}>
263
+ <CreateIcon class="size-5" />
264
+ {#if creation.label}
265
+ <span>{creation.label}</span>
266
+ {:else}
267
+ <span>{strings.create}<span class="hidden sm:inline">&nbsp;{labelOne}</span></span>
268
+ {/if}
269
+ </Button>
270
+ {/snippet}
271
+ </Header>
272
+
273
+ <div class="table-wrapper">
274
+ <PaginatedTable
275
+ {data}
276
+ {columns}
277
+ {pageSize}
278
+ selectable={allowSelection}
279
+ {selected}
280
+ rowActions={showRowActions ? actionsCell : undefined}
281
+ {pagination}
282
+ {initialSort}
283
+ {initialFilters}
284
+ {onPaginationChange}
285
+ />
286
+ </div>
214
287
  </div>
215
288
 
216
289
  {#if pendingDeletion !== null}
217
- <Modal title={deleteLabel} onClose={() => (pendingDeletion = null)}>
218
- <p>{strings.deleteConfirm(pendingDeletion.length, deleteLabel)}</p>
219
- <div class="flex justify-end gap-2 mt-4">
220
- <Button variant="ghost" onclick={() => (pendingDeletion = null)}>
221
- {strings.cancel}
222
- </Button>
223
- <Button variant="error" onclick={confirmDeletion}>
224
- {strings.confirm}
225
- </Button>
226
- </div>
227
- </Modal>
290
+ <Modal title={deleteLabel} onClose={() => (pendingDeletion = null)}>
291
+ <p>{strings.deleteConfirm(pendingDeletion.length, deleteLabel)}</p>
292
+ <div class="flex justify-end gap-2 mt-4">
293
+ <Button variant="ghost" onclick={() => (pendingDeletion = null)}>
294
+ {strings.cancel}
295
+ </Button>
296
+ <Button variant="error" onclick={confirmDeletion}>
297
+ {strings.confirm}
298
+ </Button>
299
+ </div>
300
+ </Modal>
301
+ {/if}
302
+
303
+ {#if pendingBulkAction !== null}
304
+ <Modal title={pendingBulkAction.action.label} onClose={() => (pendingBulkAction = null)}>
305
+ <p>{strings.deleteConfirm(pendingBulkAction.items.length, pendingBulkAction.action.label)}</p>
306
+ <div class="flex justify-end gap-2 mt-4">
307
+ <Button variant="ghost" onclick={() => (pendingBulkAction = null)}>
308
+ {strings.cancel}
309
+ </Button>
310
+ <Button variant={pendingBulkAction.action.variant ?? 'primary'} onclick={confirmBulkAction}>
311
+ {strings.confirm}
312
+ </Button>
313
+ </div>
314
+ </Modal>
228
315
  {/if}
229
316
 
230
317
  <style>
231
- .table-wrapper {
232
- width: 100%;
233
- max-width: var(--runeforge-crud-max-width);
234
- margin-inline: auto;
235
- }
318
+ .table-wrapper {
319
+ width: 100%;
320
+ max-width: var(--runeforge-crud-max-width);
321
+ margin-inline: auto;
322
+ }
236
323
  </style>
@@ -1,4 +1,4 @@
1
- import type { ActionConfiguration, ColumnDefinition, CustomAction } from '../../../types/crud.js';
1
+ import type { ActionConfiguration, ColumnDefinition, CustomAction, CustomBulkAction, SearchConfiguration } from '../../../types/crud.js';
2
2
  import type { FilterSnapshot, ServerPagination, SortDirection, TableQuery } from '../../../types/table.js';
3
3
  declare function $$render<T extends object = Record<string, unknown>>(): {
4
4
  props: {
@@ -13,6 +13,8 @@ declare function $$render<T extends object = Record<string, unknown>>(): {
13
13
  read?: ActionConfiguration<T>;
14
14
  deletion?: ActionConfiguration<T>;
15
15
  actions?: CustomAction<T>[];
16
+ customBulkActions?: CustomBulkAction<T>[];
17
+ search?: SearchConfiguration;
16
18
  columns?: ColumnDefinition<T>[];
17
19
  pagination?: ServerPagination;
18
20
  initialSort?: {
@@ -107,7 +107,7 @@
107
107
  <calendar-month></calendar-month>
108
108
  </calendar-range>
109
109
  {#if dateRange.from || dateRange.to}
110
- <p class="mt-2 text-xs text-base-content/60">
110
+ <p class="mt-2 text-center text-xs text-base-content/60">
111
111
  {dateRange.from || '…'} → {dateRange.to || '…'}
112
112
  </p>
113
113
  {/if}