@svadmin/ui 0.88.1 → 0.92.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 (172) hide show
  1. package/dist/app.css +4 -0
  2. package/dist/app.theme.css +4 -0
  3. package/dist/component-registry.svelte.d.ts +2 -2
  4. package/dist/components/AboutSettings.svelte +23 -4
  5. package/dist/components/AdminApp.svelte +6 -4
  6. package/dist/components/AdminApp.svelte.d.ts +3 -3
  7. package/dist/components/ApiSettings.svelte +43 -27
  8. package/dist/components/ApprovalActionCard.svelte +8 -9
  9. package/dist/components/ApprovalCenter.svelte +24 -5
  10. package/dist/components/AuditLogViewer.svelte +2 -2
  11. package/dist/components/AutoComplete.svelte +610 -0
  12. package/dist/components/AutoComplete.svelte.d.ts +25 -0
  13. package/dist/components/AutoForm.svelte +265 -18
  14. package/dist/components/AutoForm.svelte.d.ts +19 -2
  15. package/dist/components/AutoTable.svelte +141 -36
  16. package/dist/components/AutoTable.svelte.d.ts +8 -0
  17. package/dist/components/BatchActionBar.svelte +42 -14
  18. package/dist/components/BatchActionBar.svelte.d.ts +5 -0
  19. package/dist/components/BoundRecordDetailDrawer.svelte +5 -3
  20. package/dist/components/CancelTaskButton.svelte +14 -1
  21. package/dist/components/Cascader.svelte +412 -53
  22. package/dist/components/Cascader.svelte.d.ts +7 -1
  23. package/dist/components/ColorPicker.svelte +1 -1
  24. package/dist/components/ColorPicker.svelte.d.ts +1 -1
  25. package/dist/components/ComboboxField.svelte +270 -34
  26. package/dist/components/ComboboxField.svelte.d.ts +4 -0
  27. package/dist/components/CommandPalette.svelte +4 -4
  28. package/dist/components/DashboardView.svelte +73 -7
  29. package/dist/components/DashboardView.svelte.d.ts +3 -1
  30. package/dist/components/DateRangeInput.svelte +8 -1
  31. package/dist/components/DetailWorkspaceAsyncSection.svelte +107 -0
  32. package/dist/components/DetailWorkspaceAsyncSection.svelte.d.ts +36 -0
  33. package/dist/components/DetailWorkspaceSections.svelte +233 -0
  34. package/dist/components/DetailWorkspaceSections.svelte.d.ts +9 -0
  35. package/dist/components/DrawerForm.svelte +78 -9
  36. package/dist/components/DrawerForm.svelte.d.ts +3 -3
  37. package/dist/components/ErrorBoundary.svelte +1 -1
  38. package/dist/components/ErrorComponent.svelte +19 -6
  39. package/dist/components/ErrorComponent.svelte.d.ts +1 -0
  40. package/dist/components/ErrorSummary.svelte +9 -4
  41. package/dist/components/FieldRenderer.svelte +203 -90
  42. package/dist/components/FieldRenderer.svelte.d.ts +16 -1
  43. package/dist/components/FileUpload.svelte +116 -51
  44. package/dist/components/FileUpload.svelte.d.ts +7 -27
  45. package/dist/components/FileUploadField.svelte +170 -0
  46. package/dist/components/FileUploadField.svelte.d.ts +21 -0
  47. package/dist/components/FilterBuilder.svelte +13 -7
  48. package/dist/components/FilterCollectionInput.svelte +9 -7
  49. package/dist/components/FilterCollectionInput.svelte.d.ts +3 -0
  50. package/dist/components/ForgotPasswordPage.svelte +1 -1
  51. package/dist/components/Header.svelte +1 -1
  52. package/dist/components/ImportWizard.svelte +197 -28
  53. package/dist/components/ImportWizard.svelte.d.ts +5 -1
  54. package/dist/components/InferencerPanel.svelte +4 -2
  55. package/dist/components/InfiniteList.svelte +52 -9
  56. package/dist/components/JsonSchemaForm.svelte +237 -42
  57. package/dist/components/JsonSchemaForm.svelte.d.ts +10 -3
  58. package/dist/components/KanbanBoard.svelte +43 -7
  59. package/dist/components/KanbanBoard.svelte.d.ts +2 -2
  60. package/dist/components/Layout.svelte +1 -1
  61. package/dist/components/LoginPage.svelte +1 -1
  62. package/dist/components/MetricStrip.svelte +8 -3
  63. package/dist/components/MetricStrip.svelte.d.ts +1 -0
  64. package/dist/components/ModalForm.svelte +85 -9
  65. package/dist/components/ModalForm.svelte.d.ts +3 -3
  66. package/dist/components/MoneyInput.svelte +5 -3
  67. package/dist/components/MoneyInput.svelte.d.ts +4 -2
  68. package/dist/components/MultiTabKeepAlive.svelte +502 -43
  69. package/dist/components/MultiTabKeepAlive.svelte.d.ts +40 -6
  70. package/dist/components/NotificationsSettings.svelte +10 -10
  71. package/dist/components/NumberInput.svelte +5 -3
  72. package/dist/components/NumberInput.svelte.d.ts +4 -2
  73. package/dist/components/NumericInput.svelte +277 -25
  74. package/dist/components/NumericInput.svelte.d.ts +3 -1
  75. package/dist/components/PercentInput.svelte +5 -3
  76. package/dist/components/PercentInput.svelte.d.ts +4 -2
  77. package/dist/components/PermissionMatrix.svelte +44 -8
  78. package/dist/components/PermissionMatrix.svelte.d.ts +1 -0
  79. package/dist/components/ProfilePage.svelte +38 -13
  80. package/dist/components/RadioGroup.svelte +203 -4
  81. package/dist/components/RadioGroup.svelte.d.ts +1 -1
  82. package/dist/components/Rate.svelte +3 -3
  83. package/dist/components/Rate.svelte.d.ts +2 -2
  84. package/dist/components/RecordDetailDrawer.svelte +1 -1
  85. package/dist/components/RecordDetailDrawer.svelte.d.ts +1 -1
  86. package/dist/components/RegisterPage.svelte +1 -1
  87. package/dist/components/RelationPicker.svelte +39 -11
  88. package/dist/components/ResourceAccessGuard.svelte +7 -2
  89. package/dist/components/RetryTaskButton.svelte +14 -1
  90. package/dist/components/RolesSettings.svelte +74 -28
  91. package/dist/components/SecuritySettings.svelte +66 -51
  92. package/dist/components/SegmentedControl.svelte +4 -2
  93. package/dist/components/SegmentedControl.svelte.d.ts +1 -1
  94. package/dist/components/Sidebar.svelte +1 -1
  95. package/dist/components/StepsForm.svelte +62 -10
  96. package/dist/components/StepsForm.svelte.d.ts +4 -2
  97. package/dist/components/SvadminLogo.svelte +1 -0
  98. package/dist/components/SvarDataGrid.svelte +42 -24
  99. package/dist/components/SvarDataGrid.svelte.d.ts +10 -10
  100. package/dist/components/SvarResourceRead.svelte +2 -0
  101. package/dist/components/SvarResourceTableView.svelte +4 -2
  102. package/dist/components/TagsInput.svelte +3 -3
  103. package/dist/components/TagsInput.svelte.d.ts +2 -2
  104. package/dist/components/TaskDetails.svelte +22 -16
  105. package/dist/components/TaskDetails.svelte.d.ts +1 -1
  106. package/dist/components/TaskList.svelte +57 -15
  107. package/dist/components/TaskQueueDrawer.svelte +54 -34
  108. package/dist/components/Toast.svelte +13 -2
  109. package/dist/components/Transfer.svelte +67 -5
  110. package/dist/components/Transfer.svelte.d.ts +4 -0
  111. package/dist/components/TreeSelect.svelte +337 -77
  112. package/dist/components/TreeSelect.svelte.d.ts +4 -0
  113. package/dist/components/TreeTable.svelte +6 -1
  114. package/dist/components/TwoFactorAuthPage.svelte +8 -4
  115. package/dist/components/UpdatePasswordPage.svelte +1 -1
  116. package/dist/components/VersionDiffViewer.svelte +9 -7
  117. package/dist/components/account/CompanyProfilePage.svelte +13 -12
  118. package/dist/components/account/MemberDirectory.svelte +14 -10
  119. package/dist/components/account/SettingsEnterprisePage.svelte +18 -18
  120. package/dist/components/content/AuditTimeline.svelte +8 -6
  121. package/dist/components/content/DataState.svelte +1 -1
  122. package/dist/components/content/FileList.svelte +44 -13
  123. package/dist/components/content/FileList.svelte.d.ts +4 -2
  124. package/dist/components/content/FileListRow.svelte +87 -0
  125. package/dist/components/content/FileListRow.svelte.d.ts +11 -0
  126. package/dist/components/content/ImportDropzone.svelte +109 -12
  127. package/dist/components/content/ImportDropzone.svelte.d.ts +1 -0
  128. package/dist/components/content/IntegrationCard.svelte +3 -3
  129. package/dist/components/content/IntegrationCard.svelte.d.ts +2 -0
  130. package/dist/components/content/MediaThumbnail.svelte +6 -2
  131. package/dist/components/detail-workspace.d.ts +60 -0
  132. package/dist/components/detail-workspace.js +3 -0
  133. package/dist/components/enterprise/json-schema-form.d.ts +1 -1
  134. package/dist/components/enterprise/recursive-schema-form.d.ts +15 -1
  135. package/dist/components/enterprise/recursive-schema-form.js +288 -14
  136. package/dist/components/enterprise/schema-form-references.d.ts +14 -0
  137. package/dist/components/enterprise/schema-form-references.js +316 -0
  138. package/dist/components/fields/ArrayField.svelte +178 -14
  139. package/dist/components/fields/ArrayField.svelte.d.ts +17 -3
  140. package/dist/components/fields/array-field-metadata.d.ts +4 -0
  141. package/dist/components/fields/array-field-metadata.js +36 -0
  142. package/dist/components/file-upload-reference.d.ts +11 -0
  143. package/dist/components/file-upload-reference.js +50 -0
  144. package/dist/components/file-upload-types.d.ts +32 -0
  145. package/dist/components/file-upload-types.js +1 -0
  146. package/dist/components/form-contract.upload-type-fixture.svelte +108 -0
  147. package/dist/components/form-contract.upload-type-fixture.svelte.d.ts +18 -0
  148. package/dist/components/json-schema-form-state.d.ts +19 -0
  149. package/dist/components/list-deletion.svelte.js +6 -1
  150. package/dist/components/list-state.svelte.d.ts +12 -1
  151. package/dist/components/list-state.svelte.js +56 -16
  152. package/dist/components/numeric-format.d.ts +44 -0
  153. package/dist/components/numeric-format.js +198 -0
  154. package/dist/components/record-detail.svelte.js +2 -0
  155. package/dist/components/saved-list-views.d.ts +9 -2
  156. package/dist/components/saved-list-views.js +42 -11
  157. package/dist/components/saved-list-views.svelte.d.ts +3 -0
  158. package/dist/components/saved-list-views.svelte.js +56 -7
  159. package/dist/components/task-action.svelte.d.ts +1 -0
  160. package/dist/components/task-action.svelte.js +55 -3
  161. package/dist/components/task-utils.d.ts +1 -0
  162. package/dist/components/task-utils.js +18 -0
  163. package/dist/components/ui/input/input.svelte +1 -1
  164. package/dist/components/ui/input/input.svelte.d.ts +1 -1
  165. package/dist/components/ui/sheet/sheet.svelte +4 -1
  166. package/dist/components/version-history.d.ts +14 -0
  167. package/dist/components/version-history.js +32 -0
  168. package/dist/components/workspace-tab-state.d.ts +78 -0
  169. package/dist/components/workspace-tab-state.js +587 -0
  170. package/dist/index.d.ts +13 -1
  171. package/dist/index.js +4 -0
  172. package/package.json +5 -5
