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.
- package/dist/components/crud/GenericCRUD.svelte +331 -311
- package/dist/components/crud/GenericCRUD.svelte.d.ts +3 -1
- package/dist/components/crud/SearchInput.svelte +53 -0
- package/dist/components/crud/SearchInput.svelte.d.ts +7 -0
- package/dist/components/crud/views/List.svelte +308 -221
- package/dist/components/crud/views/List.svelte.d.ts +3 -1
- package/dist/components/table/ColumnFilter.svelte +1 -1
- package/dist/i18n/en.js +2 -1
- package/dist/i18n/es.js +2 -1
- package/dist/i18n/types.d.ts +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/types/crud.d.ts +13 -0
- package/package.json +1 -1
|
@@ -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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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"> {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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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}
|