@svadmin/ui 0.30.2 → 0.31.1
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/AdminApp.svelte +12 -2
- package/dist/components/AnimatedCounter.svelte +8 -2
- package/dist/components/AnomalyBadge.svelte +5 -1
- package/dist/components/AutoTable.svelte +2 -2
- package/dist/components/Breadcrumbs.svelte +3 -3
- package/dist/components/ChatDialog.svelte +39 -1
- package/dist/components/CommandPalette.svelte +2 -2
- package/dist/components/DraggableHeader.svelte +1 -2
- package/dist/components/Header.svelte +1 -1
- package/dist/components/Layout.svelte +10 -3
- package/dist/components/LiveIndicator.svelte +7 -1
- package/dist/components/SmartSuggest.svelte +12 -2
- package/dist/components/StepsForm.svelte +22 -26
- package/dist/components/VirtualTable.svelte +8 -6
- package/dist/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +11 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu.svelte +4 -0
- package/dist/router-state.svelte.js +5 -3
- package/package.json +14 -12
- package/src/index.ts +0 -155
|
@@ -87,10 +87,19 @@
|
|
|
87
87
|
const resolvedRouter = $derived(routerProvider ?? createHashRouterProvider());
|
|
88
88
|
const resolvedRouteMode = $derived(routeMode ?? (routerProvider ? 'auto' : 'hash'));
|
|
89
89
|
|
|
90
|
+
// Set up initial context synchronously so children can access resources immediately during first render
|
|
91
|
+
setDataProvider(dataProvider);
|
|
92
|
+
setAuthProvider(authProvider);
|
|
93
|
+
setResources(resources);
|
|
94
|
+
setRouterProvider(resolvedRouter);
|
|
95
|
+
if (locale) setLocale(locale);
|
|
96
|
+
if (userThemeConfig) configureTheme(userThemeConfig);
|
|
97
|
+
if (defaultTheme) setTheme(defaultTheme);
|
|
98
|
+
|
|
90
99
|
// Set up context — use $effect so prop changes are tracked
|
|
91
100
|
$effect.pre(() => {
|
|
92
101
|
setDataProvider(dataProvider);
|
|
93
|
-
|
|
102
|
+
setAuthProvider(authProvider);
|
|
94
103
|
setResources(resources);
|
|
95
104
|
setRouterProvider(resolvedRouter);
|
|
96
105
|
if (locale) setLocale(locale);
|
|
@@ -104,7 +113,8 @@
|
|
|
104
113
|
},
|
|
105
114
|
});
|
|
106
115
|
|
|
107
|
-
// Initialize router with provider
|
|
116
|
+
// Initialize router with provider synchronously
|
|
117
|
+
initRouter(resolvedRouter);
|
|
108
118
|
$effect.pre(() => {
|
|
109
119
|
initRouter(resolvedRouter);
|
|
110
120
|
});
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
let displayValue = $state('0');
|
|
16
16
|
let prevTarget = 0;
|
|
17
|
+
let currentRaf: number | null = null;
|
|
17
18
|
|
|
18
19
|
$effect(() => {
|
|
19
20
|
const target = typeof value === 'number' ? value : parseInt(value, 10);
|
|
@@ -35,11 +36,16 @@
|
|
|
35
36
|
displayValue = current.toLocaleString();
|
|
36
37
|
|
|
37
38
|
if (progress < 1) {
|
|
38
|
-
requestAnimationFrame(tick);
|
|
39
|
+
currentRaf = requestAnimationFrame(tick);
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
if (currentRaf) cancelAnimationFrame(currentRaf);
|
|
44
|
+
currentRaf = requestAnimationFrame(tick);
|
|
45
|
+
|
|
46
|
+
return () => {
|
|
47
|
+
if (currentRaf) cancelAnimationFrame(currentRaf);
|
|
48
|
+
};
|
|
43
49
|
});
|
|
44
50
|
</script>
|
|
45
51
|
|
|
@@ -27,7 +27,11 @@
|
|
|
27
27
|
}: Props = $props();
|
|
28
28
|
|
|
29
29
|
// Calculate percentage difference
|
|
30
|
-
const diff = $derived(
|
|
30
|
+
const diff = $derived(
|
|
31
|
+
baseline === 0
|
|
32
|
+
? (value === 0 ? 0 : (value > 0 ? Infinity : -Infinity))
|
|
33
|
+
: (value - baseline) / Math.abs(baseline)
|
|
34
|
+
);
|
|
31
35
|
const isAnomaly = $derived(Math.abs(diff) >= threshold);
|
|
32
36
|
|
|
33
37
|
// Determine sentiment (good, bad, neutral)
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
type ColumnVisibilityState,
|
|
10
10
|
type ExpandedState,
|
|
11
11
|
} from '@tanstack/svelte-table';
|
|
12
|
-
import {
|
|
12
|
+
import { getCoreRowModel } from '@tanstack/table-core';
|
|
13
13
|
|
|
14
14
|
import { useList, useDelete, getResource } from '@svadmin/core';
|
|
15
15
|
import type { Pagination as PaginationState, Sort, Filter, FieldDefinition } from '@svadmin/core';
|
|
@@ -265,7 +265,7 @@
|
|
|
265
265
|
const tbl = createTable({
|
|
266
266
|
get data() { return query.data?.data ?? []; },
|
|
267
267
|
get columns() { return columns; },
|
|
268
|
-
getCoreRowModel:
|
|
268
|
+
getCoreRowModel: getCoreRowModel(),
|
|
269
269
|
manualPagination: true,
|
|
270
270
|
manualSorting: true,
|
|
271
271
|
getRowId: (row: any) => String(row[primaryKey]),
|
|
@@ -26,9 +26,6 @@
|
|
|
26
26
|
if (resourceNames.includes(seg)) {
|
|
27
27
|
const res = resources.find(r => r.name === seg)!;
|
|
28
28
|
result.push({ label: res.label, href: formatLink(currentPath) });
|
|
29
|
-
} else if (i > 0 && resourceNames.includes(segments[i-1])) {
|
|
30
|
-
// This is a parent ID (e.g. /teams/123/users)
|
|
31
|
-
result.push({ label: `#${seg}`, href: formatLink(currentPath) });
|
|
32
29
|
} else if (seg === 'create') {
|
|
33
30
|
result.push({ label: t('common.create') ?? 'Create', href: formatLink(currentPath) });
|
|
34
31
|
} else if (['edit', 'show', 'clone'].includes(seg) && segments[i+1]) {
|
|
@@ -39,6 +36,9 @@
|
|
|
39
36
|
currentPath += `/${id}`; // advance path by id
|
|
40
37
|
result.push({ label: `${actionLabel} #${id}`, href: formatLink(currentPath) });
|
|
41
38
|
i++; // skip next segment since we consumed the id
|
|
39
|
+
} else if (i > 0 && resourceNames.includes(segments[i-1])) {
|
|
40
|
+
// This is a parent ID (e.g. /teams/123/users) and we are not an action
|
|
41
|
+
result.push({ label: `#${seg}`, href: formatLink(currentPath) });
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -53,6 +53,19 @@
|
|
|
53
53
|
});
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
+
// ─── Listen for cross-component triggers ─────────────────────
|
|
57
|
+
$effect(() => {
|
|
58
|
+
if (typeof window === 'undefined') return;
|
|
59
|
+
const handler = (e: CustomEvent<string>) => {
|
|
60
|
+
open = true;
|
|
61
|
+
minimized = false;
|
|
62
|
+
inputValue = e.detail;
|
|
63
|
+
sendMessage();
|
|
64
|
+
};
|
|
65
|
+
window.addEventListener('svadmin:ask-ai', handler as EventListener);
|
|
66
|
+
return () => window.removeEventListener('svadmin:ask-ai', handler as EventListener);
|
|
67
|
+
});
|
|
68
|
+
|
|
56
69
|
// ─── Restore persisted history on mount ─────────────────────
|
|
57
70
|
$effect(() => {
|
|
58
71
|
if (initialized) return;
|
|
@@ -142,6 +155,12 @@
|
|
|
142
155
|
|
|
143
156
|
messages = [...messages, userMsg];
|
|
144
157
|
inputValue = '';
|
|
158
|
+
doSend();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function doSend() {
|
|
162
|
+
if (isStreaming || (!provider && !agent)) return;
|
|
163
|
+
|
|
145
164
|
scrollToBottom();
|
|
146
165
|
|
|
147
166
|
// Create placeholder assistant message
|
|
@@ -200,6 +219,16 @@
|
|
|
200
219
|
// Register in the core approval system
|
|
201
220
|
const reqId = event.id;
|
|
202
221
|
pendingApprovalIds = [...pendingApprovalIds, reqId];
|
|
222
|
+
|
|
223
|
+
registerApproval(reqId, (approved) => {
|
|
224
|
+
messages = [...messages, {
|
|
225
|
+
id: genId(),
|
|
226
|
+
role: 'user',
|
|
227
|
+
content: approved ? `User approved execution of tool '${event.tool}'` : `User rejected execution of tool '${event.tool}'`,
|
|
228
|
+
timestamp: Date.now()
|
|
229
|
+
}];
|
|
230
|
+
doSend();
|
|
231
|
+
});
|
|
203
232
|
|
|
204
233
|
collectedActions.push(
|
|
205
234
|
{
|
|
@@ -319,9 +348,18 @@
|
|
|
319
348
|
}
|
|
320
349
|
}
|
|
321
350
|
|
|
351
|
+
function escapeHtml(unsafe: string) {
|
|
352
|
+
return unsafe
|
|
353
|
+
.replace(/&/g, "&")
|
|
354
|
+
.replace(/</g, "<")
|
|
355
|
+
.replace(/>/g, ">")
|
|
356
|
+
.replace(/"/g, """)
|
|
357
|
+
.replace(/'/g, "'");
|
|
358
|
+
}
|
|
359
|
+
|
|
322
360
|
/** Simple markdown→html: bold, italic, code blocks, inline code, line breaks */
|
|
323
361
|
function renderMarkdown(text: string): string {
|
|
324
|
-
return text
|
|
362
|
+
return escapeHtml(text)
|
|
325
363
|
.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre class="chat-code-block"><code>$2</code></pre>')
|
|
326
364
|
.replace(/`([^`]+)`/g, '<code class="chat-inline-code">$1</code>')
|
|
327
365
|
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
{#if hasAI && searchValue.trim()}
|
|
72
72
|
<Command.Group heading="AI">
|
|
73
73
|
<Command.Item
|
|
74
|
-
value="ask-ai-
|
|
74
|
+
value={"ask-ai-" + searchValue}
|
|
75
75
|
onSelect={askAI}
|
|
76
76
|
class={itemClass}
|
|
77
77
|
>
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
<Command.Group heading={t('common.actions')}>
|
|
112
112
|
{#each resources as r}
|
|
113
113
|
<Command.Item
|
|
114
|
-
value="create-
|
|
114
|
+
value={"create-" + r.name}
|
|
115
115
|
onSelect={() => act(() => navigate(`/${r.name}/create`))}
|
|
116
116
|
class={itemClass}
|
|
117
117
|
>
|
|
@@ -29,10 +29,9 @@
|
|
|
29
29
|
let dragIndex = $state<number | null>(null);
|
|
30
30
|
let dropIndex = $state<number | null>(null);
|
|
31
31
|
|
|
32
|
-
// Restore saved order from localStorage (once on mount)
|
|
33
32
|
let _restored = false;
|
|
34
33
|
$effect(() => {
|
|
35
|
-
if (_restored) return;
|
|
34
|
+
if (_restored || columns.length === 0) return;
|
|
36
35
|
const storageKey = `svadmin-colorder-${resourceName}`;
|
|
37
36
|
if (typeof window === 'undefined') return;
|
|
38
37
|
_restored = true;
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
}>();
|
|
28
28
|
|
|
29
29
|
// Retrieve optional component overrides from registry
|
|
30
|
-
const registry = getComponentRegistry();
|
|
30
|
+
const registry = getComponentRegistry() || {} as any;
|
|
31
31
|
const CustomBreadcrumbs = registry.Breadcrumbs;
|
|
32
32
|
const CustomThemeToggle = registry.ThemeToggle;
|
|
33
33
|
const CustomUserMenu = registry.UserMenu;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { getAuthProvider } from '@svadmin/core';
|
|
13
13
|
import type { Identity, MenuItem } from '@svadmin/core';
|
|
14
14
|
import { navigate } from '@svadmin/core/router';
|
|
15
|
-
import {
|
|
15
|
+
import { getPath } from '../router-state.svelte.js';
|
|
16
16
|
import { Skeleton } from './ui/skeleton/index.js';
|
|
17
17
|
import { Button } from './ui/button/index.js';
|
|
18
18
|
import * as Sheet from './ui/sheet/index.js';
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
|
|
151
151
|
<!-- Content area -->
|
|
152
152
|
<main class="flex-1 overflow-y-auto p-6 md:p-8">
|
|
153
|
-
{#key
|
|
153
|
+
{#key getPath()}
|
|
154
154
|
<div in:fly={{ x: 20, duration: 150 }} out:fade={{ duration: 80 }}>
|
|
155
155
|
{@render children()}
|
|
156
156
|
</div>
|
|
@@ -158,7 +158,14 @@
|
|
|
158
158
|
</main>
|
|
159
159
|
</div>
|
|
160
160
|
</div>
|
|
161
|
-
<CommandPalette
|
|
161
|
+
<CommandPalette
|
|
162
|
+
bind:open={commandOpen}
|
|
163
|
+
onAskAI={(q) => {
|
|
164
|
+
if (typeof window !== 'undefined') {
|
|
165
|
+
window.dispatchEvent(new CustomEvent('svadmin:ask-ai', { detail: q }));
|
|
166
|
+
}
|
|
167
|
+
}}
|
|
168
|
+
/>
|
|
162
169
|
<KeyboardShortcuts bind:open={shortcutsOpen} />
|
|
163
170
|
<ChatDialog />
|
|
164
171
|
<Toast />
|
|
@@ -23,9 +23,15 @@
|
|
|
23
23
|
type StatusKey = 'connected' | 'connecting' | 'disconnected';
|
|
24
24
|
const cfg = $derived(statusConfigs[status as StatusKey]);
|
|
25
25
|
|
|
26
|
+
let now = $state(Date.now());
|
|
27
|
+
$effect(() => {
|
|
28
|
+
const itv = setInterval(() => { now = Date.now(); }, 1000);
|
|
29
|
+
return () => clearInterval(itv);
|
|
30
|
+
});
|
|
31
|
+
|
|
26
32
|
const timeSinceEvent = $derived(
|
|
27
33
|
lastEvent?.timestamp
|
|
28
|
-
? Math.floor((
|
|
34
|
+
? Math.floor((now - lastEvent.timestamp) / 1000)
|
|
29
35
|
: null
|
|
30
36
|
);
|
|
31
37
|
</script>
|
|
@@ -49,10 +49,20 @@
|
|
|
49
49
|
return '';
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
+
import { getChatProvider } from '@svadmin/core';
|
|
53
|
+
|
|
52
54
|
const suffixText = $derived(ghostText ? ghostText.slice(value.length) : '');
|
|
53
55
|
|
|
56
|
+
const resolvedProvider = $derived(provider ?? (() => {
|
|
57
|
+
try {
|
|
58
|
+
return getChatProvider();
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
})());
|
|
63
|
+
|
|
54
64
|
async function predict() {
|
|
55
|
-
if (!
|
|
65
|
+
if (!resolvedProvider || !value.trim()) {
|
|
56
66
|
suggestion = '';
|
|
57
67
|
return;
|
|
58
68
|
}
|
|
@@ -70,7 +80,7 @@ Provide ONLY the most likely completion of the exact phrase the user is typing.
|
|
|
70
80
|
Include the user's input in your returned string.
|
|
71
81
|
Do not include any other text, Markdown formatting, quotes, or explanations.`;
|
|
72
82
|
|
|
73
|
-
const result =
|
|
83
|
+
const result = resolvedProvider.sendMessage(
|
|
74
84
|
[{ id: 'prompt', role: 'user', content: prompt, timestamp: Date.now() }],
|
|
75
85
|
{ signal: abortController.signal }
|
|
76
86
|
);
|
|
@@ -67,12 +67,12 @@
|
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
const totalSteps = $derived(steps.length);
|
|
70
|
-
const progressValue = $derived(((form.currentStep + 1) / totalSteps) * 100);
|
|
71
|
-
const isLastStep = $derived(form.currentStep >= totalSteps - 1);
|
|
70
|
+
const progressValue = $derived(((form.steps.currentStep + 1) / totalSteps) * 100);
|
|
71
|
+
const isLastStep = $derived(form.steps.currentStep >= totalSteps - 1);
|
|
72
72
|
|
|
73
73
|
// Fields for the current step
|
|
74
74
|
const currentFields = $derived(
|
|
75
|
-
steps[form.currentStep]?.fields
|
|
75
|
+
steps[form.steps.currentStep]?.fields
|
|
76
76
|
.map(key => resource.fields.find(f => f.key === key))
|
|
77
77
|
.filter((f): f is FieldDefinition => f != null && f.key !== primaryKey) ?? []
|
|
78
78
|
);
|
|
@@ -81,15 +81,11 @@
|
|
|
81
81
|
function validateCurrentStep(): boolean {
|
|
82
82
|
let valid = true;
|
|
83
83
|
form.clearErrors();
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
} else if (field.validate) {
|
|
90
|
-
const msg = field.validate(value);
|
|
91
|
-
if (msg) {
|
|
92
|
-
form.setFieldError(field.key, msg);
|
|
84
|
+
const errors = validator(form.values);
|
|
85
|
+
if (errors) {
|
|
86
|
+
for (const field of currentFields) {
|
|
87
|
+
if (errors[field.key]) {
|
|
88
|
+
form.setFieldError(field.key, errors[field.key]);
|
|
93
89
|
valid = false;
|
|
94
90
|
}
|
|
95
91
|
}
|
|
@@ -101,7 +97,7 @@
|
|
|
101
97
|
if (!validateCurrentStep()) return;
|
|
102
98
|
|
|
103
99
|
if (!isLastStep) {
|
|
104
|
-
form.
|
|
100
|
+
form.steps.nextStep();
|
|
105
101
|
return;
|
|
106
102
|
}
|
|
107
103
|
|
|
@@ -118,17 +114,17 @@
|
|
|
118
114
|
<Button
|
|
119
115
|
variant="ghost"
|
|
120
116
|
type="button"
|
|
121
|
-
class="flex items-center gap-2 transition-colors h-auto py-1 px-2 {i === form.currentStep ? 'text-primary font-semibold' : 'text-muted-foreground'}"
|
|
122
|
-
onclick={() => form.gotoStep(i)}
|
|
117
|
+
class="flex items-center gap-2 transition-colors h-auto py-1 px-2 {i === form.steps.currentStep ? 'text-primary font-semibold' : 'text-muted-foreground'}"
|
|
118
|
+
onclick={() => form.steps.gotoStep(i)}
|
|
123
119
|
>
|
|
124
120
|
<span
|
|
125
121
|
class="flex h-7 w-7 items-center justify-center rounded-full text-xs font-medium transition-colors"
|
|
126
|
-
class:bg-primary={i <= form.currentStep}
|
|
127
|
-
class:text-primary-foreground={i <= form.currentStep}
|
|
128
|
-
class:bg-muted={i > form.currentStep}
|
|
129
|
-
class:text-muted-foreground={i > form.currentStep}
|
|
122
|
+
class:bg-primary={i <= form.steps.currentStep}
|
|
123
|
+
class:text-primary-foreground={i <= form.steps.currentStep}
|
|
124
|
+
class:bg-muted={i > form.steps.currentStep}
|
|
125
|
+
class:text-muted-foreground={i > form.steps.currentStep}
|
|
130
126
|
>
|
|
131
|
-
{#if i < form.currentStep}
|
|
127
|
+
{#if i < form.steps.currentStep}
|
|
132
128
|
<Check class="h-3.5 w-3.5" />
|
|
133
129
|
{:else}
|
|
134
130
|
{i + 1}
|
|
@@ -137,7 +133,7 @@
|
|
|
137
133
|
<span class="hidden sm:inline">{step.title}</span>
|
|
138
134
|
</Button>
|
|
139
135
|
{#if i < steps.length - 1}
|
|
140
|
-
<div class="flex-1 mx-2 h-px bg-border" class:bg-primary={i < form.currentStep}></div>
|
|
136
|
+
<div class="flex-1 mx-2 h-px bg-border" class:bg-primary={i < form.steps.currentStep}></div>
|
|
141
137
|
{/if}
|
|
142
138
|
{/each}
|
|
143
139
|
</div>
|
|
@@ -147,13 +143,13 @@
|
|
|
147
143
|
<!-- Step content -->
|
|
148
144
|
<Card.Root>
|
|
149
145
|
<Card.CardHeader>
|
|
150
|
-
<Card.CardTitle>{steps[form.currentStep]?.title ?? ''}</Card.CardTitle>
|
|
146
|
+
<Card.CardTitle>{steps[form.steps.currentStep]?.title ?? ''}</Card.CardTitle>
|
|
151
147
|
<Badge variant="outline" class="w-fit">
|
|
152
|
-
{t('common.step')} {form.currentStep + 1} / {totalSteps}
|
|
148
|
+
{t('common.step')} {form.steps.currentStep + 1} / {totalSteps}
|
|
153
149
|
</Badge>
|
|
154
150
|
</Card.CardHeader>
|
|
155
151
|
<Card.CardContent>
|
|
156
|
-
{#key form.currentStep}
|
|
152
|
+
{#key form.steps.currentStep}
|
|
157
153
|
<form class="space-y-5" onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} in:fly={{ x: 30, duration: 250 }}>
|
|
158
154
|
{#each currentFields as field (field.key)}
|
|
159
155
|
<FieldRenderer
|
|
@@ -170,10 +166,10 @@
|
|
|
170
166
|
<Button
|
|
171
167
|
type="button"
|
|
172
168
|
variant="outline"
|
|
173
|
-
onclick={() => form.currentStep === 0 ? navigate(`/${resourceName}`) : form.
|
|
169
|
+
onclick={() => form.steps.currentStep === 0 ? navigate(`/${resourceName}`) : form.steps.prevStep()}
|
|
174
170
|
>
|
|
175
171
|
<ArrowLeft class="h-4 w-4" data-icon="inline-start" />
|
|
176
|
-
{form.currentStep === 0 ? t('common.cancel') : t('common.back')}
|
|
172
|
+
{form.steps.currentStep === 0 ? t('common.cancel') : t('common.back')}
|
|
177
173
|
</Button>
|
|
178
174
|
|
|
179
175
|
<Button type="submit" disabled={form.submitting}>
|
|
@@ -66,11 +66,13 @@
|
|
|
66
66
|
style="height: {containerHeight}px;"
|
|
67
67
|
onscroll={handleScroll}
|
|
68
68
|
>
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
<
|
|
69
|
+
<table class="w-full relative" style="border-spacing: 0;">
|
|
70
|
+
<tbody style="display: block;">
|
|
71
|
+
<tr style="height: {visibleRange.start * rowHeight}px; border: none; padding: 0; display: block;"></tr>
|
|
72
|
+
{#each visibleItems as { item, index } (index)}
|
|
72
73
|
{@render row(item, index)}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
{/each}
|
|
75
|
+
<tr style="height: {Math.max(0, (items.length - visibleRange.end - 1) * rowHeight)}px; border: none; padding: 0; display: block;"></tr>
|
|
76
|
+
</tbody>
|
|
77
|
+
</table>
|
|
76
78
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { cn, type WithElementRef } from "../../../utils.js";
|
|
3
3
|
import type { HTMLButtonAttributes } from "svelte/elements";
|
|
4
|
+
import { getContext } from "svelte";
|
|
4
5
|
|
|
5
6
|
type Props = WithElementRef<HTMLButtonAttributes, HTMLButtonElement>;
|
|
6
7
|
|
|
@@ -11,16 +12,25 @@
|
|
|
11
12
|
child,
|
|
12
13
|
...restProps
|
|
13
14
|
}: Props & { child?: import("svelte").Snippet<[{ props: Record<string, unknown> }]> } = $props();
|
|
15
|
+
|
|
16
|
+
const toggle = getContext<() => void>("svadmin-dropdown-toggle");
|
|
17
|
+
|
|
18
|
+
function handleClick(e: MouseEvent) {
|
|
19
|
+
e.stopPropagation();
|
|
20
|
+
toggle?.();
|
|
21
|
+
restProps.onclick?.(e as any);
|
|
22
|
+
}
|
|
14
23
|
</script>
|
|
15
24
|
|
|
16
25
|
{#if child}
|
|
17
|
-
{@render child({ props: { ...restProps, class: cn(className), "data-slot": "dropdown-menu-trigger" } })}
|
|
26
|
+
{@render child({ props: { ...restProps, class: cn(className), "data-slot": "dropdown-menu-trigger", onclick: handleClick } })}
|
|
18
27
|
{:else}
|
|
19
28
|
<button
|
|
20
29
|
bind:this={ref}
|
|
21
30
|
data-slot="dropdown-menu-trigger"
|
|
22
31
|
class={cn(className)}
|
|
23
32
|
{...restProps}
|
|
33
|
+
onclick={handleClick}
|
|
24
34
|
>
|
|
25
35
|
{@render children?.()}
|
|
26
36
|
</button>
|
|
@@ -43,11 +43,13 @@ function sync() {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
export function initRouter(provider) {
|
|
46
|
-
if (_initialized)
|
|
47
|
-
return;
|
|
48
|
-
_initialized = true;
|
|
49
46
|
_provider = provider;
|
|
50
47
|
setActiveRouterProvider(provider);
|
|
48
|
+
if (_initialized) {
|
|
49
|
+
sync(); // Re-sync manually since provider might have switched format/URLs
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
_initialized = true;
|
|
51
53
|
sync();
|
|
52
54
|
if (typeof window !== 'undefined') {
|
|
53
55
|
window.addEventListener('hashchange', sync);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.1",
|
|
4
4
|
"description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -9,22 +9,23 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist"
|
|
11
11
|
],
|
|
12
|
-
"main": "
|
|
13
|
-
"types": "
|
|
12
|
+
"main": "dist/index.js",
|
|
13
|
+
"types": "dist/index.d.ts",
|
|
14
14
|
"exports": {
|
|
15
15
|
".": {
|
|
16
|
-
"types": "./
|
|
17
|
-
"
|
|
18
|
-
"
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/index.js",
|
|
18
|
+
"svelte": "./dist/index.js"
|
|
19
19
|
},
|
|
20
20
|
"./app.css": {
|
|
21
|
-
"style": "./
|
|
22
|
-
"default": "./
|
|
21
|
+
"style": "./dist/app.css",
|
|
22
|
+
"default": "./dist/app.css",
|
|
23
|
+
"svelte": "./dist/app.css"
|
|
23
24
|
},
|
|
24
25
|
"./components/*": "./dist/components/*"
|
|
25
26
|
},
|
|
26
27
|
"peerDependencies": {
|
|
27
|
-
"@svadmin/core": "^0.
|
|
28
|
+
"@svadmin/core": "^0.22.1",
|
|
28
29
|
"@tanstack/svelte-query": "^6.0.0",
|
|
29
30
|
"highlight.js": "^11.11.1",
|
|
30
31
|
"isomorphic-dompurify": "^3.7.1",
|
|
@@ -51,8 +52,7 @@
|
|
|
51
52
|
},
|
|
52
53
|
"dependencies": {
|
|
53
54
|
"@lucide/svelte": "^1.7.0",
|
|
54
|
-
"@tanstack/svelte-table": "9.0.0-alpha.
|
|
55
|
-
"@tanstack/table-core": "9.0.0-alpha.32",
|
|
55
|
+
"@tanstack/svelte-table": "^9.0.0-alpha.10",
|
|
56
56
|
"bits-ui": "^2.17.1",
|
|
57
57
|
"clsx": "^2.1.1",
|
|
58
58
|
"cmdk-sv": "^0.0.19",
|
|
@@ -71,7 +71,9 @@
|
|
|
71
71
|
"bugs": {
|
|
72
72
|
"url": "https://github.com/zuohuadong/svadmin/issues"
|
|
73
73
|
},
|
|
74
|
-
"devDependencies": {
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@tanstack/table-core": "8.21.3"
|
|
76
|
+
},
|
|
75
77
|
"scripts": {
|
|
76
78
|
"build": "svelte-package -i src -o dist"
|
|
77
79
|
}
|
package/src/index.ts
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
// @svadmin/ui — Pre-built admin UI components
|
|
2
|
-
|
|
3
|
-
// Entry component
|
|
4
|
-
export { default as AdminApp } from './components/AdminApp.svelte';
|
|
5
|
-
|
|
6
|
-
// Admin components
|
|
7
|
-
export { default as AutoTable } from './components/AutoTable.svelte';
|
|
8
|
-
export { default as AutoForm } from './components/AutoForm.svelte';
|
|
9
|
-
export { default as ShowPage } from './components/ShowPage.svelte';
|
|
10
|
-
export { default as ConfirmDialog } from './components/ConfirmDialog.svelte';
|
|
11
|
-
export { default as Sidebar } from './components/Sidebar.svelte';
|
|
12
|
-
export { default as Layout } from './components/Layout.svelte';
|
|
13
|
-
export { default as Header } from './components/Header.svelte';
|
|
14
|
-
export { default as ErrorBoundary } from './components/ErrorBoundary.svelte';
|
|
15
|
-
export { default as Toast } from './components/Toast.svelte';
|
|
16
|
-
export { default as Breadcrumbs } from './components/Breadcrumbs.svelte';
|
|
17
|
-
export { default as FieldRenderer } from './components/FieldRenderer.svelte';
|
|
18
|
-
export { default as EmptyState } from './components/EmptyState.svelte';
|
|
19
|
-
export { default as StatsCard } from './components/StatsCard.svelte';
|
|
20
|
-
export { default as PageHeader } from './components/PageHeader.svelte';
|
|
21
|
-
export { default as LoginPage } from './components/LoginPage.svelte';
|
|
22
|
-
export { default as RegisterPage } from './components/RegisterPage.svelte';
|
|
23
|
-
export { default as ForgotPasswordPage } from './components/ForgotPasswordPage.svelte';
|
|
24
|
-
export { default as CanAccess } from './components/CanAccess.svelte';
|
|
25
|
-
export { default as Can } from './components/CanAccess.svelte';
|
|
26
|
-
export { default as UndoableNotification } from './components/UndoableNotification.svelte';
|
|
27
|
-
export { default as ModalForm } from './components/ModalForm.svelte';
|
|
28
|
-
export { default as DrawerForm } from './components/DrawerForm.svelte';
|
|
29
|
-
export { default as DevTools } from './components/DevTools.svelte';
|
|
30
|
-
export { default as Authenticated } from './components/Authenticated.svelte';
|
|
31
|
-
export { default as UpdatePasswordPage } from './components/UpdatePasswordPage.svelte';
|
|
32
|
-
export { default as ProfilePage } from './components/ProfilePage.svelte';
|
|
33
|
-
export { default as ConfigErrorScreen } from './components/ConfigErrorScreen.svelte';
|
|
34
|
-
export { default as InferencerPanel } from './components/InferencerPanel.svelte';
|
|
35
|
-
export { default as LiveIndicator } from './components/LiveIndicator.svelte';
|
|
36
|
-
export { default as ListPage } from './components/ListPage.svelte';
|
|
37
|
-
export { default as CreatePage } from './components/CreatePage.svelte';
|
|
38
|
-
export { default as EditPage } from './components/EditPage.svelte';
|
|
39
|
-
export { default as AutoSaveIndicator } from './components/AutoSaveIndicator.svelte';
|
|
40
|
-
export { default as ErrorComponent } from './components/ErrorComponent.svelte';
|
|
41
|
-
export { default as PageSkeleton } from './components/PageSkeleton.svelte';
|
|
42
|
-
export { default as NavigateToResource } from './components/NavigateToResource.svelte';
|
|
43
|
-
export { default as CatchAllNavigate } from './components/CatchAllNavigate.svelte';
|
|
44
|
-
export { default as ThemedTitle } from './components/ThemedTitle.svelte';
|
|
45
|
-
export { default as CommandPalette } from './components/CommandPalette.svelte';
|
|
46
|
-
export { default as StepsForm } from './components/StepsForm.svelte';
|
|
47
|
-
export { default as InfiniteList } from './components/InfiniteList.svelte';
|
|
48
|
-
export { default as ComboboxField } from './components/ComboboxField.svelte';
|
|
49
|
-
export { default as PasswordInput } from './components/PasswordInput.svelte';
|
|
50
|
-
export { default as ChatDialog } from "./components/ChatDialog.svelte";
|
|
51
|
-
export { default as MarkdownRenderer } from "./components/MarkdownRenderer.svelte";
|
|
52
|
-
export { default as SmartSuggest } from "./components/SmartSuggest.svelte";
|
|
53
|
-
export { default as AICommandBar } from "./components/AICommandBar.svelte";
|
|
54
|
-
export { default as CopilotPanel } from "./components/CopilotPanel.svelte";
|
|
55
|
-
export { default as InsightCard } from "./components/InsightCard.svelte";
|
|
56
|
-
export { default as AnomalyBadge } from "./components/AnomalyBadge.svelte";
|
|
57
|
-
export { default as VoiceInput } from "./components/VoiceInput.svelte";
|
|
58
|
-
export { default as AnimatedCounter } from "./components/AnimatedCounter.svelte";
|
|
59
|
-
export { default as KeyboardShortcuts } from "./components/KeyboardShortcuts.svelte";
|
|
60
|
-
export { default as InlineEdit } from "./components/InlineEdit.svelte";
|
|
61
|
-
export { default as VirtualTable } from "./components/VirtualTable.svelte";
|
|
62
|
-
export { default as DraggableHeader } from "./components/DraggableHeader.svelte";
|
|
63
|
-
export { default as SettingsPage } from "./components/SettingsPage.svelte";
|
|
64
|
-
|
|
65
|
-
// Editor Registry
|
|
66
|
-
export { setRichTextEditor, getRichTextEditor } from "./editor-config.svelte.js";
|
|
67
|
-
|
|
68
|
-
// Settings & Preferences
|
|
69
|
-
export { default as AppearanceSettings } from "./components/AppearanceSettings.svelte";
|
|
70
|
-
export { default as AboutSettings } from "./components/AboutSettings.svelte";
|
|
71
|
-
export { default as PermissionMatrix } from "./components/PermissionMatrix.svelte";
|
|
72
|
-
export type { RoleInfo, ResourceInfo, ActionInfo } from "./types.js";
|
|
73
|
-
export { default as RolesSettings } from "./components/RolesSettings.svelte";
|
|
74
|
-
export { default as AuditLogViewer } from "./components/AuditLogViewer.svelte";
|
|
75
|
-
export { default as TenantSwitcher } from "./components/TenantSwitcher.svelte";
|
|
76
|
-
export type { Tenant } from "./types.js";
|
|
77
|
-
export { default as TaskQueueDrawer } from "./components/TaskQueueDrawer.svelte";
|
|
78
|
-
export type { BackgroundTask, TaskStatus } from "./types.js";
|
|
79
|
-
export { default as DraggableGrid } from "./components/DraggableGrid.svelte";
|
|
80
|
-
export type { GridModule } from "./types.js";
|
|
81
|
-
|
|
82
|
-
// Dashboard Charts (zero-dependency SVG)
|
|
83
|
-
export { BarChart, LineChart, PieChart } from './components/charts/index.js';
|
|
84
|
-
// Field display components
|
|
85
|
-
export { default as NumberField } from './components/fields/NumberField.svelte';
|
|
86
|
-
export { default as DateField } from './components/fields/DateField.svelte';
|
|
87
|
-
export { default as EmailField } from './components/fields/EmailField.svelte';
|
|
88
|
-
export { default as UrlField } from './components/fields/UrlField.svelte';
|
|
89
|
-
export { default as BooleanField } from './components/fields/BooleanField.svelte';
|
|
90
|
-
export { default as TagField } from './components/fields/TagField.svelte';
|
|
91
|
-
export { default as FileField } from './components/fields/FileField.svelte';
|
|
92
|
-
export { default as MarkdownField } from './components/fields/MarkdownField.svelte';
|
|
93
|
-
export { default as TextField } from './components/fields/TextField.svelte';
|
|
94
|
-
export { default as ImageField } from './components/fields/ImageField.svelte';
|
|
95
|
-
export { default as SelectField } from './components/fields/SelectField.svelte';
|
|
96
|
-
export { default as MultiSelectField } from './components/fields/MultiSelectField.svelte';
|
|
97
|
-
export { default as RelationField } from './components/fields/RelationField.svelte';
|
|
98
|
-
export { default as JsonField } from './components/fields/JsonField.svelte';
|
|
99
|
-
export { default as RichTextField } from './components/fields/RichTextField.svelte';
|
|
100
|
-
|
|
101
|
-
// Rich text editor (requires @svadmin/editor) as a direct import in consumer app
|
|
102
|
-
|
|
103
|
-
// CRUD Buttons
|
|
104
|
-
export {
|
|
105
|
-
CreateButton, EditButton, DeleteButton, ShowButton, ListButton,
|
|
106
|
-
RefreshButton, ExportButton, ImportButton, SaveButton, CloneButton,
|
|
107
|
-
} from './components/buttons/index.js';
|
|
108
|
-
|
|
109
|
-
// Base UI components (shadcn-svelte)
|
|
110
|
-
export { Button, buttonVariants } from './components/ui/button/index.js';
|
|
111
|
-
export { Input } from './components/ui/input/index.js';
|
|
112
|
-
export { Textarea } from './components/ui/textarea/index.js';
|
|
113
|
-
export { Select } from './components/ui/select/index.js';
|
|
114
|
-
export { Switch } from './components/ui/switch/index.js';
|
|
115
|
-
export { Checkbox } from './components/ui/checkbox/index.js';
|
|
116
|
-
export { Badge } from './components/ui/badge/index.js';
|
|
117
|
-
export { Separator } from './components/ui/separator/index.js';
|
|
118
|
-
export { Avatar } from './components/ui/avatar/index.js';
|
|
119
|
-
export { Skeleton } from './components/ui/skeleton/index.js';
|
|
120
|
-
export * as Sheet from './components/ui/sheet/index.js';
|
|
121
|
-
export * as Alert from './components/ui/alert/index.js';
|
|
122
|
-
export * as Card from './components/ui/card/index.js';
|
|
123
|
-
export * as Dialog from './components/ui/dialog/index.js';
|
|
124
|
-
export * as Table from './components/ui/table/index.js';
|
|
125
|
-
export * as Tabs from './components/ui/tabs/index.js';
|
|
126
|
-
export * as Tooltip from './components/ui/tooltip/index.js';
|
|
127
|
-
export * as DropdownMenu from './components/ui/dropdown-menu/index.js';
|
|
128
|
-
|
|
129
|
-
// Additional shadcn components
|
|
130
|
-
export * as Breadcrumb from './components/ui/breadcrumb/index.js';
|
|
131
|
-
export * as Pagination from './components/ui/pagination/index.js';
|
|
132
|
-
export * as ContextMenu from './components/ui/context-menu/index.js';
|
|
133
|
-
|
|
134
|
-
export * as Collapsible from './components/ui/collapsible/index.js';
|
|
135
|
-
export { Label } from './components/ui/label/index.js';
|
|
136
|
-
export { Command } from './components/ui/command/index.js';
|
|
137
|
-
|
|
138
|
-
// Utils
|
|
139
|
-
export { cn } from './utils';
|
|
140
|
-
|
|
141
|
-
// Component Registry (DI)
|
|
142
|
-
export {
|
|
143
|
-
setComponentRegistry, getComponentRegistry, useComponent,
|
|
144
|
-
type ComponentRegistry,
|
|
145
|
-
} from './component-registry.svelte';
|
|
146
|
-
|
|
147
|
-
// Field component registry
|
|
148
|
-
export {
|
|
149
|
-
builtinDisplayComponents, registerDisplayComponent,
|
|
150
|
-
getDisplayComponent, hasDisplayComponent,
|
|
151
|
-
} from './components/fieldComponentMap';
|
|
152
|
-
export type { FieldComponentMap } from './components/fieldComponentMap';
|
|
153
|
-
|
|
154
|
-
// Svelte Actions
|
|
155
|
-
export { clickOutside, shortcut, intersect, copyOnClick } from './actions';
|