runeforge 0.0.62 → 0.0.64
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/LICENSE +21 -21
- package/README.md +1410 -1407
- package/dist/components/Avatar.svelte +31 -31
- package/dist/components/IconRenderer.svelte +73 -73
- package/dist/components/Modal.svelte +75 -75
- package/dist/components/common/Header.svelte +40 -40
- package/dist/components/crud/EmbeddedField.svelte +203 -203
- package/dist/components/crud/Field.svelte +122 -122
- package/dist/components/crud/GenericCRUD.svelte +426 -426
- package/dist/components/crud/SearchInput.svelte +53 -53
- package/dist/components/crud/columns/Avatar.svelte +15 -15
- package/dist/components/crud/columns/Icon.svelte +8 -8
- package/dist/components/crud/fields/BooleanField.svelte +38 -38
- package/dist/components/crud/fields/DatetimeField.svelte +250 -250
- package/dist/components/crud/fields/DefaultField.svelte +67 -67
- package/dist/components/crud/fields/FileField.svelte +70 -70
- package/dist/components/crud/fields/MultiSelectField.svelte +70 -70
- package/dist/components/crud/fields/SelectField.svelte +67 -67
- package/dist/components/crud/fields/TextareaField.svelte +55 -55
- package/dist/components/crud/fields/TreeField.svelte +1 -0
- package/dist/components/crud/utils/resolution.js +1 -0
- package/dist/components/crud/utils/tree.d.ts +5 -0
- package/dist/components/crud/utils/tree.js +29 -0
- package/dist/components/crud/views/Create.svelte +239 -239
- package/dist/components/crud/views/Read.svelte +131 -131
- package/dist/components/crud/views/Update.svelte +215 -215
- package/dist/components/crud/views/list/List.svelte +291 -291
- package/dist/components/crud/views/list/Modals.svelte +56 -56
- package/dist/components/crud/views/list/Table.svelte +173 -173
- package/dist/components/crud/views/list/Toolbar.svelte +340 -340
- package/dist/components/form/Button.svelte +27 -27
- package/dist/components/form/Label.svelte +37 -37
- package/dist/components/form/MultiSelect.svelte +252 -248
- package/dist/components/form/PasswordInput.svelte +68 -68
- package/dist/components/form/Required.svelte +1 -1
- package/dist/components/form/Select.svelte +209 -209
- package/dist/components/form/Tree.svelte +44 -5
- package/dist/components/form/Tree.svelte.d.ts +1 -0
- package/dist/components/form/TreeNode.svelte +31 -2
- package/dist/components/form/TreeNode.svelte.d.ts +2 -0
- package/dist/components/navigation/Breadcrumbs.svelte +111 -111
- package/dist/components/table/ColumnFilter.svelte +168 -168
- package/dist/components/table/PaginatedTable.svelte +542 -542
- package/dist/components/table/Paginator.svelte +113 -113
- package/dist/components/table/SortHeader.svelte +43 -43
- package/dist/components/table/TableBody.svelte +150 -150
- package/dist/components/table/TableHeader.svelte +88 -88
- package/dist/icons/defaults/Clear.svelte +6 -6
- package/dist/icons/defaults/Create.svelte +6 -6
- package/dist/icons/defaults/Delete.svelte +6 -6
- package/dist/icons/defaults/Download.svelte +7 -7
- package/dist/icons/defaults/Edit.svelte +7 -7
- package/dist/icons/defaults/Filter.svelte +6 -6
- package/dist/icons/defaults/FilterActive.svelte +6 -6
- package/dist/icons/defaults/Folder.svelte +6 -6
- package/dist/icons/defaults/Grip.svelte +7 -7
- package/dist/icons/defaults/Home.svelte +6 -6
- package/dist/icons/defaults/PasswordHide.svelte +9 -9
- package/dist/icons/defaults/PasswordShow.svelte +7 -7
- package/dist/icons/defaults/SortAsc.svelte +6 -6
- package/dist/icons/defaults/SortDesc.svelte +6 -6
- package/dist/icons/defaults/SortNone.svelte +6 -6
- package/dist/icons/defaults/View.svelte +7 -7
- package/dist/types/attribute.d.ts +3 -0
- package/dist/types/crud/fields.d.ts +3 -0
- package/package.json +1 -1
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
<script lang="ts" generics="T extends object = Record<string, unknown>">
|
|
2
|
-
import type { Snippet } from 'svelte';
|
|
3
|
-
import Avatar from '../../Avatar.svelte';
|
|
4
|
-
import { fieldLabel, initials } from '../utils/misc.js';
|
|
5
|
-
import type { FieldDefinition } from '../../../types/crud/fields.js';
|
|
6
|
-
|
|
7
|
-
let {
|
|
8
|
-
field,
|
|
9
|
-
record = $bindable({} as Record<string, unknown>),
|
|
10
|
-
parentRecord = undefined,
|
|
11
|
-
error = '',
|
|
12
|
-
readonly = false,
|
|
13
|
-
labelRow,
|
|
14
|
-
errorRow
|
|
15
|
-
}: {
|
|
16
|
-
field: FieldDefinition<T>;
|
|
17
|
-
record?: Record<string, unknown>;
|
|
18
|
-
parentRecord?: Record<string, unknown>;
|
|
19
|
-
error?: string;
|
|
20
|
-
readonly?: boolean;
|
|
21
|
-
labelRow: Snippet;
|
|
22
|
-
errorRow: Snippet;
|
|
23
|
-
} = $props();
|
|
24
|
-
|
|
25
|
-
const name = $derived(readonly ? undefined : field.attribute);
|
|
26
|
-
const saved = $derived(record[field.attribute]);
|
|
27
|
-
const labelText = $derived(fieldLabel(field));
|
|
28
|
-
const avatarInitials = $derived(initials(record.firstName as string, record.lastName as string));
|
|
29
|
-
const fieldDisabled = $derived(field.disabled ? field.disabled(record, parentRecord) : false);
|
|
30
|
-
|
|
31
|
-
let filePreview = $state<string | null>(null);
|
|
32
|
-
const preview = $derived(filePreview ?? (typeof saved === 'string' && saved ? saved : null));
|
|
33
|
-
|
|
34
|
-
function onFileChange(e: Event & { currentTarget: HTMLInputElement }) {
|
|
35
|
-
const file = e.currentTarget.files?.[0];
|
|
36
|
-
if (filePreview) URL.revokeObjectURL(filePreview);
|
|
37
|
-
filePreview = file && file.type.startsWith('image/') ? URL.createObjectURL(file) : null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
$effect(() => () => {
|
|
41
|
-
if (filePreview) URL.revokeObjectURL(filePreview);
|
|
42
|
-
});
|
|
43
|
-
</script>
|
|
44
|
-
|
|
45
|
-
<div class="flex justify-center">
|
|
46
|
-
<Avatar
|
|
47
|
-
src={preview}
|
|
48
|
-
text={avatarInitials}
|
|
49
|
-
alt={labelText}
|
|
50
|
-
class="w-20 rounded-full"
|
|
51
|
-
textClass="text-xl"
|
|
52
|
-
/>
|
|
53
|
-
</div>
|
|
54
|
-
|
|
55
|
-
{@render labelRow()}
|
|
56
|
-
|
|
57
|
-
{#if !readonly}
|
|
58
|
-
<input
|
|
59
|
-
type="file"
|
|
60
|
-
id={field.attribute}
|
|
61
|
-
{name}
|
|
62
|
-
class="file-input file-input-bordered w-full"
|
|
63
|
-
class:file-input-error={!!error}
|
|
64
|
-
disabled={fieldDisabled}
|
|
65
|
-
onchange={onFileChange}
|
|
66
|
-
/>
|
|
67
|
-
{/if}
|
|
68
|
-
<!-- read-only file value is shown as the avatar above -->
|
|
69
|
-
|
|
70
|
-
{@render errorRow()}
|
|
1
|
+
<script lang="ts" generics="T extends object = Record<string, unknown>">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import Avatar from '../../Avatar.svelte';
|
|
4
|
+
import { fieldLabel, initials } from '../utils/misc.js';
|
|
5
|
+
import type { FieldDefinition } from '../../../types/crud/fields.js';
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
field,
|
|
9
|
+
record = $bindable({} as Record<string, unknown>),
|
|
10
|
+
parentRecord = undefined,
|
|
11
|
+
error = '',
|
|
12
|
+
readonly = false,
|
|
13
|
+
labelRow,
|
|
14
|
+
errorRow
|
|
15
|
+
}: {
|
|
16
|
+
field: FieldDefinition<T>;
|
|
17
|
+
record?: Record<string, unknown>;
|
|
18
|
+
parentRecord?: Record<string, unknown>;
|
|
19
|
+
error?: string;
|
|
20
|
+
readonly?: boolean;
|
|
21
|
+
labelRow: Snippet;
|
|
22
|
+
errorRow: Snippet;
|
|
23
|
+
} = $props();
|
|
24
|
+
|
|
25
|
+
const name = $derived(readonly ? undefined : field.attribute);
|
|
26
|
+
const saved = $derived(record[field.attribute]);
|
|
27
|
+
const labelText = $derived(fieldLabel(field));
|
|
28
|
+
const avatarInitials = $derived(initials(record.firstName as string, record.lastName as string));
|
|
29
|
+
const fieldDisabled = $derived(field.disabled ? field.disabled(record, parentRecord) : false);
|
|
30
|
+
|
|
31
|
+
let filePreview = $state<string | null>(null);
|
|
32
|
+
const preview = $derived(filePreview ?? (typeof saved === 'string' && saved ? saved : null));
|
|
33
|
+
|
|
34
|
+
function onFileChange(e: Event & { currentTarget: HTMLInputElement }) {
|
|
35
|
+
const file = e.currentTarget.files?.[0];
|
|
36
|
+
if (filePreview) URL.revokeObjectURL(filePreview);
|
|
37
|
+
filePreview = file && file.type.startsWith('image/') ? URL.createObjectURL(file) : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
$effect(() => () => {
|
|
41
|
+
if (filePreview) URL.revokeObjectURL(filePreview);
|
|
42
|
+
});
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<div class="flex justify-center">
|
|
46
|
+
<Avatar
|
|
47
|
+
src={preview}
|
|
48
|
+
text={avatarInitials}
|
|
49
|
+
alt={labelText}
|
|
50
|
+
class="w-20 rounded-full"
|
|
51
|
+
textClass="text-xl"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
{@render labelRow()}
|
|
56
|
+
|
|
57
|
+
{#if !readonly}
|
|
58
|
+
<input
|
|
59
|
+
type="file"
|
|
60
|
+
id={field.attribute}
|
|
61
|
+
{name}
|
|
62
|
+
class="file-input file-input-bordered w-full"
|
|
63
|
+
class:file-input-error={!!error}
|
|
64
|
+
disabled={fieldDisabled}
|
|
65
|
+
onchange={onFileChange}
|
|
66
|
+
/>
|
|
67
|
+
{/if}
|
|
68
|
+
<!-- read-only file value is shown as the avatar above -->
|
|
69
|
+
|
|
70
|
+
{@render errorRow()}
|
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
<script lang="ts" generics="T extends object = Record<string, unknown>">
|
|
2
|
-
import type { Snippet } from 'svelte';
|
|
3
|
-
import MultiSelect from '../../form/MultiSelect.svelte';
|
|
4
|
-
import type { FieldDefinition } from '../../../types/crud/fields.js';
|
|
5
|
-
|
|
6
|
-
let {
|
|
7
|
-
field,
|
|
8
|
-
record = $bindable({} as Record<string, unknown>),
|
|
9
|
-
parentRecord = undefined,
|
|
10
|
-
error = '',
|
|
11
|
-
readonly = false,
|
|
12
|
-
labelRow,
|
|
13
|
-
errorRow
|
|
14
|
-
}: {
|
|
15
|
-
field: FieldDefinition<T>;
|
|
16
|
-
record?: Record<string, unknown>;
|
|
17
|
-
parentRecord?: Record<string, unknown>;
|
|
18
|
-
error?: string;
|
|
19
|
-
readonly?: boolean;
|
|
20
|
-
labelRow: Snippet;
|
|
21
|
-
errorRow: Snippet;
|
|
22
|
-
} = $props();
|
|
23
|
-
|
|
24
|
-
const saved = $derived(record[field.attribute]);
|
|
25
|
-
const selectOptions = $derived(
|
|
26
|
-
field.dependentOptions ? field.dependentOptions(record, parentRecord) : (field.options ?? [])
|
|
27
|
-
);
|
|
28
|
-
const fieldDisabled = $derived(field.disabled ? field.disabled(record, parentRecord) : false);
|
|
29
|
-
|
|
30
|
-
// See SelectField's equivalent effect — same idea, but pruning the array
|
|
31
|
-
// down to values still present in the (possibly just-changed) options
|
|
32
|
-
// instead of clearing a single scalar value outright.
|
|
33
|
-
$effect(() => {
|
|
34
|
-
if (!field.dependentOptions) return;
|
|
35
|
-
const current = record[field.attribute];
|
|
36
|
-
if (!Array.isArray(current)) return;
|
|
37
|
-
const validValues = new Set(selectOptions.map((o) => o.value));
|
|
38
|
-
const pruned = current.filter((v) => validValues.has(String(v)));
|
|
39
|
-
if (pruned.length !== current.length) {
|
|
40
|
-
record[field.attribute] = pruned;
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
</script>
|
|
44
|
-
|
|
45
|
-
{@render labelRow()}
|
|
46
|
-
|
|
47
|
-
{#if readonly}
|
|
48
|
-
<input
|
|
49
|
-
type="text"
|
|
50
|
-
id={field.attribute}
|
|
51
|
-
class="input input-bordered w-full"
|
|
52
|
-
value={(Array.isArray(saved) ? saved : [])
|
|
53
|
-
.map((v) => selectOptions.find((o) => o.value === String(v))?.label ?? String(v))
|
|
54
|
-
.join(', ')}
|
|
55
|
-
disabled
|
|
56
|
-
/>
|
|
57
|
-
{:else}
|
|
58
|
-
<MultiSelect
|
|
59
|
-
id={field.attribute}
|
|
60
|
-
name={field.attribute}
|
|
61
|
-
bind:value={record[field.attribute] as string[]}
|
|
62
|
-
options={selectOptions}
|
|
63
|
-
search={field.search}
|
|
64
|
-
placeholder={field.placeholder}
|
|
65
|
-
disabled={fieldDisabled}
|
|
66
|
-
{error}
|
|
67
|
-
/>
|
|
68
|
-
{/if}
|
|
69
|
-
|
|
70
|
-
{@render errorRow()}
|
|
1
|
+
<script lang="ts" generics="T extends object = Record<string, unknown>">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import MultiSelect from '../../form/MultiSelect.svelte';
|
|
4
|
+
import type { FieldDefinition } from '../../../types/crud/fields.js';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
field,
|
|
8
|
+
record = $bindable({} as Record<string, unknown>),
|
|
9
|
+
parentRecord = undefined,
|
|
10
|
+
error = '',
|
|
11
|
+
readonly = false,
|
|
12
|
+
labelRow,
|
|
13
|
+
errorRow
|
|
14
|
+
}: {
|
|
15
|
+
field: FieldDefinition<T>;
|
|
16
|
+
record?: Record<string, unknown>;
|
|
17
|
+
parentRecord?: Record<string, unknown>;
|
|
18
|
+
error?: string;
|
|
19
|
+
readonly?: boolean;
|
|
20
|
+
labelRow: Snippet;
|
|
21
|
+
errorRow: Snippet;
|
|
22
|
+
} = $props();
|
|
23
|
+
|
|
24
|
+
const saved = $derived(record[field.attribute]);
|
|
25
|
+
const selectOptions = $derived(
|
|
26
|
+
field.dependentOptions ? field.dependentOptions(record, parentRecord) : (field.options ?? [])
|
|
27
|
+
);
|
|
28
|
+
const fieldDisabled = $derived(field.disabled ? field.disabled(record, parentRecord) : false);
|
|
29
|
+
|
|
30
|
+
// See SelectField's equivalent effect — same idea, but pruning the array
|
|
31
|
+
// down to values still present in the (possibly just-changed) options
|
|
32
|
+
// instead of clearing a single scalar value outright.
|
|
33
|
+
$effect(() => {
|
|
34
|
+
if (!field.dependentOptions) return;
|
|
35
|
+
const current = record[field.attribute];
|
|
36
|
+
if (!Array.isArray(current)) return;
|
|
37
|
+
const validValues = new Set(selectOptions.map((o) => o.value));
|
|
38
|
+
const pruned = current.filter((v) => validValues.has(String(v)));
|
|
39
|
+
if (pruned.length !== current.length) {
|
|
40
|
+
record[field.attribute] = pruned;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
{@render labelRow()}
|
|
46
|
+
|
|
47
|
+
{#if readonly}
|
|
48
|
+
<input
|
|
49
|
+
type="text"
|
|
50
|
+
id={field.attribute}
|
|
51
|
+
class="input input-bordered w-full"
|
|
52
|
+
value={(Array.isArray(saved) ? saved : [])
|
|
53
|
+
.map((v) => selectOptions.find((o) => o.value === String(v))?.label ?? String(v))
|
|
54
|
+
.join(', ')}
|
|
55
|
+
disabled
|
|
56
|
+
/>
|
|
57
|
+
{:else}
|
|
58
|
+
<MultiSelect
|
|
59
|
+
id={field.attribute}
|
|
60
|
+
name={field.attribute}
|
|
61
|
+
bind:value={record[field.attribute] as string[]}
|
|
62
|
+
options={selectOptions}
|
|
63
|
+
search={field.search}
|
|
64
|
+
placeholder={field.placeholder}
|
|
65
|
+
disabled={fieldDisabled}
|
|
66
|
+
{error}
|
|
67
|
+
/>
|
|
68
|
+
{/if}
|
|
69
|
+
|
|
70
|
+
{@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 Select from '../../form/Select.svelte';
|
|
4
|
-
import type { FieldDefinition } from '../../../types/crud/fields.js';
|
|
5
|
-
|
|
6
|
-
let {
|
|
7
|
-
field,
|
|
8
|
-
record = $bindable({} as Record<string, unknown>),
|
|
9
|
-
parentRecord = undefined,
|
|
10
|
-
error = '',
|
|
11
|
-
readonly = false,
|
|
12
|
-
labelRow,
|
|
13
|
-
errorRow
|
|
14
|
-
}: {
|
|
15
|
-
field: FieldDefinition<T>;
|
|
16
|
-
record?: Record<string, unknown>;
|
|
17
|
-
parentRecord?: Record<string, unknown>;
|
|
18
|
-
error?: string;
|
|
19
|
-
readonly?: boolean;
|
|
20
|
-
labelRow: Snippet;
|
|
21
|
-
errorRow: Snippet;
|
|
22
|
-
} = $props();
|
|
23
|
-
|
|
24
|
-
const saved = $derived(record[field.attribute]);
|
|
25
|
-
const displayValue = $derived(saved == null ? '' : String(saved));
|
|
26
|
-
const selectOptions = $derived(
|
|
27
|
-
field.dependentOptions ? field.dependentOptions(record, parentRecord) : (field.options ?? [])
|
|
28
|
-
);
|
|
29
|
-
const fieldDisabled = $derived(field.disabled ? field.disabled(record, parentRecord) : false);
|
|
30
|
-
|
|
31
|
-
// A previously-valid selection can fall outside a dependentOptions list
|
|
32
|
-
// that just changed (e.g. its options depend on a sibling/parent field) —
|
|
33
|
-
// clears it instead of silently keeping a value the user can no longer see
|
|
34
|
-
// or re-pick from the dropdown.
|
|
35
|
-
$effect(() => {
|
|
36
|
-
if (!field.dependentOptions) return;
|
|
37
|
-
const current = record[field.attribute];
|
|
38
|
-
if (current && !selectOptions.some((o) => o.value === String(current))) {
|
|
39
|
-
record[field.attribute] = '';
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
</script>
|
|
43
|
-
|
|
44
|
-
{@render labelRow()}
|
|
45
|
-
|
|
46
|
-
{#if readonly}
|
|
47
|
-
<input
|
|
48
|
-
type="text"
|
|
49
|
-
id={field.attribute}
|
|
50
|
-
class="input input-bordered w-full"
|
|
51
|
-
value={selectOptions.find((o) => o.value === String(saved))?.label ?? displayValue}
|
|
52
|
-
disabled
|
|
53
|
-
/>
|
|
54
|
-
{:else}
|
|
55
|
-
<Select
|
|
56
|
-
id={field.attribute}
|
|
57
|
-
name={field.attribute}
|
|
58
|
-
bind:value={record[field.attribute] as string}
|
|
59
|
-
options={selectOptions}
|
|
60
|
-
search={field.search}
|
|
61
|
-
placeholder={field.placeholder}
|
|
62
|
-
disabled={fieldDisabled}
|
|
63
|
-
{error}
|
|
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 Select from '../../form/Select.svelte';
|
|
4
|
+
import type { FieldDefinition } from '../../../types/crud/fields.js';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
field,
|
|
8
|
+
record = $bindable({} as Record<string, unknown>),
|
|
9
|
+
parentRecord = undefined,
|
|
10
|
+
error = '',
|
|
11
|
+
readonly = false,
|
|
12
|
+
labelRow,
|
|
13
|
+
errorRow
|
|
14
|
+
}: {
|
|
15
|
+
field: FieldDefinition<T>;
|
|
16
|
+
record?: Record<string, unknown>;
|
|
17
|
+
parentRecord?: Record<string, unknown>;
|
|
18
|
+
error?: string;
|
|
19
|
+
readonly?: boolean;
|
|
20
|
+
labelRow: Snippet;
|
|
21
|
+
errorRow: Snippet;
|
|
22
|
+
} = $props();
|
|
23
|
+
|
|
24
|
+
const saved = $derived(record[field.attribute]);
|
|
25
|
+
const displayValue = $derived(saved == null ? '' : String(saved));
|
|
26
|
+
const selectOptions = $derived(
|
|
27
|
+
field.dependentOptions ? field.dependentOptions(record, parentRecord) : (field.options ?? [])
|
|
28
|
+
);
|
|
29
|
+
const fieldDisabled = $derived(field.disabled ? field.disabled(record, parentRecord) : false);
|
|
30
|
+
|
|
31
|
+
// A previously-valid selection can fall outside a dependentOptions list
|
|
32
|
+
// that just changed (e.g. its options depend on a sibling/parent field) —
|
|
33
|
+
// clears it instead of silently keeping a value the user can no longer see
|
|
34
|
+
// or re-pick from the dropdown.
|
|
35
|
+
$effect(() => {
|
|
36
|
+
if (!field.dependentOptions) return;
|
|
37
|
+
const current = record[field.attribute];
|
|
38
|
+
if (current && !selectOptions.some((o) => o.value === String(current))) {
|
|
39
|
+
record[field.attribute] = '';
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
{@render labelRow()}
|
|
45
|
+
|
|
46
|
+
{#if readonly}
|
|
47
|
+
<input
|
|
48
|
+
type="text"
|
|
49
|
+
id={field.attribute}
|
|
50
|
+
class="input input-bordered w-full"
|
|
51
|
+
value={selectOptions.find((o) => o.value === String(saved))?.label ?? displayValue}
|
|
52
|
+
disabled
|
|
53
|
+
/>
|
|
54
|
+
{:else}
|
|
55
|
+
<Select
|
|
56
|
+
id={field.attribute}
|
|
57
|
+
name={field.attribute}
|
|
58
|
+
bind:value={record[field.attribute] as string}
|
|
59
|
+
options={selectOptions}
|
|
60
|
+
search={field.search}
|
|
61
|
+
placeholder={field.placeholder}
|
|
62
|
+
disabled={fieldDisabled}
|
|
63
|
+
{error}
|
|
64
|
+
/>
|
|
65
|
+
{/if}
|
|
66
|
+
|
|
67
|
+
{@render errorRow()}
|
|
@@ -1,55 +1,55 @@
|
|
|
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
|
-
<textarea
|
|
36
|
-
id={field.attribute}
|
|
37
|
-
rows={field.rows}
|
|
38
|
-
class="textarea textarea-bordered bg-base-100 w-full"
|
|
39
|
-
value={formattedValue}
|
|
40
|
-
disabled
|
|
41
|
-
></textarea>
|
|
42
|
-
{:else}
|
|
43
|
-
<textarea
|
|
44
|
-
id={field.attribute}
|
|
45
|
-
{name}
|
|
46
|
-
rows={field.rows}
|
|
47
|
-
placeholder={field.placeholder ?? ''}
|
|
48
|
-
bind:value={record[field.attribute]}
|
|
49
|
-
class="textarea textarea-bordered bg-base-100 w-full"
|
|
50
|
-
class:textarea-error={!!error}
|
|
51
|
-
disabled={fieldDisabled}
|
|
52
|
-
></textarea>
|
|
53
|
-
{/if}
|
|
54
|
-
|
|
55
|
-
{@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
|
+
<textarea
|
|
36
|
+
id={field.attribute}
|
|
37
|
+
rows={field.rows}
|
|
38
|
+
class="textarea textarea-bordered bg-base-100 w-full"
|
|
39
|
+
value={formattedValue}
|
|
40
|
+
disabled
|
|
41
|
+
></textarea>
|
|
42
|
+
{:else}
|
|
43
|
+
<textarea
|
|
44
|
+
id={field.attribute}
|
|
45
|
+
{name}
|
|
46
|
+
rows={field.rows}
|
|
47
|
+
placeholder={field.placeholder ?? ''}
|
|
48
|
+
bind:value={record[field.attribute]}
|
|
49
|
+
class="textarea textarea-bordered bg-base-100 w-full"
|
|
50
|
+
class:textarea-error={!!error}
|
|
51
|
+
disabled={fieldDisabled}
|
|
52
|
+
></textarea>
|
|
53
|
+
{/if}
|
|
54
|
+
|
|
55
|
+
{@render errorRow()}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import type { SelectOption } from '../../../types/attribute.js';
|
|
2
2
|
export declare function buildChildrenByParent(options: SelectOption[]): Map<string | null, SelectOption[]>;
|
|
3
3
|
export declare function collectDescendantIds(value: string, childrenByParent: Map<string | null, SelectOption[]>): string[];
|
|
4
|
+
export interface TreeSearchState {
|
|
5
|
+
visible: Set<string>;
|
|
6
|
+
forceExpanded: Set<string>;
|
|
7
|
+
}
|
|
8
|
+
export declare function buildTreeSearchState(query: string, childrenByParent: Map<string | null, SelectOption[]>): TreeSearchState | null;
|
|
@@ -25,3 +25,32 @@ export function collectDescendantIds(value, childrenByParent) {
|
|
|
25
25
|
visit(value);
|
|
26
26
|
return ids;
|
|
27
27
|
}
|
|
28
|
+
// Shared by Tree.svelte: narrows the tree to whatever matches the typed
|
|
29
|
+
// query, by label, case-insensitively. Returns null for a blank query so
|
|
30
|
+
// callers can tell "no filter" apart from "filter matched nothing".
|
|
31
|
+
export function buildTreeSearchState(query, childrenByParent) {
|
|
32
|
+
const needle = query.trim().toLowerCase();
|
|
33
|
+
if (!needle)
|
|
34
|
+
return null;
|
|
35
|
+
const visible = new Set();
|
|
36
|
+
const forceExpanded = new Set();
|
|
37
|
+
function visit(node) {
|
|
38
|
+
let hasMatchingDescendant = false;
|
|
39
|
+
for (const child of childrenByParent.get(node.value) ?? []) {
|
|
40
|
+
if (visit(child))
|
|
41
|
+
hasMatchingDescendant = true;
|
|
42
|
+
}
|
|
43
|
+
const selfMatches = node.label.toLowerCase().includes(needle);
|
|
44
|
+
if (selfMatches || hasMatchingDescendant) {
|
|
45
|
+
visible.add(node.value);
|
|
46
|
+
if (hasMatchingDescendant)
|
|
47
|
+
forceExpanded.add(node.value);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
for (const root of childrenByParent.get(null) ?? []) {
|
|
53
|
+
visit(root);
|
|
54
|
+
}
|
|
55
|
+
return { visible, forceExpanded };
|
|
56
|
+
}
|