@lobb-js/studio 0.47.0 → 0.49.0

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 (72) hide show
  1. package/dist/actions.d.ts +2 -28
  2. package/dist/actions.js +4 -60
  3. package/dist/components/detailView/detailView.svelte +12 -62
  4. package/dist/components/detailView/detailView.svelte.d.ts +1 -1
  5. package/dist/components/detailView/fieldInput.svelte +161 -288
  6. package/dist/components/detailView/fieldInput.svelte.d.ts +1 -1
  7. package/dist/components/detailView/fields/BoolField.svelte +42 -0
  8. package/dist/components/detailView/fields/BoolField.svelte.d.ts +13 -0
  9. package/dist/components/detailView/fields/CodeField.svelte +30 -0
  10. package/dist/components/detailView/fields/CodeField.svelte.d.ts +13 -0
  11. package/dist/components/detailView/fields/CustomInputField.svelte +50 -0
  12. package/dist/components/detailView/fields/CustomInputField.svelte.d.ts +18 -0
  13. package/dist/components/detailView/fields/DateField.svelte +47 -0
  14. package/dist/components/detailView/fields/DateField.svelte.d.ts +14 -0
  15. package/dist/components/detailView/fields/EmbeddedField.svelte +139 -0
  16. package/dist/components/detailView/fields/EmbeddedField.svelte.d.ts +13 -0
  17. package/dist/components/detailView/fields/EmbeddedPolymorphicField.svelte +197 -0
  18. package/dist/components/detailView/fields/EmbeddedPolymorphicField.svelte.d.ts +13 -0
  19. package/dist/components/detailView/fields/EnumField.svelte +70 -0
  20. package/dist/components/detailView/fields/EnumField.svelte.d.ts +17 -0
  21. package/dist/components/detailView/fields/FieldWrapper.svelte +68 -0
  22. package/dist/components/detailView/fields/FieldWrapper.svelte.d.ts +18 -0
  23. package/dist/components/detailView/fields/ForeignKeyField.svelte +78 -0
  24. package/dist/components/detailView/fields/ForeignKeyField.svelte.d.ts +17 -0
  25. package/dist/components/detailView/fields/IdField.svelte +21 -0
  26. package/dist/components/detailView/fields/IdField.svelte.d.ts +12 -0
  27. package/dist/components/detailView/fields/NumberField.svelte +38 -0
  28. package/dist/components/detailView/fields/NumberField.svelte.d.ts +16 -0
  29. package/dist/components/detailView/fields/PasswordField.svelte +29 -0
  30. package/dist/components/detailView/fields/PasswordField.svelte.d.ts +12 -0
  31. package/dist/components/detailView/fields/PolymorphicField.svelte +51 -0
  32. package/dist/components/detailView/fields/PolymorphicField.svelte.d.ts +16 -0
  33. package/dist/components/detailView/fields/RichTextField.svelte +30 -0
  34. package/dist/components/detailView/fields/RichTextField.svelte.d.ts +13 -0
  35. package/dist/components/detailView/fields/StringField.svelte +35 -0
  36. package/dist/components/detailView/fields/StringField.svelte.d.ts +14 -0
  37. package/dist/components/detailView/fields/TextField.svelte +35 -0
  38. package/dist/components/detailView/fields/TextField.svelte.d.ts +14 -0
  39. package/dist/components/foreingKeyInput.svelte +1 -1
  40. package/dist/components/polymorphicInput.svelte +1 -1
  41. package/dist/components/popup/popup.svelte +23 -4
  42. package/dist/components/popup/popup.svelte.d.ts +2 -0
  43. package/dist/extensions/extension.types.d.ts +5 -2
  44. package/dist/extensions/extensionUtils.js +4 -1
  45. package/dist/popup.d.ts +31 -0
  46. package/dist/popup.js +104 -0
  47. package/package.json +7 -6
  48. package/src/lib/actions.ts +5 -95
  49. package/src/lib/components/detailView/detailView.svelte +12 -62
  50. package/src/lib/components/detailView/fieldInput.svelte +161 -288
  51. package/src/lib/components/detailView/fields/BoolField.svelte +42 -0
  52. package/src/lib/components/detailView/fields/CodeField.svelte +30 -0
  53. package/src/lib/components/detailView/fields/CustomInputField.svelte +50 -0
  54. package/src/lib/components/detailView/fields/DateField.svelte +47 -0
  55. package/src/lib/components/detailView/fields/EmbeddedField.svelte +139 -0
  56. package/src/lib/components/detailView/fields/EmbeddedPolymorphicField.svelte +197 -0
  57. package/src/lib/components/detailView/fields/EnumField.svelte +70 -0
  58. package/src/lib/components/detailView/fields/FieldWrapper.svelte +68 -0
  59. package/src/lib/components/detailView/fields/ForeignKeyField.svelte +78 -0
  60. package/src/lib/components/detailView/fields/IdField.svelte +21 -0
  61. package/src/lib/components/detailView/fields/NumberField.svelte +38 -0
  62. package/src/lib/components/detailView/fields/PasswordField.svelte +29 -0
  63. package/src/lib/components/detailView/fields/PolymorphicField.svelte +51 -0
  64. package/src/lib/components/detailView/fields/RichTextField.svelte +30 -0
  65. package/src/lib/components/detailView/fields/StringField.svelte +35 -0
  66. package/src/lib/components/detailView/fields/TextField.svelte +35 -0
  67. package/src/lib/components/foreingKeyInput.svelte +1 -1
  68. package/src/lib/components/polymorphicInput.svelte +1 -1
  69. package/src/lib/components/popup/popup.svelte +23 -4
  70. package/src/lib/extensions/extension.types.ts +4 -2
  71. package/src/lib/extensions/extensionUtils.ts +4 -1
  72. package/src/lib/popup.ts +147 -0
