@svadmin/ui 0.10.0 → 0.11.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/ui",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
4
4
  "description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
5
5
  "type": "module",
6
6
  "sideEffects": [
package/src/app.css CHANGED
@@ -34,6 +34,12 @@
34
34
  --chart-3: oklch(0.398 0.07 227.392);
35
35
  --chart-4: oklch(0.828 0.189 84.429);
36
36
  --chart-5: oklch(0.769 0.188 70.08);
37
+ --success: oklch(0.6 0.2 145);
38
+ --success-foreground: oklch(0.985 0 0);
39
+ --warning: oklch(0.75 0.18 85);
40
+ --warning-foreground: oklch(0.3 0.08 60);
41
+ --info: oklch(0.6 0.15 250);
42
+ --info-foreground: oklch(0.985 0 0);
37
43
  --sidebar: oklch(1 0 0 / 80%);
38
44
  --sidebar-foreground: oklch(0.13 0.02 265);
39
45
  --sidebar-primary: oklch(0.488 0.243 264.376);
@@ -69,6 +75,12 @@
69
75
  --chart-3: oklch(0.769 0.188 70.08);
70
76
  --chart-4: oklch(0.627 0.265 303.9);
71
77
  --chart-5: oklch(0.645 0.246 16.439);
78
+ --success: oklch(0.65 0.2 150);
79
+ --success-foreground: oklch(0.985 0 0);
80
+ --warning: oklch(0.8 0.15 85);
81
+ --warning-foreground: oklch(0.25 0.05 60);
82
+ --info: oklch(0.65 0.15 250);
83
+ --info-foreground: oklch(0.985 0 0);
72
84
  --sidebar: oklch(0.16 0.015 265 / 80%);
73
85
  --sidebar-foreground: oklch(0.985 0 0);
74
86
  --sidebar-primary: oklch(0.585 0.233 264);
@@ -146,6 +158,12 @@ body {
146
158
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
147
159
  --color-sidebar-border: var(--sidebar-border);
148
160
  --color-sidebar-ring: var(--sidebar-ring);
161
+ --color-success: var(--success);
162
+ --color-success-foreground: var(--success-foreground);
163
+ --color-warning: var(--warning);
164
+ --color-warning-foreground: var(--warning-foreground);
165
+ --color-info: var(--info);
166
+ --color-info-foreground: var(--info-foreground);
149
167
  --radius-sm: calc(var(--radius) - 4px);
150
168
  --radius-md: calc(var(--radius) - 2px);
151
169
  --radius-lg: var(--radius);
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Component Registry — Svelte Context-based DI for component injection.
3
+ *
4
+ * Allows users to override any registered component via the `components`
5
+ * prop on `AdminApp`, while providing sensible defaults.
6
+ */
7
+ import { getContext, setContext, type Component } from 'svelte';
8
+
9
+ // ─── Overridable component slots ────────────────────────────────
10
+ export interface ComponentRegistry {
11
+ // Layout primitives
12
+ Layout: Component<any>;
13
+ Sidebar: Component<any>;
14
+ Header: Component<any>;
15
+
16
+ // Pages
17
+ LoginPage: Component<any>;
18
+ AutoTable: Component<any>;
19
+ AutoForm: Component<any>;
20
+ ShowPage: Component<any>;
21
+
22
+ // Shadcn primitives
23
+ Button: Component<any>;
24
+ Input: Component<any>;
25
+ Badge: Component<any>;
26
+ Skeleton: Component<any>;
27
+ }
28
+
29
+ const REGISTRY_KEY = 'svadmin:components';
30
+
31
+ /** Set the component registry in context (called by AdminApp). */
32
+ export function setComponentRegistry(registry: ComponentRegistry): void {
33
+ setContext(REGISTRY_KEY, registry);
34
+ }
35
+
36
+ /** Retrieve the full component registry from context. */
37
+ export function getComponentRegistry(): ComponentRegistry {
38
+ return getContext<ComponentRegistry>(REGISTRY_KEY);
39
+ }
40
+
41
+ /** Retrieve a single component by name from the registry. */
42
+ export function useComponent<K extends keyof ComponentRegistry>(
43
+ name: K,
44
+ ): ComponentRegistry[K] {
45
+ return getComponentRegistry()[name];
46
+ }
@@ -59,8 +59,8 @@
59
59
  } else {
60
60
  aiResponse = await result;
61
61
  }
62
- } catch (err: any) {
63
- if (err.name !== 'AbortError') {
62
+ } catch (err: unknown) {
63
+ if (err instanceof Error && err.name !== 'AbortError') {
64
64
  aiResponse = 'Failed to get response from AI.';
65
65
  }
66
66
  } finally {
@@ -91,7 +91,7 @@
91
91
  >
92
92
  <div class="flex items-center border-b px-3 h-12 relative" data-cmdk-input-wrapper="">
93
93
  {#if aiMode}
94
- <Sparkles class="mr-2 h-4 w-4 shrink-0 text-amber-500 animate-pulse" />
94
+ <Sparkles class="mr-2 h-4 w-4 shrink-0 text-warning animate-pulse" />
95
95
  {:else}
96
96
  <Search class="mr-2 h-4 w-4 shrink-0 opacity-50" />
97
97
  {/if}
@@ -103,7 +103,7 @@
103
103
  />
104
104
  {#if searchValue.trim() && !aiMode}
105
105
  <button
106
- class="absolute right-3 flex items-center gap-1 text-[10px] font-medium text-amber-500 bg-amber-500/10 hover:bg-amber-500/20 px-2 py-1 rounded transition-colors"
106
+ class="absolute right-3 flex items-center gap-1 text-[10px] font-medium text-warning bg-warning/10 hover:bg-warning/20 px-2 py-1 rounded transition-colors"
107
107
  onclick={askAI}
108
108
  >
109
109
  <Sparkles class="h-3 w-3" />
@@ -152,7 +152,7 @@
152
152
  <!-- AI Response Area -->
153
153
  <div class="h-[360px] max-h-[360px] overflow-y-auto p-4 bg-muted/20">
154
154
  <div class="flex items-start gap-3">
155
- <div class="mt-0.5 w-8 h-8 rounded-full bg-amber-500/10 text-amber-500 flex items-center justify-center shrink-0">
155
+ <div class="mt-0.5 w-8 h-8 rounded-full bg-warning/10 text-warning flex items-center justify-center shrink-0">
156
156
  <Sparkles class="h-4 w-4" />
157
157
  </div>
158
158
  <div class="flex-1 w-full text-sm">
@@ -7,6 +7,7 @@
7
7
  import { t } from '@svadmin/core/i18n';
8
8
  import { navigate } from '@svadmin/core/router';
9
9
  import { QueryClient, QueryClientProvider } from '@tanstack/svelte-query';
10
+ import { setComponentRegistry, type ComponentRegistry } from '../component-registry.svelte.js';
10
11
  import Layout from './Layout.svelte';
11
12
  import AutoTable from './AutoTable.svelte';
12
13
  import AutoForm from './AutoForm.svelte';
@@ -18,6 +19,12 @@
18
19
  import UpdatePasswordPage from './UpdatePasswordPage.svelte';
19
20
  import ConfigErrorScreen from './ConfigErrorScreen.svelte';
20
21
  import DevTools from './DevTools.svelte';
22
+ import Sidebar from './Sidebar.svelte';
23
+ import Header from './Header.svelte';
24
+ import { Button } from './ui/button/index.js';
25
+ import { Input } from './ui/input/index.js';
26
+ import { Badge } from './ui/badge/index.js';
27
+ import { Skeleton } from './ui/skeleton/index.js';
21
28
  import { initRouter, getRoute, getParams } from '../router-state.svelte.js';
22
29
 
23
30
  interface Props {
@@ -30,6 +37,8 @@
30
37
  defaultTheme?: ThemeMode;
31
38
  dashboard?: Snippet;
32
39
  loginPage?: Snippet;
40
+ /** Override default components via DI */
41
+ components?: Partial<ComponentRegistry>;
33
42
  }
34
43
 
35
44
  let {
@@ -42,8 +51,22 @@
42
51
  defaultTheme,
43
52
  dashboard,
44
53
  loginPage,
54
+ components: userComponents,
45
55
  }: Props = $props();
46
56
 
57
+ // Default component registry
58
+ const defaultComponents: ComponentRegistry = {
59
+ Layout, Sidebar, Header,
60
+ LoginPage, AutoTable, AutoForm, ShowPage,
61
+ Button: Button as unknown as ComponentRegistry['Button'],
62
+ Input: Input as unknown as ComponentRegistry['Input'],
63
+ Badge: Badge as unknown as ComponentRegistry['Badge'],
64
+ Skeleton: Skeleton as unknown as ComponentRegistry['Skeleton'],
65
+ };
66
+
67
+ // Merge user overrides and set context
68
+ setComponentRegistry({ ...defaultComponents, ...userComponents });
69
+
47
70
  // Resolve router provider (default to hash)
48
71
  const resolvedRouter = $derived(routerProvider ?? createHashRouterProvider());
49
72
 
@@ -77,6 +100,9 @@
77
100
  let authChecked = $state(false);
78
101
 
79
102
  $effect(() => {
103
+ // Explicitly track route so auth is re-checked on navigation
104
+ const _route = route;
105
+
80
106
  if (!authProvider) {
81
107
  isAuthenticated = true;
82
108
  authChecked = true;
@@ -85,7 +111,7 @@
85
111
  authProvider.check().then(result => {
86
112
  isAuthenticated = result.authenticated;
87
113
  authChecked = true;
88
- if (!result.authenticated && route !== '/login' && route !== '/register' && route !== '/forgot-password' && route !== '/update-password') {
114
+ if (!result.authenticated && _route !== '/login' && _route !== '/register' && _route !== '/forgot-password' && _route !== '/update-password') {
89
115
  navigate(result.redirectTo ?? '/login');
90
116
  }
91
117
  });
@@ -52,7 +52,7 @@
52
52
  >
53
53
  <Badge
54
54
  variant={isGood ? 'default' : 'destructive'}
55
- class="flex items-center gap-1 font-mono text-xs px-1.5 py-0 {isGood ? 'bg-green-500 hover:bg-green-600 text-white' : ''} {className}"
55
+ class="flex items-center gap-1 font-mono text-xs px-1.5 py-0 {isGood ? 'bg-success hover:bg-success/90 text-success-foreground' : ''} {className}"
56
56
  >
57
57
  {#if diff > 0}
58
58
  <TrendingUp class="h-3 w-3" />
@@ -34,10 +34,10 @@
34
34
  // Action semantic color mapping
35
35
  function getActionColor(action: string) {
36
36
  switch (action) {
37
- case 'create': return 'bg-green-500';
38
- case 'update': return 'bg-blue-500';
39
- case 'delete': return 'bg-red-500';
40
- default: return 'bg-slate-400';
37
+ case 'create': return 'bg-success';
38
+ case 'update': return 'bg-info';
39
+ case 'delete': return 'bg-destructive';
40
+ default: return 'bg-muted-foreground';
41
41
  }
42
42
  }
43
43
  </script>
@@ -224,7 +224,7 @@
224
224
  </TooltipButton>
225
225
  <h1 class="text-xl font-semibold text-foreground">{pageTitle}</h1>
226
226
  {#if isDirty}
227
- <Badge variant="outline" class="border-amber-200 bg-amber-50 text-amber-700">{t('common.unsaved')}</Badge>
227
+ <Badge variant="outline" class="border-warning/30 bg-warning/10 text-warning-foreground">{t('common.unsaved')}</Badge>
228
228
  {/if}
229
229
  </div>
230
230
 
@@ -523,7 +523,7 @@
523
523
  {#if cellRenderer && field}
524
524
  {@render cellRenderer({ field, value: cell.getValue(), record })}
525
525
  {:else if field?.type === 'boolean'}
526
- <span class="inline-block h-2 w-2 rounded-full {cell.getValue() ? 'bg-green-500' : 'bg-muted-foreground/30'}"></span>
526
+ <span class="inline-block h-2 w-2 rounded-full {cell.getValue() ? 'bg-success' : 'bg-muted-foreground/30'}"></span>
527
527
  {:else if field?.type === 'date' && cell.getValue()}
528
528
  {new Date(cell.getValue() as string).toLocaleDateString()}
529
529
  {:else if field?.type === 'tags' && Array.isArray(cell.getValue())}
@@ -635,7 +635,7 @@
635
635
  {#if cellRenderer}
636
636
  {@render cellRenderer({ field, value, record })}
637
637
  {:else if field.type === 'boolean'}
638
- <span class="inline-block h-2 w-2 rounded-full {value ? 'bg-green-500' : 'bg-muted-foreground/30'}"></span>
638
+ <span class="inline-block h-2 w-2 rounded-full {value ? 'bg-success' : 'bg-muted-foreground/30'}"></span>
639
639
  {:else if field.type === 'date' && value}
640
640
  {new Date(value as string).toLocaleDateString()}
641
641
  {:else if field.type === 'tags' && Array.isArray(value)}
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { getChatProvider, getChatContext } from '@svadmin/core';
3
- import { Sparkles, X, ChevronRight, Check } from 'lucide-svelte';
3
+ import { Sparkles, X, ChevronRight, Check, RefreshCcw } from 'lucide-svelte';
4
4
  import { fade, slide } from 'svelte/transition';
5
5
  import { Button } from './ui/button/index.js';
6
6
  import MarkdownRenderer from './MarkdownRenderer.svelte';
@@ -56,8 +56,8 @@ Keep it concise, professional, and use bullet points.`;
56
56
  } else {
57
57
  insightText = await result;
58
58
  }
59
- } catch (err: any) {
60
- if (err.name !== 'AbortError') {
59
+ } catch (err: unknown) {
60
+ if (err instanceof Error && err.name !== 'AbortError') {
61
61
  insightText = 'Failed to generate insights for this context.';
62
62
  }
63
63
  } finally {
@@ -115,7 +115,7 @@ Keep it concise, professional, and use bullet points.`;
115
115
  </span>
116
116
  {:else}
117
117
  <Button variant="ghost" size="icon-sm" class="h-5 w-5 hover:bg-muted" onclick={getInsights}>
118
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/><path d="M16 21v-5h5"/></svg>
118
+ <RefreshCcw class="h-3 w-3" />
119
119
  </Button>
120
120
  {/if}
121
121
  </h4>
@@ -221,7 +221,7 @@
221
221
  {#if multiVal.length > 0}
222
222
  <div class="flex flex-wrap gap-1 mt-1">
223
223
  {#each multiVal as v}
224
- {@const label = field.options?.find((o: any) => o.value === v)?.label ?? String(v)}
224
+ {@const label = field.options?.find((o: { label: string; value: string | number }) => o.value === v)?.label ?? String(v)}
225
225
  <Badge variant="secondary" class="gap-1">
226
226
  {label}
227
227
  <button type="button" onclick={() => toggleMulti(v)} class="ml-0.5 rounded-sm hover:text-destructive hover:bg-destructive/10 transition-colors" aria-label={t('common.clear')}>×</button>
@@ -21,7 +21,7 @@
21
21
  }>();
22
22
  </script>
23
23
 
24
- <header class="sticky top-0 z-30 flex h-14 w-full shrink-0 items-center justify-between bg-white/60 dark:bg-slate-900/60 backdrop-blur-md px-4 md:px-6">
24
+ <header class="sticky top-0 z-30 flex h-14 w-full shrink-0 items-center justify-between bg-card/60 backdrop-blur-md px-4 md:px-6">
25
25
  <div class="flex items-center gap-4">
26
26
  {#if children}
27
27
  {@render children()}
@@ -55,8 +55,8 @@ ${context}`;
55
55
  } else {
56
56
  insightText = await result;
57
57
  }
58
- } catch (err: any) {
59
- if (err.name !== 'AbortError') {
58
+ } catch (err: unknown) {
59
+ if (err instanceof Error && err.name !== 'AbortError') {
60
60
  insightText = 'Failed to generate insights.';
61
61
  }
62
62
  } finally {
@@ -83,7 +83,7 @@
83
83
  </div>
84
84
  </div>
85
85
  {:else}
86
- <div class="flex h-screen bg-slate-50 dark:bg-slate-950" in:fade={{ duration: 200, delay: 50 }}>
86
+ <div class="flex h-screen bg-background" in:fade={{ duration: 200, delay: 50 }}>
87
87
  <!-- Desktop sidebar -->
88
88
  <div class="hidden md:block">
89
89
  <Sidebar {collapsed} {identity} {title} onToggle={() => collapsed = !collapsed} onLogout={handleLogout} />
@@ -16,9 +16,9 @@
16
16
  }>();
17
17
 
18
18
  const statusConfigs = {
19
- connected: { variant: 'default' as const, dotClass: 'bg-emerald-500', label: 'Live', icon: Wifi },
20
- connecting: { variant: 'secondary' as const, dotClass: 'bg-amber-500', label: 'Connecting...', icon: Loader2 as typeof Wifi },
21
- disconnected: { variant: 'destructive' as const, dotClass: 'bg-red-500', label: 'Offline', icon: WifiOff },
19
+ connected: { variant: 'default' as const, dotClass: 'bg-success', label: 'Live', icon: Wifi },
20
+ connecting: { variant: 'secondary' as const, dotClass: 'bg-warning', label: 'Connecting...', icon: Loader2 as typeof Wifi },
21
+ disconnected: { variant: 'destructive' as const, dotClass: 'bg-destructive', label: 'Offline', icon: WifiOff },
22
22
  } as const;
23
23
  type StatusKey = 'connected' | 'connecting' | 'disconnected';
24
24
  const cfg = $derived(statusConfigs[status as StatusKey]);
@@ -69,7 +69,7 @@
69
69
  pres.forEach(pre => {
70
70
  pre.classList.add('enhanced');
71
71
  const wrapper = document.createElement('div');
72
- wrapper.className = 'code-block-wrapper group relative my-4 rounded-md bg-zinc-950';
72
+ wrapper.className = 'code-block-wrapper group relative my-4 rounded-md bg-foreground/95';
73
73
 
74
74
  const header = document.createElement('div');
75
75
  header.className = 'flex items-center justify-between px-4 py-2 text-xs text-zinc-400 border-b border-zinc-800';
@@ -43,8 +43,8 @@
43
43
 
44
44
  const strengthColor = $derived(
45
45
  strength < 30 ? 'bg-destructive' :
46
- strength < 60 ? 'bg-amber-500' :
47
- strength < 80 ? 'bg-blue-500' : 'bg-emerald-500'
46
+ strength < 60 ? 'bg-warning' :
47
+ strength < 80 ? 'bg-info' : 'bg-success'
48
48
  );
49
49
 
50
50
  const strengthLabel = $derived(
@@ -130,7 +130,7 @@
130
130
  {/if}
131
131
 
132
132
  {#if pwSuccess}
133
- <Alert.Root class="border-green-500/30 bg-green-500/5 text-green-600 dark:text-green-400">
133
+ <Alert.Root class="border-success/30 bg-success/5 text-success">
134
134
  <CheckCircle class="h-4 w-4" />
135
135
  <Alert.Description>{t('profile.passwordChanged')}</Alert.Description>
136
136
  </Alert.Root>
@@ -88,8 +88,8 @@ Do not include any other text, Markdown formatting, quotes, or explanations.`;
88
88
 
89
89
  // Clean up the suggestion
90
90
  suggestion = suggestion.trim().replace(/^["']|["']$/g, '');
91
- } catch (err: any) {
92
- if (err.name !== 'AbortError') {
91
+ } catch (err: unknown) {
92
+ if (err instanceof Error && err.name !== 'AbortError') {
93
93
  suggestion = '';
94
94
  }
95
95
  } finally {
@@ -7,10 +7,10 @@
7
7
 
8
8
  const colorMap: Record<ColorVariant, string> = {
9
9
  primary: 'bg-primary/10 text-primary',
10
- success: 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400',
11
- warning: 'bg-amber-500/10 text-amber-600 dark:text-amber-400',
12
- danger: 'bg-red-500/10 text-red-600 dark:text-red-400',
13
- info: 'bg-blue-500/10 text-blue-600 dark:text-blue-400',
10
+ success: 'bg-success/10 text-success',
11
+ warning: 'bg-warning/10 text-warning',
12
+ danger: 'bg-destructive/10 text-destructive',
13
+ info: 'bg-info/10 text-info',
14
14
  };
15
15
 
16
16
  const variantMap: Record<StyleVariant, string> = {
@@ -60,7 +60,7 @@
60
60
  <p class="text-xl font-semibold text-foreground">{value}</p>
61
61
  {#if trend}
62
62
  <span
63
- class="text-xs font-medium {trend.value >= 0 ? 'text-emerald-600 dark:text-emerald-400' : 'text-red-600 dark:text-red-400'}"
63
+ class="text-xs font-medium {trend.value >= 0 ? 'text-success' : 'text-destructive'}"
64
64
  >
65
65
  {trend.value >= 0 ? '↑' : '↓'}{Math.abs(trend.value)}%
66
66
  {#if trend.label}
@@ -2,6 +2,25 @@
2
2
  import { Mic, MicOff, Loader2 } from 'lucide-svelte';
3
3
  import TooltipButton from './TooltipButton.svelte';
4
4
 
5
+ // Web Speech API type declarations
6
+ interface SpeechRecognitionEvent extends Event {
7
+ results: SpeechRecognitionResultList;
8
+ }
9
+ interface SpeechRecognitionErrorEvent extends Event {
10
+ error: string;
11
+ }
12
+ interface SpeechRecognitionInstance extends EventTarget {
13
+ continuous: boolean;
14
+ interimResults: boolean;
15
+ lang: string;
16
+ onstart: (() => void) | null;
17
+ onresult: ((event: SpeechRecognitionEvent) => void) | null;
18
+ onerror: ((event: SpeechRecognitionErrorEvent) => void) | null;
19
+ onend: (() => void) | null;
20
+ start(): void;
21
+ stop(): void;
22
+ }
23
+
5
24
  interface Props {
6
25
  /** Called when a speech recognition result is finalized */
7
26
  onresult?: (text: string) => void;
@@ -25,23 +44,23 @@
25
44
 
26
45
  let isListening = $state(false);
27
46
  let notSupported = $state(false);
28
- let recognition: any = null;
47
+ let recognition: SpeechRecognitionInstance | null = null;
29
48
 
30
49
  // Initialize SpeechRecognition on mount
31
50
  $effect(() => {
32
- // @ts-ignore - Browser specific APIs
33
- const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
51
+ const SpeechRecognitionCtor = (
52
+ (globalThis as Record<string, unknown>).SpeechRecognition ||
53
+ (globalThis as Record<string, unknown>).webkitSpeechRecognition
54
+ ) as (new () => SpeechRecognitionInstance) | undefined;
34
55
 
35
- if (!SpeechRecognition) {
56
+ if (!SpeechRecognitionCtor) {
36
57
  notSupported = true;
37
58
  return;
38
59
  }
39
60
 
40
- recognition = new SpeechRecognition();
61
+ recognition = new SpeechRecognitionCtor();
41
62
  recognition.continuous = false;
42
- // Tries to return interim results, but we mainly care about final
43
63
  recognition.interimResults = false;
44
- // Uses the browser locale by default
45
64
  recognition.lang = navigator.language || 'en-US';
46
65
 
47
66
  recognition.onstart = () => {
@@ -49,10 +68,10 @@
49
68
  if (onstart) onstart();
50
69
  };
51
70
 
52
- recognition.onresult = (event: any) => {
71
+ recognition.onresult = (event: SpeechRecognitionEvent) => {
53
72
  const transcript = Array.from(event.results)
54
- .map((result: any) => result[0])
55
- .map((result: any) => result.transcript)
73
+ .map((result: SpeechRecognitionResult) => result[0])
74
+ .map((result: SpeechRecognitionAlternative) => result.transcript)
56
75
  .join('');
57
76
 
58
77
  if (onresult && transcript) {
@@ -60,7 +79,7 @@
60
79
  }
61
80
  };
62
81
 
63
- recognition.onerror = (event: any) => {
82
+ recognition.onerror = (event: SpeechRecognitionErrorEvent) => {
64
83
  console.warn('SpeechRecognition error:', event.error);
65
84
  isListening = false;
66
85
  if (onend) onend();
@@ -3,11 +3,11 @@
3
3
  let { value, trueIcon = '✓', falseIcon = '✗' }: Props = $props();
4
4
  </script>
5
5
  {#if value}
6
- <span class="inline-flex items-center justify-center w-6 h-6 rounded-full text-sm bg-emerald-100 text-emerald-600 dark:bg-emerald-500/20 dark:text-emerald-400">
6
+ <span class="inline-flex items-center justify-center w-6 h-6 rounded-full text-sm bg-success/10 text-success">
7
7
  {trueIcon}
8
8
  </span>
9
9
  {:else}
10
- <span class="inline-flex items-center justify-center w-6 h-6 rounded-full text-sm bg-red-50 text-red-600 dark:bg-red-500/20 dark:text-red-400">
10
+ <span class="inline-flex items-center justify-center w-6 h-6 rounded-full text-sm bg-destructive/10 text-destructive">
11
11
  {falseIcon}
12
12
  </span>
13
13
  {/if}
@@ -7,7 +7,7 @@
7
7
  }>();
8
8
 
9
9
  const label = $derived(
10
- options?.find((o: any) => o.value === value)?.label ?? (value != null ? String(value) : '—')
10
+ options?.find((o: { label: string; value: string | number }) => o.value === value)?.label ?? (value != null ? String(value) : '—')
11
11
  );
12
12
  </script>
13
13
 
@@ -9,8 +9,8 @@
9
9
  variant: {
10
10
  default: "bg-card text-foreground",
11
11
  destructive: "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
12
- warning: "border-amber-500/50 text-amber-700 dark:text-amber-400 bg-amber-50 dark:bg-amber-950/20 [&>svg]:text-current",
13
- success: "border-emerald-500/50 text-emerald-700 dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-950/20 [&>svg]:text-current",
12
+ warning: "border-warning/50 text-warning-foreground bg-warning/10 [&>svg]:text-current",
13
+ success: "border-success/50 text-success bg-success/10 [&>svg]:text-current",
14
14
  },
15
15
  },
16
16
  defaultVariants: { variant: "default" },
package/src/index.ts CHANGED
@@ -109,6 +109,12 @@ export { Command } from './components/ui/command/index.js';
109
109
  // Utils
110
110
  export { cn } from './utils';
111
111
 
112
+ // Component Registry (DI)
113
+ export {
114
+ setComponentRegistry, getComponentRegistry, useComponent,
115
+ type ComponentRegistry,
116
+ } from './component-registry.svelte';
117
+
112
118
  // Field component registry
113
119
  export {
114
120
  builtinDisplayComponents, registerDisplayComponent,