@svadmin/ui 0.69.1 → 0.72.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.
- package/dist/actions.js +1 -1
- package/dist/app.css +102 -63
- package/dist/app.theme.css +102 -63
- package/dist/component-registry.svelte.d.ts +33 -19
- package/dist/component-registry.svelte.js +6 -4
- package/dist/components/AdminApp.svelte +55 -53
- package/dist/components/AdminApp.svelte.d.ts +2 -2
- package/dist/components/ApiSettings.svelte +19 -1
- package/dist/components/AuditLogDrawer.svelte +14 -3
- package/dist/components/AutoForm.svelte +146 -48
- package/dist/components/AutoForm.svelte.d.ts +2 -0
- package/dist/components/AutoTable.svelte +358 -258
- package/dist/components/AutoTable.svelte.d.ts +1 -0
- package/dist/components/BoundRecordDetailDrawer.svelte +166 -0
- package/dist/components/BoundRecordDetailDrawer.svelte.d.ts +9 -0
- package/dist/components/Breadcrumbs.svelte +7 -4
- package/dist/components/CancelTaskButton.svelte +9 -5
- package/dist/components/ColumnHeaderFilter.svelte +4 -3
- package/dist/components/ColumnSettings.svelte +1 -0
- package/dist/components/ComboboxField.svelte +90 -30
- package/dist/components/ComboboxField.svelte.d.ts +2 -2
- package/dist/components/CreatePage.svelte +3 -1
- package/dist/components/DetailDrawer.svelte +4 -0
- package/dist/components/DetailDrawer.svelte.d.ts +1 -0
- package/dist/components/DevTools.svelte +1 -1
- package/dist/components/DraggableGrid.svelte +1 -0
- package/dist/components/DraggableHeader.svelte +5 -9
- package/dist/components/DraggableHeader.svelte.d.ts +43 -18
- package/dist/components/DraggableRowTable.svelte +2 -2
- package/dist/components/DynamicFormList.svelte +17 -17
- package/dist/components/DynamicFormList.svelte.d.ts +2 -1
- package/dist/components/EditPage.svelte +3 -1
- package/dist/components/EditableTable.svelte +4 -0
- package/dist/components/ErrorPage.svelte +3 -1
- package/dist/components/FieldDisplay.svelte +14 -0
- package/dist/components/FieldDisplay.svelte.d.ts +7 -0
- package/dist/components/FieldRenderer.svelte +9 -5
- package/dist/components/FilterBuilder.svelte +19 -8
- package/dist/components/FilterDropdown.svelte.d.ts +1 -1
- package/dist/components/Header.svelte +5 -6
- package/dist/components/ImportWizard.svelte +3 -2
- package/dist/components/InfiniteList.svelte +22 -8
- package/dist/components/InfiniteList.svelte.d.ts +5 -3
- package/dist/components/InlineEdit.svelte +114 -44
- package/dist/components/JsonSchemaForm.svelte +19 -14
- package/dist/components/Layout.svelte +19 -15
- package/dist/components/LazyTaskQueueDrawer.svelte +26 -0
- package/dist/components/LazyTaskQueueDrawer.svelte.d.ts +8 -0
- package/dist/components/ListPage.svelte +7 -6
- package/dist/components/MasterDetailView.svelte +3 -3
- package/dist/components/MediaLibraryModal.svelte +5 -2
- package/dist/components/MentionsInput.svelte +4 -3
- package/dist/components/NavigateToResource.svelte +3 -2
- package/dist/components/PermissionMatrix.svelte +4 -3
- package/dist/components/ProfilePage.svelte +2 -2
- package/dist/components/QuickEditDrawer.svelte +39 -0
- package/dist/components/QuickEditDrawer.svelte.d.ts +8 -0
- package/dist/components/RecordDetailDrawer.svelte +14 -108
- package/dist/components/RecordRowActions.svelte +41 -0
- package/dist/components/RecordRowActions.svelte.d.ts +14 -0
- package/dist/components/ResourceAccessGuard.svelte +7 -5
- package/dist/components/RetryTaskButton.svelte +9 -5
- package/dist/components/RowActions.svelte +5 -3
- package/dist/components/SensitiveDataMask.svelte +3 -3
- package/dist/components/SettingsPage.svelte +1 -1
- package/dist/components/ShowPage.svelte +44 -38
- package/dist/components/Sidebar.svelte +10 -8
- package/dist/components/SplitPaneLayout.svelte +4 -2
- package/dist/components/StepsForm.svelte +161 -44
- package/dist/components/StepsForm.svelte.d.ts +1 -0
- package/dist/components/TaskDetails.svelte +20 -7
- package/dist/components/TaskList.svelte +19 -7
- package/dist/components/TaskQueueDrawer.svelte +21 -22
- package/dist/components/Toast.svelte +4 -2
- package/dist/components/TooltipButton.svelte +1 -1
- package/dist/components/TreeSelect.svelte +4 -2
- package/dist/components/ValidatedField.svelte +27 -0
- package/dist/components/ValidatedField.svelte.d.ts +35 -0
- package/dist/components/account/ImportMembersPage.svelte +2 -2
- package/dist/components/account/SecurityLogPage.svelte +3 -1
- package/dist/components/account/SettingsEnterprisePage.svelte +7 -5
- package/dist/components/account/TeamMembersPage.svelte +1 -1
- package/dist/components/buttons/CloneButton.svelte +6 -4
- package/dist/components/buttons/DeleteButton.svelte +95 -29
- package/dist/components/buttons/DeleteButton.svelte.d.ts +2 -0
- package/dist/components/buttons/EditButton.svelte +6 -4
- package/dist/components/buttons/ExportButton.svelte +22 -4
- package/dist/components/buttons/ImportButton.svelte +83 -38
- package/dist/components/buttons/ImportButton.svelte.d.ts +6 -10
- package/dist/components/buttons/RefreshButton.svelte +48 -8
- package/dist/components/buttons/ShowButton.svelte +6 -4
- package/dist/components/content/ApiKeyList.svelte +3 -1
- package/dist/components/content/ContentPageShell.svelte +3 -1
- package/dist/components/content/FileList.svelte +3 -1
- package/dist/components/content/MemberList.svelte +3 -1
- package/dist/components/content/NetworkTable.svelte +3 -1
- package/dist/components/content/SecurityEventTable.svelte +3 -1
- package/dist/components/content/StatusTabs.svelte +2 -2
- package/dist/components/fieldComponentMap.d.ts +18 -13
- package/dist/components/fieldComponentMap.js +95 -47
- package/dist/components/fields/AvatarField.svelte +1 -1
- package/dist/components/fields/ChoiceField.svelte +16 -0
- package/dist/components/fields/ChoiceField.svelte.d.ts +10 -0
- package/dist/components/fields/CodeField.svelte +11 -12
- package/dist/components/fields/CodeField.svelte.d.ts +1 -1
- package/dist/components/fields/DateRangeField.svelte +2 -4
- package/dist/components/fields/ImageField.svelte +3 -1
- package/dist/components/fields/ImagesField.svelte +23 -0
- package/dist/components/fields/ImagesField.svelte.d.ts +6 -0
- package/dist/components/fields/JsonField.svelte +19 -13
- package/dist/components/fields/PasswordField.svelte +5 -0
- package/dist/components/fields/PasswordField.svelte.d.ts +6 -0
- package/dist/components/fields/json-value.d.ts +11 -0
- package/dist/components/fields/json-value.js +28 -0
- package/dist/components/profile/ActivityTimeline.svelte +2 -2
- package/dist/components/record-detail.svelte.d.ts +16 -0
- package/dist/components/record-detail.svelte.js +28 -0
- package/dist/components/saved-list-views.js +29 -29
- package/dist/components/table-contract.d.ts +13 -0
- package/dist/components/table-contract.js +42 -0
- package/dist/components/task-utils.js +4 -2
- package/dist/components/ui/alert/alert-variants.d.ts +2 -3
- package/dist/components/ui/alert/alert-variants.js +1 -2
- package/dist/components/ui/alert/alert.svelte +4 -5
- package/dist/components/ui/alert/alert.svelte.d.ts +3 -8
- package/dist/components/ui/alert/index.js +0 -2
- package/dist/components/ui/avatar/avatar-variants.d.ts +2 -3
- package/dist/components/ui/avatar/avatar-variants.js +1 -2
- package/dist/components/ui/avatar/avatar.svelte +5 -6
- package/dist/components/ui/avatar/avatar.svelte.d.ts +3 -8
- package/dist/components/ui/badge/badge-variants.d.ts +2 -3
- package/dist/components/ui/badge/badge-variants.js +1 -2
- package/dist/components/ui/badge/badge.svelte +4 -5
- package/dist/components/ui/badge/badge.svelte.d.ts +3 -8
- package/dist/components/ui/badge/index.js +0 -2
- package/dist/components/ui/button/button-variants.d.ts +2 -3
- package/dist/components/ui/button/button-variants.js +1 -2
- package/dist/components/ui/button/button.svelte +100 -44
- package/dist/components/ui/button/button.svelte.d.ts +8 -12
- package/dist/components/ui/button/index.js +0 -2
- package/dist/components/ui/command/command-dialog.svelte +3 -1
- package/dist/components/ui/command/command-dialog.svelte.d.ts +1 -1
- package/dist/components/ui/command/command-group.svelte +4 -2
- package/dist/components/ui/command/command-item.svelte +4 -2
- package/dist/components/ui/command/command-separator.svelte +3 -1
- package/dist/components/ui/command/index.d.ts +5 -5
- package/dist/components/ui/input/input.svelte +40 -20
- package/dist/components/ui/input/input.svelte.d.ts +5 -4
- package/dist/components/ui/sheet/sheet.svelte +9 -3
- package/dist/components/ui/sheet/sheet.svelte.d.ts +1 -0
- package/dist/components/ui/tooltip/tooltip.svelte +4 -2
- package/dist/index.d.ts +1 -1
- package/dist/inline-value.d.ts +5 -0
- package/dist/inline-value.js +24 -0
- package/dist/numeric-input.d.ts +2 -0
- package/dist/numeric-input.js +9 -0
- package/dist/types.d.ts +3 -3
- package/package.json +11 -5
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
-
import { useInvalidate,
|
|
3
|
+
import { useTranslation, useInvalidate, useResourceContract, captureAdminContext } from '@svadmin/core';
|
|
4
|
+
import { definedReactiveOptions } from '@svadmin/core/options';
|
|
4
5
|
import { Button } from '../ui/button/index.js';
|
|
5
6
|
import { RefreshCw } from '@lucide/svelte';
|
|
6
7
|
|
|
@@ -20,19 +21,53 @@
|
|
|
20
21
|
class?: string;
|
|
21
22
|
}>();
|
|
22
23
|
|
|
23
|
-
const
|
|
24
|
+
const binding = useResourceContract(() => resource);
|
|
25
|
+
const context = captureAdminContext();
|
|
26
|
+
const invalidate = useInvalidate(definedReactiveOptions({
|
|
27
|
+
get resource() { return binding.resource; },
|
|
28
|
+
get dataProviderName() { return binding.dataProviderName; },
|
|
29
|
+
}));
|
|
30
|
+
const scope = $derived({
|
|
31
|
+
contract: binding.resource,
|
|
32
|
+
provider: context.providers?.[binding.dataProviderName],
|
|
33
|
+
tenant: context.tenantCacheKey?.__svadminTenant,
|
|
34
|
+
meta: binding.meta,
|
|
35
|
+
});
|
|
24
36
|
let spinning = $state(false);
|
|
25
|
-
let
|
|
37
|
+
let failed = $state(false);
|
|
38
|
+
let active: object | undefined;
|
|
39
|
+
let previousScope: typeof scope | undefined;
|
|
26
40
|
|
|
27
41
|
$effect(() => {
|
|
28
|
-
|
|
42
|
+
if (previousScope !== scope) {
|
|
43
|
+
active = undefined;
|
|
44
|
+
spinning = false;
|
|
45
|
+
failed = false;
|
|
46
|
+
previousScope = scope;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
$effect(() => () => {
|
|
50
|
+
active = undefined;
|
|
29
51
|
});
|
|
30
52
|
|
|
31
|
-
function refresh() {
|
|
53
|
+
async function refresh() {
|
|
54
|
+
if (spinning) return;
|
|
55
|
+
const token = {};
|
|
56
|
+
const capturedScope = scope;
|
|
57
|
+
previousScope = capturedScope;
|
|
58
|
+
active = token;
|
|
32
59
|
spinning = true;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
60
|
+
failed = false;
|
|
61
|
+
try {
|
|
62
|
+
await invalidate({ invalidates: ['list', 'many'] });
|
|
63
|
+
} catch {
|
|
64
|
+
if (active === token && scope === capturedScope) failed = true;
|
|
65
|
+
} finally {
|
|
66
|
+
if (active === token && scope === capturedScope) {
|
|
67
|
+
active = undefined;
|
|
68
|
+
spinning = false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
36
71
|
}
|
|
37
72
|
|
|
38
73
|
const displayText = $derived(label ?? i18n.t('common.refresh'));
|
|
@@ -43,6 +78,8 @@
|
|
|
43
78
|
size={hideText ? 'icon' : 'sm'}
|
|
44
79
|
class={className}
|
|
45
80
|
aria-label={hideText ? displayText : undefined}
|
|
81
|
+
aria-busy={spinning}
|
|
82
|
+
disabled={spinning}
|
|
46
83
|
onclick={refresh}
|
|
47
84
|
>
|
|
48
85
|
<RefreshCw class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3 {spinning ? 'svadmin-u-afbdd13a380e' : ''}" />
|
|
@@ -56,3 +93,6 @@
|
|
|
56
93
|
</span>
|
|
57
94
|
{/if}
|
|
58
95
|
</Button>
|
|
96
|
+
{#if failed}
|
|
97
|
+
<p role="alert">{i18n.t('common.error')}</p>
|
|
98
|
+
{/if}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
3
|
+
|
|
2
4
|
import type { Snippet } from 'svelte';
|
|
3
5
|
import { getResource, useNavigation, useCan, useTranslation } from '@svadmin/core';
|
|
4
6
|
import { Button } from '../ui/button/index.js';
|
|
@@ -17,7 +19,7 @@
|
|
|
17
19
|
accessControl = { enabled: true, hideIfUnauthorized: true },
|
|
18
20
|
onBeforeNavigate,
|
|
19
21
|
class: className = '',
|
|
20
|
-
}
|
|
22
|
+
}: {
|
|
21
23
|
resource: string;
|
|
22
24
|
recordItemId: string | number;
|
|
23
25
|
label?: string;
|
|
@@ -26,7 +28,7 @@
|
|
|
26
28
|
accessControl?: ButtonAccessControl;
|
|
27
29
|
onBeforeNavigate?: (navigate: () => void) => void;
|
|
28
30
|
class?: string;
|
|
29
|
-
}
|
|
31
|
+
} = $props();
|
|
30
32
|
|
|
31
33
|
const nav = useNavigation();
|
|
32
34
|
const resourceDefinition = $derived.by(() => {
|
|
@@ -36,13 +38,13 @@
|
|
|
36
38
|
return null;
|
|
37
39
|
}
|
|
38
40
|
});
|
|
39
|
-
const can = useCan(() => ({
|
|
41
|
+
const can = useCan(() => (definedOptions({
|
|
40
42
|
resource,
|
|
41
43
|
action: 'show',
|
|
42
44
|
params: withRecordId(accessControl?.params, recordItemId),
|
|
43
45
|
meta: accessControl?.meta,
|
|
44
46
|
queryOptions: { enabled: accessControl?.enabled ?? true }
|
|
45
|
-
}));
|
|
47
|
+
})));
|
|
46
48
|
const hidden = $derived(resourceDefinition?.canShow === false || (accessControl?.hideIfUnauthorized && !can.allowed));
|
|
47
49
|
const displayText = $derived(label ?? i18n.t('common.detail'));
|
|
48
50
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
3
|
+
|
|
2
4
|
import { Check, Copy, KeyRound, Trash2 } from '@lucide/svelte';
|
|
3
5
|
import { useTranslation } from '@svadmin/core/i18n';
|
|
4
6
|
import { Badge } from '../ui/badge/index.js';
|
|
@@ -21,7 +23,7 @@
|
|
|
21
23
|
const isZh = $derived(i18n.locale === 'zh-CN');
|
|
22
24
|
</script>
|
|
23
25
|
{#if resolvedState}
|
|
24
|
-
<DataState state={resolvedState} title
|
|
26
|
+
<DataState state={resolvedState} {...definedOptions({ title: stateTitle, description: stateDescription, retry, retryLabel, loadingLabel })} class={className} />
|
|
25
27
|
{:else}
|
|
26
28
|
<div class={'svadmin-u-fa6acbf81d74 svadmin-u-e783642739e3 svadmin-u-2cd02d11d1af svadmin-u-5f22e64f2282 svadmin-u-ca6bcd4b6f3f svadmin-u-18049387f0af svadmin-u-cd0ad9a56558 ' + className}>
|
|
27
29
|
{#each keys as apiKey (apiKey.id)}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
3
|
+
|
|
2
4
|
import type { Snippet } from 'svelte';
|
|
3
5
|
import ContentPageHeader from './ContentPageHeader.svelte';
|
|
4
6
|
|
|
@@ -34,6 +36,6 @@
|
|
|
34
36
|
</script>
|
|
35
37
|
|
|
36
38
|
<div data-svadmin-content-page={pageId} class={'svadmin-u-0e12dc7de920 svadmin-u-6da6a3c3f741 ' + widthClass + ' svadmin-u-b3542e058833 ' + className}>
|
|
37
|
-
{#if title}<ContentPageHeader {title} {eyebrow} {description} {actions} />{/if}
|
|
39
|
+
{#if title}<ContentPageHeader {title} {...definedOptions({ "eyebrow": eyebrow })} {...definedOptions({ "description": description })} {...definedOptions({ "actions": actions })} />{/if}
|
|
38
40
|
{@render children()}
|
|
39
41
|
</div>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
3
|
+
|
|
2
4
|
import { Download, File, Trash2 } from '@lucide/svelte';
|
|
3
5
|
import { useTranslation } from '@svadmin/core/i18n';
|
|
4
6
|
import { Button } from '../ui/button/index.js';
|
|
@@ -11,7 +13,7 @@
|
|
|
11
13
|
const resolvedState = $derived(state ?? (files.length === 0 ? 'empty' : undefined));
|
|
12
14
|
</script>
|
|
13
15
|
{#if resolvedState}
|
|
14
|
-
<DataState state={resolvedState} title
|
|
16
|
+
<DataState state={resolvedState} {...definedOptions({ title: stateTitle ?? emptyTitle, description: stateDescription ?? emptyDescription, retry, retryLabel, loadingLabel })} class={className} />
|
|
15
17
|
{:else}
|
|
16
18
|
<div class={'svadmin-u-fa6acbf81d74 svadmin-u-e783642739e3 svadmin-u-2cd02d11d1af svadmin-u-5f22e64f2282 svadmin-u-ca6bcd4b6f3f svadmin-u-18049387f0af svadmin-u-cd0ad9a56558 ' + className}>
|
|
17
19
|
{#each files as file, index (file.id ?? file.name + '-' + index)}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
3
|
+
|
|
2
4
|
import { MoreHorizontal } from '@lucide/svelte';
|
|
3
5
|
import { useTranslation } from '@svadmin/core/i18n';
|
|
4
6
|
import { Avatar } from '../ui/avatar/index.js';
|
|
@@ -14,7 +16,7 @@
|
|
|
14
16
|
const initials = (name: string) => name.split(' ').map((part) => part[0]).join('').slice(0, 2).toUpperCase();
|
|
15
17
|
</script>
|
|
16
18
|
{#if resolvedState}
|
|
17
|
-
<DataState state={resolvedState} title
|
|
19
|
+
<DataState state={resolvedState} {...definedOptions({ title: stateTitle ?? emptyTitle, description: stateDescription ?? emptyDescription, retry, retryLabel, loadingLabel })} class={className} />
|
|
18
20
|
{:else}
|
|
19
21
|
<div class={'svadmin-u-fa6acbf81d74 svadmin-u-e783642739e3 svadmin-u-2cd02d11d1af svadmin-u-5f22e64f2282 svadmin-u-ca6bcd4b6f3f svadmin-u-18049387f0af svadmin-u-cd0ad9a56558 ' + className}>
|
|
20
22
|
{#each members as member (member.id)}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts" generics="T extends object">
|
|
2
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
3
|
+
|
|
2
4
|
import type { Snippet } from 'svelte';
|
|
3
5
|
import * as Table from '../ui/table/index.js';
|
|
4
6
|
import DataState from './DataState.svelte';
|
|
@@ -9,7 +11,7 @@
|
|
|
9
11
|
const resolvedState = $derived(state ?? (rows.length === 0 ? 'empty' : undefined));
|
|
10
12
|
</script>
|
|
11
13
|
{#if resolvedState}
|
|
12
|
-
<DataState state={resolvedState} title
|
|
14
|
+
<DataState state={resolvedState} {...definedOptions({ title: stateTitle ?? emptyTitle, description: stateDescription ?? emptyDescription, retry, retryLabel, loadingLabel })} class={className} />
|
|
13
15
|
{:else}
|
|
14
16
|
<div class={'svadmin-u-1384f66f41d0 svadmin-u-5f22e64f2282 svadmin-u-ca6bcd4b6f3f svadmin-u-18049387f0af svadmin-u-cd0ad9a56558 ' + className}>
|
|
15
17
|
<Table.Root data-svadmin-datatable>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
3
|
+
|
|
2
4
|
import * as Table from '../ui/table/index.js';
|
|
3
5
|
import { useTranslation } from '@svadmin/core/i18n';
|
|
4
6
|
import StatusBadge from './StatusBadge.svelte';
|
|
@@ -12,7 +14,7 @@
|
|
|
12
14
|
const isZh = $derived(i18n.locale === 'zh-CN');
|
|
13
15
|
</script>
|
|
14
16
|
{#if resolvedState}
|
|
15
|
-
<DataState state={resolvedState} title
|
|
17
|
+
<DataState state={resolvedState} {...definedOptions({ title: stateTitle ?? emptyTitle, description: stateDescription ?? emptyDescription, retry, retryLabel, loadingLabel })} class={className} />
|
|
16
18
|
{:else}
|
|
17
19
|
<div class={'svadmin-u-1384f66f41d0 svadmin-u-5f22e64f2282 svadmin-u-ca6bcd4b6f3f svadmin-u-18049387f0af svadmin-u-cd0ad9a56558 ' + className}>
|
|
18
20
|
<Table.Root data-svadmin-datatable>
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
|
|
64
64
|
const isCompact = $derived(density === 'compact');
|
|
65
65
|
|
|
66
|
-
const toneClasses: Record<
|
|
66
|
+
const toneClasses: Record<NonNullable<StatusTabItem['tone']>, { activeBadge: string; inactiveBadge: string }> = {
|
|
67
67
|
default: {
|
|
68
68
|
activeBadge: 'svadmin-u-6a1572b6bf5a svadmin-u-20aaf08a7ed1',
|
|
69
69
|
inactiveBadge: 'svadmin-u-2ef11f1cb219 svadmin-u-bfa603190748',
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
{#each items as item (item.key)}
|
|
108
108
|
{@const active = value === item.key}
|
|
109
109
|
{@const tone = item.tone ?? 'default'}
|
|
110
|
-
{@const badgeTone = toneClasses[tone] ?? toneClasses
|
|
110
|
+
{@const badgeTone = toneClasses[tone] ?? toneClasses['default']}
|
|
111
111
|
<button
|
|
112
112
|
type="button"
|
|
113
113
|
role="tab"
|
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
import type { Component } from 'svelte';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import type { FieldDefinition } from '@svadmin/core';
|
|
3
|
+
import { type Static, type TSchema } from '@sinclair/typebox';
|
|
4
|
+
import { type SafeSchema } from '@svadmin/core/schema';
|
|
5
|
+
export interface FieldDisplayProps {
|
|
6
|
+
value: unknown;
|
|
7
|
+
options?: {
|
|
8
|
+
label: string;
|
|
9
|
+
value: string | number;
|
|
10
|
+
}[] | undefined;
|
|
11
|
+
resourceName?: string | undefined;
|
|
4
12
|
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*/
|
|
9
|
-
export declare const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* The component will be used by ShowPage and any other display contexts.
|
|
13
|
-
*/
|
|
14
|
-
export declare function registerDisplayComponent(fieldType: string, component: Component<any>): void;
|
|
13
|
+
export type FieldComponentMap = Readonly<Record<string, Component<FieldDisplayProps>>>;
|
|
14
|
+
/** Every dynamic renderer validates its complete projected props before mounting. */
|
|
15
|
+
export declare const builtinDisplayComponents: FieldComponentMap & Readonly<Record<FieldDefinition['type'], Component<FieldDisplayProps>>>;
|
|
16
|
+
/** Custom renderers receive only values proven by their closed schema. */
|
|
17
|
+
export declare function registerDisplayComponent<const S extends TSchema>(fieldType: string, schema: S & SafeSchema<S>, component: Component<{
|
|
18
|
+
value: Static<NoInfer<S>>;
|
|
19
|
+
}>): void;
|
|
15
20
|
/**
|
|
16
21
|
* Get the display component for a field type.
|
|
17
22
|
* Custom components take precedence over built-in ones.
|
|
18
23
|
* Returns undefined if no component is registered for the type.
|
|
19
24
|
*/
|
|
20
|
-
export declare function getDisplayComponent(fieldType: string): Component<
|
|
25
|
+
export declare function getDisplayComponent(fieldType: string): Component<FieldDisplayProps> | undefined;
|
|
21
26
|
/**
|
|
22
27
|
* Check if a field type has a display component (custom or built-in).
|
|
23
28
|
*/
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// Pre-import all field display components (tree-shaken by bundler)
|
|
1
|
+
import { Type } from '@sinclair/typebox';
|
|
2
|
+
import { closeContractSchema, createExactSchemaValidator } from '@svadmin/core/schema';
|
|
3
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
4
|
+
import ValidatedField from './ValidatedField.svelte';
|
|
6
5
|
import TextField from './fields/TextField.svelte';
|
|
7
6
|
import BooleanField from './fields/BooleanField.svelte';
|
|
8
7
|
import ImageField from './fields/ImageField.svelte';
|
|
8
|
+
import ImagesField from './fields/ImagesField.svelte';
|
|
9
9
|
import TagField from './fields/TagField.svelte';
|
|
10
10
|
import DateField from './fields/DateField.svelte';
|
|
11
11
|
import DateRangeField from './fields/DateRangeField.svelte';
|
|
@@ -26,47 +26,94 @@ import AvatarField from './fields/AvatarField.svelte';
|
|
|
26
26
|
import CopyField from './fields/CopyField.svelte';
|
|
27
27
|
import FileField from './fields/FileField.svelte';
|
|
28
28
|
import MarkdownField from './fields/MarkdownField.svelte';
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
29
|
+
import { jsonValueSchema } from './fields/json-value';
|
|
30
|
+
import PasswordField from './fields/PasswordField.svelte';
|
|
31
|
+
import ChoiceField from './fields/ChoiceField.svelte';
|
|
32
|
+
function validatedDisplay(schema, component, project = input => ({ value: input.value })) {
|
|
33
|
+
const validator = createExactSchemaValidator(schema);
|
|
34
|
+
const resolve = (input) => {
|
|
35
|
+
const props = project(input);
|
|
36
|
+
return validator.Check(props) ? { ok: true, props } : { ok: false };
|
|
37
|
+
};
|
|
38
|
+
// The host owns the reactive validation; evaluating here would only check mount.
|
|
39
|
+
return (internals, input) => ValidatedField(internals, { component, resolve, input });
|
|
40
|
+
}
|
|
41
|
+
const nullable = (schema) => Type.Union([schema, Type.Null(), Type.Undefined()]);
|
|
42
|
+
const text = nullable(Type.String());
|
|
43
|
+
const numeric = nullable(Type.Number());
|
|
44
|
+
const scalar = nullable(Type.Union([Type.String(), Type.Number()]));
|
|
45
|
+
const date = nullable(Type.Union([Type.String(), Type.Number(), Type.Date()]));
|
|
46
|
+
const dateProperty = Type.Optional(Type.Union([Type.String(), Type.Number(), Type.Date(), Type.Null()]));
|
|
47
|
+
const choices = Type.Optional(Type.Array(Type.Object({
|
|
48
|
+
label: Type.String(), value: Type.Union([Type.String(), Type.Number()]),
|
|
49
|
+
}, { additionalProperties: false })));
|
|
50
|
+
const props = (value) => Type.Object({ value }, { additionalProperties: false });
|
|
51
|
+
const choiceProps = (input) => definedOptions({
|
|
52
|
+
value: input.value,
|
|
53
|
+
options: input.options?.map(({ label, value }) => ({ label, value })),
|
|
54
|
+
});
|
|
55
|
+
const textDisplay = validatedDisplay(props(text), TextField);
|
|
56
|
+
const multiChoiceDisplay = validatedDisplay(Type.Object({
|
|
57
|
+
value: nullable(Type.Array(Type.Union([Type.String(), Type.Number()]))), options: choices,
|
|
58
|
+
}), MultiSelectField, choiceProps);
|
|
59
|
+
const treeChoiceDisplay = validatedDisplay(Type.Object({
|
|
60
|
+
value: nullable(Type.Union([Type.String(), Type.Number(), Type.Array(Type.Union([Type.String(), Type.Number()]))])),
|
|
61
|
+
options: choices,
|
|
62
|
+
}), ChoiceField, choiceProps);
|
|
63
|
+
const ratingDisplay = validatedDisplay(props(numeric), RatingField);
|
|
64
|
+
/** Every dynamic renderer validates its complete projected props before mounting. */
|
|
65
|
+
export const builtinDisplayComponents = Object.freeze({
|
|
66
|
+
text: textDisplay,
|
|
67
|
+
string: textDisplay,
|
|
68
|
+
boolean: validatedDisplay(props(nullable(Type.Boolean())), BooleanField),
|
|
69
|
+
image: validatedDisplay(props(text), ImageField),
|
|
70
|
+
images: validatedDisplay(props(nullable(Type.Array(Type.String()))), ImagesField),
|
|
71
|
+
tags: validatedDisplay(props(nullable(Type.Union([Type.String(), Type.Array(Type.String())]))), TagField),
|
|
72
|
+
date: validatedDisplay(props(date), DateField),
|
|
73
|
+
daterange: validatedDisplay(props(nullable(Type.Union([
|
|
74
|
+
Type.Tuple([date, date]),
|
|
75
|
+
Type.Object({ start: dateProperty, end: dateProperty, from: dateProperty, to: dateProperty }, { additionalProperties: false }),
|
|
76
|
+
]))), DateRangeField),
|
|
77
|
+
email: validatedDisplay(props(text), EmailField),
|
|
78
|
+
url: validatedDisplay(props(text), UrlField),
|
|
79
|
+
select: validatedDisplay(Type.Object({ value: scalar, options: choices }), SelectField, choiceProps),
|
|
80
|
+
multiselect: multiChoiceDisplay,
|
|
81
|
+
'tree-select': treeChoiceDisplay,
|
|
82
|
+
treeselect: treeChoiceDisplay,
|
|
83
|
+
cascader: multiChoiceDisplay,
|
|
84
|
+
transfer: multiChoiceDisplay,
|
|
85
|
+
relation: validatedDisplay(Type.Object({
|
|
86
|
+
value: scalar, resourceName: Type.Optional(Type.String()),
|
|
87
|
+
}), RelationField, input => definedOptions({ value: input.value, resourceName: input.resourceName })),
|
|
88
|
+
json: validatedDisplay(props(nullable(jsonValueSchema)), JsonField),
|
|
89
|
+
array: validatedDisplay(props(nullable(Type.Array(jsonValueSchema))), JsonField),
|
|
90
|
+
color: textDisplay,
|
|
91
|
+
password: validatedDisplay(props(text), PasswordField),
|
|
92
|
+
richtext: validatedDisplay(props(text), RichTextField),
|
|
93
|
+
textarea: validatedDisplay(props(text), RichTextField),
|
|
94
|
+
number: validatedDisplay(props(numeric), NumberField),
|
|
95
|
+
currency: validatedDisplay(props(numeric), CurrencyField),
|
|
96
|
+
phone: validatedDisplay(props(scalar), PhoneField),
|
|
97
|
+
percent: validatedDisplay(props(numeric), PercentField),
|
|
98
|
+
rating: ratingDisplay,
|
|
99
|
+
rate: ratingDisplay,
|
|
100
|
+
code: validatedDisplay(props(nullable(jsonValueSchema)), CodeField),
|
|
101
|
+
avatar: validatedDisplay(Type.Object({ src: text }), AvatarField, input => ({ src: input.value })),
|
|
102
|
+
copy: validatedDisplay(props(scalar), CopyField),
|
|
103
|
+
file: validatedDisplay(props(text), FileField),
|
|
104
|
+
markdown: validatedDisplay(props(text), MarkdownField),
|
|
105
|
+
});
|
|
63
106
|
const customDisplayComponents = new Map();
|
|
64
|
-
/**
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
107
|
+
/** Custom renderers receive only values proven by their closed schema. */
|
|
108
|
+
export function registerDisplayComponent(fieldType, schema, component) {
|
|
109
|
+
if (!fieldType.trim())
|
|
110
|
+
throw new TypeError('A field type is required');
|
|
111
|
+
const validator = createExactSchemaValidator(closeContractSchema(schema));
|
|
112
|
+
const resolve = (input) => {
|
|
113
|
+
const value = input.value;
|
|
114
|
+
return validator.Check(value) ? { ok: true, props: { value } } : { ok: false };
|
|
115
|
+
};
|
|
116
|
+
customDisplayComponents.set(fieldType, (internals, input) => ValidatedField(internals, { component, resolve, input }));
|
|
70
117
|
}
|
|
71
118
|
/**
|
|
72
119
|
* Get the display component for a field type.
|
|
@@ -74,11 +121,12 @@ export function registerDisplayComponent(fieldType, component) {
|
|
|
74
121
|
* Returns undefined if no component is registered for the type.
|
|
75
122
|
*/
|
|
76
123
|
export function getDisplayComponent(fieldType) {
|
|
77
|
-
return customDisplayComponents.get(fieldType)
|
|
124
|
+
return customDisplayComponents.get(fieldType)
|
|
125
|
+
?? (Object.hasOwn(builtinDisplayComponents, fieldType) ? builtinDisplayComponents[fieldType] : undefined);
|
|
78
126
|
}
|
|
79
127
|
/**
|
|
80
128
|
* Check if a field type has a display component (custom or built-in).
|
|
81
129
|
*/
|
|
82
130
|
export function hasDisplayComponent(fieldType) {
|
|
83
|
-
return
|
|
131
|
+
return getDisplayComponent(fieldType) !== undefined;
|
|
84
132
|
}
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
|
|
62
62
|
const parts = trimmed.split(/\s+/);
|
|
63
63
|
if (parts.length >= 2) {
|
|
64
|
-
return `${
|
|
64
|
+
return `${trimmed.charAt(0)}${parts.at(-1)?.charAt(0) ?? ''}`.toUpperCase();
|
|
65
65
|
}
|
|
66
66
|
return trimmed.slice(0, 2).toUpperCase();
|
|
67
67
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
3
|
+
import SelectField from './SelectField.svelte';
|
|
4
|
+
import MultiSelectField from './MultiSelectField.svelte';
|
|
5
|
+
|
|
6
|
+
let { value, options }: {
|
|
7
|
+
value: string | number | (string | number)[] | null | undefined;
|
|
8
|
+
options?: { label: string; value: string | number }[];
|
|
9
|
+
} = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
{#if Array.isArray(value)}
|
|
13
|
+
<MultiSelectField {value} {...definedOptions({ options })} />
|
|
14
|
+
{:else}
|
|
15
|
+
<SelectField {value} {...definedOptions({ options })} />
|
|
16
|
+
{/if}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
value: string | number | (string | number)[] | null | undefined;
|
|
3
|
+
options?: {
|
|
4
|
+
label: string;
|
|
5
|
+
value: string | number;
|
|
6
|
+
}[];
|
|
7
|
+
};
|
|
8
|
+
declare const ChoiceField: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
9
|
+
type ChoiceField = ReturnType<typeof ChoiceField>;
|
|
10
|
+
export default ChoiceField;
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
import { Copy, Check } from '@lucide/svelte';
|
|
4
4
|
import { Badge } from '../ui/badge/index.js';
|
|
5
5
|
import { cn } from '../../utils.js';
|
|
6
|
+
import { useTranslation } from '@svadmin/core/i18n';
|
|
7
|
+
import { formatJsonValue } from './json-value';
|
|
6
8
|
|
|
7
9
|
interface Props {
|
|
8
|
-
value?: string | number | Record<string, unknown> | unknown[] | null | undefined;
|
|
10
|
+
value?: string | number | boolean | Record<string, unknown> | unknown[] | null | undefined;
|
|
9
11
|
language?: string;
|
|
10
12
|
copyable?: boolean;
|
|
11
13
|
title?: string;
|
|
@@ -29,22 +31,17 @@
|
|
|
29
31
|
let copied = $state(false);
|
|
30
32
|
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
31
33
|
let destroyed = false;
|
|
34
|
+
const i18n = useTranslation();
|
|
32
35
|
|
|
33
36
|
onDestroy(() => {
|
|
34
37
|
destroyed = true;
|
|
35
38
|
if (timeoutId) clearTimeout(timeoutId);
|
|
36
39
|
});
|
|
37
40
|
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
return JSON.stringify(value, null, 2);
|
|
44
|
-
} catch {
|
|
45
|
-
return String(value);
|
|
46
|
-
}
|
|
47
|
-
});
|
|
41
|
+
const display = $derived(formatJsonValue(value));
|
|
42
|
+
const formattedCode = $derived(display.status === 'valid'
|
|
43
|
+
? typeof value === 'string' ? value : display.formatted
|
|
44
|
+
: '');
|
|
48
45
|
|
|
49
46
|
async function handleCopy(event: MouseEvent) {
|
|
50
47
|
event.stopPropagation();
|
|
@@ -69,7 +66,9 @@
|
|
|
69
66
|
}
|
|
70
67
|
</script>
|
|
71
68
|
|
|
72
|
-
{#if
|
|
69
|
+
{#if display.status === 'invalid'}
|
|
70
|
+
<span role="status" data-svadmin-invalid-field>{i18n.t('validation.invalidFormat')}</span>
|
|
71
|
+
{:else if !formattedCode}
|
|
73
72
|
<span class={cn('field-code svadmin-u-bfa603190748 svadmin-u-fc7473ca09eb', className)}>{nullLabel}</span>
|
|
74
73
|
{:else}
|
|
75
74
|
<div class={cn('field-code svadmin-u-d89972fe17d6 group svadmin-u-421ac2be5045 svadmin-u-ca6bcd4b6f3f svadmin-u-c9ed8c5f79ae svadmin-u-b00f43c30c2b svadmin-u-359090c2d529 svadmin-u-0e65706bcccd', className)}>
|
|
@@ -41,8 +41,7 @@
|
|
|
41
41
|
if (startDate !== undefined) return startDate;
|
|
42
42
|
if (Array.isArray(value)) return value[0];
|
|
43
43
|
if (value && typeof value === 'object') {
|
|
44
|
-
|
|
45
|
-
return (obj.start ?? obj.from) as string | number | Date | null | undefined;
|
|
44
|
+
return value.start ?? value.from;
|
|
46
45
|
}
|
|
47
46
|
return undefined;
|
|
48
47
|
});
|
|
@@ -51,8 +50,7 @@
|
|
|
51
50
|
if (endDate !== undefined) return endDate;
|
|
52
51
|
if (Array.isArray(value)) return value[1];
|
|
53
52
|
if (value && typeof value === 'object') {
|
|
54
|
-
|
|
55
|
-
return (obj.end ?? obj.to) as string | number | Date | null | undefined;
|
|
53
|
+
return value.end ?? value.to;
|
|
56
54
|
}
|
|
57
55
|
return undefined;
|
|
58
56
|
});
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
3
|
+
|
|
2
4
|
import MediaThumbnail from '../content/MediaThumbnail.svelte';
|
|
3
5
|
|
|
4
6
|
interface Props {
|
|
@@ -17,7 +19,7 @@
|
|
|
17
19
|
|
|
18
20
|
{#if value}
|
|
19
21
|
<div style={`width: ${width}px; height: ${height}px;`} class="svadmin-u-bb0c4bfc52bd svadmin-u-012fbd121f37">
|
|
20
|
-
<MediaThumbnail src={value} {alt} {title} size="full" fit="cover" showOverlay={previewable} class={className} />
|
|
22
|
+
<MediaThumbnail src={value} {alt} {...definedOptions({ "title": title })} size="full" fit="cover" showOverlay={previewable} class={className} />
|
|
21
23
|
</div>
|
|
22
24
|
{:else}
|
|
23
25
|
<MediaThumbnail src={null} {emptyLabel} class={className} />
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import ImageField from './ImageField.svelte';
|
|
3
|
+
|
|
4
|
+
let { value }: { value: string[] | null | undefined } = $props();
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
{#if value?.length}
|
|
8
|
+
<div class="field-images">
|
|
9
|
+
{#each value as image, index (index)}
|
|
10
|
+
<ImageField value={image} />
|
|
11
|
+
{/each}
|
|
12
|
+
</div>
|
|
13
|
+
{:else}
|
|
14
|
+
<ImageField value={null} />
|
|
15
|
+
{/if}
|
|
16
|
+
|
|
17
|
+
<style>
|
|
18
|
+
.field-images {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-wrap: wrap;
|
|
21
|
+
gap: 8px;
|
|
22
|
+
}
|
|
23
|
+
</style>
|