@svadmin/ui 0.60.1 → 0.64.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 (35) hide show
  1. package/dist/components/AutoTable.svelte +8 -0
  2. package/dist/components/AutoTable.svelte.d.ts +7 -1
  3. package/dist/components/Cascader.svelte +205 -0
  4. package/dist/components/Cascader.svelte.d.ts +21 -0
  5. package/dist/components/DynamicFormList.svelte +214 -0
  6. package/dist/components/DynamicFormList.svelte.d.ts +62 -0
  7. package/dist/components/FieldRenderer.svelte +32 -0
  8. package/dist/components/FilterBuilder.svelte +310 -0
  9. package/dist/components/FilterBuilder.svelte.d.ts +23 -0
  10. package/dist/components/InferencerPanel.svelte +66 -6
  11. package/dist/components/Transfer.svelte +352 -0
  12. package/dist/components/Transfer.svelte.d.ts +28 -0
  13. package/dist/components/TreeSelect.svelte +307 -0
  14. package/dist/components/TreeSelect.svelte.d.ts +22 -0
  15. package/dist/components/ui/navigation-menu/index.d.ts +11 -0
  16. package/dist/components/ui/navigation-menu/index.js +13 -0
  17. package/dist/components/ui/navigation-menu/navigation-menu-content.svelte +21 -0
  18. package/dist/components/ui/navigation-menu/navigation-menu-content.svelte.d.ts +4 -0
  19. package/dist/components/ui/navigation-menu/navigation-menu-indicator.svelte +22 -0
  20. package/dist/components/ui/navigation-menu/navigation-menu-indicator.svelte.d.ts +4 -0
  21. package/dist/components/ui/navigation-menu/navigation-menu-item.svelte +17 -0
  22. package/dist/components/ui/navigation-menu/navigation-menu-item.svelte.d.ts +4 -0
  23. package/dist/components/ui/navigation-menu/navigation-menu-link.svelte +20 -0
  24. package/dist/components/ui/navigation-menu/navigation-menu-link.svelte.d.ts +4 -0
  25. package/dist/components/ui/navigation-menu/navigation-menu-list.svelte +17 -0
  26. package/dist/components/ui/navigation-menu/navigation-menu-list.svelte.d.ts +4 -0
  27. package/dist/components/ui/navigation-menu/navigation-menu-trigger.svelte +28 -0
  28. package/dist/components/ui/navigation-menu/navigation-menu-trigger.svelte.d.ts +4 -0
  29. package/dist/components/ui/navigation-menu/navigation-menu-viewport.svelte +22 -0
  30. package/dist/components/ui/navigation-menu/navigation-menu-viewport.svelte.d.ts +4 -0
  31. package/dist/components/ui/navigation-menu/navigation-menu.svelte +24 -0
  32. package/dist/components/ui/navigation-menu/navigation-menu.svelte.d.ts +4 -0
  33. package/dist/index.d.ts +10 -0
  34. package/dist/index.js +7 -0
  35. package/package.json +16 -16
@@ -121,6 +121,8 @@
121
121
  sorters?: Sort[];
122
122
  /** Custom batch actions to render when rows are selected */
123
123
  batchActions?: Snippet<[{ selectedIds: (string | number)[] }]>;
124
+ /** Summary row rendered at table footer */
125
+ summary?: Snippet<[{ data: BaseRecord[]; total: number; visibleColumnsCount: number }]>;
124
126
  }
125
127
 
126
128
  let {
@@ -139,6 +141,7 @@
139
141
  emptyState,
140
142
  expandedRowRender,
141
143
  batchActions,
144
+ summary,
142
145
  pagination: externalPagination,
143
146
  sorters: externalSorters,
144
147
  }: Props = $props();
@@ -1643,6 +1646,11 @@
1643
1646
  </Table.Row>
1644
1647
  {/each}
1645
1648
  </Table.Body>
