@svadmin/ui 0.63.0 → 0.65.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/components/ActivityFeed.svelte +135 -0
- package/dist/components/ActivityFeed.svelte.d.ts +21 -0
- package/dist/components/ApprovalActionCard.svelte +248 -0
- package/dist/components/ApprovalActionCard.svelte.d.ts +22 -0
- package/dist/components/AutoTable.svelte +8 -0
- package/dist/components/AutoTable.svelte.d.ts +7 -1
- package/dist/components/CanvasAnnotation.svelte +389 -0
- package/dist/components/CanvasAnnotation.svelte.d.ts +17 -0
- package/dist/components/Cascader.svelte +205 -0
- package/dist/components/Cascader.svelte.d.ts +21 -0
- package/dist/components/ColumnHeaderFilter.svelte +178 -0
- package/dist/components/ColumnHeaderFilter.svelte.d.ts +12 -0
- package/dist/components/ColumnSettings.svelte +198 -0
- package/dist/components/ColumnSettings.svelte.d.ts +19 -0
- package/dist/components/DecisionTable.svelte +240 -0
- package/dist/components/DecisionTable.svelte.d.ts +22 -0
- package/dist/components/DraggableRowTable.svelte +117 -0
- package/dist/components/DraggableRowTable.svelte.d.ts +15 -0
- package/dist/components/DrawerForm.svelte +110 -26
- package/dist/components/DrawerForm.svelte.d.ts +20 -8
- package/dist/components/DynamicFormList.svelte +214 -0
- package/dist/components/DynamicFormList.svelte.d.ts +62 -0
- package/dist/components/EditableTable.svelte +195 -0
- package/dist/components/EditableTable.svelte.d.ts +25 -0
- package/dist/components/FieldRenderer.svelte +32 -0
- package/dist/components/FilterBuilder.svelte +310 -0
- package/dist/components/FilterBuilder.svelte.d.ts +23 -0
- package/dist/components/GanttChart.svelte +144 -0
- package/dist/components/GanttChart.svelte.d.ts +20 -0
- package/dist/components/ImageCropper.svelte +122 -0
- package/dist/components/ImageCropper.svelte.d.ts +12 -0
- package/dist/components/ImportWizard.svelte +336 -0
- package/dist/components/ImportWizard.svelte.d.ts +12 -0
- package/dist/components/JsonSchemaForm.svelte +149 -0
- package/dist/components/JsonSchemaForm.svelte.d.ts +10 -0
- package/dist/components/KanbanBoard.svelte +212 -0
- package/dist/components/KanbanBoard.svelte.d.ts +30 -0
- package/dist/components/MasterDetailView.svelte +134 -0
- package/dist/components/MasterDetailView.svelte.d.ts +16 -0
- package/dist/components/MediaLibraryModal.svelte +195 -0
- package/dist/components/MediaLibraryModal.svelte.d.ts +22 -0
- package/dist/components/MentionsInput.svelte +174 -0
- package/dist/components/MentionsInput.svelte.d.ts +21 -0
- package/dist/components/ModalForm.svelte +113 -28
- package/dist/components/ModalForm.svelte.d.ts +20 -8
- package/dist/components/MultiTabKeepAlive.svelte +143 -0
- package/dist/components/MultiTabKeepAlive.svelte.d.ts +21 -0
- package/dist/components/OfflineSyncBanner.svelte +172 -0
- package/dist/components/OfflineSyncBanner.svelte.d.ts +19 -0
- package/dist/components/PdfDocumentViewer.svelte +255 -0
- package/dist/components/PdfDocumentViewer.svelte.d.ts +22 -0
- package/dist/components/PivotTable.svelte +140 -0
- package/dist/components/PivotTable.svelte.d.ts +16 -0
- package/dist/components/PresenceAvatarGroup.svelte +89 -0
- package/dist/components/PresenceAvatarGroup.svelte.d.ts +15 -0
- package/dist/components/PrintableBill.svelte +149 -0
- package/dist/components/PrintableBill.svelte.d.ts +24 -0
- package/dist/components/SensitiveDataMask.svelte +125 -0
- package/dist/components/SensitiveDataMask.svelte.d.ts +13 -0
- package/dist/components/SignaturePad.svelte +264 -0
- package/dist/components/SignaturePad.svelte.d.ts +17 -0
- package/dist/components/SplitPaneLayout.svelte +113 -0
- package/dist/components/SplitPaneLayout.svelte.d.ts +13 -0
- package/dist/components/SpreadsheetView.svelte +363 -0
- package/dist/components/SpreadsheetView.svelte.d.ts +18 -0
- package/dist/components/StepForm.svelte +180 -0
- package/dist/components/StepForm.svelte.d.ts +23 -0
- package/dist/components/TableSummary.svelte +110 -0
- package/dist/components/TableSummary.svelte.d.ts +20 -0
- package/dist/components/Transfer.svelte +352 -0
- package/dist/components/Transfer.svelte.d.ts +28 -0
- package/dist/components/TreeSelect.svelte +307 -0
- package/dist/components/TreeSelect.svelte.d.ts +22 -0
- package/dist/components/TreeTable.svelte +237 -0
- package/dist/components/TreeTable.svelte.d.ts +28 -0
- package/dist/components/UnsavedChangesPrompt.svelte +44 -0
- package/dist/components/UnsavedChangesPrompt.svelte.d.ts +7 -0
- package/dist/components/VersionDiffViewer.svelte +196 -0
- package/dist/components/VersionDiffViewer.svelte.d.ts +12 -0
- package/dist/components/VirtualTable.svelte +86 -54
- package/dist/components/VirtualTable.svelte.d.ts +14 -21
- package/dist/components/Watermark.svelte +166 -0
- package/dist/components/Watermark.svelte.d.ts +26 -0
- package/dist/components/ui/navigation-menu/index.d.ts +11 -0
- package/dist/components/ui/navigation-menu/index.js +13 -0
- package/dist/components/ui/navigation-menu/navigation-menu-content.svelte +21 -0
- package/dist/components/ui/navigation-menu/navigation-menu-content.svelte.d.ts +4 -0
- package/dist/components/ui/navigation-menu/navigation-menu-indicator.svelte +22 -0
- package/dist/components/ui/navigation-menu/navigation-menu-indicator.svelte.d.ts +4 -0
- package/dist/components/ui/navigation-menu/navigation-menu-item.svelte +17 -0
- package/dist/components/ui/navigation-menu/navigation-menu-item.svelte.d.ts +4 -0
- package/dist/components/ui/navigation-menu/navigation-menu-link.svelte +20 -0
- package/dist/components/ui/navigation-menu/navigation-menu-link.svelte.d.ts +4 -0
- package/dist/components/ui/navigation-menu/navigation-menu-list.svelte +17 -0
- package/dist/components/ui/navigation-menu/navigation-menu-list.svelte.d.ts +4 -0
- package/dist/components/ui/navigation-menu/navigation-menu-trigger.svelte +28 -0
- package/dist/components/ui/navigation-menu/navigation-menu-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/navigation-menu/navigation-menu-viewport.svelte +22 -0
- package/dist/components/ui/navigation-menu/navigation-menu-viewport.svelte.d.ts +4 -0
- package/dist/components/ui/navigation-menu/navigation-menu.svelte +24 -0
- package/dist/components/ui/navigation-menu/navigation-menu.svelte.d.ts +4 -0
- package/dist/index.d.ts +68 -3
- package/dist/index.js +42 -3
- package/package.json +2 -2
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { GripVertical } from '@lucide/svelte';
|
|
3
|
+
import { cn } from '../utils.js';
|
|
4
|
+
|
|
5
|
+
export interface DraggableTableColumn {
|
|
6
|
+
key: string;
|
|
7
|
+
label: string;
|
|
8
|
+
width?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
columns: DraggableTableColumn[];
|
|
13
|
+
items?: Record<string, unknown>[];
|
|
14
|
+
rowKey?: string;
|
|
15
|
+
onreorder?: (reorderedItems: Record<string, unknown>[]) => void;
|
|
16
|
+
class?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
columns,
|
|
21
|
+
items = $bindable([]),
|
|
22
|
+
rowKey = 'id',
|
|
23
|
+
onreorder,
|
|
24
|
+
class: className = '',
|
|
25
|
+
}: Props = $props();
|
|
26
|
+
|
|
27
|
+
let draggedIndex = $state<number | null>(null);
|
|
28
|
+
let overIndex = $state<number | null>(null);
|
|
29
|
+
|
|
30
|
+
function handleDragStart(index: number, e: DragEvent) {
|
|
31
|
+
draggedIndex = index;
|
|
32
|
+
if (e.dataTransfer) {
|
|
33
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
34
|
+
e.dataTransfer.setData('text/plain', String(index));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function handleDragOver(index: number, e: DragEvent) {
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
if (draggedIndex === null || draggedIndex === index) return;
|
|
41
|
+
overIndex = index;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function handleDrop(index: number, e: DragEvent) {
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
if (draggedIndex === null || draggedIndex === index) {
|
|
47
|
+
draggedIndex = null;
|
|
48
|
+
overIndex = null;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const nextItems = [...items];
|
|
53
|
+
const [movedItem] = nextItems.splice(draggedIndex, 1);
|
|
54
|
+
nextItems.splice(index, 0, movedItem);
|
|
55
|
+
|
|
56
|
+
items = nextItems;
|
|
57
|
+
onreorder?.(items);
|
|
58
|
+
|
|
59
|
+
draggedIndex = null;
|
|
60
|
+
overIndex = null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function handleDragEnd() {
|
|
64
|
+
draggedIndex = null;
|
|
65
|
+
overIndex = null;
|
|
66
|
+
}
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<div class={cn('w-full rounded-xl border border-border bg-card shadow-xs overflow-hidden text-xs', className)}>
|
|
70
|
+
<table class="w-full text-left border-collapse">
|
|
71
|
+
<thead class="bg-muted/40 font-semibold text-muted-foreground border-b border-border/60 select-none">
|
|
72
|
+
<tr>
|
|
73
|
+
<th class="w-10 px-2 py-2.5 text-center">#</th>
|
|
74
|
+
{#each columns as col (col.key)}
|
|
75
|
+
<th style={col.width ? `width: ${col.width};` : ''} class="px-4 py-2.5">
|
|
76
|
+
{col.label}
|
|
77
|
+
</th>
|
|
78
|
+
{/each}
|
|
79
|
+
</tr>
|
|
80
|
+
</thead>
|
|
81
|
+
<tbody class="divide-y divide-border/40">
|
|
82
|
+
{#each items as item, index (item[rowKey] ?? index)}
|
|
83
|
+
{@const isDragging = draggedIndex === index}
|
|
84
|
+
{@const isOver = overIndex === index}
|
|
85
|
+
<tr
|
|
86
|
+
draggable="true"
|
|
87
|
+
ondragstart={(e) => handleDragStart(index, e)}
|
|
88
|
+
ondragover={(e) => handleDragOver(index, e)}
|
|
89
|
+
ondrop={(e) => handleDrop(index, e)}
|
|
90
|
+
ondragend={handleDragEnd}
|
|
91
|
+
class={cn(
|
|
92
|
+
'transition-colors cursor-move select-none',
|
|
93
|
+
isDragging ? 'opacity-30 bg-muted/50' : 'hover:bg-muted/20',
|
|
94
|
+
isOver ? 'border-t-2 border-primary bg-primary/5' : ''
|
|
95
|
+
)}
|
|
96
|
+
>
|
|
97
|
+
<td class="px-2 py-2.5 text-center text-muted-foreground/60 hover:text-foreground">
|
|
98
|
+
<GripVertical class="h-4 w-4 mx-auto" />
|
|
99
|
+
</td>
|
|
100
|
+
{#each columns as col (col.key)}
|
|
101
|
+
<td class="px-4 py-2.5 font-medium text-foreground">
|
|
102
|
+
{item[col.key] ?? '—'}
|
|
103
|
+
</td>
|
|
104
|
+
{/each}
|
|
105
|
+
</tr>
|
|
106
|
+
{/each}
|
|
107
|
+
|
|
108
|
+
{#if items.length === 0}
|
|
109
|
+
<tr>
|
|
110
|
+
<td colspan={columns.length + 1} class="py-8 text-center text-muted-foreground">
|
|
111
|
+
No items to display
|
|
112
|
+
</td>
|
|
113
|
+
</tr>
|
|
114
|
+
{/if}
|
|
115
|
+
</tbody>
|
|
116
|
+
</table>
|
|
117
|
+
</div>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface DraggableTableColumn {
|
|
2
|
+
key: string;
|
|
3
|
+
label: string;
|
|
4
|
+
width?: string;
|
|
5
|
+
}
|
|
6
|
+
interface Props {
|
|
7
|
+
columns: DraggableTableColumn[];
|
|
8
|
+
items?: Record<string, unknown>[];
|
|
9
|
+
rowKey?: string;
|
|
10
|
+
onreorder?: (reorderedItems: Record<string, unknown>[]) => void;
|
|
11
|
+
class?: string;
|
|
12
|
+
}
|
|
13
|
+
declare const DraggableRowTable: import("svelte").Component<Props, {}, "items">;
|
|
14
|
+
type DraggableRowTable = ReturnType<typeof DraggableRowTable>;
|
|
15
|
+
export default DraggableRowTable;
|
|
@@ -1,38 +1,122 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import { useTranslation } from '@svadmin/core/i18n';
|
|
4
|
-
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
5
3
|
import * as Sheet from './ui/sheet/index.js';
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
import { Button } from './ui/button/index.js';
|
|
5
|
+
import { Loader2 } from '@lucide/svelte';
|
|
6
|
+
import { cn } from '../utils.js';
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
interface Props {
|
|
9
|
+
open?: boolean;
|
|
10
|
+
title: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
triggerText?: string;
|
|
13
|
+
triggerVariant?: 'default' | 'outline' | 'secondary' | 'ghost' | 'destructive';
|
|
14
|
+
triggerSize?: 'default' | 'sm' | 'lg' | 'icon';
|
|
15
|
+
submitText?: string;
|
|
16
|
+
cancelText?: string;
|
|
15
17
|
side?: 'left' | 'right';
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
widthClass?: string;
|
|
19
|
+
isSubmitting?: boolean;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
onsubmit?: () => void | Promise<void>;
|
|
22
|
+
oncancel?: () => void;
|
|
23
|
+
class?: string;
|
|
24
|
+
triggerSnippet?: Snippet;
|
|
25
|
+
children?: Snippet;
|
|
26
|
+
}
|
|
18
27
|
|
|
19
|
-
|
|
28
|
+
let {
|
|
29
|
+
open = $bindable(false),
|
|
30
|
+
title,
|
|
31
|
+
description,
|
|
32
|
+
triggerText = 'Open Drawer',
|
|
33
|
+
triggerVariant = 'default',
|
|
34
|
+
triggerSize = 'sm',
|
|
35
|
+
submitText = 'Save',
|
|
36
|
+
cancelText = 'Cancel',
|
|
37
|
+
side = 'right',
|
|
38
|
+
widthClass = 'sm:max-w-md w-full',
|
|
39
|
+
isSubmitting = false,
|
|
40
|
+
disabled = false,
|
|
41
|
+
onsubmit,
|
|
42
|
+
oncancel,
|
|
43
|
+
class: className = '',
|
|
44
|
+
triggerSnippet,
|
|
45
|
+
children,
|
|
46
|
+
}: Props = $props();
|
|
20
47
|
|
|
21
|
-
|
|
48
|
+
let loading = $state(false);
|
|
49
|
+
|
|
50
|
+
async function handleSubmit(e: SubmitEvent) {
|
|
51
|
+
e.preventDefault();
|
|
52
|
+
loading = true;
|
|
53
|
+
try {
|
|
54
|
+
await onsubmit?.();
|
|
55
|
+
open = false;
|
|
56
|
+
} finally {
|
|
57
|
+
loading = false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function handleCancel() {
|
|
62
|
+
oncancel?.();
|
|
22
63
|
open = false;
|
|
23
|
-
onSuccess?.();
|
|
24
64
|
}
|
|
25
65
|
</script>
|
|
26
66
|
|
|
27
|
-
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
67
|
+
{#if triggerSnippet}
|
|
68
|
+
<button
|
|
69
|
+
type="button"
|
|
70
|
+
class="inline-flex cursor-pointer bg-transparent border-0 p-0 text-left"
|
|
71
|
+
onclick={() => { open = true; }}
|
|
72
|
+
{disabled}
|
|
73
|
+
>
|
|
74
|
+
{@render triggerSnippet()}
|
|
75
|
+
</button>
|
|
76
|
+
{:else if triggerText}
|
|
77
|
+
<Button variant={triggerVariant} size={triggerSize} {disabled} onclick={() => { open = true; }}>
|
|
78
|
+
{triggerText}
|
|
79
|
+
</Button>
|
|
80
|
+
{/if}
|
|
81
|
+
|
|
82
|
+
<Sheet.Root bind:open {side} onClose={handleCancel} class={cn(widthClass, 'p-0 overflow-hidden', className)}>
|
|
83
|
+
<Sheet.Content class="flex flex-col h-full gap-0 p-0">
|
|
84
|
+
<form onsubmit={handleSubmit} class="flex flex-col h-full">
|
|
85
|
+
<Sheet.Header class="px-6 py-4 border-b border-border/60">
|
|
86
|
+
<Sheet.Title>{title}</Sheet.Title>
|
|
87
|
+
{#if description}
|
|
88
|
+
<Sheet.Description>{description}</Sheet.Description>
|
|
89
|
+
{/if}
|
|
90
|
+
</Sheet.Header>
|
|
91
|
+
|
|
92
|
+
<div class="flex-1 overflow-y-auto p-6 text-sm text-foreground space-y-4">
|
|
93
|
+
{#if children}
|
|
94
|
+
{@render children()}
|
|
95
|
+
{/if}
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<Sheet.Footer class="gap-2 px-6 py-4 border-t border-border/60 mt-auto">
|
|
99
|
+
<Button
|
|
100
|
+
type="button"
|
|
101
|
+
variant="outline"
|
|
102
|
+
size="sm"
|
|
103
|
+
disabled={loading || isSubmitting}
|
|
104
|
+
onclick={handleCancel}
|
|
105
|
+
>
|
|
106
|
+
{cancelText}
|
|
107
|
+
</Button>
|
|
108
|
+
<Button
|
|
109
|
+
type="submit"
|
|
110
|
+
size="sm"
|
|
111
|
+
disabled={loading || isSubmitting || disabled}
|
|
112
|
+
class="gap-1.5 min-w-20"
|
|
113
|
+
>
|
|
114
|
+
{#if loading || isSubmitting}
|
|
115
|
+
<Loader2 class="h-4 w-4 animate-spin" />
|
|
116
|
+
{/if}
|
|
117
|
+
{submitText}
|
|
118
|
+
</Button>
|
|
119
|
+
</Sheet.Footer>
|
|
120
|
+
</form>
|
|
37
121
|
</Sheet.Content>
|
|
38
122
|
</Sheet.Root>
|
|
@@ -1,11 +1,23 @@
|
|
|
1
|
-
type
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
open?: boolean;
|
|
4
|
+
title: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
triggerText?: string;
|
|
7
|
+
triggerVariant?: 'default' | 'outline' | 'secondary' | 'ghost' | 'destructive';
|
|
8
|
+
triggerSize?: 'default' | 'sm' | 'lg' | 'icon';
|
|
9
|
+
submitText?: string;
|
|
10
|
+
cancelText?: string;
|
|
6
11
|
side?: 'left' | 'right';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
widthClass?: string;
|
|
13
|
+
isSubmitting?: boolean;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
onsubmit?: () => void | Promise<void>;
|
|
16
|
+
oncancel?: () => void;
|
|
17
|
+
class?: string;
|
|
18
|
+
triggerSnippet?: Snippet;
|
|
19
|
+
children?: Snippet;
|
|
20
|
+
}
|
|
21
|
+
declare const DrawerForm: import("svelte").Component<Props, {}, "open">;
|
|
10
22
|
type DrawerForm = ReturnType<typeof DrawerForm>;
|
|
11
23
|
export default DrawerForm;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
<script lang="ts" generics="T = Record<string, unknown>">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { Plus, Trash2, ArrowUp, ArrowDown, Copy } from '@lucide/svelte';
|
|
4
|
+
import { Button } from './ui/button/index.js';
|
|
5
|
+
import { useTranslation } from '@svadmin/core/i18n';
|
|
6
|
+
import TooltipButton from './TooltipButton.svelte';
|
|
7
|
+
|
|
8
|
+
const i18n = useTranslation();
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
items?: T[];
|
|
12
|
+
label?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
addButtonLabel?: string;
|
|
15
|
+
minItems?: number;
|
|
16
|
+
maxItems?: number;
|
|
17
|
+
defaultItem?: T | (() => T);
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
emptyText?: string;
|
|
20
|
+
onchange?: (items: T[]) => void;
|
|
21
|
+
children?: Snippet<[{
|
|
22
|
+
item: T;
|
|
23
|
+
index: number;
|
|
24
|
+
remove: () => void;
|
|
25
|
+
moveUp: () => void;
|
|
26
|
+
moveDown: () => void;
|
|
27
|
+
duplicate: () => void;
|
|
28
|
+
isFirst: boolean;
|
|
29
|
+
isLast: boolean;
|
|
30
|
+
}]>;
|
|
31
|
+
headerExtra?: Snippet<[{ count: number; add: (item?: T) => void }]>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let {
|
|
35
|
+
items = $bindable([]),
|
|
36
|
+
label,
|
|
37
|
+
description,
|
|
38
|
+
addButtonLabel,
|
|
39
|
+
minItems = 0,
|
|
40
|
+
maxItems = Infinity,
|
|
41
|
+
defaultItem,
|
|
42
|
+
disabled = false,
|
|
43
|
+
emptyText,
|
|
44
|
+
onchange,
|
|
45
|
+
children,
|
|
46
|
+
headerExtra,
|
|
47
|
+
}: Props = $props();
|
|
48
|
+
|
|
49
|
+
function createNewItem(): T {
|
|
50
|
+
if (typeof defaultItem === 'function') {
|
|
51
|
+
return (defaultItem as () => T)();
|
|
52
|
+
}
|
|
53
|
+
if (defaultItem !== undefined) {
|
|
54
|
+
return structuredClone(defaultItem);
|
|
55
|
+
}
|
|
56
|
+
return {} as T;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function add(customItem?: T): void {
|
|
60
|
+
if (disabled || items.length >= maxItems) return;
|
|
61
|
+
const itemToAdd = customItem !== undefined ? structuredClone(customItem) : createNewItem();
|
|
62
|
+
items = [...items, itemToAdd];
|
|
63
|
+
onchange?.(items);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function remove(index: number): void {
|
|
67
|
+
if (disabled || items.length <= minItems || index < 0 || index >= items.length) return;
|
|
68
|
+
const next = [...items];
|
|
69
|
+
next.splice(index, 1);
|
|
70
|
+
items = next;
|
|
71
|
+
onchange?.(items);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function moveUp(index: number): void {
|
|
75
|
+
if (disabled || index <= 0 || index >= items.length) return;
|
|
76
|
+
const next = [...items];
|
|
77
|
+
const temp = next[index - 1];
|
|
78
|
+
next[index - 1] = next[index];
|
|
79
|
+
next[index] = temp;
|
|
80
|
+
items = next;
|
|
81
|
+
onchange?.(items);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function moveDown(index: number): void {
|
|
85
|
+
if (disabled || index < 0 || index >= items.length - 1) return;
|
|
86
|
+
const next = [...items];
|
|
87
|
+
const temp = next[index + 1];
|
|
88
|
+
next[index + 1] = next[index];
|
|
89
|
+
next[index] = temp;
|
|
90
|
+
items = next;
|
|
91
|
+
onchange?.(items);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function duplicate(index: number): void {
|
|
95
|
+
if (disabled || items.length >= maxItems || index < 0 || index >= items.length) return;
|
|
96
|
+
const next = [...items];
|
|
97
|
+
const cloned = structuredClone(next[index]);
|
|
98
|
+
next.splice(index + 1, 0, cloned);
|
|
99
|
+
items = next;
|
|
100
|
+
onchange?.(items);
|
|
101
|
+
}
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<div class="w-full space-y-3" data-testid="dynamic-form-list">
|
|
105
|
+
{#if label || description || headerExtra}
|
|
106
|
+
<div class="flex items-center justify-between gap-2">
|
|
107
|
+
<div>
|
|
108
|
+
{#if label}
|
|
109
|
+
<div class="text-sm font-medium text-foreground">{label}</div>
|
|
110
|
+
{/if}
|
|
111
|
+
{#if description}
|
|
112
|
+
<div class="text-xs text-muted-foreground">{description}</div>
|
|
113
|
+
{/if}
|
|
114
|
+
</div>
|
|
115
|
+
{#if headerExtra}
|
|
116
|
+
{@render headerExtra({ count: items.length, add })}
|
|
117
|
+
{/if}
|
|
118
|
+
</div>
|
|
119
|
+
{/if}
|
|
120
|
+
|
|
121
|
+
<div class="space-y-2">
|
|
122
|
+
{#each items as item, index (index)}
|
|
123
|
+
{@const isFirst = index === 0}
|
|
124
|
+
{@const isLast = index === items.length - 1}
|
|
125
|
+
<div class="group relative rounded-lg border border-border/70 bg-card p-3 shadow-xs transition-colors hover:border-border">
|
|
126
|
+
<div class="flex items-start gap-3">
|
|
127
|
+
<div class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-muted text-[11px] font-medium text-muted-foreground">
|
|
128
|
+
{index + 1}
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<div class="min-w-0 flex-1">
|
|
132
|
+
{#if children}
|
|
133
|
+
{@render children({
|
|
134
|
+
item,
|
|
135
|
+
index,
|
|
136
|
+
remove: () => remove(index),
|
|
137
|
+
moveUp: () => moveUp(index),
|
|
138
|
+
moveDown: () => moveDown(index),
|
|
139
|
+
duplicate: () => duplicate(index),
|
|
140
|
+
isFirst,
|
|
141
|
+
isLast,
|
|
142
|
+
})}
|
|
143
|
+
{:else}
|
|
144
|
+
<div class="text-sm text-foreground/80">
|
|
145
|
+
{JSON.stringify(item)}
|
|
146
|
+
</div>
|
|
147
|
+
{/if}
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<div class="flex shrink-0 items-center gap-0.5 opacity-80 group-hover:opacity-100">
|
|
151
|
+
<TooltipButton
|
|
152
|
+
tooltip={i18n.t('common.moveUp', undefined) ?? '上移'}
|
|
153
|
+
variant="ghost"
|
|
154
|
+
size="icon-sm"
|
|
155
|
+
disabled={disabled || isFirst}
|
|
156
|
+
onclick={() => moveUp(index)}
|
|
157
|
+
>
|
|
158
|
+
<ArrowUp class="h-3.5 w-3.5" />
|
|
159
|
+
</TooltipButton>
|
|
160
|
+
|
|
161
|
+
<TooltipButton
|
|
162
|
+
tooltip={i18n.t('common.moveDown', undefined) ?? '下移'}
|
|
163
|
+
variant="ghost"
|
|
164
|
+
size="icon-sm"
|
|
165
|
+
disabled={disabled || isLast}
|
|
166
|
+
onclick={() => moveDown(index)}
|
|
167
|
+
>
|
|
168
|
+
<ArrowDown class="h-3.5 w-3.5" />
|
|
169
|
+
</TooltipButton>
|
|
170
|
+
|
|
171
|
+
<TooltipButton
|
|
172
|
+
tooltip={i18n.t('common.clone', undefined) ?? '复制'}
|
|
173
|
+
variant="ghost"
|
|
174
|
+
size="icon-sm"
|
|
175
|
+
disabled={disabled || items.length >= maxItems}
|
|
176
|
+
onclick={() => duplicate(index)}
|
|
177
|
+
>
|
|
178
|
+
<Copy class="h-3.5 w-3.5" />
|
|
179
|
+
</TooltipButton>
|
|
180
|
+
|
|
181
|
+
<TooltipButton
|
|
182
|
+
tooltip={i18n.t('common.delete', undefined) ?? '删除'}
|
|
183
|
+
variant="ghost"
|
|
184
|
+
size="icon-sm"
|
|
185
|
+
class="text-muted-foreground hover:text-destructive"
|
|
186
|
+
disabled={disabled || items.length <= minItems}
|
|
187
|
+
onclick={() => remove(index)}
|
|
188
|
+
>
|
|
189
|
+
<Trash2 class="h-3.5 w-3.5" />
|
|
190
|
+
</TooltipButton>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
{:else}
|
|
195
|
+
<div class="rounded-lg border border-dashed border-border/80 p-6 text-center text-xs text-muted-foreground">
|
|
196
|
+
{emptyText ?? (i18n.t('common.noData', undefined) ?? '暂无项目')}
|
|
197
|
+
</div>
|
|
198
|
+
{/each}
|
|
199
|
+
</div>
|
|
200
|
+
|
|
201
|
+
{#if items.length < maxItems}
|
|
202
|
+
<Button
|
|
203
|
+
type="button"
|
|
204
|
+
variant="outline"
|
|
205
|
+
size="sm"
|
|
206
|
+
class="w-full gap-1.5 border-dashed"
|
|
207
|
+
{disabled}
|
|
208
|
+
onclick={() => add()}
|
|
209
|
+
>
|
|
210
|
+
<Plus class="h-4 w-4" />
|
|
211
|
+
{addButtonLabel ?? (i18n.t('common.add', undefined) ?? '添加一项')}
|
|
212
|
+
</Button>
|
|
213
|
+
{/if}
|
|
214
|
+
</div>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
declare function $$render<T = Record<string, unknown>>(): {
|
|
3
|
+
props: {
|
|
4
|
+
items?: T[];
|
|
5
|
+
label?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
addButtonLabel?: string;
|
|
8
|
+
minItems?: number;
|
|
9
|
+
maxItems?: number;
|
|
10
|
+
defaultItem?: T | (() => T);
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
emptyText?: string;
|
|
13
|
+
onchange?: (items: T[]) => void;
|
|
14
|
+
children?: Snippet<[{
|
|
15
|
+
item: T;
|
|
16
|
+
index: number;
|
|
17
|
+
remove: () => void;
|
|
18
|
+
moveUp: () => void;
|
|
19
|
+
moveDown: () => void;
|
|
20
|
+
duplicate: () => void;
|
|
21
|
+
isFirst: boolean;
|
|
22
|
+
isLast: boolean;
|
|
23
|
+
}]>;
|
|
24
|
+
headerExtra?: Snippet<[{
|
|
25
|
+
count: number;
|
|
26
|
+
add: (item?: T) => void;
|
|
27
|
+
}]>;
|
|
28
|
+
};
|
|
29
|
+
exports: {
|
|
30
|
+
add: (customItem?: T) => void;
|
|
31
|
+
remove: (index: number) => void;
|
|
32
|
+
moveUp: (index: number) => void;
|
|
33
|
+
moveDown: (index: number) => void;
|
|
34
|
+
duplicate: (index: number) => void;
|
|
35
|
+
};
|
|
36
|
+
bindings: "items";
|
|
37
|
+
slots: {};
|
|
38
|
+
events: {};
|
|
39
|
+
};
|
|
40
|
+
declare class __sveltets_Render<T = Record<string, unknown>> {
|
|
41
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
42
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
43
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
44
|
+
bindings(): "items";
|
|
45
|
+
exports(): {
|
|
46
|
+
add: (customItem?: T | undefined) => void;
|
|
47
|
+
remove: (index: number) => void;
|
|
48
|
+
moveUp: (index: number) => void;
|
|
49
|
+
moveDown: (index: number) => void;
|
|
50
|
+
duplicate: (index: number) => void;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
interface $$IsomorphicComponent {
|
|
54
|
+
new <T = Record<string, unknown>>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
55
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
56
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
57
|
+
<T = Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
58
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
59
|
+
}
|
|
60
|
+
declare const DynamicFormList: $$IsomorphicComponent;
|
|
61
|
+
type DynamicFormList<T = Record<string, unknown>> = InstanceType<typeof DynamicFormList<T>>;
|
|
62
|
+
export default DynamicFormList;
|