@svadmin/ui 0.88.0 → 0.91.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/app.css +4 -0
- package/dist/app.theme.css +4 -0
- package/dist/component-registry.svelte.d.ts +2 -2
- package/dist/components/AboutSettings.svelte +23 -4
- package/dist/components/AdminApp.svelte +6 -4
- package/dist/components/AdminApp.svelte.d.ts +3 -3
- package/dist/components/ApiSettings.svelte +43 -27
- package/dist/components/ApprovalActionCard.svelte +8 -9
- package/dist/components/ApprovalCenter.svelte +24 -5
- package/dist/components/AuditLogViewer.svelte +2 -2
- package/dist/components/AutoComplete.svelte +610 -0
- package/dist/components/AutoComplete.svelte.d.ts +25 -0
- package/dist/components/AutoForm.svelte +263 -18
- package/dist/components/AutoForm.svelte.d.ts +19 -2
- package/dist/components/AutoTable.svelte +135 -35
- package/dist/components/AutoTable.svelte.d.ts +8 -0
- package/dist/components/BatchActionBar.svelte +42 -14
- package/dist/components/BatchActionBar.svelte.d.ts +5 -0
- package/dist/components/BoundRecordDetailDrawer.svelte +5 -3
- package/dist/components/CancelTaskButton.svelte +14 -1
- package/dist/components/Cascader.svelte +412 -53
- package/dist/components/Cascader.svelte.d.ts +7 -1
- package/dist/components/ColorPicker.svelte +1 -1
- package/dist/components/ColorPicker.svelte.d.ts +1 -1
- package/dist/components/ComboboxField.svelte +270 -34
- package/dist/components/ComboboxField.svelte.d.ts +4 -0
- package/dist/components/CommandPalette.svelte +4 -4
- package/dist/components/DashboardView.svelte +73 -7
- package/dist/components/DashboardView.svelte.d.ts +3 -1
- package/dist/components/DateRangeInput.svelte +8 -1
- package/dist/components/DetailWorkspaceAsyncSection.svelte +107 -0
- package/dist/components/DetailWorkspaceAsyncSection.svelte.d.ts +36 -0
- package/dist/components/DetailWorkspaceSections.svelte +233 -0
- package/dist/components/DetailWorkspaceSections.svelte.d.ts +9 -0
- package/dist/components/DrawerForm.svelte +78 -9
- package/dist/components/DrawerForm.svelte.d.ts +3 -3
- package/dist/components/ErrorBoundary.svelte +1 -1
- package/dist/components/ErrorComponent.svelte +19 -6
- package/dist/components/ErrorComponent.svelte.d.ts +1 -0
- package/dist/components/ErrorSummary.svelte +9 -4
- package/dist/components/FieldRenderer.svelte +203 -90
- package/dist/components/FieldRenderer.svelte.d.ts +16 -1
- package/dist/components/FileUpload.svelte +116 -51
- package/dist/components/FileUpload.svelte.d.ts +7 -27
- package/dist/components/FileUploadField.svelte +170 -0
- package/dist/components/FileUploadField.svelte.d.ts +21 -0
- package/dist/components/FilterBuilder.svelte +13 -7
- package/dist/components/FilterCollectionInput.svelte +9 -7
- package/dist/components/FilterCollectionInput.svelte.d.ts +3 -0
- package/dist/components/ForgotPasswordPage.svelte +1 -1
- package/dist/components/Header.svelte +1 -1
- package/dist/components/ImportWizard.svelte +197 -28
- package/dist/components/ImportWizard.svelte.d.ts +5 -1
- package/dist/components/InferencerPanel.svelte +4 -2
- package/dist/components/InfiniteList.svelte +52 -9
- package/dist/components/JsonSchemaForm.svelte +237 -42
- package/dist/components/JsonSchemaForm.svelte.d.ts +10 -3
- package/dist/components/KanbanBoard.svelte +43 -7
- package/dist/components/KanbanBoard.svelte.d.ts +2 -2
- package/dist/components/Layout.svelte +1 -1
- package/dist/components/LoginPage.svelte +1 -1
- package/dist/components/MetricStrip.svelte +8 -3
- package/dist/components/MetricStrip.svelte.d.ts +1 -0
- package/dist/components/ModalForm.svelte +85 -9
- package/dist/components/ModalForm.svelte.d.ts +3 -3
- package/dist/components/MoneyInput.svelte +5 -3
- package/dist/components/MoneyInput.svelte.d.ts +4 -2
- package/dist/components/MultiTabKeepAlive.svelte +502 -43
- package/dist/components/MultiTabKeepAlive.svelte.d.ts +40 -6
- package/dist/components/NotificationsSettings.svelte +10 -10
- package/dist/components/NumberInput.svelte +5 -3
- package/dist/components/NumberInput.svelte.d.ts +4 -2
- package/dist/components/NumericInput.svelte +277 -25
- package/dist/components/NumericInput.svelte.d.ts +3 -1
- package/dist/components/PercentInput.svelte +5 -3
- package/dist/components/PercentInput.svelte.d.ts +4 -2
- package/dist/components/PermissionMatrix.svelte +44 -8
- package/dist/components/PermissionMatrix.svelte.d.ts +1 -0
- package/dist/components/ProfilePage.svelte +38 -13
- package/dist/components/RadioGroup.svelte +203 -4
- package/dist/components/RadioGroup.svelte.d.ts +1 -1
- package/dist/components/Rate.svelte +3 -3
- package/dist/components/Rate.svelte.d.ts +2 -2
- package/dist/components/RecordDetailDrawer.svelte +1 -1
- package/dist/components/RecordDetailDrawer.svelte.d.ts +1 -1
- package/dist/components/RegisterPage.svelte +1 -1
- package/dist/components/RelationPicker.svelte +39 -11
- package/dist/components/ResourceAccessGuard.svelte +7 -2
- package/dist/components/RetryTaskButton.svelte +14 -1
- package/dist/components/RolesSettings.svelte +74 -28
- package/dist/components/SecuritySettings.svelte +66 -51
- package/dist/components/SegmentedControl.svelte +4 -2
- package/dist/components/SegmentedControl.svelte.d.ts +1 -1
- package/dist/components/Sidebar.svelte +1 -1
- package/dist/components/StepsForm.svelte +60 -10
- package/dist/components/StepsForm.svelte.d.ts +4 -2
- package/dist/components/SvadminLogo.svelte +1 -0
- package/dist/components/SvarDataGrid.svelte +42 -24
- package/dist/components/SvarDataGrid.svelte.d.ts +10 -10
- package/dist/components/SvarResourceRead.svelte +2 -0
- package/dist/components/SvarResourceTableView.svelte +4 -2
- package/dist/components/TagsInput.svelte +3 -3
- package/dist/components/TagsInput.svelte.d.ts +2 -2
- package/dist/components/TaskDetails.svelte +22 -16
- package/dist/components/TaskDetails.svelte.d.ts +1 -1
- package/dist/components/TaskList.svelte +57 -15
- package/dist/components/TaskQueueDrawer.svelte +54 -34
- package/dist/components/Transfer.svelte +67 -5
- package/dist/components/Transfer.svelte.d.ts +4 -0
- package/dist/components/TreeSelect.svelte +337 -77
- package/dist/components/TreeSelect.svelte.d.ts +4 -0
- package/dist/components/TreeTable.svelte +6 -1
- package/dist/components/TwoFactorAuthPage.svelte +8 -4
- package/dist/components/UpdatePasswordPage.svelte +1 -1
- package/dist/components/VersionDiffViewer.svelte +9 -7
- package/dist/components/account/CompanyProfilePage.svelte +13 -12
- package/dist/components/account/MemberDirectory.svelte +14 -10
- package/dist/components/account/SettingsEnterprisePage.svelte +18 -18
- package/dist/components/content/AuditTimeline.svelte +8 -6
- package/dist/components/content/DataState.svelte +1 -1
- package/dist/components/content/FileList.svelte +44 -13
- package/dist/components/content/FileList.svelte.d.ts +4 -2
- package/dist/components/content/FileListRow.svelte +87 -0
- package/dist/components/content/FileListRow.svelte.d.ts +11 -0
- package/dist/components/content/ImportDropzone.svelte +109 -12
- package/dist/components/content/ImportDropzone.svelte.d.ts +1 -0
- package/dist/components/content/IntegrationCard.svelte +3 -3
- package/dist/components/content/IntegrationCard.svelte.d.ts +2 -0
- package/dist/components/content/MediaThumbnail.svelte +6 -2
- package/dist/components/detail-workspace.d.ts +60 -0
- package/dist/components/detail-workspace.js +3 -0
- package/dist/components/enterprise/json-schema-form.d.ts +1 -1
- package/dist/components/enterprise/recursive-schema-form.d.ts +15 -1
- package/dist/components/enterprise/recursive-schema-form.js +288 -14
- package/dist/components/enterprise/schema-form-references.d.ts +14 -0
- package/dist/components/enterprise/schema-form-references.js +316 -0
- package/dist/components/fields/ArrayField.svelte +178 -14
- package/dist/components/fields/ArrayField.svelte.d.ts +17 -3
- package/dist/components/fields/array-field-metadata.d.ts +4 -0
- package/dist/components/fields/array-field-metadata.js +36 -0
- package/dist/components/file-upload-reference.d.ts +11 -0
- package/dist/components/file-upload-reference.js +50 -0
- package/dist/components/file-upload-types.d.ts +32 -0
- package/dist/components/file-upload-types.js +1 -0
- package/dist/components/form-contract.upload-type-fixture.svelte +108 -0
- package/dist/components/form-contract.upload-type-fixture.svelte.d.ts +18 -0
- package/dist/components/json-schema-form-state.d.ts +19 -0
- package/dist/components/list-deletion.svelte.js +6 -1
- package/dist/components/list-state.svelte.d.ts +12 -1
- package/dist/components/list-state.svelte.js +56 -16
- package/dist/components/numeric-format.d.ts +44 -0
- package/dist/components/numeric-format.js +198 -0
- package/dist/components/record-detail.svelte.js +2 -0
- package/dist/components/saved-list-views.d.ts +9 -2
- package/dist/components/saved-list-views.js +42 -11
- package/dist/components/saved-list-views.svelte.d.ts +3 -0
- package/dist/components/saved-list-views.svelte.js +56 -7
- package/dist/components/task-action.svelte.d.ts +1 -0
- package/dist/components/task-action.svelte.js +55 -3
- package/dist/components/task-utils.d.ts +1 -0
- package/dist/components/task-utils.js +18 -0
- package/dist/components/ui/input/input.svelte +1 -1
- package/dist/components/ui/input/input.svelte.d.ts +1 -1
- package/dist/components/ui/sheet/sheet.svelte +4 -1
- package/dist/components/version-history.d.ts +14 -0
- package/dist/components/version-history.js +32 -0
- package/dist/components/workspace-tab-state.d.ts +78 -0
- package/dist/components/workspace-tab-state.js +587 -0
- package/dist/index.d.ts +13 -1
- package/dist/index.js +4 -0
- package/package.json +5 -5
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { Plus, GripVertical, RotateCw, ArrowUp, ArrowDown } from '@lucide/svelte';
|
|
5
5
|
import { cn } from '../utils.js';
|
|
6
6
|
import { useTranslation } from '@svadmin/core/i18n';
|
|
7
|
+
import { snapshotPlainData } from '@svadmin/core/schema';
|
|
7
8
|
import { onDestroy } from 'svelte';
|
|
8
9
|
|
|
9
10
|
export interface KanbanCard {
|
|
@@ -27,9 +28,9 @@
|
|
|
27
28
|
columns: KanbanColumn[];
|
|
28
29
|
cards?: KanbanCard[];
|
|
29
30
|
allowAddCard?: boolean;
|
|
30
|
-
oncardmove?: (cardId: string, targetColumnId: string, targetIndex: number) =>
|
|
31
|
+
oncardmove?: ((cardId: string, targetColumnId: string, targetIndex: number) => KanbanCard | undefined | Promise<KanbanCard | undefined>) | undefined;
|
|
31
32
|
canMoveCard?: (card: KanbanCard, targetColumnId: string) => boolean;
|
|
32
|
-
oncardmoveerror?: (error: unknown) => void;
|
|
33
|
+
oncardmoveerror?: ((error: unknown) => void) | undefined;
|
|
33
34
|
oncardclick?: (card: KanbanCard) => void;
|
|
34
35
|
oncardadd?: (columnId: string, title: string) => KanbanCard | Promise<KanbanCard>;
|
|
35
36
|
scopeKey?: string | number;
|
|
@@ -62,6 +63,7 @@
|
|
|
62
63
|
let newCardTitle = $state('');
|
|
63
64
|
let movingCardId = $state<string | null>(null);
|
|
64
65
|
let moveError = $state<string | null>(null);
|
|
66
|
+
let failedMove = $state<{ cardId: string; targetColumnId: string; targetIndex: number } | null>(null);
|
|
65
67
|
let adding = $state(false);
|
|
66
68
|
let mounted = true;
|
|
67
69
|
const i18n = useTranslation();
|
|
@@ -100,11 +102,26 @@
|
|
|
100
102
|
&& (card.dueDate === undefined || typeof card.dueDate === 'string')
|
|
101
103
|
&& (card.priority === undefined || ['low', 'medium', 'high', 'urgent'].includes(card.priority));
|
|
102
104
|
}
|
|
105
|
+
function decodeMoveReceipt(receipt: unknown): KanbanCard | undefined {
|
|
106
|
+
if (receipt === undefined) return undefined;
|
|
107
|
+
if (receipt === null || typeof receipt !== 'object' || Array.isArray(receipt)) {
|
|
108
|
+
throw new Error('Invalid move receipt');
|
|
109
|
+
}
|
|
110
|
+
// 将已提交卡片与回调持有方的可变对象图分离,避免别名修改已提交状态。
|
|
111
|
+
const snapshot = snapshotPlainData(receipt);
|
|
112
|
+
if (snapshot === null || typeof snapshot !== 'object' || Array.isArray(snapshot)) {
|
|
113
|
+
throw new Error('Invalid move receipt');
|
|
114
|
+
}
|
|
115
|
+
return snapshot as unknown as KanbanCard;
|
|
116
|
+
}
|
|
103
117
|
function moveAllowed(card: KanbanCard, targetColumn: string): boolean {
|
|
104
118
|
if (!oncardmove || loading || displayError || busy || !columns.some(column => column.id === targetColumn)) return false;
|
|
105
119
|
try { return canMoveCard === undefined || canMoveCard(card, targetColumn) === true; }
|
|
106
120
|
catch { return false; }
|
|
107
121
|
}
|
|
122
|
+
function canDragCard(card: KanbanCard): boolean {
|
|
123
|
+
return !!oncardmove && !loading && !displayError && !busy && columns.some(column => moveAllowed(card, column.id));
|
|
124
|
+
}
|
|
108
125
|
function active(origin: typeof scope): boolean {
|
|
109
126
|
return mounted && origin === scope;
|
|
110
127
|
}
|
|
@@ -114,11 +131,13 @@
|
|
|
114
131
|
addingColumnId = null;
|
|
115
132
|
newCardTitle = '';
|
|
116
133
|
moveError = null;
|
|
134
|
+
failedMove = null;
|
|
117
135
|
});
|
|
118
136
|
onDestroy(() => { mounted = false; });
|
|
119
137
|
|
|
120
138
|
function handleDragStart(e: DragEvent, cardId: string) {
|
|
121
|
-
|
|
139
|
+
const card = cards.find((c) => c.id === cardId);
|
|
140
|
+
if (!card || !canDragCard(card)) { e.preventDefault(); return; }
|
|
122
141
|
draggedCardId = cardId;
|
|
123
142
|
if (e.dataTransfer) {
|
|
124
143
|
e.dataTransfer.effectAllowed = 'move';
|
|
@@ -151,12 +170,23 @@
|
|
|
151
170
|
adding = false;
|
|
152
171
|
movingCardId = card.id;
|
|
153
172
|
moveError = null;
|
|
173
|
+
failedMove = null;
|
|
154
174
|
try {
|
|
155
|
-
await origin.oncardmove?.(card.id, targetColId, index);
|
|
156
|
-
if (active(origin)
|
|
175
|
+
const receipt = await origin.oncardmove?.(card.id, targetColId, index);
|
|
176
|
+
if (!active(origin) || operation !== current) return;
|
|
177
|
+
let confirmed = optimistic;
|
|
178
|
+
if (receipt !== undefined) {
|
|
179
|
+
const authoritative = decodeMoveReceipt(receipt);
|
|
180
|
+
if (!authoritative || !validCard(authoritative) || authoritative.id !== card.id || authoritative.columnId !== targetColId) {
|
|
181
|
+
throw new Error('Invalid move receipt');
|
|
182
|
+
}
|
|
183
|
+
confirmed = optimistic.map(item => item.id === card.id ? authoritative : item);
|
|
184
|
+
}
|
|
185
|
+
cards = confirmed;
|
|
157
186
|
} catch (cause) {
|
|
158
187
|
if (active(origin) && operation === current) {
|
|
159
188
|
moveError = i18n.t('kanban.moveFailed');
|
|
189
|
+
failedMove = { cardId: card.id, targetColumnId: targetColId, targetIndex: index };
|
|
160
190
|
try { oncardmoveerror?.(cause); } catch { /* 错误观察者不能再次触发写入。 */ }
|
|
161
191
|
}
|
|
162
192
|
} finally {
|
|
@@ -164,6 +194,12 @@
|
|
|
164
194
|
}
|
|
165
195
|
}
|
|
166
196
|
|
|
197
|
+
function retryFailedMove() {
|
|
198
|
+
const failed = failedMove;
|
|
199
|
+
if (!failed || busy) return;
|
|
200
|
+
void moveCard(failed.cardId, failed.targetColumnId, failed.targetIndex);
|
|
201
|
+
}
|
|
202
|
+
|
|
167
203
|
function handleDrop(targetColId: string, index?: number) {
|
|
168
204
|
const id = draggedCardId;
|
|
169
205
|
draggedCardId = null;
|
|
@@ -208,7 +244,7 @@
|
|
|
208
244
|
}
|
|
209
245
|
</script>
|
|
210
246
|
|
|
211
|
-
{#if moveError}<div role="alert">{moveError}</div>{/if}
|
|
247
|
+
{#if moveError}<div role="alert"><span>{moveError}</span>{#if failedMove}<Button type="button" variant="ghost" size="sm" disabled={busy} onclick={retryFailedMove}>{i18n.t('common.retry')}</Button>{/if}</div>{/if}
|
|
212
248
|
<div role="region" aria-label={ariaLabel ?? i18n.t('kanban.label')} aria-busy={loading || busy}
|
|
213
249
|
class={cn('svadmin-u-60fbb7713999 svadmin-u-0c3bc98565dd svadmin-u-1384f66f41d0 svadmin-u-9fcd8a13827e svadmin-u-359090c2d529 svadmin-u-7f6912283f11', className)}>
|
|
214
250
|
{#if loading}
|
|
@@ -265,7 +301,7 @@
|
|
|
265
301
|
role="group"
|
|
266
302
|
aria-label={card.title}
|
|
267
303
|
aria-busy={busy && movingCardId === card.id}
|
|
268
|
-
draggable={
|
|
304
|
+
draggable={canDragCard(card)}
|
|
269
305
|
class={cn(
|
|
270
306
|
'group svadmin-u-d89972fe17d6 svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed svadmin-u-77a2a20e90d4 svadmin-u-5f22e64f2282 svadmin-u-ca6bcd4b6f3f svadmin-u-05faf5c801ff svadmin-u-cd0ad9a56558 svadmin-u-eb6e8b881acd svadmin-u-cef5b893cf23 svadmin-u-0fe7d7d814d0 svadmin-u-8d08385288a6 svadmin-u-d9bff91ef7ee svadmin-u-512e82e6a68f svadmin-u-ab1dd417ce7d svadmin-u-2eba0d65d059',
|
|
271
307
|
draggedCardId === card.id ? 'svadmin-u-2a2db4667b27 svadmin-u-16b1efa5875e svadmin-u-2691847b25ed' : ''
|
|
@@ -20,9 +20,9 @@ interface Props {
|
|
|
20
20
|
columns: KanbanColumn[];
|
|
21
21
|
cards?: KanbanCard[];
|
|
22
22
|
allowAddCard?: boolean;
|
|
23
|
-
oncardmove?: (cardId: string, targetColumnId: string, targetIndex: number) =>
|
|
23
|
+
oncardmove?: ((cardId: string, targetColumnId: string, targetIndex: number) => KanbanCard | undefined | Promise<KanbanCard | undefined>) | undefined;
|
|
24
24
|
canMoveCard?: (card: KanbanCard, targetColumnId: string) => boolean;
|
|
25
|
-
oncardmoveerror?: (error: unknown) => void;
|
|
25
|
+
oncardmoveerror?: ((error: unknown) => void) | undefined;
|
|
26
26
|
oncardclick?: (card: KanbanCard) => void;
|
|
27
27
|
oncardadd?: (columnId: string, title: string) => KanbanCard | Promise<KanbanCard>;
|
|
28
28
|
scopeKey?: string | number;
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
|
|
126
126
|
function handleGlobalKeydown(e: KeyboardEvent) {
|
|
127
127
|
if (!ownsLayoutEvent(e)) return;
|
|
128
|
-
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
|
128
|
+
if ((e.ctrlKey || e.metaKey) && (e.key.toLowerCase() === 'k' || e.code === 'KeyK')) {
|
|
129
129
|
e.preventDefault();
|
|
130
130
|
commandOpen = true;
|
|
131
131
|
}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
};
|
|
17
17
|
icon?: import('svelte').Component<{ class?: string }>;
|
|
18
18
|
href?: string;
|
|
19
|
+
onActivate?: () => void;
|
|
19
20
|
trend?: {
|
|
20
21
|
value: number;
|
|
21
22
|
label?: string;
|
|
@@ -52,10 +53,14 @@
|
|
|
52
53
|
{@render children()}
|
|
53
54
|
{:else}
|
|
54
55
|
{#each items as item, index (item.id || index)}
|
|
55
|
-
{@const
|
|
56
|
+
{@const hasActivation = Boolean(item.onActivate)}
|
|
57
|
+
{@const isInteractive = Boolean(item.href) || hasActivation}
|
|
56
58
|
<svelte:element
|
|
57
|
-
this={
|
|
58
|
-
|
|
59
|
+
this={hasActivation ? 'button' : item.href ? 'a' : 'div'}
|
|
60
|
+
type={hasActivation ? 'button' : undefined}
|
|
61
|
+
href={hasActivation ? undefined : item.href}
|
|
62
|
+
onclick={item.onActivate}
|
|
63
|
+
role={hasActivation ? 'button' : item.href ? 'link' : undefined}
|
|
59
64
|
data-interactive={isInteractive ? 'true' : undefined}
|
|
60
65
|
data-tone={item.tone || 'default'}
|
|
61
66
|
class={cn('svadmin-metric-item', item.class)}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type
|
|
2
|
+
import { onDestroy, type Snippet } from 'svelte';
|
|
3
3
|
import * as Dialog from './ui/dialog/index.js';
|
|
4
4
|
import { Button } from './ui/button/index.js';
|
|
5
5
|
import { Loader2 } from '@lucide/svelte';
|
|
6
6
|
import { cn } from '../utils.js';
|
|
7
|
+
import { useTranslation } from '@svadmin/core/i18n';
|
|
8
|
+
import FeedbackNotice from './content/FeedbackNotice.svelte';
|
|
7
9
|
|
|
8
10
|
interface Props {
|
|
9
11
|
open?: boolean;
|
|
@@ -17,8 +19,8 @@
|
|
|
17
19
|
widthClass?: string;
|
|
18
20
|
isSubmitting?: boolean;
|
|
19
21
|
disabled?: boolean;
|
|
20
|
-
onsubmit?: () => void | Promise<void
|
|
21
|
-
oncancel?: () => void;
|
|
22
|
+
onsubmit?: (() => void | Promise<void>) | undefined;
|
|
23
|
+
oncancel?: (() => void) | undefined;
|
|
22
24
|
class?: string;
|
|
23
25
|
triggerSnippet?: Snippet;
|
|
24
26
|
children?: Snippet;
|
|
@@ -43,26 +45,96 @@
|
|
|
43
45
|
children,
|
|
44
46
|
}: Props = $props();
|
|
45
47
|
|
|
48
|
+
const i18n = useTranslation();
|
|
46
49
|
let loading = $state(false);
|
|
50
|
+
let submitError = $state('');
|
|
51
|
+
let round = 0;
|
|
52
|
+
let nextRequestId = 0;
|
|
53
|
+
let activeRequest: { id: number; round: number; onsubmit?: Props['onsubmit'] } | undefined;
|
|
54
|
+
let formElement: HTMLFormElement | undefined;
|
|
55
|
+
let destroyed = false;
|
|
56
|
+
const busy = $derived(loading || isSubmitting);
|
|
57
|
+
|
|
58
|
+
let lastOpen = open;
|
|
59
|
+
let lastOnsubmit: Props['onsubmit'];
|
|
60
|
+
let hasLastOnsubmit = false;
|
|
61
|
+
function resetRequestState() {
|
|
62
|
+
round += 1;
|
|
63
|
+
activeRequest = undefined;
|
|
64
|
+
loading = false;
|
|
65
|
+
submitError = '';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
$effect(() => {
|
|
69
|
+
if (open === lastOpen) return;
|
|
70
|
+
lastOpen = open;
|
|
71
|
+
resetRequestState();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
$effect(() => {
|
|
75
|
+
const handler = onsubmit;
|
|
76
|
+
if (!hasLastOnsubmit) {
|
|
77
|
+
hasLastOnsubmit = true;
|
|
78
|
+
lastOnsubmit = handler;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (handler === lastOnsubmit) return;
|
|
82
|
+
lastOnsubmit = handler;
|
|
83
|
+
resetRequestState();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
onDestroy(() => {
|
|
87
|
+
destroyed = true;
|
|
88
|
+
formElement = undefined;
|
|
89
|
+
resetRequestState();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
function isCurrentRequest(token: { id: number; round: number; onsubmit?: Props['onsubmit'] }) {
|
|
93
|
+
return !destroyed && activeRequest === token && token.round === round && token.onsubmit === onsubmit;
|
|
94
|
+
}
|
|
47
95
|
|
|
48
96
|
async function handleSubmit(e: SubmitEvent) {
|
|
49
97
|
e.preventDefault();
|
|
98
|
+
if (!open || destroyed || e.currentTarget !== formElement || disabled || busy) return;
|
|
99
|
+
const token = { id: ++nextRequestId, round, onsubmit };
|
|
100
|
+
activeRequest = token;
|
|
50
101
|
loading = true;
|
|
102
|
+
submitError = '';
|
|
51
103
|
try {
|
|
52
104
|
await onsubmit?.();
|
|
105
|
+
if (!isCurrentRequest(token)) return;
|
|
53
106
|
open = false;
|
|
107
|
+
} catch {
|
|
108
|
+
if (!isCurrentRequest(token)) return;
|
|
109
|
+
submitError = i18n.t('common.operationFailed');
|
|
54
110
|
} finally {
|
|
55
|
-
|
|
111
|
+
if (activeRequest === token) {
|
|
112
|
+
activeRequest = undefined;
|
|
113
|
+
loading = false;
|
|
114
|
+
}
|
|
56
115
|
}
|
|
57
116
|
}
|
|
58
117
|
|
|
59
118
|
function handleCancel() {
|
|
119
|
+
if (busy) return;
|
|
60
120
|
oncancel?.();
|
|
61
121
|
open = false;
|
|
62
122
|
}
|
|
123
|
+
|
|
124
|
+
function handleOpenChange(next: boolean) {
|
|
125
|
+
if (next) {
|
|
126
|
+
open = true;
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (busy) {
|
|
130
|
+
open = true;
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
open = false;
|
|
134
|
+
}
|
|
63
135
|
</script>
|
|
64
136
|
|
|
65
|
-
<Dialog.Root bind:open>
|
|
137
|
+
<Dialog.Root bind:open onOpenChange={handleOpenChange}>
|
|
66
138
|
{#if triggerSnippet}
|
|
67
139
|
<Dialog.Trigger>
|
|
68
140
|
{#snippet child({ props })}
|
|
@@ -82,7 +154,7 @@
|
|
|
82
154
|
{/if}
|
|
83
155
|
|
|
84
156
|
<Dialog.Content class={cn(widthClass, 'svadmin-u-b4168890eac6 svadmin-u-92bf82f493b1', className)}>
|
|
85
|
-
<form onsubmit={handleSubmit} class="svadmin-u-3e7ce58d64fa">
|
|
157
|
+
<form bind:this={formElement} onsubmit={handleSubmit} aria-busy={busy} class="svadmin-u-3e7ce58d64fa">
|
|
86
158
|
<Dialog.Header>
|
|
87
159
|
<Dialog.Title>{title}</Dialog.Title>
|
|
88
160
|
{#if description}
|
|
@@ -90,6 +162,10 @@
|
|
|
90
162
|
{/if}
|
|
91
163
|
</Dialog.Header>
|
|
92
164
|
|
|
165
|
+
{#if submitError}
|
|
166
|
+
<FeedbackNotice tone="danger" message={submitError} />
|
|
167
|
+
{/if}
|
|
168
|
+
|
|
93
169
|
<div class="svadmin-u-03b4dd7f172b svadmin-u-fc7473ca09eb svadmin-u-d4108abe6359">
|
|
94
170
|
{#if children}
|
|
95
171
|
{@render children()}
|
|
@@ -101,7 +177,7 @@
|
|
|
101
177
|
type="button"
|
|
102
178
|
variant="outline"
|
|
103
179
|
size="sm"
|
|
104
|
-
disabled={
|
|
180
|
+
disabled={busy}
|
|
105
181
|
onclick={handleCancel}
|
|
106
182
|
>
|
|
107
183
|
{cancelText}
|
|
@@ -109,10 +185,10 @@
|
|
|
109
185
|
<Button
|
|
110
186
|
type="submit"
|
|
111
187
|
size="sm"
|
|
112
|
-
disabled={
|
|
188
|
+
disabled={busy || disabled}
|
|
113
189
|
class="svadmin-u-58284b4ea568 svadmin-u-2e7a6d18a20d"
|
|
114
190
|
>
|
|
115
|
-
{#if
|
|
191
|
+
{#if busy}
|
|
116
192
|
<Loader2 class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3 svadmin-u-afbdd13a380e" />
|
|
117
193
|
{/if}
|
|
118
194
|
{submitText}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Snippet } from 'svelte';
|
|
2
2
|
interface Props {
|
|
3
3
|
open?: boolean;
|
|
4
4
|
title: string;
|
|
@@ -11,8 +11,8 @@ interface Props {
|
|
|
11
11
|
widthClass?: string;
|
|
12
12
|
isSubmitting?: boolean;
|
|
13
13
|
disabled?: boolean;
|
|
14
|
-
onsubmit?: () => void | Promise<void
|
|
15
|
-
oncancel?: () => void;
|
|
14
|
+
onsubmit?: (() => void | Promise<void>) | undefined;
|
|
15
|
+
oncancel?: (() => void) | undefined;
|
|
16
16
|
class?: string;
|
|
17
17
|
triggerSnippet?: Snippet;
|
|
18
18
|
children?: Snippet;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import NumericInput from './NumericInput.svelte';
|
|
3
|
+
import type { NumericInputLocale } from './numeric-format.js';
|
|
3
4
|
|
|
4
5
|
interface Props {
|
|
5
6
|
value?: number | null;
|
|
7
|
+
locale?: NumericInputLocale | undefined;
|
|
6
8
|
currency?: string;
|
|
7
9
|
min?: number;
|
|
8
10
|
max?: number;
|
|
@@ -14,12 +16,12 @@
|
|
|
14
16
|
disabled?: boolean;
|
|
15
17
|
required?: boolean;
|
|
16
18
|
invalid?: boolean;
|
|
17
|
-
describedby?: string;
|
|
19
|
+
describedby?: string | undefined;
|
|
18
20
|
class?: string;
|
|
19
21
|
onchange?: (value: number | null) => void;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
let props: Props = $props();
|
|
24
|
+
let { value = $bindable(null), ...props }: Props = $props();
|
|
23
25
|
</script>
|
|
24
26
|
|
|
25
|
-
<NumericInput {...props} mode="currency" />
|
|
27
|
+
<NumericInput {...props} bind:value mode="currency" />
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { NumericInputLocale } from './numeric-format.js';
|
|
1
2
|
interface Props {
|
|
2
3
|
value?: number | null;
|
|
4
|
+
locale?: NumericInputLocale | undefined;
|
|
3
5
|
currency?: string;
|
|
4
6
|
min?: number;
|
|
5
7
|
max?: number;
|
|
@@ -11,10 +13,10 @@ interface Props {
|
|
|
11
13
|
disabled?: boolean;
|
|
12
14
|
required?: boolean;
|
|
13
15
|
invalid?: boolean;
|
|
14
|
-
describedby?: string;
|
|
16
|
+
describedby?: string | undefined;
|
|
15
17
|
class?: string;
|
|
16
18
|
onchange?: (value: number | null) => void;
|
|
17
19
|
}
|
|
18
|
-
declare const MoneyInput: import("svelte").Component<Props, {}, "">;
|
|
20
|
+
declare const MoneyInput: import("svelte").Component<Props, {}, "value">;
|
|
19
21
|
type MoneyInput = ReturnType<typeof MoneyInput>;
|
|
20
22
|
export default MoneyInput;
|