1649
+ {#if summary}
1650
+ <Table.Footer class="bg-muted/30 font-medium">
1651
+ {@render summary({ data: (query.data?.data as BaseRecord[]) ?? [], total: query.data?.total ?? 0, visibleColumnsCount: columns.filter((c) => c.id != null && isColumnVisible(c.id)).length })}
1652
+ </Table.Footer>
1653
+ {/if}
1646
1654
  </Table.Root>
1647
1655
  </div>
1648
1656
 
@@ -1,4 +1,4 @@
1
- import type { FieldDefinition, Sort } from '@svadmin/core';
1
+ import type { BaseRecord, FieldDefinition, Sort } from '@svadmin/core';
2
2
  import type { Snippet } from 'svelte';
3
3
  interface Props {
4
4
  resourceName: string;
@@ -44,6 +44,12 @@ interface Props {
44
44
  batchActions?: Snippet<[{
45
45
  selectedIds: (string | number)[];
46
46
  }]>;
47
+ /** Summary row rendered at table footer */
48
+ summary?: Snippet<[{
49
+ data: BaseRecord[];
50
+ total: number;
51
+ visibleColumnsCount: number;
52
+ }]>;
47
53
  }
48
54
  declare const AutoTable: import("svelte").Component<Props, {}, "">;
49
55
  type AutoTable = ReturnType<typeof AutoTable>;
@@ -0,0 +1,205 @@
1
+ <script lang="ts">
2
+ import { ChevronRight, Check, X, ChevronsUpDown } from '@lucide/svelte';
3
+ import { cn } from '../utils.js';
4
+ import * as Popover from './ui/popover/index.js';
5
+ import { useTranslation } from '@svadmin/core/i18n';
6
+
7
+ export interface CascaderOption {
8
+ value: string | number;
9
+ label: string;
10
+ children?: CascaderOption[];
11
+ disabled?: boolean;
12
+ isLeaf?: boolean;
13
+ }
14
+
15
+ const i18n = useTranslation();
16
+
17
+ interface Props {
18
+ options?: CascaderOption[];
19
+ value?: (string | number)[];
20
+ separator?: string;
21
+ placeholder?: string;
22
+ changeOnSelect?: boolean;
23
+ disabled?: boolean;
24
+ allowClear?: boolean;
25
+ class?: string;
26
+ onchange?: (value: (string | number)[] | undefined, selectedOptions: CascaderOption[]) => void;
27
+ }
28
+
29
+ let {
30
+ options = [],
31
+ value = $bindable(undefined),
32
+ separator = ' / ',
33
+ placeholder,
34
+ changeOnSelect = false,
35
+ disabled = false,
36
+ allowClear = true,
37
+ class: className,
38
+ onchange,
39
+ }: Props = $props();
40
+
41
+ let open = $state(false);
42
+ // Active selected path while navigating panels
43
+ let activePath = $state<(string | number)[]>([]);
44
+
45
+ $effect(() => {
46
+ if (value && Array.isArray(value)) {
47
+ activePath = [...value];
48
+ } else {
49
+ activePath = [];
50
+ }
51
+ });
52
+
53
+ // Calculate columns based on activePath
54
+ const columns = $derived.by<CascaderOption[][]>(() => {
55
+ const cols: CascaderOption[][] = [options];
56
+ let currentOptions = options;
57
+
58
+ for (const val of activePath) {
59
+ const selectedOpt = currentOptions.find((o) => o.value === val);
60
+ if (selectedOpt && selectedOpt.children && selectedOpt.children.length > 0) {
61
+ cols.push(selectedOpt.children);
62
+ currentOptions = selectedOpt.children;
63
+ } else {
64
+ break;
65
+ }
66
+ }
67
+
68
+ return cols;
69
+ });
70
+
71
+ // Selected options objects corresponding to value
72
+ const selectedOptions = $derived.by<CascaderOption[]>(() => {
73
+ if (!value || !Array.isArray(value) || value.length === 0) return [];
74
+ const opts: CascaderOption[] = [];
75
+ let currentLevel = options;
76
+
77
+ for (const val of value) {
78
+ const found = currentLevel.find((o) => o.value === val);
79
+ if (found) {
80
+ opts.push(found);
81
+ currentLevel = found.children ?? [];
82
+ } else {
83
+ break;
84
+ }
85
+ }
86
+ return opts;
87
+ });
88
+
89
+ const displayLabel = $derived.by(() => {
90
+ if (selectedOptions.length === 0) return '';
91
+ return selectedOptions.map((o) => o.label).join(separator);
92
+ });
93
+
94
+ function handleSelectOption(colIndex: number, option: CascaderOption) {
95
+ if (option.disabled) return;
96
+
97
+ const nextPath = [...activePath.slice(0, colIndex), option.value];
98
+ activePath = nextPath;
99
+
100
+ const hasChildren = Boolean(option.children && option.children.length > 0 && !option.isLeaf);
101
+
102
+ if (!hasChildren || changeOnSelect) {
103
+ value = nextPath;
104
+ const currentSelected = getOptionsForPath(nextPath);
105
+ onchange?.(nextPath, currentSelected);
106
+ if (!hasChildren) {
107
+ open = false;
108
+ }
109
+ }
110
+ }
111
+
112
+ function getOptionsForPath(path: (string | number)[]): CascaderOption[] {
113
+ const result: CascaderOption[] = [];
114
+ let currentLevel = options;
115
+ for (const val of path) {
116
+ const found = currentLevel.find((o) => o.value === val);
117
+ if (found) {
118
+ result.push(found);
119
+ currentLevel = found.children ?? [];
120
+ }
121
+ }
122
+ return result;
123
+ }
124
+
125
+ function clearAll(event?: MouseEvent) {
126
+ event?.stopPropagation();
127
+ value = undefined;
128
+ activePath = [];
129
+ onchange?.(undefined, []);
130
+ }
131
+ </script>
132
+
133
+ <div class={cn('relative w-full', className)} data-testid="cascader">
134
+ <Popover.Root bind:open>
135
+ <Popover.Trigger class="w-full">
136
+ {#snippet child({ props })}
137
+ <button
138
+ type="button"
139
+ {...props}
140
+ {disabled}
141
+ class={cn(
142
+ 'flex min-h-9 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-1.5 text-xs ring-offset-background placeholder:text-muted-foreground focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 text-left',
143
+ open && 'ring-2 ring-ring ring-offset-2'
144
+ )}
145
+ >
146
+ <span class={cn('truncate', displayLabel ? 'text-foreground' : 'text-muted-foreground')}>
147
+ {displayLabel || (placeholder ?? (i18n.t('field.selectPlaceholder', undefined) ?? '请选择...'))}
148
+ </span>
149
+
150
+ <div class="flex items-center gap-1 shrink-0 ml-1 text-muted-foreground">
151
+ {#if allowClear && displayLabel && !disabled}
152
+ <span
153
+ role="button"
154
+ tabindex="0"
155
+ class="hover:text-foreground cursor-pointer p-0.5"
156
+ onclick={clearAll}
157
+ onkeydown={(e) => {
158
+ if (e.key === 'Enter') clearAll(e as never);
159
+ }}
160
+ >
161
+ <X class="h-3.5 w-3.5" />
162
+ </span>
163
+ {/if}
164
+ <ChevronsUpDown class="h-3.5 w-3.5 opacity-50" />
165
+ </div>
166
+ </button>
167
+ {/snippet}
168
+ </Popover.Trigger>
169
+
170
+ <Popover.Content class="w-auto min-w-[180px] p-0 shadow-lg border border-border" align="start">
171
+ <div class="flex divide-x divide-border max-h-64 overflow-x-auto">
172
+ {#each columns as columnOptions, colIndex (colIndex)}
173
+ <div class="w-40 min-w-[140px] max-h-64 overflow-y-auto p-1 space-y-0.5">
174
+ {#each columnOptions as option (option.value)}
175
+ {@const isSelectedInPath = activePath[colIndex] === option.value}
176
+ {@const hasChildren = Boolean(option.children && option.children.length > 0 && !option.isLeaf)}
177
+ <button
178
+ type="button"
179
+ class={cn(
180
+ 'flex w-full items-center justify-between rounded-md px-2.5 py-1.5 text-xs text-left transition-colors',
181
+ isSelectedInPath
182
+ ? 'bg-primary/10 text-primary font-medium'
183
+ : 'hover:bg-muted/60 text-foreground',
184
+ option.disabled && 'opacity-50 cursor-not-allowed pointer-events-none'
185
+ )}
186
+ onclick={() => handleSelectOption(colIndex, option)}
187
+ >
188
+ <span class="truncate flex-1">{option.label}</span>
189
+ {#if hasChildren}
190
+ <ChevronRight class="h-3.5 w-3.5 shrink-0 opacity-60 ml-1" />
191
+ {:else if isSelectedInPath && value && value[colIndex] === option.value}
192
+ <Check class="h-3.5 w-3.5 shrink-0 text-primary ml-1" />
193
+ {/if}
194
+ </button>
195
+ {:else}
196
+ <div class="py-4 text-center text-xs text-muted-foreground">
197
+ {i18n.t('common.noData', undefined) ?? '无数据'}
198
+ </div>
199
+ {/each}
200
+ </div>
201
+ {/each}
202
+ </div>
203
+ </Popover.Content>
204
+ </Popover.Root>
205
+ </div>
@@ -0,0 +1,21 @@
1
+ export interface CascaderOption {
2
+ value: string | number;
3
+ label: string;
4
+ children?: CascaderOption[];
5
+ disabled?: boolean;
6
+ isLeaf?: boolean;
7
+ }
8
+ interface Props {
9
+ options?: CascaderOption[];
10
+ value?: (string | number)[];
11
+ separator?: string;
12
+ placeholder?: string;
13
+ changeOnSelect?: boolean;
14
+ disabled?: boolean;
15
+ allowClear?: boolean;
16
+ class?: string;
17
+ onchange?: (value: (string | number)[] | undefined, selectedOptions: CascaderOption[]) => void;
18
+ }
19
+ declare const Cascader: import("svelte").Component<Props, {}, "value">;
20
+ type Cascader = ReturnType<typeof Cascader>;
21
+ export default Cascader;
@@ -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;
@@ -16,6 +16,9 @@
16
16
  import { Button } from './ui/button/index.js';
17
17
  import TooltipButton from './TooltipButton.svelte';
18
18
  import ComboboxField from './ComboboxField.svelte';
19
+ import TreeSelect, { type TreeSelectOption } from './TreeSelect.svelte';
20
+ import Cascader, { type CascaderOption } from './Cascader.svelte';
21
+ import Transfer, { type TransferItem } from './Transfer.svelte';
19
22
  import ArrayField from './fields/ArrayField.svelte';
20
23
  import MediaThumbnail from './content/MediaThumbnail.svelte';
21
24
  import { Plus, X } from '@lucide/svelte';
@@ -263,6 +266,35 @@
263
266
  class="resize-y"
264
267
  />
265
268
 
269
+ {:else if field.type === 'tree-select' || field.type === 'treeselect'}
270
+ <TreeSelect
271
+ options={(field.treeOptions ?? field.options ?? []) as TreeSelectOption[]}
272
+ value={value as string | number | (string | number)[] | undefined}
273
+ multiple={field.multiple}
274
+ disabled={disabled}
275
+ placeholder={i18n.t('field.selectPlaceholder')}
276
+ onchange={(v) => onchange(v)}
277
+ />
278
+
279
+ {:else if field.type === 'cascader'}
280
+ <Cascader
281
+ options={(field.cascaderOptions ?? field.options ?? []) as CascaderOption[]}
282
+ value={value as (string | number)[]}
283
+ separator={field.separator}
284
+ changeOnSelect={field.changeOnSelect}
285
+ disabled={disabled}
286
+ placeholder={i18n.t('field.selectPlaceholder')}
287
+ onchange={(v) => onchange(v)}
288
+ />
289
+
290
+ {:else if field.type === 'transfer'}
291
+ <Transfer
292
+ dataSource={(field.transferData ?? field.options?.map((o: { label: string; value: string | number }) => ({ key: o.value, title: o.label })) ?? []) as TransferItem[]}
293
+ targetKeys={(value as (string | number)[]) ?? []}
294
+ disabled={disabled}
295
+ onchange={(next) => onchange(next)}
296
+ />
297
+
266
298
  {:else if field.type === 'relation' && field.resource}
267
299
  <input type="hidden" name={field.key} value={value == null ? '' : String(value)} />
268
300
  <ComboboxField