runeforge 0.0.55 → 0.0.56

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 (54) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +1342 -1342
  3. package/dist/components/Avatar.svelte +31 -31
  4. package/dist/components/IconRenderer.svelte +22 -22
  5. package/dist/components/Modal.svelte +75 -75
  6. package/dist/components/common/Header.svelte +40 -40
  7. package/dist/components/crud/EmbeddedField.svelte +175 -175
  8. package/dist/components/crud/Field.svelte +469 -376
  9. package/dist/components/crud/GenericCRUD.svelte +426 -426
  10. package/dist/components/crud/SearchInput.svelte +53 -53
  11. package/dist/components/crud/columns/Avatar.svelte +15 -15
  12. package/dist/components/crud/columns/Icon.svelte +8 -8
  13. package/dist/components/crud/views/Create.svelte +232 -232
  14. package/dist/components/crud/views/Read.svelte +124 -124
  15. package/dist/components/crud/views/Update.svelte +208 -208
  16. package/dist/components/crud/views/list/List.svelte +291 -291
  17. package/dist/components/crud/views/list/Modals.svelte +56 -56
  18. package/dist/components/crud/views/list/Table.svelte +176 -176
  19. package/dist/components/crud/views/list/Toolbar.svelte +341 -341
  20. package/dist/components/form/Button.svelte +27 -27
  21. package/dist/components/form/Label.svelte +37 -37
  22. package/dist/components/form/MultiSelect.svelte +248 -248
  23. package/dist/components/form/PasswordInput.svelte +68 -68
  24. package/dist/components/form/Required.svelte +1 -1
  25. package/dist/components/form/Select.svelte +209 -209
  26. package/dist/components/form/Tree.svelte +62 -62
  27. package/dist/components/form/TreeNode.svelte +66 -66
  28. package/dist/components/navigation/Breadcrumbs.svelte +111 -111
  29. package/dist/components/table/ColumnFilter.svelte +168 -168
  30. package/dist/components/table/PaginatedTable.svelte +536 -536
  31. package/dist/components/table/Paginator.svelte +113 -113
  32. package/dist/components/table/SortHeader.svelte +43 -43
  33. package/dist/components/table/TableBody.svelte +150 -150
  34. package/dist/components/table/TableHeader.svelte +88 -88
  35. package/dist/i18n/en.js +1 -0
  36. package/dist/i18n/es.js +1 -0
  37. package/dist/i18n/types.d.ts +1 -0
  38. package/dist/icons/defaults/Clear.svelte +6 -6
  39. package/dist/icons/defaults/Create.svelte +6 -6
  40. package/dist/icons/defaults/Delete.svelte +6 -6
  41. package/dist/icons/defaults/Download.svelte +7 -7
  42. package/dist/icons/defaults/Edit.svelte +7 -7
  43. package/dist/icons/defaults/Filter.svelte +6 -6
  44. package/dist/icons/defaults/FilterActive.svelte +6 -6
  45. package/dist/icons/defaults/Folder.svelte +6 -6
  46. package/dist/icons/defaults/Grip.svelte +7 -7
  47. package/dist/icons/defaults/Home.svelte +6 -6
  48. package/dist/icons/defaults/PasswordHide.svelte +9 -9
  49. package/dist/icons/defaults/PasswordShow.svelte +7 -7
  50. package/dist/icons/defaults/SortAsc.svelte +6 -6
  51. package/dist/icons/defaults/SortDesc.svelte +6 -6
  52. package/dist/icons/defaults/SortNone.svelte +6 -6
  53. package/dist/icons/defaults/View.svelte +7 -7
  54. package/package.json +1 -1
