runeforge 0.0.53 → 0.0.55

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 (78) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +1342 -1222
  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 +376 -376
  9. package/dist/components/crud/GenericCRUD.svelte +426 -435
  10. package/dist/components/crud/GenericCRUD.svelte.d.ts +6 -9
  11. package/dist/components/crud/SearchInput.svelte +53 -53
  12. package/dist/components/crud/columns/Avatar.svelte +15 -15
  13. package/dist/components/crud/columns/Icon.svelte +8 -8
  14. package/dist/components/crud/utils/reorder.d.ts +4 -0
  15. package/dist/components/crud/utils/reorder.js +13 -0
  16. package/dist/components/crud/views/Create.svelte +232 -232
  17. package/dist/components/crud/views/Read.svelte +124 -124
  18. package/dist/components/crud/views/Update.svelte +208 -208
  19. package/dist/components/crud/views/list/List.svelte +291 -0
  20. package/dist/components/crud/views/{List.svelte.d.ts → list/List.svelte.d.ts} +4 -14
  21. package/dist/components/crud/views/list/Modals.svelte +56 -0
  22. package/dist/components/crud/views/list/Modals.svelte.d.ts +36 -0
  23. package/dist/components/crud/views/list/Table.svelte +176 -0
  24. package/dist/components/crud/views/list/Table.svelte.d.ts +59 -0
  25. package/dist/components/crud/views/list/Toolbar.svelte +341 -0
  26. package/dist/components/crud/views/list/Toolbar.svelte.d.ts +46 -0
  27. package/dist/components/form/Button.svelte +27 -27
  28. package/dist/components/form/Label.svelte +37 -37
  29. package/dist/components/form/MultiSelect.svelte +248 -248
  30. package/dist/components/form/PasswordInput.svelte +68 -68
  31. package/dist/components/form/Required.svelte +1 -1
  32. package/dist/components/form/Select.svelte +209 -209
  33. package/dist/components/form/Tree.svelte +62 -62
  34. package/dist/components/form/TreeNode.svelte +66 -66
  35. package/dist/components/navigation/Breadcrumbs.svelte +111 -111
  36. package/dist/components/table/ColumnFilter.svelte +168 -168
  37. package/dist/components/table/PaginatedTable.svelte +536 -215
  38. package/dist/components/table/PaginatedTable.svelte.d.ts +11 -2
  39. package/dist/components/table/Paginator.svelte +113 -113
  40. package/dist/components/table/SortHeader.svelte +43 -43
  41. package/dist/components/table/TableBody.svelte +150 -70
  42. package/dist/components/table/TableBody.svelte.d.ts +14 -1
  43. package/dist/components/table/TableHeader.svelte +88 -83
  44. package/dist/components/table/TableHeader.svelte.d.ts +1 -0
  45. package/dist/components/table/sortable.d.ts +27 -0
  46. package/dist/components/table/sortable.js +55 -0
  47. package/dist/components/table/utils.d.ts +28 -1
  48. package/dist/components/table/utils.js +75 -0
  49. package/dist/i18n/en.js +2 -0
  50. package/dist/i18n/es.js +2 -0
  51. package/dist/i18n/types.d.ts +2 -0
  52. package/dist/icons/defaults/Clear.svelte +6 -6
  53. package/dist/icons/defaults/Create.svelte +6 -6
  54. package/dist/icons/defaults/Delete.svelte +6 -6
  55. package/dist/icons/defaults/Download.svelte +7 -7
  56. package/dist/icons/defaults/Edit.svelte +7 -7
  57. package/dist/icons/defaults/Filter.svelte +6 -6
  58. package/dist/icons/defaults/FilterActive.svelte +6 -6
  59. package/dist/icons/defaults/Folder.svelte +6 -6
  60. package/dist/icons/defaults/Grip.svelte +7 -0
  61. package/dist/icons/defaults/Grip.svelte.d.ts +7 -0
  62. package/dist/icons/defaults/Home.svelte +6 -6
  63. package/dist/icons/defaults/PasswordHide.svelte +9 -9
  64. package/dist/icons/defaults/PasswordShow.svelte +7 -7
  65. package/dist/icons/defaults/SortAsc.svelte +6 -6
  66. package/dist/icons/defaults/SortDesc.svelte +6 -6
  67. package/dist/icons/defaults/SortNone.svelte +6 -6
  68. package/dist/icons/defaults/View.svelte +7 -7
  69. package/dist/icons/sets/bootstrap.js +2 -1
  70. package/dist/icons/sets/default.js +2 -0
  71. package/dist/icons/types.d.ts +3 -0
  72. package/dist/index.d.ts +5 -4
  73. package/dist/index.js +3 -2
  74. package/dist/types/attribute.d.ts +9 -0
  75. package/dist/types/crud.d.ts +72 -1
  76. package/dist/types/table.d.ts +56 -0
  77. package/package.json +3 -1
  78. package/dist/components/crud/views/List.svelte +0 -584