@@ -0,0 +1,11 @@
1
+ import type { FileItem } from './FileList.svelte';
2
+ type Handler = (file: FileItem) => void;
3
+ interface Props {
4
+ file: FileItem;
5
+ ondownload?: Handler;
6
+ onpreview?: Handler;
7
+ onremove?: Handler;
8
+ }
9
+ declare const FileListRow: import("svelte").Component<Props, {}, "">;
10
+ type FileListRow = ReturnType<typeof FileListRow>;
11
+ export default FileListRow;
@@ -1,19 +1,116 @@
1
1
  <script lang="ts">
2
- import { UploadCloud } from '@lucide/svelte';
2
+ import { onDestroy, untrack } from 'svelte';
3
+ import { RotateCw, UploadCloud } from '@lucide/svelte';
4
+ import { useTranslation } from '@svadmin/core/i18n';
3
5
  import { Button } from '../ui/button/index.js';
4
6
  import { Input } from '../ui/input/index.js';
5
- interface Props { accept?: string; loading?: boolean; status?: string; onimport?: (files: FileList | null) => void; class?: string; }
6
- let { accept = '.csv,.xlsx', loading = false, status = '', onimport, class: className = '' }: Props = $props();
7
- let files = $state<FileList | null>(null);
8
- function choose(event: Event) {
9
- files = (event.currentTarget as HTMLInputElement).files;
10
- if (files?.length) onimport?.(files);
7
+
8
+ interface Props {
9
+ accept?: string;
10
+ multiple?: boolean;
11
+ loading?: boolean;
12
+ status?: string;
13
+ onimport?: (files: FileList | null) => void;
14
+ class?: string;
15
+ }
16
+ let { accept = '.csv,.xlsx', multiple = false, loading = false, status = '', onimport, class: className = '' }: Props = $props();
17
+ const i18n = useTranslation();
18
+ let nativeFiles = $state<FileList>();
19
+ let retryFiles = $state<FileList>();
20
+ let pending = $state(false);
21
+ let failure = $state<'type' | 'count' | 'import'>();
22
+ let attempt = 0;
23
+ let destroyed = false;
24
+ let observedImport = untrack(() => onimport);
25
+ const busy = $derived(loading || pending);
26
+ const errorMessage = $derived(failure === 'type'
27
+ ? i18n.t('common.fileTypeNotAccepted')
28
+ : failure === 'count'
29
+ ? i18n.t('common.singleFileOnly')
30
+ : i18n.t('common.error'));
31
+
32
+ $effect.pre(() => {
33
+ if (onimport === observedImport) return;
34
+ observedImport = onimport;
35
+ attempt += 1;
36
+ pending = false;
37
+ failure = undefined;
38
+ retryFiles = undefined;
39
+ nativeFiles = new DataTransfer().files;
40
+ });
41
+
42
+ onDestroy(() => {
43
+ destroyed = true;
44
+ attempt += 1;
45
+ });
46
+
47
+ function accepts(file: File): boolean {
48
+ if (!accept.trim()) return true;
49
+ return accept.split(',').map(token => token.trim().toLowerCase()).filter(Boolean).some(token =>
50
+ token === file.type.toLowerCase()
51
+ || (token.endsWith('/*') && file.type.toLowerCase().startsWith(token.slice(0, -1)))
52
+ || (token.startsWith('.') && file.name.toLowerCase().endsWith(token)),
53
+ );
54
+ }
55
+
56
+ async function receive(next: FileList | null | undefined): Promise<void> {
57
+ if (destroyed || busy || !onimport || !next?.length) return;
58
+ failure = undefined;
59
+ retryFiles = undefined;
60
+ nativeFiles = new DataTransfer().files;
61
+ if (!multiple && next.length > 1) {
62
+ failure = 'count';
63
+ return;
64
+ }
65
+ if (Array.from(next).some(file => !accepts(file))) {
66
+ failure = 'type';
67
+ return;
68
+ }
69
+ // 拖放数据只在事件处理期间可读,异步处理与重试持有独立快照。
70
+ const transfer = new DataTransfer();
71
+ for (const file of Array.from(next)) transfer.items.add(file);
72
+ const selected = transfer.files;
73
+ const current = ++attempt;
74
+ const handler = onimport;
75
+ const isCurrent = () => !destroyed && current === attempt && onimport === handler;
76
+ pending = true;
77
+ nativeFiles = selected;
78
+ try {
79
+ await handler(selected);
80
+ } catch {
81
+ if (isCurrent()) {
82
+ failure = 'import';
83
+ retryFiles = selected;
84
+ }
85
+ } finally {
86
+ if (isCurrent()) {
87
+ pending = false;
88
+ nativeFiles = new DataTransfer().files;
89
+ }
90
+ }
91
+ }
92
+
93
+ function choose(event: Event): void {
94
+ const target = event.currentTarget;
95
+ if (target instanceof HTMLInputElement) void receive(target.files);
11
96
  }
12
97
  </script>
13
- <div class={'svadmin-u-5f22e64f2282 svadmin-u-ca6bcd4b6f3f svadmin-u-a29b7a649c77 svadmin-u-18049387f0af svadmin-u-cd0ad9a56558 svadmin-u-0478c89a150f svadmin-u-ca6bf63030aa ' + className} data-svadmin-import-dropzone>
14
- <UploadCloud class="svadmin-u-0e12dc7de920 svadmin-u-d8f5213f0fe0 svadmin-u-bfa603190748" /><p class="svadmin-u-eccd13ef4f2f svadmin-u-fc7473ca09eb svadmin-u-2689f3958069 svadmin-u-d4108abe6359">Import records</p><p class="svadmin-u-b6b02c0ebef6 svadmin-u-359090c2d529 svadmin-u-bfa603190748">CSV or spreadsheet files are accepted.</p>
15
- <Input class="svadmin-u-0e12dc7de920 svadmin-u-0ab8667228fd svadmin-u-2472e9b81a97" type="file" {accept} onchange={choose} disabled={loading} />
16
- {#if loading}<p class="svadmin-u-eccd13ef4f2f svadmin-u-359090c2d529 svadmin-u-bfa603190748" aria-live="polite">Importing...</p>{/if}
98
+
99
+ <div class={'svadmin-u-5f22e64f2282 svadmin-u-ca6bcd4b6f3f svadmin-u-a29b7a649c77 svadmin-u-18049387f0af svadmin-u-cd0ad9a56558 svadmin-u-0478c89a150f svadmin-u-ca6bf63030aa ' + className}
100
+ data-svadmin-import-dropzone role="group" aria-label={i18n.t('common.importData')} aria-busy={busy}
101
+ ondragover={event => event.preventDefault()}
102
+ ondrop={event => { event.preventDefault(); void receive(event.dataTransfer?.files); }}>
103
+ <UploadCloud aria-hidden="true" class="svadmin-u-0e12dc7de920 svadmin-u-d8f5213f0fe0 svadmin-u-bfa603190748" />
104
+ <p class="svadmin-u-eccd13ef4f2f svadmin-u-fc7473ca09eb svadmin-u-2689f3958069 svadmin-u-d4108abe6359">{i18n.t('common.importData')}</p>
105
+ {#if accept}<p class="svadmin-u-b6b02c0ebef6 svadmin-u-359090c2d529 svadmin-u-bfa603190748">{accept}</p>{/if}
106
+ <Input class="svadmin-u-0e12dc7de920 svadmin-u-0ab8667228fd svadmin-u-2472e9b81a97" type="file"
107
+ {accept} {multiple} bind:files={nativeFiles} aria-label={i18n.t('common.chooseFile')}
108
+ onchange={choose} disabled={busy || !onimport} />
109
+ {#if busy}<p class="svadmin-u-eccd13ef4f2f svadmin-u-359090c2d529 svadmin-u-bfa603190748" role="status">{i18n.t('common.importing')}</p>{/if}
110
+ {#if failure}<p class="svadmin-u-eccd13ef4f2f svadmin-u-359090c2d529 svadmin-u-76747e5e02ff" role="alert">{errorMessage}</p>{/if}
111
+ {#if failure === 'import' && retryFiles}
112
+ <Button type="button" class="svadmin-u-0ab8667228fd" size="sm" disabled={busy || !onimport}
113
+ onclick={() => { void receive(retryFiles); }}><RotateCw aria-hidden="true" />{i18n.t('common.retry')}</Button>
114
+ {/if}
17
115
  {#if status}<p class="svadmin-u-eccd13ef4f2f svadmin-u-359090c2d529 svadmin-u-76747e5e02ff" role="status">{status}</p>{/if}
18
- {#if !loading && files?.length}<Button class="svadmin-u-0ab8667228fd" size="sm" onclick={() => onimport?.(files)}>Import {files.length} file{files.length === 1 ? '' : 's'}</Button>{/if}
19
116
  </div>
@@ -1,5 +1,6 @@
1
1
  interface Props {
2
2
  accept?: string;
3
+ multiple?: boolean;
3
4
  loading?: boolean;
4
5
  status?: string;
5
6
  onimport?: (files: FileList | null) => void;
@@ -4,13 +4,13 @@
4
4
  import { Badge } from '../ui/badge/index.js';
5
5
  import * as Card from '../ui/card/index.js';
6
6
  export interface IntegrationSummary { id: string; name: string; description?: string; account?: string; connected?: boolean; }
7
- interface Props { integration: IntegrationSummary; onconnect?: () => void; class?: string; }
8
- let { integration, onconnect, class: className = '' }: Props = $props();
7
+ interface Props { integration: IntegrationSummary; onconnect?: () => void; actionLabel?: string; disabled?: boolean; class?: string; }
8
+ let { integration, onconnect, actionLabel, disabled = false, class: className = '' }: Props = $props();
9
9
  </script>
10
10
  <Card.Card class={className}>
11
11
  <Card.CardContent class="svadmin-u-60fbb7713999 svadmin-u-60541e1e26f8 svadmin-u-1004c0c3954c svadmin-u-8e63407b5ceb">
12
12
  <span class="svadmin-u-60fbb7713999 svadmin-u-665f07fe73cc svadmin-u-012fbd121f37 svadmin-u-3960ffc248d9 svadmin-u-86843cf1e227 svadmin-u-421ac2be5045 svadmin-u-2ef11f1cb219 svadmin-u-bfa603190748"><PlugZap class="svadmin-u-f7b5fa971871" /></span>
13
13
  <div class="svadmin-u-7e0b7cdf1a94 svadmin-u-36e579c0b41c"><div class="svadmin-u-60fbb7713999 svadmin-u-1eb5c6df38c1 svadmin-u-3960ffc248d9 svadmin-u-77a2a20e90d4"><h3 class="svadmin-u-fc7473ca09eb svadmin-u-e83a7042bc91 svadmin-u-d4108abe6359">{integration.name}</h3>{#if integration.connected}<Badge variant="outline" class="svadmin-u-18a6e7a36f29 svadmin-u-17a9f7af2265 svadmin-u-76747e5e02ff"><Check class="svadmin-u-ef2d6f74d3d0" />Connected</Badge>{/if}</div><p class="svadmin-u-b6b02c0ebef6 svadmin-u-fc7473ca09eb svadmin-u-bfa603190748">{integration.description ?? 'Connect this provider to unlock synchronized workflows.'}</p>{#if integration.account}<p class="svadmin-u-50d0d216a2f8 svadmin-u-359090c2d529 svadmin-u-bfa603190748">{integration.account}</p>{/if}</div>
14
- {#if onconnect}<Button variant={integration.connected ? 'outline' : 'default'} size="sm" onclick={onconnect}>{integration.connected ? 'Manage' : 'Connect'}{#if !integration.connected}<ExternalLink class="svadmin-u-783b0d9d1e2c" />{/if}</Button>{/if}
14
+ {#if onconnect}<Button variant={integration.connected ? 'outline' : 'default'} size="sm" {disabled} onclick={onconnect}>{actionLabel ?? (integration.connected ? 'Manage' : 'Connect')}{#if !integration.connected && !actionLabel}<ExternalLink class="svadmin-u-783b0d9d1e2c" />{/if}</Button>{/if}
15
15
  </Card.CardContent>
16
16
  </Card.Card>
@@ -8,6 +8,8 @@ export interface IntegrationSummary {
8
8
  interface Props {
9
9
  integration: IntegrationSummary;
10
10
  onconnect?: () => void;
11
+ actionLabel?: string;
12
+ disabled?: boolean;
11
13
  class?: string;
12
14
  }
13
15
  declare const IntegrationCard: import("svelte").Component<Props, {}, "">;
@@ -2,6 +2,7 @@
2
2
  import { Eye, FileText, ImageOff, Loader2 } from '@lucide/svelte';
3
3
  import * as Dialog from '../ui/dialog/index.js';
4
4
  import { cn } from '../../utils.js';
5
+ import { useTranslation } from '@svadmin/core/i18n';
5
6
 
6
7
  export type MediaThumbnailSize = 'sm' | 'md' | 'lg' | 'full';
7
8
  export type MediaThumbnailFit = 'contain' | 'cover';
@@ -37,11 +38,14 @@
37
38
  overlayText = 'View',
38
39
  emptyLabel = 'No media',
39
40
  loadingLabel = 'Loading media',
40
- errorLabel = 'Image unavailable',
41
+ errorLabel,
41
42
  onopen,
42
43
  class: className = '',
43
44
  }: Props = $props();
44
45
 
46
+ const i18n = useTranslation();
47
+ const resolvedErrorLabel = $derived(errorLabel ?? i18n.t('common.error'));
48
+
45
49
  const isImage = $derived(Boolean(
46
50
  (mimeType && mimeType.startsWith('image/'))
47
51
  || (src && /\.(png|jpe?g|webp|gif|svg|bmp|avif)(\?.*)?$/i.test(src))
@@ -85,7 +89,7 @@
85
89
  {#if imageState === 'loading'}
86
90
  <div class="svadmin-u-da4dbfbc4fdc svadmin-u-7b7df0449b80 svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-86843cf1e227 svadmin-u-b00f43c30c2b" role="status" aria-label={loadingLabel}><Loader2 class="svadmin-u-f7b5fa971871 svadmin-u-afbdd13a380e svadmin-u-bfa603190748" aria-hidden="true" /></div>
87
91
  {:else if imageState === 'error'}
88
- <div class="svadmin-u-da4dbfbc4fdc svadmin-u-7b7df0449b80 svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed svadmin-u-3960ffc248d9 svadmin-u-86843cf1e227 svadmin-u-44ee8ba0a421 svadmin-u-eb6e8b881acd svadmin-u-ca6bf63030aa svadmin-u-359090c2d529 svadmin-u-bfa603190748" role="img" aria-label={errorLabel} data-slot="media-thumbnail-error"><ImageOff class="svadmin-u-add63bc6753d" aria-hidden="true" /><span class="svadmin-u-4bfac3b6a99f svadmin-u-f283ea9bea0e svadmin-u-d058ca6de60f">{errorLabel}</span></div>
92
+ <div class="svadmin-u-da4dbfbc4fdc svadmin-u-7b7df0449b80 svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed svadmin-u-3960ffc248d9 svadmin-u-86843cf1e227 svadmin-u-44ee8ba0a421 svadmin-u-eb6e8b881acd svadmin-u-ca6bf63030aa svadmin-u-359090c2d529 svadmin-u-bfa603190748" role="img" aria-label={resolvedErrorLabel} data-slot="media-thumbnail-error"><ImageOff class="svadmin-u-add63bc6753d" aria-hidden="true" /><span class="svadmin-u-4bfac3b6a99f svadmin-u-f283ea9bea0e svadmin-u-d058ca6de60f">{resolvedErrorLabel}</span></div>
89
93
  {:else if showOverlay}
90
94
  <Dialog.Root bind:open={previewOpen}>
91
95
  <button type="button" class="group svadmin-u-da4dbfbc4fdc svadmin-u-7b7df0449b80 svadmin-u-3bbc8c13feec svadmin-u-df37b1fd9495 svadmin-u-793c80e97ffb svadmin-u-9c1295a6914a svadmin-u-77923ba6606a" onclick={openMedia} aria-label={alt ? `Preview ${alt}` : overlayText}>
@@ -0,0 +1,60 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { AuditEntry } from '@svadmin/core';
3
+ import type { TimelineItem } from './content/AuditTimeline.svelte';
4
+ import type { FileItem } from './content/FileList.svelte';
5
+ /** The record identity every detail workspace section is bound to. */
6
+ export interface DetailWorkspaceScope {
7
+ readonly resourceName: string;
8
+ readonly recordId: string | number;
9
+ /** Tenant/provider metadata resolved through `AdminContextAccessor.getProviderMeta`. */
10
+ readonly meta?: Record<string, unknown> | undefined;
11
+ }
12
+ /**
13
+ * A section provider is an explicit, optional data contract. It is never called
14
+ * without a concrete resourceName/recordId, and the consumer aborts it when the
15
+ * section is destroyed.
16
+ */
17
+ export interface DetailWorkspaceProvider<T> {
18
+ load(scope: DetailWorkspaceScope, signal: AbortSignal): Promise<T>;
19
+ }
20
+ export type DetailWorkspaceSnippet = Snippet<[DetailWorkspaceScope]>;
21
+ export interface DetailWorkspaceAsyncSection<T> {
22
+ provider: DetailWorkspaceProvider<T>;
23
+ /** Optional custom renderer; the component falls back to its matching display component. */
24
+ render?: Snippet<[DetailWorkspaceScope & {
25
+ data: T;
26
+ }]>;
27
+ /** Optional predicate for an explicit empty result. */
28
+ empty?: (data: T) => boolean;
29
+ emptyTitle?: string;
30
+ errorTitle?: string;
31
+ }
32
+ /**
33
+ * A section is either a provider-backed section or an explicit snippet provider.
34
+ * Snippet providers receive the same resourceName/recordId scope and are rebuilt
35
+ * when that scope changes.
36
+ */
37
+ export type DetailWorkspaceSection<T> = DetailWorkspaceSnippet | DetailWorkspaceAsyncSection<T>;
38
+ export interface DetailWorkspaceVersionData {
39
+ oldValue: Record<string, unknown>;
40
+ newValue: Record<string, unknown>;
41
+ oldTitle?: string;
42
+ newTitle?: string;
43
+ fieldLabels?: Record<string, string>;
44
+ }
45
+ /**
46
+ * Detail workspace configuration for the real AutoTable detail drawer.
47
+ *
48
+ * Audit uses the existing AuditLogProvider by default. Relations, attachments,
49
+ * activity and versions have no core provider contract yet, so they must be
50
+ * supplied explicitly as provider or snippet sections. Missing sections fail
51
+ * closed instead of rendering placeholder business data.
52
+ */
53
+ export interface DetailWorkspaceSectionsConfig {
54
+ relations?: DetailWorkspaceSnippet;
55
+ attachments?: DetailWorkspaceSection<FileItem[]>;
56
+ activity?: DetailWorkspaceSection<TimelineItem[]>;
57
+ audit?: DetailWorkspaceSection<AuditEntry[]>;
58
+ versions?: DetailWorkspaceSection<DetailWorkspaceVersionData>;
59
+ }
60
+ export declare function isDetailWorkspaceSnippet<T>(section: DetailWorkspaceSection<T> | undefined): section is DetailWorkspaceSnippet;
@@ -0,0 +1,3 @@
1
+ export function isDetailWorkspaceSnippet(section) {
2
+ return typeof section === 'function';
3
+ }
@@ -1,6 +1,6 @@
1
1
  /** JsonSchemaForm 当前支持的标量子集。未知断言必须显式拒绝,不能假装已校验。 */
2
2
  export type JsonScalar = string | number | boolean | null;
3
- export type SchemaIssueCode = 'unsupported-schema' | 'invalid-schema' | 'required' | 'type' | 'enum' | 'minimum' | 'maximum' | 'multipleOf' | 'minLength' | 'maxLength' | 'additionalProperties' | 'invalid-value' | 'minItems' | 'maxItems';
3
+ export type SchemaIssueCode = 'unsupported-schema' | 'invalid-schema' | 'ref-invalid' | 'ref-cycle' | 'ref-limit' | 'oneOf-invalid' | 'oneOf-unsupported' | 'oneOf-unselected' | 'oneOf-ambiguous' | 'required' | 'type' | 'enum' | 'minimum' | 'maximum' | 'multipleOf' | 'minLength' | 'maxLength' | 'additionalProperties' | 'invalid-value' | 'minItems' | 'maxItems';
4
4
  export interface SchemaFormIssue {
5
5
  path: string;
6
6
  code: SchemaIssueCode;
@@ -1,4 +1,4 @@
1
- import { type SchemaFormModel, type SchemaFormIssue } from './json-schema-form.js';
1
+ import { type JsonScalar, type SchemaFormModel, type SchemaFormIssue } from './json-schema-form.js';
2
2
  type Node = {
3
3
  kind: 'object';
4
4
  properties: Record<string, Node>;
@@ -11,10 +11,18 @@ type Node = {
11
11
  min: number | undefined;
12
12
  max: number | undefined;
13
13
  nullable: boolean;
14
+ } | {
15
+ kind: 'oneOf';
16
+ branches: Node[];
17
+ propertyName: string;
18
+ values: JsonScalar[][];
19
+ nullable: boolean;
14
20
  } | {
15
21
  kind: 'scalar';
16
22
  model: SchemaFormModel;
17
23
  };
24
+ export type RecursiveSchemaFormNode = Node;
25
+ export type OneOfSelection = number | 'oneOf-unselected' | 'oneOf-ambiguous';
18
26
  export interface RecursiveSchemaFormModel {
19
27
  node: Node | undefined;
20
28
  issues: SchemaFormIssue[];
@@ -25,6 +33,12 @@ export declare const schemaFormPointer: (path: readonly string[]) => string;
25
33
  * TypeBox's symbol annotations are not JSON assertion keywords and are not executed.
26
34
  */
27
35
  export declare function readRecursiveSchemaForm(schema: unknown): RecursiveSchemaFormModel;
36
+ /** Return the unique branch selected by data; missing or overlapping values never fall back to first match. */
37
+ export declare function selectRecursiveOneOfBranch(node: RecursiveSchemaFormNode, current: unknown): OneOfSelection;
38
+ /** Switch the explicit discriminator and drop keys that exist only on unselected branches. */
39
+ export declare function switchRecursiveOneOfBranch(node: RecursiveSchemaFormNode, current: unknown, index: number, preferred?: JsonScalar): Record<string, unknown> | undefined;
40
+ /** Return a detached payload with stale unselected `oneOf` branch keys removed. */
41
+ export declare function pruneRecursiveSchemaFormValue(model: RecursiveSchemaFormModel, value: unknown): Record<string, unknown>;
28
42
  /** Validate a detached JSON payload without defaults, coercion, cleaning or partial acceptance. */
29
43
  export declare function validateRecursiveSchemaForm(model: RecursiveSchemaFormModel, value: unknown): SchemaFormIssue[];
30
44
  export {};