@@ -0,0 +1,139 @@
1
+ <script lang="ts">
2
+ import { onMount, untrack } from "svelte";
3
+ import { Ban, Plus, Table, icons } from "lucide-svelte";
4
+ import Button from "../../ui/button/button.svelte";
5
+ import { getStudioContext } from "../../../context";
6
+ import DetailView from "../detailView.svelte";
7
+ import { getDefaultEntry } from "../utils";
8
+ import { getChangedProperties } from "../../../utils";
9
+ import FieldWrapper from "./FieldWrapper.svelte";
10
+
11
+ const { lobb, ctx } = getStudioContext();
12
+
13
+ interface Props {
14
+ collectionName: string;
15
+ fieldName: string;
16
+ value?: any;
17
+ entry: Record<string, any>;
18
+ isDisabled?: boolean;
19
+ errorMessages?: any;
20
+ header?: any;
21
+ onClear?: () => void;
22
+ }
23
+
24
+ let {
25
+ collectionName,
26
+ fieldName,
27
+ value = $bindable(),
28
+ entry = $bindable(),
29
+ isDisabled = false,
30
+ errorMessages = [],
31
+ header,
32
+ onClear,
33
+ }: Props = $props();
34
+
35
+ const destructive = $derived(Array.isArray(errorMessages) && errorMessages.length > 0);
36
+
37
+ const CollectionIcon = $derived.by(() => {
38
+ const name = ctx.meta.collections[collectionName]?.ui?.icon;
39
+ return name && name in icons ? (icons as any)[name] : Table;
40
+ });
41
+
42
+ const fieldNames = Object.keys(ctx.meta.collections[collectionName]?.fields ?? {});
43
+ let embeddedValues = $state(getDefaultEntry(ctx, fieldNames, collectionName, {}));
44
+ let initialValues = $state<Record<string, any> | null>(null);
45
+ let loaded = $state(false);
46
+ let creating = $state(false);
47
+
48
+ const hasValue = $derived(value != null);
49
+ const hasNestedErrors = $derived(
50
+ errorMessages && typeof errorMessages === 'object' && !Array.isArray(errorMessages) &&
51
+ errorMessages.details && Object.keys(errorMessages.details).length > 0
52
+ );
53
+ const showForm = $derived(hasValue || creating || hasNestedErrors);
54
+
55
+ onMount(async () => {
56
+ const fkId = typeof value === 'number' ? value : null;
57
+ if (fkId != null) {
58
+ try {
59
+ const res = await lobb.findAll(collectionName, { filter: { id: fkId }, limit: 1 });
60
+ const result = await res.json();
61
+ const record = result.data[0] ?? {};
62
+ embeddedValues = getDefaultEntry(ctx, fieldNames, collectionName, record);
63
+ initialValues = { ...embeddedValues };
64
+ } catch { /* leave defaults */ }
65
+ } else if (value && typeof value === 'object' && value.create) {
66
+ embeddedValues = getDefaultEntry(ctx, fieldNames, collectionName, value.create);
67
+ creating = true;
68
+ }
69
+ loaded = true;
70
+ });
71
+
72
+ $effect(() => {
73
+ if (!loaded) return;
74
+ const snap = { ...embeddedValues };
75
+ const isCreating = creating; // track so the effect re-runs when user clicks Create
76
+ untrack(() => {
77
+ const fkId = typeof value === 'number' ? value : (value?.id ?? null);
78
+ if (fkId != null && initialValues) {
79
+ const changed = getChangedProperties(initialValues, snap);
80
+ value = Object.keys(changed).length > 0 ? { id: fkId, update: changed } : fkId;
81
+ } else if (isCreating) {
82
+ const data: Record<string, any> = {};
83
+ for (const [k, v] of Object.entries(snap)) {
84
+ if (v !== null && v !== undefined && v !== '') data[k] = v;
85
+ }
86
+ value = { create: data };
87
+ }
88
+ });
89
+ });
90
+
91
+ function handleRemove() {
92
+ creating = false;
93
+ embeddedValues = getDefaultEntry(ctx, fieldNames, collectionName, {});
94
+ initialValues = null;
95
+ onClear?.();
96
+ }
97
+ </script>
98
+
99
+ <FieldWrapper span={2} {isDisabled} {errorMessages}>
100
+ {#snippet children()}
101
+ <div class="rounded-md border bg-muted/30 flex flex-col {destructive ? 'border-destructive' : ''}">
102
+ {#if showForm}
103
+ <div class="flex items-center justify-between gap-2 px-4 py-2.5 border-b">
104
+ {#if header}
105
+ <div class="flex items-center gap-2">
106
+ <CollectionIcon size={14} class="text-muted-foreground" />
107
+ <span class="text-sm font-medium">{header.text}</span>
108
+ <span class="text-[0.7rem] text-muted-foreground">{collectionName}</span>
109
+ </div>
110
+ {/if}
111
+ {#if !isDisabled}
112
+ <Button onclick={handleRemove} variant="outline" class="aspect-square h-6 w-6 shrink-0 p-0" Icon={Ban} tabindex={-1} />
113
+ {/if}
114
+ </div>
115
+ {#if loaded}
116
+ <DetailView {collectionName} bind:entry={embeddedValues} fieldsErrors={errorMessages?.details ?? {}} />
117
+ {/if}
118
+ {:else}
119
+ <div class="flex flex-col items-center gap-3 py-8 px-4 text-center">
120
+ <div class="flex h-10 w-10 items-center justify-center rounded-full bg-muted">
121
+ <CollectionIcon size={20} class="text-muted-foreground" />
122
+ </div>
123
+ <div class="flex flex-col items-center gap-2">
124
+ <div class="flex items-center gap-2">
125
+ <span class="text-sm font-semibold">No record attached in</span>
126
+ <span class="rounded-md border bg-background px-2.5 py-0.5 text-xs font-semibold tracking-wide">{header?.text ?? collectionName}</span>
127
+ </div>
128
+ <span class="text-xs text-muted-foreground">Create a new record to attach it here.</span>
129
+ </div>
130
+ {#if !isDisabled}
131
+ <Button onclick={() => creating = true} class="mt-1 gap-1.5" Icon={Plus}>
132
+ Create
133
+ </Button>
134
+ {/if}
135
+ </div>
136
+ {/if}
137
+ </div>
138
+ {/snippet}
139
+ </FieldWrapper>
@@ -0,0 +1,197 @@
1
+ <script lang="ts">
2
+ import { onMount, untrack } from "svelte";
3
+ import { Ban, ChevronDown, GitFork, Plus, Table, icons } from "lucide-svelte";
4
+ import Button from "../../ui/button/button.svelte";
5
+ import * as Popover from "../../ui/popover/index";
6
+ import { getStudioContext } from "../../../context";
7
+ import DetailView from "../detailView.svelte";
8
+ import { getDefaultEntry } from "../utils";
9
+ import { getChangedProperties } from "../../../utils";
10
+ import FieldWrapper from "./FieldWrapper.svelte";
11
+
12
+ const { lobb, ctx } = getStudioContext();
13
+
14
+ interface Props {
15
+ virtualField: string;
16
+ collectionField: string;
17
+ idField: string;
18
+ targetCollections: string[];
19
+ entry: Record<string, any>;
20
+ isDisabled?: boolean;
21
+ errorMessages?: any;
22
+ header?: any;
23
+ }
24
+
25
+ let {
26
+ virtualField,
27
+ collectionField,
28
+ idField,
29
+ targetCollections,
30
+ entry = $bindable(),
31
+ isDisabled = false,
32
+ errorMessages = [],
33
+ header,
34
+ }: Props = $props();
35
+
36
+ const selectedCollection = $derived(
37
+ (entry[virtualField]?.collection ?? entry[collectionField]) as string | null ?? null
38
+ );
39
+
40
+ const CollectionIcon = $derived.by(() => {
41
+ if (!selectedCollection) return GitFork;
42
+ const name = ctx.meta.collections[selectedCollection]?.ui?.icon;
43
+ return name && name in icons ? (icons as any)[name] : Table;
44
+ });
45
+
46
+ const destructive = $derived(Array.isArray(errorMessages) && errorMessages.length > 0);
47
+ const hasNestedErrors = $derived(
48
+ errorMessages && typeof errorMessages === 'object' && !Array.isArray(errorMessages) &&
49
+ errorMessages.details && Object.keys(errorMessages.details).length > 0
50
+ );
51
+
52
+ const showForm = $derived(!!selectedCollection || hasNestedErrors);
53
+
54
+ // --- embedded form state ---
55
+ let embeddedValues = $state<Record<string, any>>({});
56
+ let initialValues = $state<Record<string, any> | null>(null);
57
+ let loaded = $state(false);
58
+
59
+ async function loadEmbedded(col: string) {
60
+ loaded = false;
61
+ const fieldNames = Object.keys(ctx.meta.collections[col]?.fields ?? {});
62
+ const fkId = typeof entry[idField] === 'number' ? entry[idField] : null;
63
+ const virtualVal = entry[virtualField];
64
+
65
+ if (fkId != null) {
66
+ try {
67
+ const res = await lobb.findAll(col, { filter: { id: fkId }, limit: 1 });
68
+ const result = await res.json();
69
+ const record = result.data[0] ?? {};
70
+ embeddedValues = getDefaultEntry(ctx, fieldNames, col, record);
71
+ initialValues = { ...embeddedValues };
72
+ } catch { /* leave defaults */ }
73
+ } else if (virtualVal?.create && typeof virtualVal.create === 'object') {
74
+ embeddedValues = getDefaultEntry(ctx, fieldNames, col, virtualVal.create);
75
+ } else {
76
+ embeddedValues = getDefaultEntry(ctx, fieldNames, col, {});
77
+ }
78
+ loaded = true;
79
+ }
80
+
81
+ onMount(() => {
82
+ if (selectedCollection) loadEmbedded(selectedCollection);
83
+ else loaded = true;
84
+ });
85
+
86
+ $effect(() => {
87
+ if (!loaded || !selectedCollection) return;
88
+ const col = selectedCollection;
89
+ const snap = { ...embeddedValues };
90
+ untrack(() => {
91
+ const fkId = typeof entry[idField] === 'number' ? entry[idField] : null;
92
+ if (fkId != null && initialValues) {
93
+ const changed = getChangedProperties(initialValues, snap);
94
+ entry[virtualField] = Object.keys(changed).length > 0
95
+ ? { collection: col, id: fkId, update: changed }
96
+ : null;
97
+ } else {
98
+ const data: Record<string, any> = {};
99
+ for (const [k, v] of Object.entries(snap)) {
100
+ if (v !== null && v !== undefined && v !== '') data[k] = v;
101
+ }
102
+ entry[virtualField] = { collection: col, create: data };
103
+ }
104
+ });
105
+ });
106
+
107
+ function handleSelect(name: string) {
108
+ entry[virtualField] = { collection: name };
109
+ embeddedValues = {};
110
+ initialValues = null;
111
+ loadEmbedded(name);
112
+ }
113
+
114
+ function handleClear() {
115
+ entry[virtualField] = null;
116
+ embeddedValues = {};
117
+ initialValues = null;
118
+ loaded = true;
119
+ }
120
+
121
+ function getCollectionIcon(name: string) {
122
+ const iconName = ctx.meta.collections[name]?.ui?.icon;
123
+ return iconName && iconName in icons ? (icons as any)[iconName] : Table;
124
+ }
125
+ </script>
126
+
127
+ <FieldWrapper span={2} {isDisabled} {errorMessages}>
128
+ {#snippet children()}
129
+ <div class="rounded-md border bg-muted/30 flex flex-col {destructive ? 'border-destructive' : ''}">
130
+ {#if showForm}
131
+ <div class="flex items-center justify-between gap-2 px-4 py-2.5 border-b">
132
+ <div class="flex items-center gap-2">
133
+ <CollectionIcon size={14} class="text-muted-foreground" />
134
+ <span class="text-sm font-medium">{header?.text ?? ''}</span>
135
+ {#if selectedCollection}
136
+ <span class="text-[0.7rem] text-muted-foreground">{selectedCollection}</span>
137
+ {/if}
138
+ </div>
139
+ {#if !isDisabled}
140
+ <Button onclick={handleClear} variant="outline" class="aspect-square h-6 w-6 shrink-0 p-0" Icon={Ban} tabindex={-1} />
141
+ {/if}
142
+ </div>
143
+ {#if selectedCollection && loaded}
144
+ <DetailView
145
+ collectionName={selectedCollection}
146
+ bind:entry={embeddedValues}
147
+ fieldsErrors={errorMessages?.details ?? {}}
148
+ />
149
+ {/if}
150
+ {:else}
151
+ <div class="flex flex-col items-center gap-3 py-8 px-4 text-center">
152
+ <div class="flex h-10 w-10 items-center justify-center rounded-full bg-muted">
153
+ <GitFork size={20} class="text-muted-foreground" />
154
+ </div>
155
+ <div class="flex flex-col items-center gap-2">
156
+ <div class="flex items-center gap-2">
157
+ <span class="text-sm font-semibold">No record attached in</span>
158
+ <span class="rounded-md border bg-background px-2.5 py-0.5 text-xs font-semibold tracking-wide">{header?.text ?? 'content'}</span>
159
+ </div>
160
+ <span class="text-xs text-muted-foreground">Create a new record to link it here.</span>
161
+ </div>
162
+ {#if !isDisabled}
163
+ <Popover.Root>
164
+ <Popover.Trigger>
165
+ {#snippet child({ props })}
166
+ <button
167
+ {...props}
168
+ class="mt-1 inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors"
169
+ >
170
+ <Plus size={14} />
171
+ Create
172
+ <ChevronDown size={12} class="opacity-70" />
173
+ </button>
174
+ {/snippet}
175
+ </Popover.Trigger>
176
+ <Popover.Content class="w-48 p-2">
177
+ <div class="flex flex-col gap-1">
178
+ {#each targetCollections as name}
179
+ {@const Icon = getCollectionIcon(name)}
180
+ <Button
181
+ variant="ghost"
182
+ class="justify-start text-xs font-normal h-7 px-2 gap-2"
183
+ onclick={() => handleSelect(name)}
184
+ >
185
+ <Icon size={12} />
186
+ {name}
187
+ </Button>
188
+ {/each}
189
+ </div>
190
+ </Popover.Content>
191
+ </Popover.Root>
192
+ {/if}
193
+ </div>
194
+ {/if}
195
+ </div>
196
+ {/snippet}
197
+ </FieldWrapper>
@@ -0,0 +1,70 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import type { EnumOption } from "@lobb-js/core";
4
+ import * as Select from "../../ui/select/index";
5
+ import EnumBadge from "../../dataTable/enumBadge.svelte";
6
+ import FieldWrapper from "./FieldWrapper.svelte";
7
+
8
+ interface Props {
9
+ value?: any;
10
+ rawEnum: (string | number | EnumOption)[];
11
+ isNumeric?: boolean;
12
+ placeholder?: string;
13
+ destructive?: boolean;
14
+ changedClass?: string;
15
+ isDisabled?: boolean;
16
+ errorMessages?: any;
17
+ header?: any;
18
+ clearBtn?: Snippet<[]>;
19
+ }
20
+ let { value = $bindable(), rawEnum, isNumeric = false, placeholder = "NULL", destructive = false, changedClass = '', isDisabled = false, errorMessages = [], header, clearBtn }: Props = $props();
21
+
22
+ const isEnumOption = rawEnum.length > 0 && typeof rawEnum[0] === "object";
23
+ const enumOptions = isEnumOption ? rawEnum as EnumOption[] : undefined;
24
+ </script>
25
+
26
+ <FieldWrapper span={1} {isDisabled} {errorMessages} {header}>
27
+ {#snippet children()}
28
+ <div class="relative">
29
+ {#if clearBtn}
30
+ <div class="absolute right-1 inset-y-0 z-10 flex items-center">
31
+ {@render clearBtn()}
32
+ </div>
33
+ {/if}
34
+ <Select.Root
35
+ type="single"
36
+ bind:value={
37
+ () => value != null ? String(value) : undefined,
38
+ (v) => {
39
+ if (v == null) { value = null; return; }
40
+ value = isNumeric ? Number(v) : v;
41
+ }
42
+ }
43
+ >
44
+ <Select.Trigger class="h-9 w-full bg-muted pr-8 {changedClass} {destructive ? 'border-destructive' : ''}">
45
+ {#if value != null && enumOptions}
46
+ <EnumBadge value={String(value)} enum={enumOptions} />
47
+ {:else if value != null}
48
+ {value}
49
+ {:else}
50
+ <span class="text-muted-foreground">{placeholder}</span>
51
+ {/if}
52
+ </Select.Trigger>
53
+ <Select.Content>
54
+ <Select.Group>
55
+ {#each rawEnum as option}
56
+ {@const optionValue = typeof option === "object" ? String(option.value) : String(option)}
57
+ <Select.Item value={optionValue} label={optionValue}>
58
+ {#if enumOptions}
59
+ <EnumBadge value={optionValue} enum={enumOptions} />
60
+ {:else}
61
+ {optionValue}
62
+ {/if}
63
+ </Select.Item>
64
+ {/each}
65
+ </Select.Group>
66
+ </Select.Content>
67
+ </Select.Root>
68
+ </div>
69
+ {/snippet}
70
+ </FieldWrapper>
@@ -0,0 +1,68 @@
1
+ <script lang="ts">
2
+ import { CircleAlert, CircleHelp } from "lucide-svelte";
3
+ import * as Tooltip from "../../ui/tooltip";
4
+ import type { Snippet } from "svelte";
5
+
6
+ interface FieldHeader {
7
+ text: string;
8
+ icon?: any;
9
+ typeText?: string;
10
+ description?: string;
11
+ }
12
+
13
+ interface Props {
14
+ span?: 1 | 2;
15
+ isDisabled?: boolean;
16
+ disabledClasses?: string;
17
+ errorMessages?: any;
18
+ header?: FieldHeader;
19
+ children: Snippet<[]>;
20
+ }
21
+
22
+ let {
23
+ span = 1,
24
+ isDisabled = false,
25
+ disabledClasses = "pointer-events-none opacity-50",
26
+ errorMessages = [],
27
+ header,
28
+ children,
29
+ }: Props = $props();
30
+ </script>
31
+
32
+ <div class="{span === 2 ? 'col-span-2' : 'col-span-1'} flex flex-col gap-2 {isDisabled ? 'cursor-not-allowed' : ''}">
33
+ {#if header}
34
+ <div class="flex items-center gap-1.5 text-xs">
35
+ <span>{header.text}</span>
36
+ {#if header.icon || header.typeText}
37
+ <span class="flex items-center gap-1 text-[0.7rem] text-muted-foreground">
38
+ {#if header.icon}
39
+ {@const HeaderIcon = header.icon}
40
+ <HeaderIcon size={12} />
41
+ {/if}
42
+ {header.typeText ?? ''}
43
+ </span>
44
+ {/if}
45
+ {#if header.description}
46
+ <Tooltip.Root>
47
+ <Tooltip.Trigger>
48
+ <CircleHelp size={12} class="text-muted-foreground" />
49
+ </Tooltip.Trigger>
50
+ <Tooltip.Content class="max-w-64 text-xs">
51
+ {header.description}
52
+ </Tooltip.Content>
53
+ </Tooltip.Root>
54
+ {/if}
55
+ </div>
56
+ {/if}
57
+ <div class="{isDisabled ? disabledClasses : ''}">
58
+ {@render children()}
59
+ {#if Array.isArray(errorMessages) && errorMessages.length}
60
+ {#each errorMessages as message}
61
+ <div class="flex gap-1 text-destructive mt-1">
62
+ <CircleAlert size="15" class="translate-y-[0.025rem]" />
63
+ <div class="text-[0.7rem]">{message}</div>
64
+ </div>
65
+ {/each}
66
+ {/if}
67
+ </div>
68
+ </div>
@@ -0,0 +1,78 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import { getStudioContext } from "../../../context";
4
+ import { getExtensionUtils, loadExtensionComponents } from "../../../extensions/extensionUtils";
5
+ import ForeingKeyInput from "../../foreingKeyInput.svelte";
6
+ import ExtensionsComponents from "../../extensionsComponents.svelte";
7
+ import FieldWrapper from "./FieldWrapper.svelte";
8
+
9
+ const { lobb, ctx } = getStudioContext();
10
+
11
+ interface Props {
12
+ parentCollectionName: string;
13
+ collectionName: string;
14
+ fieldName: string;
15
+ value?: any;
16
+ entry?: Record<string, any>;
17
+ destructive?: boolean;
18
+ changedClass?: string;
19
+ isDisabled?: boolean;
20
+ errorMessages?: any;
21
+ header?: any;
22
+ clearBtn?: Snippet<[]>;
23
+ }
24
+
25
+ let {
26
+ parentCollectionName,
27
+ collectionName,
28
+ fieldName,
29
+ value = $bindable(),
30
+ entry = $bindable(),
31
+ destructive = false,
32
+ isDisabled = false,
33
+ errorMessages = [],
34
+ header,
35
+ clearBtn,
36
+ }: Props = $props();
37
+
38
+ const hasExtension = $derived(
39
+ loadExtensionComponents(ctx, `detailView.fields.foreignKey.${collectionName}`).length > 0
40
+ );
41
+ </script>
42
+
43
+ {#if hasExtension}
44
+ <ExtensionsComponents
45
+ name="detailView.fields.foreignKey.{collectionName}"
46
+ utils={getExtensionUtils(lobb, ctx)}
47
+ {parentCollectionName}
48
+ {collectionName}
49
+ bind:value
50
+ entry={entry!}
51
+ {fieldName}
52
+ {destructive}
53
+ {header}
54
+ {isDisabled}
55
+ {errorMessages}
56
+ {clearBtn}
57
+ />
58
+ {:else}
59
+ <FieldWrapper span={1} {isDisabled} {errorMessages} {header}>
60
+ {#snippet children()}
61
+ <div class="relative">
62
+ {#if clearBtn}
63
+ <div class="absolute right-1 top-1 z-10">
64
+ {@render clearBtn()}
65
+ </div>
66
+ {/if}
67
+ <ForeingKeyInput
68
+ {parentCollectionName}
69
+ {collectionName}
70
+ bind:value
71
+ entry={entry!}
72
+ {fieldName}
73
+ {destructive}
74
+ />
75
+ </div>
76
+ {/snippet}
77
+ </FieldWrapper>
78
+ {/if}
@@ -0,0 +1,21 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import Input from "../../ui/input/input.svelte";
4
+ import FieldWrapper from "./FieldWrapper.svelte";
5
+
6
+ interface Props {
7
+ value?: any;
8
+ changedClass?: string;
9
+ isDisabled?: boolean;
10
+ errorMessages?: any;
11
+ header?: any;
12
+ clearBtn?: Snippet<[]>;
13
+ }
14
+ let { value = $bindable(), changedClass = '', isDisabled = false, errorMessages = [], header, clearBtn }: Props = $props();
15
+ </script>
16
+
17
+ <FieldWrapper span={1} {isDisabled} {errorMessages} {header}>
18
+ {#snippet children()}
19
+ <Input placeholder="AUTO GENERATED" class="bg-muted text-xs {changedClass}" bind:value />
20
+ {/snippet}
21
+ </FieldWrapper>
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import NumberInput from "../../ui/input/numberInput.svelte";
4
+ import FieldWrapper from "./FieldWrapper.svelte";
5
+
6
+ interface Props {
7
+ value?: any;
8
+ scale?: number;
9
+ groupDigits?: boolean;
10
+ placeholder?: string;
11
+ destructive?: boolean;
12
+ changedClass?: string;
13
+ isDisabled?: boolean;
14
+ errorMessages?: any;
15
+ header?: any;
16
+ clearBtn?: Snippet<[]>;
17
+ }
18
+ let { value = $bindable(), scale = 0, groupDigits = false, placeholder = "NULL", destructive = false, changedClass = '', isDisabled = false, errorMessages = [], header, clearBtn }: Props = $props();
19
+ </script>
20
+
21
+ <FieldWrapper span={1} {isDisabled} {errorMessages} {header}>
22
+ {#snippet children()}
23
+ <div class="relative">
24
+ {#if clearBtn}
25
+ <div class="absolute right-1 inset-y-0 z-10 flex items-center">
26
+ {@render clearBtn()}
27
+ </div>
28
+ {/if}
29
+ <NumberInput
30
+ {placeholder}
31
+ {scale}
32
+ {groupDigits}
33
+ class="bg-muted text-xs pr-9 {changedClass} {destructive ? 'border-destructive' : ''}"
34
+ bind:value
35
+ />
36
+ </div>
37
+ {/snippet}
38
+ </FieldWrapper>
@@ -0,0 +1,29 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import PasswordInput from "../passwordInput.svelte";
4
+ import FieldWrapper from "./FieldWrapper.svelte";
5
+
6
+ interface Props {
7
+ value?: any;
8
+ destructive?: boolean;
9
+ isDisabled?: boolean;
10
+ errorMessages?: any;
11
+ header?: any;
12
+ clearBtn?: Snippet<[]>;
13
+ }
14
+
15
+ let { value = $bindable(), destructive = false, isDisabled = false, errorMessages = [], header, clearBtn }: Props = $props();
16
+ </script>
17
+
18
+ <FieldWrapper span={1} {isDisabled} {errorMessages} {header}>
19
+ {#snippet children()}
20
+ <div class="relative">
21
+ {#if clearBtn}
22
+ <div class="absolute right-1 inset-y-0 z-10 flex items-center">
23
+ {@render clearBtn()}
24
+ </div>
25
+ {/if}
26
+ <PasswordInput bind:value {destructive} />
27
+ </div>
28
+ {/snippet}
29
+ </FieldWrapper>