@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
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
function text(value) {
|
|
2
|
+
return typeof value === 'string' && value.trim().length > 0 ? value : undefined;
|
|
3
|
+
}
|
|
4
|
+
export function uploadReference(value) {
|
|
5
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
6
|
+
return undefined;
|
|
7
|
+
const descriptor = (key) => Object.getOwnPropertyDescriptor(value, key);
|
|
8
|
+
const read = (key) => {
|
|
9
|
+
const entry = descriptor(key);
|
|
10
|
+
return entry && 'value' in entry ? text(entry.value) : undefined;
|
|
11
|
+
};
|
|
12
|
+
const url = read('url');
|
|
13
|
+
const uploadId = read('uploadId');
|
|
14
|
+
if (url === undefined && uploadId === undefined)
|
|
15
|
+
return undefined;
|
|
16
|
+
return {
|
|
17
|
+
...(url === undefined ? {} : { url }),
|
|
18
|
+
...(uploadId === undefined ? {} : { uploadId }),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function uploadReferenceFor(item) {
|
|
22
|
+
return item.status === 'success' ? uploadReference(item) : undefined;
|
|
23
|
+
}
|
|
24
|
+
/** 兼容历史字符串基线;只读取 plain object 自身数据属性。 */
|
|
25
|
+
export function normalizeUploadReferences(value) {
|
|
26
|
+
const entries = Array.isArray(value) ? value : value === null || value === undefined ? [] : [value];
|
|
27
|
+
return entries.flatMap(entry => {
|
|
28
|
+
if (typeof entry === 'string' && entry.length > 0)
|
|
29
|
+
return [{ url: entry }];
|
|
30
|
+
const reference = uploadReference(entry);
|
|
31
|
+
return reference === undefined ? [] : [reference];
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export function uploadReferenceKey(reference) {
|
|
35
|
+
return JSON.stringify([reference.url ?? null, reference.uploadId ?? null]);
|
|
36
|
+
}
|
|
37
|
+
/** 纯文本回显:优先 URL,其次 uploadId,避免把引用渲染成可点击链接。 */
|
|
38
|
+
export function uploadReferenceLabel(reference) {
|
|
39
|
+
return reference.url ?? reference.uploadId ?? '';
|
|
40
|
+
}
|
|
41
|
+
export function mergeUploadReferences(baseline, uploaded) {
|
|
42
|
+
const seen = new Set();
|
|
43
|
+
return [...baseline, ...uploaded].filter(reference => {
|
|
44
|
+
const key = uploadReferenceKey(reference);
|
|
45
|
+
if (seen.has(key))
|
|
46
|
+
return false;
|
|
47
|
+
seen.add(key);
|
|
48
|
+
return true;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** FileUpload 的共享队列与回调契约;单独文件便于非 Svelte 单元静态检查。 */
|
|
2
|
+
export type UploadItemStatus = 'queued' | 'uploading' | 'success' | 'error' | 'cancelled';
|
|
3
|
+
export interface UploadItem {
|
|
4
|
+
id: string;
|
|
5
|
+
file: File;
|
|
6
|
+
status: UploadItemStatus;
|
|
7
|
+
progress: number;
|
|
8
|
+
url?: string;
|
|
9
|
+
error?: string;
|
|
10
|
+
uploadId?: string;
|
|
11
|
+
cleanupStatus?: 'pending' | 'success' | 'error';
|
|
12
|
+
}
|
|
13
|
+
export interface UploadSession {
|
|
14
|
+
signal: AbortSignal;
|
|
15
|
+
idempotencyKey: string;
|
|
16
|
+
setUploadId: (uploadId: string) => void;
|
|
17
|
+
onProgress: (progress: number) => void;
|
|
18
|
+
}
|
|
19
|
+
export interface UploadCancellation {
|
|
20
|
+
uploadId: string;
|
|
21
|
+
idempotencyKey: string;
|
|
22
|
+
reason: 'cancel' | 'remove' | 'replace' | 'scope-change' | 'unmount';
|
|
23
|
+
}
|
|
24
|
+
export interface UploadHandler {
|
|
25
|
+
(file: File, session: UploadSession): Promise<{
|
|
26
|
+
url?: string;
|
|
27
|
+
uploadId?: string;
|
|
28
|
+
} | undefined> | Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
export interface UploadCancelHandler {
|
|
31
|
+
(file: File, cancellation: UploadCancellation): Promise<void>;
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { FieldDefinition } from '@svadmin/core';
|
|
3
|
+
import AutoForm from './AutoForm.svelte';
|
|
4
|
+
import FieldRenderer from './FieldRenderer.svelte';
|
|
5
|
+
import ArrayField from './fields/ArrayField.svelte';
|
|
6
|
+
import FileUploadField from './FileUploadField.svelte';
|
|
7
|
+
import type {
|
|
8
|
+
UploadCancellation, UploadCancelHandler, UploadHandler, UploadSession,
|
|
9
|
+
} from './file-upload-types.js';
|
|
10
|
+
|
|
11
|
+
type Assignable<Value extends Expected, Expected> = Value;
|
|
12
|
+
type UploadReceipt = { url?: string; uploadId?: string } | undefined;
|
|
13
|
+
type ReceiptContract = Assignable<Exclude<Awaited<ReturnType<UploadHandler>>, void>, UploadReceipt>;
|
|
14
|
+
|
|
15
|
+
const upload: UploadHandler = async (file: File, session: UploadSession) => {
|
|
16
|
+
const signal: AbortSignal = session.signal;
|
|
17
|
+
const idempotencyKey: string = session.idempotencyKey;
|
|
18
|
+
session.setUploadId(`upload-${file.name}`);
|
|
19
|
+
session.onProgress(0.5);
|
|
20
|
+
void signal;
|
|
21
|
+
void idempotencyKey;
|
|
22
|
+
return { url: `/uploads/${file.name}`, uploadId: `upload-${file.name}` };
|
|
23
|
+
};
|
|
24
|
+
const emptyUpload: UploadHandler = async () => {};
|
|
25
|
+
const cancelUpload: UploadCancelHandler = async (file: File, cancellation: UploadCancellation) => {
|
|
26
|
+
const uploadId: string = cancellation.uploadId;
|
|
27
|
+
const idempotencyKey: string = cancellation.idempotencyKey;
|
|
28
|
+
const reason: UploadCancellation['reason'] = cancellation.reason;
|
|
29
|
+
void file;
|
|
30
|
+
void uploadId;
|
|
31
|
+
void idempotencyKey;
|
|
32
|
+
void reason;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// @ts-expect-error Upload receipts expose only the checked reference identifiers.
|
|
36
|
+
const invalidReceipt: UploadHandler = async () => ({ localPath: '/tmp/private' });
|
|
37
|
+
// @ts-expect-error Cancellation reasons come from the checked lifecycle set.
|
|
38
|
+
const invalidReason: UploadCancellation['reason'] = 'done';
|
|
39
|
+
|
|
40
|
+
const field: FieldDefinition = { key: 'attachment', label: 'Attachment', type: 'file' };
|
|
41
|
+
const nestedArray: FieldDefinition = {
|
|
42
|
+
key: 'rows',
|
|
43
|
+
label: 'Rows',
|
|
44
|
+
type: 'array',
|
|
45
|
+
subFields: [
|
|
46
|
+
{ key: 'attachment', label: 'Attachment', type: 'file' },
|
|
47
|
+
{ key: 'children', label: 'Children', type: 'array',
|
|
48
|
+
subFields: [{ key: 'attachment', label: 'Attachment', type: 'file' }] },
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
const value: unknown = null;
|
|
52
|
+
const onchange = (_next: unknown): void => {};
|
|
53
|
+
const onUploadStateChange = (state: { busy: boolean; unresolved: boolean }): void => {
|
|
54
|
+
const busy: boolean = state.busy;
|
|
55
|
+
const unresolved: boolean = state.unresolved;
|
|
56
|
+
void busy;
|
|
57
|
+
void unresolved;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const receiptContract: ReceiptContract | undefined = undefined;
|
|
61
|
+
void emptyUpload;
|
|
62
|
+
void invalidReceipt;
|
|
63
|
+
void invalidReason;
|
|
64
|
+
void receiptContract;
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
{#snippet uploadActions({ isLoading, isDisabled, onSubmit }: {
|
|
68
|
+
isLoading: boolean; isDisabled: boolean; onSubmit: () => void;
|
|
69
|
+
})}
|
|
70
|
+
{@const typedLoading: boolean = isLoading}
|
|
71
|
+
{@const typedDisabled: boolean = isDisabled}
|
|
72
|
+
<button type="button" disabled={typedDisabled} onclick={onSubmit} data-loading={typedLoading}>
|
|
73
|
+
Save
|
|
74
|
+
</button>
|
|
75
|
+
{/snippet}
|
|
76
|
+
|
|
77
|
+
<AutoForm
|
|
78
|
+
resourceName="records"
|
|
79
|
+
upload={upload}
|
|
80
|
+
cancelUpload={cancelUpload}
|
|
81
|
+
formActions={uploadActions}
|
|
82
|
+
/>
|
|
83
|
+
<FileUploadField
|
|
84
|
+
{field}
|
|
85
|
+
{value}
|
|
86
|
+
{onchange}
|
|
87
|
+
upload={upload}
|
|
88
|
+
cancelUpload={cancelUpload}
|
|
89
|
+
{onUploadStateChange}
|
|
90
|
+
/>
|
|
91
|
+
<FieldRenderer
|
|
92
|
+
{field}
|
|
93
|
+
{value}
|
|
94
|
+
{onchange}
|
|
95
|
+
upload={upload}
|
|
96
|
+
cancelUpload={cancelUpload}
|
|
97
|
+
fileReferenceMode
|
|
98
|
+
{onUploadStateChange}
|
|
99
|
+
/>
|
|
100
|
+
<ArrayField
|
|
101
|
+
field={nestedArray}
|
|
102
|
+
{value}
|
|
103
|
+
{onchange}
|
|
104
|
+
upload={upload}
|
|
105
|
+
cancelUpload={cancelUpload}
|
|
106
|
+
fileReferenceMode
|
|
107
|
+
{onUploadStateChange}
|
|
108
|
+
/>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const FormContract: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type FormContract = InstanceType<typeof FormContract>;
|
|
18
|
+
export default FormContract;
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
/** 嵌套表单的草稿与传输语义;不是替代服务端的 JSON Schema 校验器。 */
|
|
2
2
|
export interface JsonSchemaFormSchema {
|
|
3
|
+
$schema?: string;
|
|
4
|
+
$id?: string;
|
|
5
|
+
$comment?: string;
|
|
6
|
+
$ref?: string;
|
|
7
|
+
$defs?: Record<string, JsonSchemaFormSchema>;
|
|
3
8
|
type?: string | string[];
|
|
4
9
|
const?: string | number | boolean | null;
|
|
5
10
|
readOnly?: boolean;
|
|
11
|
+
writeOnly?: boolean;
|
|
12
|
+
deprecated?: boolean;
|
|
13
|
+
examples?: unknown[];
|
|
6
14
|
minimum?: number;
|
|
7
15
|
maximum?: number;
|
|
8
16
|
exclusiveMinimum?: number;
|
|
@@ -13,13 +21,24 @@ export interface JsonSchemaFormSchema {
|
|
|
13
21
|
minItems?: number;
|
|
14
22
|
maxItems?: number;
|
|
15
23
|
additionalProperties?: boolean;
|
|
24
|
+
pattern?: string;
|
|
16
25
|
title?: string;
|
|
17
26
|
description?: string;
|
|
18
27
|
properties?: Record<string, JsonSchemaFormSchema>;
|
|
19
28
|
items?: JsonSchemaFormSchema;
|
|
29
|
+
oneOf?: JsonSchemaFormSchema[];
|
|
30
|
+
discriminator?: {
|
|
31
|
+
propertyName: string;
|
|
32
|
+
};
|
|
20
33
|
enum?: Array<string | number | boolean | null>;
|
|
21
34
|
default?: unknown;
|
|
22
35
|
required?: string[];
|
|
36
|
+
visibleWhen?: {
|
|
37
|
+
path: readonly string[];
|
|
38
|
+
equals?: string | number | boolean | null;
|
|
39
|
+
notEquals?: string | number | boolean | null;
|
|
40
|
+
exists?: boolean;
|
|
41
|
+
};
|
|
23
42
|
}
|
|
24
43
|
export declare const SCHEMA_FORM_LIMITS: Readonly<{
|
|
25
44
|
depth: 32;
|
|
@@ -6,6 +6,7 @@ export function createListDeletion(options) {
|
|
|
6
6
|
let message = $state('');
|
|
7
7
|
let pending = $state(false);
|
|
8
8
|
let activeDelete;
|
|
9
|
+
let criteriaVersion = 0;
|
|
9
10
|
$effect(() => {
|
|
10
11
|
if (request && !options.permission().isLoading && !options.permission().allowed) {
|
|
11
12
|
open = false;
|
|
@@ -38,6 +39,7 @@ export function createListDeletion(options) {
|
|
|
38
39
|
request = null;
|
|
39
40
|
}
|
|
40
41
|
function cancelBatchOnCriteriaChange() {
|
|
42
|
+
criteriaVersion += 1;
|
|
41
43
|
if (request?.batch && !pending)
|
|
42
44
|
cancel();
|
|
43
45
|
}
|
|
@@ -58,6 +60,7 @@ export function createListDeletion(options) {
|
|
|
58
60
|
return;
|
|
59
61
|
}
|
|
60
62
|
const scopeIsCurrent = options.captureScope();
|
|
63
|
+
const confirmedCriteriaVersion = criteriaVersion;
|
|
61
64
|
const token = {};
|
|
62
65
|
activeDelete = token;
|
|
63
66
|
pending = true;
|
|
@@ -66,7 +69,9 @@ export function createListDeletion(options) {
|
|
|
66
69
|
const selectionBeforeDelete = clearBeforeMutation ? selection.captureExplicit() : undefined;
|
|
67
70
|
if (clearBeforeMutation)
|
|
68
71
|
options.clearSelection();
|
|
69
|
-
const current = () => activeDelete === token
|
|
72
|
+
const current = () => activeDelete === token
|
|
73
|
+
&& scopeIsCurrent()
|
|
74
|
+
&& criteriaVersion === confirmedCriteriaVersion;
|
|
70
75
|
try {
|
|
71
76
|
await options.mutate([...confirmedRequest.ids]);
|
|
72
77
|
if (!current())
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import type { FieldDefinition, Filter, Pagination, Sort, readURLState, writeURLState } from '@svadmin/core';
|
|
2
2
|
import type { SavedListViewState } from './saved-list-views.js';
|
|
3
3
|
type URLState = ReturnType<typeof readURLState>;
|
|
4
|
-
|
|
4
|
+
/** AutoTable 可恢复的列表查询快照;不包含列可见性、草稿或远程视图元数据。 */
|
|
5
|
+
export interface ListStateSnapshot {
|
|
6
|
+
search: string;
|
|
7
|
+
filters: Filter[];
|
|
8
|
+
sorters: Sort[];
|
|
9
|
+
pagination: Pagination;
|
|
10
|
+
}
|
|
5
11
|
interface ListStateOptions {
|
|
6
12
|
fields: () => FieldDefinition[];
|
|
7
13
|
defaultPageSize: () => number;
|
|
@@ -9,12 +15,17 @@ interface ListStateOptions {
|
|
|
9
15
|
externalPagination: () => Pagination | undefined;
|
|
10
16
|
externalSorters: () => Sort[] | undefined;
|
|
11
17
|
initialURLState: URLState;
|
|
18
|
+
initialListState?: ListStateSnapshot | undefined;
|
|
12
19
|
initialViewState: SavedListViewState | undefined;
|
|
13
20
|
syncWithLocation: () => boolean;
|
|
14
21
|
writeURLState: (state: Parameters<typeof writeURLState>[0]) => void;
|
|
15
22
|
onDirty: () => void;
|
|
23
|
+
onStateChange?: ((state: ListStateSnapshot) => void) | undefined;
|
|
16
24
|
}
|
|
17
25
|
export declare function cloneListFilter(filter: Filter): Filter;
|
|
26
|
+
export declare function cloneListStateSnapshot(state: ListStateSnapshot): ListStateSnapshot;
|
|
27
|
+
/** 将可恢复的查询快照收敛为持久化视图契约;分页可选字段在写入前补齐。 */
|
|
28
|
+
export declare function captureSavedListViewState(snapshot: ListStateSnapshot, columns: Pick<SavedListViewState, 'columnVisibility' | 'columnOrder'>, fallbackPageSize: number): SavedListViewState;
|
|
18
29
|
export declare function createListState(options: ListStateOptions): {
|
|
19
30
|
pagination: Pagination;
|
|
20
31
|
sorters: Sort[];
|
|
@@ -11,6 +11,28 @@ export function cloneListFilter(filter) {
|
|
|
11
11
|
}
|
|
12
12
|
return { operator: filter.operator, value: filter.value.map(cloneListFilter) };
|
|
13
13
|
}
|
|
14
|
+
export function cloneListStateSnapshot(state) {
|
|
15
|
+
return {
|
|
16
|
+
search: state.search,
|
|
17
|
+
filters: state.filters.map(cloneListFilter),
|
|
18
|
+
sorters: state.sorters.map(sorter => ({ ...sorter })),
|
|
19
|
+
pagination: { ...state.pagination },
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** 将可恢复的查询快照收敛为持久化视图契约;分页可选字段在写入前补齐。 */
|
|
23
|
+
export function captureSavedListViewState(snapshot, columns, fallbackPageSize) {
|
|
24
|
+
return {
|
|
25
|
+
search: snapshot.search,
|
|
26
|
+
filters: snapshot.filters.map(cloneListFilter),
|
|
27
|
+
sorters: snapshot.sorters.map(sorter => ({ ...sorter })),
|
|
28
|
+
pagination: {
|
|
29
|
+
current: snapshot.pagination.current ?? 1,
|
|
30
|
+
pageSize: snapshot.pagination.pageSize ?? fallbackPageSize,
|
|
31
|
+
},
|
|
32
|
+
columnVisibility: { ...columns.columnVisibility },
|
|
33
|
+
columnOrder: [...columns.columnOrder],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
14
36
|
function editableValues(filters, fields) {
|
|
15
37
|
const keys = new Set(fields.filter(field => field.filterable).map(field => field.key));
|
|
16
38
|
const counts = new Map();
|
|
@@ -28,23 +50,29 @@ function editableValues(filters, fields) {
|
|
|
28
50
|
return values;
|
|
29
51
|
}
|
|
30
52
|
export function createListState(options) {
|
|
31
|
-
const url = options.initialURLState;
|
|
53
|
+
const url = untrack(() => options.syncWithLocation()) ? options.initialURLState : {};
|
|
54
|
+
const initialListState = options.initialListState
|
|
55
|
+
? cloneListStateSnapshot(options.initialListState)
|
|
56
|
+
: undefined;
|
|
32
57
|
const initialView = options.initialViewState;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
58
|
+
const initialPagination = initialListState?.pagination ?? initialView?.pagination;
|
|
59
|
+
let pagination = $state(untrack(() => options.externalPagination() ?? definedOptions({
|
|
60
|
+
...initialPagination,
|
|
61
|
+
current: url.page ?? initialPagination?.current ?? 1,
|
|
62
|
+
pageSize: url.pageSize ?? initialPagination?.pageSize ?? options.defaultPageSize(),
|
|
63
|
+
})));
|
|
37
64
|
let sorters = $state(untrack(() => {
|
|
38
65
|
const defaultSort = options.defaultSort();
|
|
39
66
|
return options.externalSorters() ?? (url.sortField
|
|
40
67
|
? [{ field: url.sortField, order: url.sortOrder ?? 'asc' }]
|
|
41
|
-
: initialView?.sorters ?? (defaultSort ? [defaultSort] : []));
|
|
68
|
+
: initialListState?.sorters ?? initialView?.sorters ?? (defaultSort ? [defaultSort] : []));
|
|
42
69
|
}));
|
|
43
|
-
let filters = $state(url.filters ?? initialView?.filters ?? []);
|
|
70
|
+
let filters = $state(url.filters ?? initialListState?.filters ?? initialView?.filters ?? []);
|
|
44
71
|
let filterValues = $state(untrack(() => editableValues(filters, options.fields())));
|
|
45
|
-
let searchText = $state(url.search ?? initialView?.search ?? '');
|
|
72
|
+
let searchText = $state(url.search ?? initialListState?.search ?? initialView?.search ?? '');
|
|
46
73
|
let appliedSearchText = $state(untrack(() => searchText));
|
|
47
74
|
let searchTimer;
|
|
75
|
+
let lastNotifiedState = untrack(() => JSON.stringify(captureState()));
|
|
48
76
|
const searchableFields = $derived(options.fields().filter(field => field.searchable));
|
|
49
77
|
const filterableFields = $derived(options.fields().filter(field => field.filterable));
|
|
50
78
|
const activeFilterItems = $derived.by(() => {
|
|
@@ -101,6 +129,14 @@ export function createListState(options) {
|
|
|
101
129
|
filters,
|
|
102
130
|
});
|
|
103
131
|
});
|
|
132
|
+
$effect(() => {
|
|
133
|
+
const snapshot = captureState();
|
|
134
|
+
const serialized = JSON.stringify(snapshot);
|
|
135
|
+
if (serialized === lastNotifiedState)
|
|
136
|
+
return;
|
|
137
|
+
lastNotifiedState = serialized;
|
|
138
|
+
untrack(() => options.onStateChange?.(snapshot));
|
|
139
|
+
});
|
|
104
140
|
$effect(() => cancelSearch);
|
|
105
141
|
function cancelSearch() {
|
|
106
142
|
if (searchTimer !== undefined)
|
|
@@ -175,20 +211,24 @@ export function createListState(options) {
|
|
|
175
211
|
search: appliedSearchText,
|
|
176
212
|
filters: filters.map(cloneListFilter),
|
|
177
213
|
sorters: sorters.map(sorter => ({ ...sorter })),
|
|
178
|
-
pagination: {
|
|
214
|
+
pagination: definedOptions({
|
|
179
215
|
current: pagination.current ?? 1,
|
|
180
216
|
pageSize: pagination.pageSize ?? options.defaultPageSize(),
|
|
181
|
-
|
|
217
|
+
mode: pagination.mode,
|
|
218
|
+
}),
|
|
182
219
|
};
|
|
183
220
|
}
|
|
184
221
|
function applyState(state) {
|
|
185
222
|
cancelSearch();
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
223
|
+
const snapshot = cloneListStateSnapshot(state);
|
|
224
|
+
const controlledPagination = options.externalPagination();
|
|
225
|
+
const controlledSorters = options.externalSorters();
|
|
226
|
+
searchText = snapshot.search;
|
|
227
|
+
appliedSearchText = snapshot.search;
|
|
228
|
+
filters = snapshot.filters;
|
|
229
|
+
filterValues = editableValues(snapshot.filters, options.fields());
|
|
230
|
+
pagination = controlledPagination ?? { ...pagination, ...snapshot.pagination };
|
|
231
|
+
sorters = controlledSorters ?? snapshot.sorters;
|
|
192
232
|
}
|
|
193
233
|
function resetToDefaults() {
|
|
194
234
|
cancelSearch();
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal, explicit locale contract for the numeric input family.
|
|
3
|
+
*
|
|
4
|
+
* Only the locales below are accepted by the public `locale` prop. The default
|
|
5
|
+
* (no `locale`) keeps the legacy `type="number"` behaviour untouched. Adding a
|
|
6
|
+
* locale is a product decision and must come with its own parsing/screen-reader
|
|
7
|
+
* matrix instead of silently widening the contract.
|
|
8
|
+
*/
|
|
9
|
+
export declare const NUMERIC_INPUT_LOCALES: readonly ["en-US", "de-DE", "zh-CN"];
|
|
10
|
+
export type NumericInputLocale = (typeof NUMERIC_INPUT_LOCALES)[number];
|
|
11
|
+
export type NumericParseResult = {
|
|
12
|
+
ok: true;
|
|
13
|
+
value: number | null;
|
|
14
|
+
} | {
|
|
15
|
+
ok: false;
|
|
16
|
+
};
|
|
17
|
+
export declare function isNumericInputLocale(value: string | null | undefined): value is NumericInputLocale;
|
|
18
|
+
export declare function clampNumericPrecision(precision: number | null | undefined): number | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Formats a stored value for display. `precision` only rounds the display; the
|
|
21
|
+
* caller keeps the original stored number. Intl's default rounding is used.
|
|
22
|
+
*/
|
|
23
|
+
export declare function formatNumericValue(locale: NumericInputLocale, value: number | null, precision?: number | null): string;
|
|
24
|
+
/**
|
|
25
|
+
* Editable text mirrors formatting but omits grouping so that inserting a digit
|
|
26
|
+
* does not make an otherwise valid number look like a mixed-separator error.
|
|
27
|
+
*/
|
|
28
|
+
export declare function formatNumericEditingText(locale: NumericInputLocale, value: number | null, precision?: number | null): string;
|
|
29
|
+
/**
|
|
30
|
+
* Parses locale-formatted text into a stored value. Group separators are
|
|
31
|
+
* display-only and never become part of the number. Invalid or mixed input
|
|
32
|
+
* returns `{ ok: false }` without echoing the raw text or an error message.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseNumericInput(locale: NumericInputLocale, text: string): NumericParseResult;
|
|
35
|
+
/** Rounds a stepped value to avoid floating-point drift when precision is set. */
|
|
36
|
+
export declare function roundToNumericPrecision(value: number, precision: number | null | undefined): number;
|
|
37
|
+
/**
|
|
38
|
+
* Adds one step without rounding to the display precision. Display precision
|
|
39
|
+
* must not erase an explicit smaller step; decimal scaling only protects the
|
|
40
|
+
* arithmetic itself. Scaling is used while its integers stay inside
|
|
41
|
+
* Number.MAX_SAFE_INTEGER; beyond that, direct Number addition is the only
|
|
42
|
+
* truthful result. Non-finite results keep the original value.
|
|
43
|
+
*/
|
|
44
|
+
export declare function addNumericStep(base: number, step: number, direction?: 1 | -1): number;
|