@@ -1,175 +1,175 @@
1
- <script lang="ts" generics="T extends object = Record<string, unknown>">
2
- import Field from './Field.svelte';
3
- import Button from '../form/Button.svelte';
4
- import Modal from '../Modal.svelte';
5
- import { emptyRecord, seedField, defaultItemLabel } from './utils/embedded.js';
6
- import { fieldLabel } from './utils/misc.js';
7
- import { validateAll } from './utils/validation.js';
8
- import { groupFields } from './utils/grouping.js';
9
- import type { FieldDefinition } from '../../types/crud.js';
10
- import { getStrings } from '../../i18n/context.js';
11
-
12
- const strings = getStrings();
13
-
14
- let {
15
- field,
16
- record = $bindable({} as Record<string, unknown>),
17
- readonly = false
18
- }: {
19
- field: FieldDefinition<T>;
20
- record?: Record<string, unknown>;
21
- readonly?: boolean;
22
- } = $props();
23
-
24
- const subFields = $derived(field.fields ?? []);
25
- const subGroups = $derived(groupFields(subFields));
26
- const items = $derived((record[field.attribute] as Record<string, unknown>[] | undefined) ?? []);
27
-
28
- let modalOpen = $state(false);
29
- // null while adding a new item; the item's index while editing an existing
30
- // one, so saveItem knows whether to append or replace in place.
31
- let editingIndex = $state<number | null>(null);
32
- let draft = $state<Record<string, unknown>>({});
33
- let draftErrors = $state<Record<string, string>>({});
34
-
35
- function openCreateModal() {
36
- editingIndex = null;
37
- draft = emptyRecord(subFields);
38
- draftErrors = {};
39
- modalOpen = true;
40
- }
41
-
42
- function openEditModal(index: number) {
43
- const item = items[index];
44
- editingIndex = index;
45
- draft = Object.fromEntries(
46
- subFields.map((f) => [f.attribute, seedField(f, f.seed ? f.seed(item) : item[f.attribute])])
47
- );
48
- draftErrors = {};
49
- modalOpen = true;
50
- }
51
-
52
- function closeModal() {
53
- modalOpen = false;
54
- }
55
-
56
- function coerce(f: FieldDefinition, raw: unknown): unknown {
57
- if (f.type === 'number') return raw === '' || raw == null ? null : Number(raw);
58
- return raw;
59
- }
60
-
61
- function saveItem() {
62
- const fd = new FormData();
63
- for (const f of subFields) fd.set(f.attribute, String(draft[f.attribute] ?? ''));
64
- const errs = validateAll(subFields, fd, strings);
65
- if (Object.keys(errs).length > 0) {
66
- draftErrors = errs;
67
- return;
68
- }
69
- const item = Object.fromEntries(
70
- subFields.map((f) => [f.attribute, coerce(f, draft[f.attribute])])
71
- );
72
- record[field.attribute] =
73
- editingIndex == null
74
- ? [...items, item]
75
- : items.map((existing, i) => (i === editingIndex ? item : existing));
76
- modalOpen = false;
77
- }
78
-
79
- function removeItem(index: number) {
80
- record[field.attribute] = items.filter((_, i) => i !== index);
81
- }
82
-
83
- function itemLabel(item: Record<string, unknown>): string {
84
- return field.itemLabel ? field.itemLabel(item) : defaultItemLabel(subFields, item);
85
- }
86
- </script>
87
-
88
- <div class="flex flex-col gap-2">
89
- {#if !readonly}
90
- <input type="hidden" name={field.attribute} value={JSON.stringify(items)} />
91
- {/if}
92
-
93
- {#if items.length === 0}
94
- <p class="text-sm text-base-content/50">{strings.noItems}</p>
95
- {:else}
96
- <ul class="flex flex-col gap-1">
97
- {#each items as item, i (i)}
98
- <li
99
- class="flex items-center justify-between gap-2 rounded-box border border-base-300 px-3 py-2 text-sm"
100
- >
101
- {#if readonly}
102
- <span>{itemLabel(item)}</span>
103
- {:else}
104
- <button
105
- type="button"
106
- class="flex-1 text-left hover:underline"
107
- onclick={() => openEditModal(i)}
108
- >
109
- {itemLabel(item)}
110
- </button>
111
- <Button
112
- variant="ghost"
113
- class="btn-xs btn-circle"
114
- aria-label={strings.remove}
115
- onclick={() => removeItem(i)}
116
- >
117
-
118
- </Button>
119
- {/if}
120
- </li>
121
- {/each}
122
- </ul>
123
- {/if}
124
-
125
- {#if !readonly}
126
- <Button variant="outline" class="btn-sm self-start" onclick={openCreateModal}>
127
- + {strings.add}
128
- </Button>
129
- {/if}
130
- </div>
131
-
132
- {#if modalOpen}
133
- <Modal title={fieldLabel(field)} onClose={closeModal}>
134
- <div class="flex flex-col gap-4">
135
- {#each subGroups as group, i (group.title ?? `_ungrouped_${i}`)}
136
- {#if group.title}
137
- <fieldset class="fieldset border border-base-300 rounded-box p-4">
138
- <legend class="fieldset-legend px-2">{group.title}</legend>
139
- <div class="flex flex-col gap-4">
140
- {#each group.rows as row (row.map((f) => f.attribute).join('|'))}
141
- {#if row.length > 1}
142
- <div class="flex flex-col gap-4 md:flex-row">
143
- {#each row as f (f.attribute)}
144
- <Field field={f} bind:record={draft} error={draftErrors[f.attribute] ?? ''} class="md:min-w-0 md:flex-1" />
145
- {/each}
146
- </div>
147
- {:else}
148
- <Field field={row[0]} bind:record={draft} error={draftErrors[row[0].attribute] ?? ''} />
149
- {/if}
150
- {/each}
151
- </div>
152
- </fieldset>
153
- {:else}
154
- {#each group.rows as row (row.map((f) => f.attribute).join('|'))}
155
- {#if row.length > 1}
156
- <div class="flex flex-col gap-4 md:flex-row">
157
- {#each row as f (f.attribute)}
158
- <Field field={f} bind:record={draft} error={draftErrors[f.attribute] ?? ''} class="md:min-w-0 md:flex-1" />
159
- {/each}
160
- </div>
161
- {:else}
162
- <Field field={row[0]} bind:record={draft} error={draftErrors[row[0].attribute] ?? ''} />
163
- {/if}
164
- {/each}
165
- {/if}
166
- {/each}
167
- <div class="flex flex-col-reverse gap-2 pt-2 sm:flex-row sm:justify-end">
168
- <Button variant="ghost" onclick={closeModal}>{strings.cancel}</Button>
169
- <Button variant="primary" onclick={saveItem}>
170
- {editingIndex == null ? strings.add : strings.edit}
171
- </Button>
172
- </div>
173
- </div>
174
- </Modal>
175
- {/if}
1
+ <script lang="ts" generics="T extends object = Record<string, unknown>">
2
+ import Field from './Field.svelte';
3
+ import Button from '../form/Button.svelte';
4
+ import Modal from '../Modal.svelte';
5
+ import { emptyRecord, seedField, defaultItemLabel } from './utils/embedded.js';
6
+ import { fieldLabel } from './utils/misc.js';
7
+ import { validateAll } from './utils/validation.js';
8
+ import { groupFields } from './utils/grouping.js';
9
+ import type { FieldDefinition } from '../../types/crud.js';
10
+ import { getStrings } from '../../i18n/context.js';
11
+
12
+ const strings = getStrings();
13
+
14
+ let {
15
+ field,
16
+ record = $bindable({} as Record<string, unknown>),
17
+ readonly = false
18
+ }: {
19
+ field: FieldDefinition<T>;
20
+ record?: Record<string, unknown>;
21
+ readonly?: boolean;
22
+ } = $props();
23
+
24
+ const subFields = $derived(field.fields ?? []);
25
+ const subGroups = $derived(groupFields(subFields));
26
+ const items = $derived((record[field.attribute] as Record<string, unknown>[] | undefined) ?? []);
27
+
28
+ let modalOpen = $state(false);
29
+ // null while adding a new item; the item's index while editing an existing
30
+ // one, so saveItem knows whether to append or replace in place.
31
+ let editingIndex = $state<number | null>(null);
32
+ let draft = $state<Record<string, unknown>>({});
33
+ let draftErrors = $state<Record<string, string>>({});
34
+
35
+ function openCreateModal() {
36
+ editingIndex = null;
37
+ draft = emptyRecord(subFields);
38
+ draftErrors = {};
39
+ modalOpen = true;
40
+ }
41
+
42
+ function openEditModal(index: number) {
43
+ const item = items[index];
44
+ editingIndex = index;
45
+ draft = Object.fromEntries(
46
+ subFields.map((f) => [f.attribute, seedField(f, f.seed ? f.seed(item) : item[f.attribute])])
47
+ );
48
+ draftErrors = {};
49
+ modalOpen = true;
50
+ }
51
+
52
+ function closeModal() {
53
+ modalOpen = false;
54
+ }
55
+
56
+ function coerce(f: FieldDefinition, raw: unknown): unknown {
57
+ if (f.type === 'number') return raw === '' || raw == null ? null : Number(raw);
58
+ return raw;
59
+ }
60
+
61
+ function saveItem() {
62
+ const fd = new FormData();
63
+ for (const f of subFields) fd.set(f.attribute, String(draft[f.attribute] ?? ''));
64
+ const errs = validateAll(subFields, fd, strings);
65
+ if (Object.keys(errs).length > 0) {
66
+ draftErrors = errs;
67
+ return;
68
+ }
69
+ const item = Object.fromEntries(
70
+ subFields.map((f) => [f.attribute, coerce(f, draft[f.attribute])])
71
+ );
72
+ record[field.attribute] =
73
+ editingIndex == null
74
+ ? [...items, item]
75
+ : items.map((existing, i) => (i === editingIndex ? item : existing));
76
+ modalOpen = false;
77
+ }
78
+
79
+ function removeItem(index: number) {
80
+ record[field.attribute] = items.filter((_, i) => i !== index);
81
+ }
82
+
83
+ function itemLabel(item: Record<string, unknown>): string {
84
+ return field.itemLabel ? field.itemLabel(item) : defaultItemLabel(subFields, item);
85
+ }
86
+ </script>
87
+
88
+ <div class="flex flex-col gap-2">
89
+ {#if !readonly}
90
+ <input type="hidden" name={field.attribute} value={JSON.stringify(items)} />
91
+ {/if}
92
+
93
+ {#if items.length === 0}
94
+ <p class="text-sm text-base-content/50">{strings.noItems}</p>
95
+ {:else}
96
+ <ul class="flex flex-col gap-1">
97
+ {#each items as item, i (i)}
98
+ <li
99
+ class="flex items-center justify-between gap-2 rounded-box border border-base-300 px-3 py-2 text-sm"
100
+ >
101
+ {#if readonly}
102
+ <span>{itemLabel(item)}</span>
103
+ {:else}
104
+ <button
105
+ type="button"
106
+ class="flex-1 text-left hover:underline"
107
+ onclick={() => openEditModal(i)}
108
+ >
109
+ {itemLabel(item)}
110
+ </button>
111
+ <Button
112
+ variant="ghost"
113
+ class="btn-xs btn-circle"
114
+ aria-label={strings.remove}
115
+ onclick={() => removeItem(i)}
116
+ >
117
+
118
+ </Button>
119
+ {/if}
120
+ </li>
121
+ {/each}
122
+ </ul>
123
+ {/if}
124
+
125
+ {#if !readonly}
126
+ <Button variant="outline" class="btn-sm self-start" onclick={openCreateModal}>
127
+ + {strings.add}
128
+ </Button>
129
+ {/if}
130
+ </div>
131
+
132
+ {#if modalOpen}
133
+ <Modal title={fieldLabel(field)} onClose={closeModal}>
134
+ <div class="flex flex-col gap-4">
135
+ {#each subGroups as group, i (group.title ?? `_ungrouped_${i}`)}
136
+ {#if group.title}
137
+ <fieldset class="fieldset border border-base-300 rounded-box p-4">
138
+ <legend class="fieldset-legend px-2">{group.title}</legend>
139
+ <div class="flex flex-col gap-4">
140
+ {#each group.rows as row (row.map((f) => f.attribute).join('|'))}
141
+ {#if row.length > 1}
142
+ <div class="flex flex-col gap-4 md:flex-row">
143
+ {#each row as f (f.attribute)}
144
+ <Field field={f} bind:record={draft} error={draftErrors[f.attribute] ?? ''} class="md:min-w-0 md:flex-1" />
145
+ {/each}
146
+ </div>
147
+ {:else}
148
+ <Field field={row[0]} bind:record={draft} error={draftErrors[row[0].attribute] ?? ''} />
149
+ {/if}
150
+ {/each}
151
+ </div>
152
+ </fieldset>
153
+ {:else}
154
+ {#each group.rows as row (row.map((f) => f.attribute).join('|'))}
155
+ {#if row.length > 1}
156
+ <div class="flex flex-col gap-4 md:flex-row">
157
+ {#each row as f (f.attribute)}
158
+ <Field field={f} bind:record={draft} error={draftErrors[f.attribute] ?? ''} class="md:min-w-0 md:flex-1" />
159
+ {/each}
160
+ </div>
161
+ {:else}
162
+ <Field field={row[0]} bind:record={draft} error={draftErrors[row[0].attribute] ?? ''} />
163
+ {/if}
164
+ {/each}
165
+ {/if}
166
+ {/each}
167
+ <div class="flex flex-col-reverse gap-2 pt-2 sm:flex-row sm:justify-end">
168
+ <Button variant="ghost" onclick={closeModal}>{strings.cancel}</Button>
169
+ <Button variant="primary" onclick={saveItem}>
170
+ {editingIndex == null ? strings.add : strings.edit}
171
+ </Button>
172
+ </div>
173
+ </div>
174
+ </Modal>
175
+ {/if}