@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.
Files changed (104) hide show
  1. package/dist/components/ActivityFeed.svelte +135 -0
  2. package/dist/components/ActivityFeed.svelte.d.ts +21 -0
  3. package/dist/components/ApprovalActionCard.svelte +248 -0
  4. package/dist/components/ApprovalActionCard.svelte.d.ts +22 -0
  5. package/dist/components/AutoTable.svelte +8 -0
  6. package/dist/components/AutoTable.svelte.d.ts +7 -1
  7. package/dist/components/CanvasAnnotation.svelte +389 -0
  8. package/dist/components/CanvasAnnotation.svelte.d.ts +17 -0
  9. package/dist/components/Cascader.svelte +205 -0
  10. package/dist/components/Cascader.svelte.d.ts +21 -0
  11. package/dist/components/ColumnHeaderFilter.svelte +178 -0
  12. package/dist/components/ColumnHeaderFilter.svelte.d.ts +12 -0
  13. package/dist/components/ColumnSettings.svelte +198 -0
  14. package/dist/components/ColumnSettings.svelte.d.ts +19 -0
  15. package/dist/components/DecisionTable.svelte +240 -0
  16. package/dist/components/DecisionTable.svelte.d.ts +22 -0
  17. package/dist/components/DraggableRowTable.svelte +117 -0
  18. package/dist/components/DraggableRowTable.svelte.d.ts +15 -0
  19. package/dist/components/DrawerForm.svelte +110 -26
  20. package/dist/components/DrawerForm.svelte.d.ts +20 -8
  21. package/dist/components/DynamicFormList.svelte +214 -0
  22. package/dist/components/DynamicFormList.svelte.d.ts +62 -0
  23. package/dist/components/EditableTable.svelte +195 -0
  24. package/dist/components/EditableTable.svelte.d.ts +25 -0
  25. package/dist/components/FieldRenderer.svelte +32 -0
  26. package/dist/components/FilterBuilder.svelte +310 -0
  27. package/dist/components/FilterBuilder.svelte.d.ts +23 -0
  28. package/dist/components/GanttChart.svelte +144 -0
  29. package/dist/components/GanttChart.svelte.d.ts +20 -0
  30. package/dist/components/ImageCropper.svelte +122 -0
  31. package/dist/components/ImageCropper.svelte.d.ts +12 -0
  32. package/dist/components/ImportWizard.svelte +336 -0
  33. package/dist/components/ImportWizard.svelte.d.ts +12 -0
  34. package/dist/components/JsonSchemaForm.svelte +149 -0
  35. package/dist/components/JsonSchemaForm.svelte.d.ts +10 -0
  36. package/dist/components/KanbanBoard.svelte +212 -0
  37. package/dist/components/KanbanBoard.svelte.d.ts +30 -0
  38. package/dist/components/MasterDetailView.svelte +134 -0
  39. package/dist/components/MasterDetailView.svelte.d.ts +16 -0
  40. package/dist/components/MediaLibraryModal.svelte +195 -0
  41. package/dist/components/MediaLibraryModal.svelte.d.ts +22 -0
  42. package/dist/components/MentionsInput.svelte +174 -0
  43. package/dist/components/MentionsInput.svelte.d.ts +21 -0
  44. package/dist/components/ModalForm.svelte +113 -28
  45. package/dist/components/ModalForm.svelte.d.ts +20 -8
  46. package/dist/components/MultiTabKeepAlive.svelte +143 -0
  47. package/dist/components/MultiTabKeepAlive.svelte.d.ts +21 -0
  48. package/dist/components/OfflineSyncBanner.svelte +172 -0
  49. package/dist/components/OfflineSyncBanner.svelte.d.ts +19 -0
  50. package/dist/components/PdfDocumentViewer.svelte +255 -0
  51. package/dist/components/PdfDocumentViewer.svelte.d.ts +22 -0
  52. package/dist/components/PivotTable.svelte +140 -0
  53. package/dist/components/PivotTable.svelte.d.ts +16 -0
  54. package/dist/components/PresenceAvatarGroup.svelte +89 -0
  55. package/dist/components/PresenceAvatarGroup.svelte.d.ts +15 -0
  56. package/dist/components/PrintableBill.svelte +149 -0
  57. package/dist/components/PrintableBill.svelte.d.ts +24 -0
  58. package/dist/components/SensitiveDataMask.svelte +125 -0
  59. package/dist/components/SensitiveDataMask.svelte.d.ts +13 -0
  60. package/dist/components/SignaturePad.svelte +264 -0
  61. package/dist/components/SignaturePad.svelte.d.ts +17 -0
  62. package/dist/components/SplitPaneLayout.svelte +113 -0
  63. package/dist/components/SplitPaneLayout.svelte.d.ts +13 -0
  64. package/dist/components/SpreadsheetView.svelte +363 -0
  65. package/dist/components/SpreadsheetView.svelte.d.ts +18 -0
  66. package/dist/components/StepForm.svelte +180 -0
  67. package/dist/components/StepForm.svelte.d.ts +23 -0
  68. package/dist/components/TableSummary.svelte +110 -0
  69. package/dist/components/TableSummary.svelte.d.ts +20 -0
  70. package/dist/components/Transfer.svelte +352 -0
  71. package/dist/components/Transfer.svelte.d.ts +28 -0
  72. package/dist/components/TreeSelect.svelte +307 -0
  73. package/dist/components/TreeSelect.svelte.d.ts +22 -0
  74. package/dist/components/TreeTable.svelte +237 -0
  75. package/dist/components/TreeTable.svelte.d.ts +28 -0
  76. package/dist/components/UnsavedChangesPrompt.svelte +44 -0
  77. package/dist/components/UnsavedChangesPrompt.svelte.d.ts +7 -0
  78. package/dist/components/VersionDiffViewer.svelte +196 -0
  79. package/dist/components/VersionDiffViewer.svelte.d.ts +12 -0
  80. package/dist/components/VirtualTable.svelte +86 -54
  81. package/dist/components/VirtualTable.svelte.d.ts +14 -21
  82. package/dist/components/Watermark.svelte +166 -0
  83. package/dist/components/Watermark.svelte.d.ts +26 -0
  84. package/dist/components/ui/navigation-menu/index.d.ts +11 -0
  85. package/dist/components/ui/navigation-menu/index.js +13 -0
  86. package/dist/components/ui/navigation-menu/navigation-menu-content.svelte +21 -0
  87. package/dist/components/ui/navigation-menu/navigation-menu-content.svelte.d.ts +4 -0
  88. package/dist/components/ui/navigation-menu/navigation-menu-indicator.svelte +22 -0
  89. package/dist/components/ui/navigation-menu/navigation-menu-indicator.svelte.d.ts +4 -0
  90. package/dist/components/ui/navigation-menu/navigation-menu-item.svelte +17 -0
  91. package/dist/components/ui/navigation-menu/navigation-menu-item.svelte.d.ts +4 -0
  92. package/dist/components/ui/navigation-menu/navigation-menu-link.svelte +20 -0
  93. package/dist/components/ui/navigation-menu/navigation-menu-link.svelte.d.ts +4 -0
  94. package/dist/components/ui/navigation-menu/navigation-menu-list.svelte +17 -0
  95. package/dist/components/ui/navigation-menu/navigation-menu-list.svelte.d.ts +4 -0
  96. package/dist/components/ui/navigation-menu/navigation-menu-trigger.svelte +28 -0
  97. package/dist/components/ui/navigation-menu/navigation-menu-trigger.svelte.d.ts +4 -0
  98. package/dist/components/ui/navigation-menu/navigation-menu-viewport.svelte +22 -0
  99. package/dist/components/ui/navigation-menu/navigation-menu-viewport.svelte.d.ts +4 -0
  100. package/dist/components/ui/navigation-menu/navigation-menu.svelte +24 -0
  101. package/dist/components/ui/navigation-menu/navigation-menu.svelte.d.ts +4 -0
  102. package/dist/index.d.ts +68 -3
  103. package/dist/index.js +42 -3
  104. 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 { getResource } from '@svadmin/core';
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 AutoForm from './AutoForm.svelte';
7
-
8
- const i18n = useTranslation();
4
+ import { Button } from './ui/button/index.js';
5
+ import { Loader2 } from '@lucide/svelte';
6
+ import { cn } from '../utils.js';
9
7
 
10
- let { resourceName, mode = 'create', id, open = $bindable(false), side = 'right', onSuccess } = $props<{
11
- resourceName: string;
12
- mode?: 'create' | 'edit';
13
- id?: string | number;
14
- open: boolean;
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
- onSuccess?: () => void;
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
- const resource = $derived(getResource(resourceName));
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
- function handleSuccess() {
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
- <Sheet.Root bind:open {side}>
28
- <Sheet.Content>
29
- <Sheet.Header>
30
- <Sheet.Title>
31
- {mode === 'create' ? `${i18n.t('common.create')}${resource.label}` : `${i18n.t('common.edit')}${resource.label}`}
32
- </Sheet.Title>
33
- </Sheet.Header>
34
- <div class="flex-1 overflow-y-auto">
35
- <AutoForm {resourceName} {mode} {id} showHeader={false} onSuccess={handleSuccess} />
36
- </div>
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 $$ComponentProps = {
2
- resourceName: string;
3
- mode?: 'create' | 'edit';
4
- id?: string | number;
5
- open: boolean;
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
- onSuccess?: () => void;
8
- };
9
- declare const DrawerForm: import("svelte").Component<$$ComponentProps, {}, "open">;
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;