runeforge 0.0.61 → 0.0.63

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 (67) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +1410 -1407
  3. package/dist/components/Avatar.svelte +31 -31
  4. package/dist/components/IconRenderer.svelte +73 -73
  5. package/dist/components/Modal.svelte +75 -75
  6. package/dist/components/common/Header.svelte +40 -40
  7. package/dist/components/crud/EmbeddedField.svelte +203 -203
  8. package/dist/components/crud/Field.svelte +122 -122
  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/fields/BooleanField.svelte +38 -38
  14. package/dist/components/crud/fields/DatetimeField.svelte +250 -250
  15. package/dist/components/crud/fields/DefaultField.svelte +67 -67
  16. package/dist/components/crud/fields/FileField.svelte +70 -70
  17. package/dist/components/crud/fields/MultiSelectField.svelte +70 -70
  18. package/dist/components/crud/fields/SelectField.svelte +67 -67
  19. package/dist/components/crud/fields/TextareaField.svelte +55 -55
  20. package/dist/components/crud/fields/TreeField.svelte +1 -0
  21. package/dist/components/crud/utils/resolution.js +1 -0
  22. package/dist/components/crud/utils/tree.d.ts +5 -0
  23. package/dist/components/crud/utils/tree.js +29 -0
  24. package/dist/components/crud/views/Create.svelte +239 -239
  25. package/dist/components/crud/views/Read.svelte +131 -131
  26. package/dist/components/crud/views/Update.svelte +215 -215
  27. package/dist/components/crud/views/list/List.svelte +291 -291
  28. package/dist/components/crud/views/list/Modals.svelte +56 -56
  29. package/dist/components/crud/views/list/Table.svelte +173 -173
  30. package/dist/components/crud/views/list/Toolbar.svelte +340 -340
  31. package/dist/components/form/Button.svelte +27 -27
  32. package/dist/components/form/Label.svelte +37 -37
  33. package/dist/components/form/MultiSelect.svelte +248 -248
  34. package/dist/components/form/PasswordInput.svelte +68 -68
  35. package/dist/components/form/Required.svelte +1 -1
  36. package/dist/components/form/Select.svelte +209 -209
  37. package/dist/components/form/Tree.svelte +43 -4
  38. package/dist/components/form/Tree.svelte.d.ts +1 -0
  39. package/dist/components/form/TreeNode.svelte +31 -2
  40. package/dist/components/form/TreeNode.svelte.d.ts +2 -0
  41. package/dist/components/navigation/Breadcrumbs.svelte +111 -111
  42. package/dist/components/table/ColumnFilter.svelte +168 -168
  43. package/dist/components/table/PaginatedTable.svelte +542 -542
  44. package/dist/components/table/Paginator.svelte +113 -113
  45. package/dist/components/table/SortHeader.svelte +43 -43
  46. package/dist/components/table/TableBody.svelte +150 -150
  47. package/dist/components/table/TableHeader.svelte +88 -88
  48. package/dist/components/table/state.svelte.js +7 -2
  49. package/dist/icons/defaults/Clear.svelte +6 -6
  50. package/dist/icons/defaults/Create.svelte +6 -6
  51. package/dist/icons/defaults/Delete.svelte +6 -6
  52. package/dist/icons/defaults/Download.svelte +7 -7
  53. package/dist/icons/defaults/Edit.svelte +7 -7
  54. package/dist/icons/defaults/Filter.svelte +6 -6
  55. package/dist/icons/defaults/FilterActive.svelte +6 -6
  56. package/dist/icons/defaults/Folder.svelte +6 -6
  57. package/dist/icons/defaults/Grip.svelte +7 -7
  58. package/dist/icons/defaults/Home.svelte +6 -6
  59. package/dist/icons/defaults/PasswordHide.svelte +9 -9
  60. package/dist/icons/defaults/PasswordShow.svelte +7 -7
  61. package/dist/icons/defaults/SortAsc.svelte +6 -6
  62. package/dist/icons/defaults/SortDesc.svelte +6 -6
  63. package/dist/icons/defaults/SortNone.svelte +6 -6
  64. package/dist/icons/defaults/View.svelte +7 -7
  65. package/dist/types/attribute.d.ts +3 -0
  66. package/dist/types/crud/fields.d.ts +3 -0
  67. package/package.json +1 -1