@@ -1,168 +1,168 @@
1
- <script lang="ts" generics="T extends object">
2
- import { onMount } from 'svelte';
3
- import Label from '../form/Label.svelte';
4
- import Button from '../form/Button.svelte';
5
- import { getIconSet } from '../../icons/context.js';
6
- import { defaultIconSet } from '../../icons/sets/default.js';
7
- import { getStrings } from '../../i18n/context.js';
8
-
9
- const strings = getStrings();
10
-
11
- onMount(() => {
12
- import('cally');
13
- });
14
- import type { DistinctEntry } from '../../types/table.js';
15
- import type { FilterState } from './state.svelte.js';
16
- import type { ColumnDefinition } from '../../types/crud.js';
17
-
18
- let {
19
- column,
20
- entries = [],
21
- filter,
22
- maxCheckboxValues = 20,
23
- onchange
24
- }: {
25
- column: ColumnDefinition<T>;
26
- entries?: DistinctEntry<T>[];
27
- filter: FilterState;
28
- maxCheckboxValues?: number;
29
- onchange?: () => void;
30
- } = $props();
31
-
32
- const icons = $derived(getIconSet() ?? defaultIconSet);
33
- const popId = $derived(`filter-pop-${column.attribute}`);
34
- const anchor = $derived(`--filter-anchor-${column.attribute}`);
35
- const isDatetime = $derived(column.type === 'datetime');
36
-
37
- const dateRange = $derived(filter.dateRangeFor(column.attribute));
38
- const calendarValue = $derived(
39
- dateRange.from || dateRange.to ? `${dateRange.from}/${dateRange.to}` : ''
40
- );
41
-
42
- let calendarEl: HTMLElement | undefined = $state();
43
-
44
- $effect(() => {
45
- if (!calendarEl) return;
46
- function handler() {
47
- const value = (calendarEl as HTMLElement & { value: string }).value ?? '';
48
- const [from = '', to = ''] = value.split('/');
49
- filter.setDateRange(column.attribute, from, to);
50
- onchange?.();
51
- }
52
- calendarEl.addEventListener('change', handler);
53
- return () => calendarEl?.removeEventListener('change', handler);
54
- });
55
-
56
- let debounceTimer: ReturnType<typeof setTimeout>;
57
- function setText(value: string) {
58
- filter.setText(column.attribute, value);
59
- clearTimeout(debounceTimer);
60
- debounceTimer = setTimeout(() => onchange?.(), 300);
61
- }
62
- function toggle(value: string) {
63
- filter.toggleValue(column.attribute, value);
64
- onchange?.();
65
- }
66
- function clear() {
67
- filter.clear(column.attribute);
68
- onchange?.();
69
- }
70
- </script>
71
-
72
- <Button
73
- variant="ghost"
74
- class={['btn-xs btn-square', filter.hasActive(column.attribute) && 'text-primary']}
75
- popovertarget={popId}
76
- style="anchor-name:{anchor}"
77
- aria-label={strings.filterColumn(column.title ?? column.attribute)}
78
- title={strings.filter}
79
- >
80
- {#if filter.hasActive(column.attribute)}
81
- {@const Icon = icons.filterActive}
82
- <Icon class="size-3.5" />
83
- {:else}
84
- {@const Icon = icons.filter}
85
- <Icon class="size-3.5" />
86
- {/if}
87
- </Button>
88
-
89
- <div
90
- popover="auto"
91
- id={popId}
92
- style="position-anchor:{anchor}; position-try-fallbacks:flip-block;"
93
- class="dropdown dropdown-end rounded-box border border-base-content/10 bg-base-100 p-3 shadow-lg"
94
- class:w-56={!isDatetime}
95
- >
96
- {#if isDatetime}
97
- <calendar-range class="cally bg-base-100" value={calendarValue} bind:this={calendarEl}>
98
- <svg
99
- aria-label={strings.previous}
100
- class="fill-current size-4"
101
- {...{ slot: 'previous' }}
102
- xmlns="http://www.w3.org/2000/svg"
103
- viewBox="0 0 24 24"><path fill="currentColor" d="M15.75 19.5 8.25 12l7.5-7.5" /></svg
104
- >
105
- <svg
106
- aria-label={strings.next}
107
- class="fill-current size-4"
108
- {...{ slot: 'next' }}
109
- xmlns="http://www.w3.org/2000/svg"
110
- viewBox="0 0 24 24"><path fill="currentColor" d="m8.25 4.5 7.5 7.5-7.5 7.5" /></svg
111
- >
112
- <calendar-month></calendar-month>
113
- </calendar-range>
114
- {#if dateRange.from || dateRange.to}
115
- <p class="mt-2 text-center text-xs text-base-content/60">
116
- {dateRange.from || '…'} → {dateRange.to || '…'}
117
- </p>
118
- {/if}
119
- {#if filter.hasActive(column.attribute)}
120
- <Button variant="ghost" class="btn-sm mt-2 w-full" onclick={clear}>
121
- {strings.clearFilter}
122
- </Button>
123
- {/if}
124
- {:else}
125
- <div class="relative">
126
- <input
127
- type="text"
128
- class={[
129
- 'input input-bordered input-sm w-full',
130
- filter.hasActive(column.attribute) && 'pr-7'
131
- ]}
132
- placeholder={strings.filterPlaceholder}
133
- value={filter.textFor(column.attribute)}
134
- oninput={(e) => setText(e.currentTarget.value)}
135
- />
136
- {#if filter.hasActive(column.attribute)}
137
- {@const Icon = icons.clear}
138
- <Button
139
- variant="ghost"
140
- class="btn-xs btn-square btn-circle absolute top-1/2 right-1 -translate-y-1/2"
141
- aria-label={strings.clearFilter}
142
- title={strings.clearFilter}
143
- onclick={clear}
144
- >
145
- <Icon class="size-3" />
146
- </Button>
147
- {/if}
148
- </div>
149
-
150
- {#if entries.length > 0 && entries.length < maxCheckboxValues}
151
- <div class="mt-2 flex max-h-60 flex-col gap-1 overflow-y-auto">
152
- {#each entries as entry (entry.key)}
153
- <Label class="flex cursor-pointer items-center gap-2 text-sm font-normal normal-case">
154
- <input
155
- type="checkbox"
156
- class="checkbox checkbox-xs shrink-0"
157
- checked={filter.isChecked(column.attribute, entry.key)}
158
- onchange={() => toggle(entry.key)}
159
- />
160
- <span class="truncate">
161
- {entry.key === '' ? strings.emptyValue : (entry.label ?? entry.key)}
162
- </span>
163
- </Label>
164
- {/each}
165
- </div>
166
- {/if}
167
- {/if}
168
- </div>
1
+ <script lang="ts" generics="T extends object">
2
+ import { onMount } from 'svelte';
3
+ import Label from '../form/Label.svelte';
4
+ import Button from '../form/Button.svelte';
5
+ import { getIconSet } from '../../icons/context.js';
6
+ import { defaultIconSet } from '../../icons/sets/default.js';
7
+ import { getStrings } from '../../i18n/context.js';
8
+
9
+ const strings = getStrings();
10
+
11
+ onMount(() => {
12
+ import('cally');
13
+ });
14
+ import type { DistinctEntry } from '../../types/table.js';
15
+ import type { FilterState } from './state.svelte.js';
16
+ import type { ColumnDefinition } from '../../types/crud.js';
17
+
18
+ let {
19
+ column,
20
+ entries = [],
21
+ filter,
22
+ maxCheckboxValues = 20,
23
+ onchange
24
+ }: {
25
+ column: ColumnDefinition<T>;
26
+ entries?: DistinctEntry<T>[];
27
+ filter: FilterState;
28
+ maxCheckboxValues?: number;
29
+ onchange?: () => void;
30
+ } = $props();
31
+
32
+ const icons = $derived(getIconSet() ?? defaultIconSet);
33
+ const popId = $derived(`filter-pop-${column.attribute}`);
34
+ const anchor = $derived(`--filter-anchor-${column.attribute}`);
35
+ const isDatetime = $derived(column.type === 'datetime');
36
+
37
+ const dateRange = $derived(filter.dateRangeFor(column.attribute));
38
+ const calendarValue = $derived(
39
+ dateRange.from || dateRange.to ? `${dateRange.from}/${dateRange.to}` : ''
40
+ );
41
+
42
+ let calendarEl: HTMLElement | undefined = $state();
43
+
44
+ $effect(() => {
45
+ if (!calendarEl) return;
46
+ function handler() {
47
+ const value = (calendarEl as HTMLElement & { value: string }).value ?? '';
48
+ const [from = '', to = ''] = value.split('/');
49
+ filter.setDateRange(column.attribute, from, to);
50
+ onchange?.();
51
+ }
52
+ calendarEl.addEventListener('change', handler);
53
+ return () => calendarEl?.removeEventListener('change', handler);
54
+ });
55
+
56
+ let debounceTimer: ReturnType<typeof setTimeout>;
57
+ function setText(value: string) {
58
+ filter.setText(column.attribute, value);
59
+ clearTimeout(debounceTimer);
60
+ debounceTimer = setTimeout(() => onchange?.(), 300);
61
+ }
62
+ function toggle(value: string) {
63
+ filter.toggleValue(column.attribute, value);
64
+ onchange?.();
65
+ }
66
+ function clear() {
67
+ filter.clear(column.attribute);
68
+ onchange?.();
69
+ }
70
+ </script>
71
+
72
+ <Button
73
+ variant="ghost"
74
+ class={['btn-xs btn-square', filter.hasActive(column.attribute) && 'text-primary']}
75
+ popovertarget={popId}
76
+ style="anchor-name:{anchor}"
77
+ aria-label={strings.filterColumn(column.title ?? column.attribute)}
78
+ title={strings.filter}
79
+ >
80
+ {#if filter.hasActive(column.attribute)}
81
+ {@const Icon = icons.filterActive}
82
+ <Icon class="size-3.5" />
83
+ {:else}
84
+ {@const Icon = icons.filter}
85
+ <Icon class="size-3.5" />
86
+ {/if}
87
+ </Button>
88
+
89
+ <div
90
+ popover="auto"
91
+ id={popId}
92
+ style="position-anchor:{anchor}; position-try-fallbacks:flip-block;"
93
+ class="dropdown dropdown-end rounded-box border border-base-content/10 bg-base-100 p-3 shadow-lg"
94
+ class:w-56={!isDatetime}
95
+ >
96
+ {#if isDatetime}
97
+ <calendar-range class="cally bg-base-100" value={calendarValue} bind:this={calendarEl}>
98
+ <svg
99
+ aria-label={strings.previous}
100
+ class="fill-current size-4"
101
+ {...{ slot: 'previous' }}
102
+ xmlns="http://www.w3.org/2000/svg"
103
+ viewBox="0 0 24 24"><path fill="currentColor" d="M15.75 19.5 8.25 12l7.5-7.5" /></svg
104
+ >
105
+ <svg
106
+ aria-label={strings.next}
107
+ class="fill-current size-4"
108
+ {...{ slot: 'next' }}
109
+ xmlns="http://www.w3.org/2000/svg"
110
+ viewBox="0 0 24 24"><path fill="currentColor" d="m8.25 4.5 7.5 7.5-7.5 7.5" /></svg
111
+ >
112
+ <calendar-month></calendar-month>
113
+ </calendar-range>
114
+ {#if dateRange.from || dateRange.to}
115
+ <p class="mt-2 text-center text-xs text-base-content/60">
116
+ {dateRange.from || '…'} → {dateRange.to || '…'}
117
+ </p>
118
+ {/if}
119
+ {#if filter.hasActive(column.attribute)}
120
+ <Button variant="ghost" class="btn-sm mt-2 w-full" onclick={clear}>
121
+ {strings.clearFilter}
122
+ </Button>
123
+ {/if}
124
+ {:else}
125
+ <div class="relative">
126
+ <input
127
+ type="text"
128
+ class={[
129
+ 'input input-bordered input-sm w-full',
130
+ filter.hasActive(column.attribute) && 'pr-7'
131
+ ]}
132
+ placeholder={strings.filterPlaceholder}
133
+ value={filter.textFor(column.attribute)}
134
+ oninput={(e) => setText(e.currentTarget.value)}
135
+ />
136
+ {#if filter.hasActive(column.attribute)}
137
+ {@const Icon = icons.clear}
138
+ <Button
139
+ variant="ghost"
140
+ class="btn-xs btn-square btn-circle absolute top-1/2 right-1 -translate-y-1/2"
141
+ aria-label={strings.clearFilter}
142
+ title={strings.clearFilter}
143
+ onclick={clear}
144
+ >
145
+ <Icon class="size-3" />
146
+ </Button>
147
+ {/if}
148
+ </div>
149
+
150
+ {#if entries.length > 0 && entries.length < maxCheckboxValues}
151
+ <div class="mt-2 flex max-h-60 flex-col gap-1 overflow-y-auto">
152
+ {#each entries as entry (entry.key)}
153
+ <Label class="flex cursor-pointer items-center gap-2 text-sm font-normal normal-case">
154
+ <input
155
+ type="checkbox"
156
+ class="checkbox checkbox-xs shrink-0"
157
+ checked={filter.isChecked(column.attribute, entry.key)}
158
+ onchange={() => toggle(entry.key)}
159
+ />
160
+ <span class="truncate">
161
+ {entry.key === '' ? strings.emptyValue : (entry.label ?? entry.key)}
162
+ </span>
163
+ </Label>
164
+ {/each}
165
+ </div>
166
+ {/if}
167
+ {/if}
168
+ </div>