@@ -1,250 +1,250 @@
1
- <script lang="ts" generics="T extends object = Record<string, unknown>">
2
- import { onMount } from 'svelte';
3
- import type { Snippet } from 'svelte';
4
- import { getStrings } from '../../../i18n/context.js';
5
- import type { FieldDefinition } from '../../../types/crud/fields.js';
6
-
7
- const strings = getStrings();
8
-
9
- onMount(() => {
10
- import('cally');
11
- });
12
-
13
- let {
14
- field,
15
- record = $bindable({} as Record<string, unknown>),
16
- parentRecord = undefined,
17
- error = '',
18
- readonly = false,
19
- labelRow,
20
- errorRow
21
- }: {
22
- field: FieldDefinition<T>;
23
- record?: Record<string, unknown>;
24
- parentRecord?: Record<string, unknown>;
25
- error?: string;
26
- readonly?: boolean;
27
- labelRow: Snippet;
28
- errorRow: Snippet;
29
- } = $props();
30
-
31
- const datePopId = $props.id();
32
- const dateAnchorName = `--date-anchor-${datePopId}`;
33
- let datePopoverEl: HTMLElement | undefined = $state();
34
- let calendarDateEl: (HTMLElement & { value: string }) | undefined = $state();
35
-
36
- const name = $derived(readonly ? undefined : field.attribute);
37
- const saved = $derived(record[field.attribute]);
38
- const displayValue = $derived(saved == null ? '' : String(saved));
39
- const formattedValue = $derived(
40
- readonly && field.formatter ? field.formatter(saved, record) : displayValue
41
- );
42
- const fieldDisabled = $derived(field.disabled ? field.disabled(record, parentRecord) : false);
43
-
44
- function pad(n: number): string {
45
- return String(n).padStart(2, '0');
46
- }
47
-
48
- // ISO (yyyy-mm-dd) <-> display (dd/mm/yyyy) conversion and typing mask.
49
- function isoDateToDisplay(iso: string): string {
50
- const [year, month, day] = iso.split('-');
51
- return `${day}/${month}/${year}`;
52
- }
53
-
54
- function displayToIsoDate(display: string): string | null {
55
- const match = display.trim().match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
56
- if (!match) return null;
57
- const [, dd, mm, yyyy] = match;
58
- const day = Number(dd);
59
- const month = Number(mm);
60
- const year = Number(yyyy);
61
- // Rejects invalid dates (e.g. 31/02).
62
- const date = new Date(Date.UTC(year, month - 1, day));
63
- if (
64
- date.getUTCFullYear() !== year ||
65
- date.getUTCMonth() !== month - 1 ||
66
- date.getUTCDate() !== day
67
- ) {
68
- return null;
69
- }
70
- return `${yyyy}-${mm.padStart(2, '0')}-${dd.padStart(2, '0')}`;
71
- }
72
-
73
- // Inserts "/" as digits are typed: "21" -> "21/", "211" -> "21/1".
74
- function maskDateDigits(digits: string): string {
75
- const day = digits.slice(0, 2);
76
- const month = digits.slice(2, 4);
77
- const year = digits.slice(4, 8);
78
- let out = day;
79
- if (digits.length >= 2) out += '/';
80
- out += month;
81
- if (digits.length >= 4) out += '/';
82
- return out + year;
83
- }
84
-
85
- // `record[field.attribute]` may be empty, a bare "YYYY-MM-DD" (freshly
86
- // picked, before any time is set) or a full ISO datetime (loaded from an
87
- // existing instance) — parsed once here so the date button, the time
88
- // input and cally's own `value` all derive from the same local-time
89
- // breakdown instead of each re-parsing the raw string their own way.
90
- const parsedDateTime = $derived.by(() => {
91
- const raw = record[field.attribute];
92
- if (raw == null || raw === '') return null;
93
- const d = new Date(String(raw));
94
- return isNaN(d.getTime()) ? null : d;
95
- });
96
- const datePart = $derived(
97
- parsedDateTime
98
- ? `${parsedDateTime.getFullYear()}-${pad(parsedDateTime.getMonth() + 1)}-${pad(parsedDateTime.getDate())}`
99
- : ''
100
- );
101
- const timePart = $derived(
102
- parsedDateTime ? `${pad(parsedDateTime.getHours())}:${pad(parsedDateTime.getMinutes())}` : ''
103
- );
104
-
105
- function setDateTime(nextDatePart: string, nextTimePart: string) {
106
- record[field.attribute] = nextDatePart ? `${nextDatePart}T${nextTimePart || '00:00'}` : '';
107
- }
108
-
109
- // Typed display text for the date part, synced from `datePart`.
110
- // eslint-disable-next-line svelte/prefer-writable-derived
111
- let dateTextValue = $state('');
112
- $effect(() => {
113
- dateTextValue = datePart ? isoDateToDisplay(datePart) : '';
114
- });
115
-
116
- function onDateTextInput(event: Event) {
117
- const raw = (event.currentTarget as HTMLInputElement).value;
118
- const prevDigits = dateTextValue.replace(/\D/g, '');
119
- let digits = raw.replace(/\D/g, '');
120
- // Drops one extra digit when backspacing over an inserted "/".
121
- if (raw.length < dateTextValue.length && digits.length === prevDigits.length) {
122
- digits = digits.slice(0, -1);
123
- }
124
- digits = digits.slice(0, 8);
125
- dateTextValue = maskDateDigits(digits);
126
- if (!digits) {
127
- setDateTime('', timePart);
128
- return;
129
- }
130
- const parsed = displayToIsoDate(dateTextValue);
131
- if (parsed) setDateTime(parsed, timePart);
132
- }
133
-
134
- function onDateTextBlur() {
135
- if (!dateTextValue.trim()) {
136
- setDateTime('', timePart);
137
- return;
138
- }
139
- if (!displayToIsoDate(dateTextValue)) {
140
- // Discards invalid input.
141
- dateTextValue = datePart ? isoDateToDisplay(datePart) : '';
142
- }
143
- }
144
-
145
- // cally's `change` event doesn't bubble, so the usual `onchange={...}` prop
146
- // never fires — Svelte 5 delegates events like `change` to a listener on
147
- // a shared ancestor, which only ever sees events that bubble up to it.
148
- // Attaching directly on the element (same fix ColumnFilter's date-range
149
- // picker already uses) sidesteps delegation entirely.
150
- $effect(() => {
151
- if (!calendarDateEl) return;
152
- function handler() {
153
- if (fieldDisabled) return;
154
- setDateTime(calendarDateEl!.value, timePart);
155
- datePopoverEl?.hidePopover();
156
- }
157
- calendarDateEl.addEventListener('change', handler);
158
- return () => calendarDateEl?.removeEventListener('change', handler);
159
- });
160
- </script>
161
-
162
- {@render labelRow()}
163
-
164
- {#if readonly}
165
- <input
166
- type="text"
167
- id={field.attribute}
168
- class="input input-bordered w-full"
169
- value={formattedValue}
170
- disabled
171
- />
172
- {:else}
173
- <input
174
- type="hidden"
175
- {name}
176
- value={String(record[field.attribute] ?? '')}
177
- disabled={fieldDisabled}
178
- />
179
- <div class="flex gap-2">
180
- <div class="relative flex-1" style="anchor-name:{dateAnchorName}">
181
- <input
182
- type="text"
183
- id={field.attribute}
184
- placeholder={field.placeholder ?? 'dd/mm/aaaa'}
185
- value={dateTextValue}
186
- oninput={onDateTextInput}
187
- onblur={onDateTextBlur}
188
- autocomplete="off"
189
- disabled={fieldDisabled}
190
- class="input input-bordered w-full pr-9"
191
- class:input-error={!!error}
192
- />
193
- <button
194
- type="button"
195
- popovertarget={datePopId}
196
- aria-label={strings.chooseDate}
197
- disabled={fieldDisabled}
198
- class="absolute inset-y-0 right-0 flex items-center px-2.5 text-base-content/50 hover:text-base-content disabled:pointer-events-none disabled:opacity-40"
199
- >
200
- <svg
201
- class="size-4"
202
- xmlns="http://www.w3.org/2000/svg"
203
- viewBox="0 0 24 24"
204
- fill="none"
205
- stroke="currentColor"
206
- stroke-width="2"
207
- stroke-linecap="round"
208
- stroke-linejoin="round"
209
- >
210
- <rect x="3" y="4" width="18" height="18" rx="2" />
211
- <path d="M16 2v4M8 2v4M3 10h18" />
212
- </svg>
213
- </button>
214
- </div>
215
- <input
216
- type="time"
217
- class="input input-bordered w-32 shrink-0"
218
- value={timePart}
219
- disabled={fieldDisabled || !record[field.attribute]}
220
- onchange={(e) => setDateTime(datePart, (e.currentTarget as HTMLInputElement).value)}
221
- />
222
- </div>
223
- <div
224
- popover="auto"
225
- id={datePopId}
226
- bind:this={datePopoverEl}
227
- style="position-anchor:{dateAnchorName}; position-try-fallbacks:flip-block;"
228
- class="dropdown mt-1 rounded-box border border-base-content/10 bg-base-100 p-2 shadow-lg"
229
- >
230
- <calendar-date class="cally" bind:this={calendarDateEl} value={datePart}>
231
- <svg
232
- aria-label={strings.previous}
233
- class="fill-current size-4"
234
- {...{ slot: 'previous' }}
235
- xmlns="http://www.w3.org/2000/svg"
236
- viewBox="0 0 24 24"><path fill="currentColor" d="M15.75 19.5 8.25 12l7.5-7.5" /></svg
237
- >
238
- <svg
239
- aria-label={strings.next}
240
- class="fill-current size-4"
241
- {...{ slot: 'next' }}
242
- xmlns="http://www.w3.org/2000/svg"
243
- viewBox="0 0 24 24"><path fill="currentColor" d="m8.25 4.5 7.5 7.5-7.5 7.5" /></svg
244
- >
245
- <calendar-month></calendar-month>
246
- </calendar-date>
247
- </div>
248
- {/if}
249
-
250
- {@render errorRow()}
1
+ <script lang="ts" generics="T extends object = Record<string, unknown>">
2
+ import { onMount } from 'svelte';
3
+ import type { Snippet } from 'svelte';
4
+ import { getStrings } from '../../../i18n/context.js';
5
+ import type { FieldDefinition } from '../../../types/crud/fields.js';
6
+
7
+ const strings = getStrings();
8
+
9
+ onMount(() => {
10
+ import('cally');
11
+ });
12
+
13
+ let {
14
+ field,
15
+ record = $bindable({} as Record<string, unknown>),
16
+ parentRecord = undefined,
17
+ error = '',
18
+ readonly = false,
19
+ labelRow,
20
+ errorRow
21
+ }: {
22
+ field: FieldDefinition<T>;
23
+ record?: Record<string, unknown>;
24
+ parentRecord?: Record<string, unknown>;
25
+ error?: string;
26
+ readonly?: boolean;
27
+ labelRow: Snippet;
28
+ errorRow: Snippet;
29
+ } = $props();
30
+
31
+ const datePopId = $props.id();
32
+ const dateAnchorName = `--date-anchor-${datePopId}`;
33
+ let datePopoverEl: HTMLElement | undefined = $state();
34
+ let calendarDateEl: (HTMLElement & { value: string }) | undefined = $state();
35
+
36
+ const name = $derived(readonly ? undefined : field.attribute);
37
+ const saved = $derived(record[field.attribute]);
38
+ const displayValue = $derived(saved == null ? '' : String(saved));
39
+ const formattedValue = $derived(
40
+ readonly && field.formatter ? field.formatter(saved, record) : displayValue
41
+ );
42
+ const fieldDisabled = $derived(field.disabled ? field.disabled(record, parentRecord) : false);
43
+
44
+ function pad(n: number): string {
45
+ return String(n).padStart(2, '0');
46
+ }
47
+
48
+ // ISO (yyyy-mm-dd) <-> display (dd/mm/yyyy) conversion and typing mask.
49
+ function isoDateToDisplay(iso: string): string {
50
+ const [year, month, day] = iso.split('-');
51
+ return `${day}/${month}/${year}`;
52
+ }
53
+
54
+ function displayToIsoDate(display: string): string | null {
55
+ const match = display.trim().match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
56
+ if (!match) return null;
57
+ const [, dd, mm, yyyy] = match;
58
+ const day = Number(dd);
59
+ const month = Number(mm);
60
+ const year = Number(yyyy);
61
+ // Rejects invalid dates (e.g. 31/02).
62
+ const date = new Date(Date.UTC(year, month - 1, day));
63
+ if (
64
+ date.getUTCFullYear() !== year ||
65
+ date.getUTCMonth() !== month - 1 ||
66
+ date.getUTCDate() !== day
67
+ ) {
68
+ return null;
69
+ }
70
+ return `${yyyy}-${mm.padStart(2, '0')}-${dd.padStart(2, '0')}`;
71
+ }
72
+
73
+ // Inserts "/" as digits are typed: "21" -> "21/", "211" -> "21/1".
74
+ function maskDateDigits(digits: string): string {
75
+ const day = digits.slice(0, 2);
76
+ const month = digits.slice(2, 4);
77
+ const year = digits.slice(4, 8);
78
+ let out = day;
79
+ if (digits.length >= 2) out += '/';
80
+ out += month;
81
+ if (digits.length >= 4) out += '/';
82
+ return out + year;
83
+ }
84
+
85
+ // `record[field.attribute]` may be empty, a bare "YYYY-MM-DD" (freshly
86
+ // picked, before any time is set) or a full ISO datetime (loaded from an
87
+ // existing instance) — parsed once here so the date button, the time
88
+ // input and cally's own `value` all derive from the same local-time
89
+ // breakdown instead of each re-parsing the raw string their own way.
90
+ const parsedDateTime = $derived.by(() => {
91
+ const raw = record[field.attribute];
92
+ if (raw == null || raw === '') return null;
93
+ const d = new Date(String(raw));
94
+ return isNaN(d.getTime()) ? null : d;
95
+ });
96
+ const datePart = $derived(
97
+ parsedDateTime
98
+ ? `${parsedDateTime.getFullYear()}-${pad(parsedDateTime.getMonth() + 1)}-${pad(parsedDateTime.getDate())}`
99
+ : ''
100
+ );
101
+ const timePart = $derived(
102
+ parsedDateTime ? `${pad(parsedDateTime.getHours())}:${pad(parsedDateTime.getMinutes())}` : ''
103
+ );
104
+
105
+ function setDateTime(nextDatePart: string, nextTimePart: string) {
106
+ record[field.attribute] = nextDatePart ? `${nextDatePart}T${nextTimePart || '00:00'}` : '';
107
+ }
108
+
109
+ // Typed display text for the date part, synced from `datePart`.
110
+ // eslint-disable-next-line svelte/prefer-writable-derived
111
+ let dateTextValue = $state('');
112
+ $effect(() => {
113
+ dateTextValue = datePart ? isoDateToDisplay(datePart) : '';
114
+ });
115
+
116
+ function onDateTextInput(event: Event) {
117
+ const raw = (event.currentTarget as HTMLInputElement).value;
118
+ const prevDigits = dateTextValue.replace(/\D/g, '');
119
+ let digits = raw.replace(/\D/g, '');
120
+ // Drops one extra digit when backspacing over an inserted "/".
121
+ if (raw.length < dateTextValue.length && digits.length === prevDigits.length) {
122
+ digits = digits.slice(0, -1);
123
+ }
124
+ digits = digits.slice(0, 8);
125
+ dateTextValue = maskDateDigits(digits);
126
+ if (!digits) {
127
+ setDateTime('', timePart);
128
+ return;
129
+ }
130
+ const parsed = displayToIsoDate(dateTextValue);
131
+ if (parsed) setDateTime(parsed, timePart);
132
+ }
133
+
134
+ function onDateTextBlur() {
135
+ if (!dateTextValue.trim()) {
136
+ setDateTime('', timePart);
137
+ return;
138
+ }
139
+ if (!displayToIsoDate(dateTextValue)) {
140
+ // Discards invalid input.
141
+ dateTextValue = datePart ? isoDateToDisplay(datePart) : '';
142
+ }
143
+ }
144
+
145
+ // cally's `change` event doesn't bubble, so the usual `onchange={...}` prop
146
+ // never fires — Svelte 5 delegates events like `change` to a listener on
147
+ // a shared ancestor, which only ever sees events that bubble up to it.
148
+ // Attaching directly on the element (same fix ColumnFilter's date-range
149
+ // picker already uses) sidesteps delegation entirely.
150
+ $effect(() => {
151
+ if (!calendarDateEl) return;
152
+ function handler() {
153
+ if (fieldDisabled) return;
154
+ setDateTime(calendarDateEl!.value, timePart);
155
+ datePopoverEl?.hidePopover();
156
+ }
157
+ calendarDateEl.addEventListener('change', handler);
158
+ return () => calendarDateEl?.removeEventListener('change', handler);
159
+ });
160
+ </script>
161
+
162
+ {@render labelRow()}
163
+
164
+ {#if readonly}
165
+ <input
166
+ type="text"
167
+ id={field.attribute}
168
+ class="input input-bordered w-full"
169
+ value={formattedValue}
170
+ disabled
171
+ />
172
+ {:else}
173
+ <input
174
+ type="hidden"
175
+ {name}
176
+ value={String(record[field.attribute] ?? '')}
177
+ disabled={fieldDisabled}
178
+ />
179
+ <div class="flex gap-2">
180
+ <div class="relative flex-1" style="anchor-name:{dateAnchorName}">
181
+ <input
182
+ type="text"
183
+ id={field.attribute}
184
+ placeholder={field.placeholder ?? 'dd/mm/aaaa'}
185
+ value={dateTextValue}
186
+ oninput={onDateTextInput}
187
+ onblur={onDateTextBlur}
188
+ autocomplete="off"
189
+ disabled={fieldDisabled}
190
+ class="input input-bordered w-full pr-9"
191
+ class:input-error={!!error}
192
+ />
193
+ <button
194
+ type="button"
195
+ popovertarget={datePopId}
196
+ aria-label={strings.chooseDate}
197
+ disabled={fieldDisabled}
198
+ class="absolute inset-y-0 right-0 flex items-center px-2.5 text-base-content/50 hover:text-base-content disabled:pointer-events-none disabled:opacity-40"
199
+ >
200
+ <svg
201
+ class="size-4"
202
+ xmlns="http://www.w3.org/2000/svg"
203
+ viewBox="0 0 24 24"
204
+ fill="none"
205
+ stroke="currentColor"
206
+ stroke-width="2"
207
+ stroke-linecap="round"
208
+ stroke-linejoin="round"
209
+ >
210
+ <rect x="3" y="4" width="18" height="18" rx="2" />
211
+ <path d="M16 2v4M8 2v4M3 10h18" />
212
+ </svg>
213
+ </button>
214
+ </div>
215
+ <input
216
+ type="time"
217
+ class="input input-bordered w-32 shrink-0"
218
+ value={timePart}
219
+ disabled={fieldDisabled || !record[field.attribute]}
220
+ onchange={(e) => setDateTime(datePart, (e.currentTarget as HTMLInputElement).value)}
221
+ />
222
+ </div>
223
+ <div
224
+ popover="auto"
225
+ id={datePopId}
226
+ bind:this={datePopoverEl}
227
+ style="position-anchor:{dateAnchorName}; position-try-fallbacks:flip-block;"
228
+ class="dropdown mt-1 rounded-box border border-base-content/10 bg-base-100 p-2 shadow-lg"
229
+ >
230
+ <calendar-date class="cally" bind:this={calendarDateEl} value={datePart}>
231
+ <svg
232
+ aria-label={strings.previous}
233
+ class="fill-current size-4"
234
+ {...{ slot: 'previous' }}
235
+ xmlns="http://www.w3.org/2000/svg"
236
+ viewBox="0 0 24 24"><path fill="currentColor" d="M15.75 19.5 8.25 12l7.5-7.5" /></svg
237
+ >
238
+ <svg
239
+ aria-label={strings.next}
240
+ class="fill-current size-4"
241
+ {...{ slot: 'next' }}
242
+ xmlns="http://www.w3.org/2000/svg"
243
+ viewBox="0 0 24 24"><path fill="currentColor" d="m8.25 4.5 7.5 7.5-7.5 7.5" /></svg
244
+ >
245
+ <calendar-month></calendar-month>
246
+ </calendar-date>
247
+ </div>
248
+ {/if}
249
+
250
+ {@render errorRow()}
@@ -1,67 +1,67 @@
1
- <script lang="ts" generics="T extends object = Record<string, unknown>">
2
- import type { Snippet } from 'svelte';
3
- import type { FieldDefinition } from '../../../types/crud/fields.js';
4
-
5
- let {
6
- field,
7
- record = $bindable({} as Record<string, unknown>),
8
- parentRecord = undefined,
9
- error = '',
10
- readonly = false,
11
- labelRow,
12
- errorRow
13
- }: {
14
- field: FieldDefinition<T>;
15
- record?: Record<string, unknown>;
16
- parentRecord?: Record<string, unknown>;
17
- error?: string;
18
- readonly?: boolean;
19
- labelRow: Snippet;
20
- errorRow: Snippet;
21
- } = $props();
22
-
23
- const name = $derived(readonly ? undefined : field.attribute);
24
- const saved = $derived(record[field.attribute]);
25
- const displayValue = $derived(saved == null ? '' : String(saved));
26
- const formattedValue = $derived(
27
- readonly && field.formatter ? field.formatter(saved, record) : displayValue
28
- );
29
- const fieldDisabled = $derived(field.disabled ? field.disabled(record, parentRecord) : false);
30
- </script>
31
-
32
- {@render labelRow()}
33
-
34
- {#if readonly}
35
- {#if field.formatter}
36
- <div
37
- id={field.attribute}
38
- class="input input-bordered w-full bg-base-200 border-base-200 text-base-content/40 shadow-none cursor-not-allowed"
39
- >
40
- <!-- eslint-disable-next-line svelte/no-at-html-tags -->
41
- {@html formattedValue}
42
- </div>
43
- {:else}
44
- <input
45
- type={field.type ?? 'text'}
46
- id={field.attribute}
47
- class="input input-bordered w-full"
48
- value={formattedValue}
49
- disabled
50
- />
51
- {/if}
52
- {:else}
53
- <input
54
- type={field.type ?? 'text'}
55
- id={field.attribute}
56
- {name}
57
- placeholder={field.placeholder ?? ''}
58
- bind:value={record[field.attribute]}
59
- autocomplete={field.autocomplete}
60
- step={field.type === 'number' ? 'any' : undefined}
61
- class="input input-bordered w-full"
62
- class:input-error={!!error}
63
- disabled={fieldDisabled}
64
- />
65
- {/if}
66
-
67
- {@render errorRow()}
1
+ <script lang="ts" generics="T extends object = Record<string, unknown>">
2
+ import type { Snippet } from 'svelte';
3
+ import type { FieldDefinition } from '../../../types/crud/fields.js';
4
+
5
+ let {
6
+ field,
7
+ record = $bindable({} as Record<string, unknown>),
8
+ parentRecord = undefined,
9
+ error = '',
10
+ readonly = false,
11
+ labelRow,
12
+ errorRow
13
+ }: {
14
+ field: FieldDefinition<T>;
15
+ record?: Record<string, unknown>;
16
+ parentRecord?: Record<string, unknown>;
17
+ error?: string;
18
+ readonly?: boolean;
19
+ labelRow: Snippet;
20
+ errorRow: Snippet;
21
+ } = $props();
22
+
23
+ const name = $derived(readonly ? undefined : field.attribute);
24
+ const saved = $derived(record[field.attribute]);
25
+ const displayValue = $derived(saved == null ? '' : String(saved));
26
+ const formattedValue = $derived(
27
+ readonly && field.formatter ? field.formatter(saved, record) : displayValue
28
+ );
29
+ const fieldDisabled = $derived(field.disabled ? field.disabled(record, parentRecord) : false);
30
+ </script>
31
+
32
+ {@render labelRow()}
33
+
34
+ {#if readonly}
35
+ {#if field.formatter}
36
+ <div
37
+ id={field.attribute}
38
+ class="input input-bordered w-full bg-base-200 border-base-200 text-base-content/40 shadow-none cursor-not-allowed"
39
+ >
40
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
41
+ {@html formattedValue}
42
+ </div>
43
+ {:else}
44
+ <input
45
+ type={field.type ?? 'text'}
46
+ id={field.attribute}
47
+ class="input input-bordered w-full"
48
+ value={formattedValue}
49
+ disabled
50
+ />
51
+ {/if}
52
+ {:else}
53
+ <input
54
+ type={field.type ?? 'text'}
55
+ id={field.attribute}
56
+ {name}
57
+ placeholder={field.placeholder ?? ''}
58
+ bind:value={record[field.attribute]}
59
+ autocomplete={field.autocomplete}
60
+ step={field.type === 'number' ? 'any' : undefined}
61
+ class="input input-bordered w-full"
62
+ class:input-error={!!error}
63
+ disabled={fieldDisabled}
64
+ />
65
+ {/if}
66
+
67
+ {@render errorRow()}