@svadmin/ui 0.29.4 → 0.30.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.
@@ -47,6 +47,8 @@
47
47
  menu?: MenuItem[];
48
48
  /** External URL to the main application or workspace (renders a shortcut in the header) */
49
49
  siteUrl?: string;
50
+ /** Routing strategy for Sidebar links ('hash' | 'path' | 'auto') */
51
+ routeMode?: 'hash' | 'path' | 'auto';
50
52
  }
51
53
 
52
54
  let {
@@ -63,6 +65,7 @@
63
65
  components: userComponents,
64
66
  menu,
65
67
  siteUrl,
68
+ routeMode,
66
69
  }: Props = $props();
67
70
 
68
71
  // Default component registry
@@ -76,10 +79,13 @@
76
79
  };
77
80
 
78
81
  // Merge user overrides and set context
79
- setComponentRegistry({ ...defaultComponents, ...userComponents });
82
+ $effect(() => {
83
+ setComponentRegistry({ ...defaultComponents, ...userComponents });
84
+ });
80
85
 
81
86
  // Resolve router provider (default to hash)
82
87
  const resolvedRouter = $derived(routerProvider ?? createHashRouterProvider());
88
+ const resolvedRouteMode = $derived(routeMode ?? (routerProvider ? 'auto' : 'hash'));
83
89
 
84
90
  // Set up context — use $effect so prop changes are tracked
85
91
  $effect.pre(() => {
@@ -151,7 +157,7 @@
151
157
  {:else if route === '/login' || route === '/register' || route === '/forgot-password' || route === '/update-password'}
152
158
  <ConfigErrorScreen title="{title} — {t('common.configRequired')}" />
153
159
  {:else if isAuthenticated || !authProvider}
154
- <Layout {title} {menu} {siteUrl}>
160
+ <Layout {title} {menu} {siteUrl} routeMode={resolvedRouteMode}>
155
161
  {#key route + (params.resource ?? '') + (params.id ?? '')}
156
162
  <div class="svadmin-page-enter">
157
163
  {#if currentPath().startsWith('/settings')}
@@ -20,6 +20,8 @@ interface Props {
20
20
  menu?: MenuItem[];
21
21
  /** External URL to the main application or workspace (renders a shortcut in the header) */
22
22
  siteUrl?: string;
23
+ /** Routing strategy for Sidebar links ('hash' | 'path' | 'auto') */
24
+ routeMode?: 'hash' | 'path' | 'auto';
23
25
  }
24
26
  declare const AdminApp: import("svelte").Component<Props, {}, "">;
25
27
  type AdminApp = ReturnType<typeof AdminApp>;
@@ -69,15 +69,17 @@
69
69
  return d;
70
70
  })());
71
71
 
72
+ const validator = $derived(deriveValidator(formFields));
73
+
72
74
  // ─── useForm: single source of truth for values, errors, tainted ──
73
75
  const form = useForm({
74
76
  get resource() { return resourceName; },
75
77
  get action() { return mode; },
76
78
  get id() { return id; },
77
- defaultValues: defaults,
79
+ get defaultValues() { return defaults; },
78
80
  redirect: 'list',
79
81
  warnWhenUnsavedChanges: true,
80
- validate: deriveValidator(formFields),
82
+ get validate() { return validator; },
81
83
  });
82
84
 
83
85
  // ─── Submission error (non-field, e.g. network error) ─────────────
@@ -149,7 +149,7 @@
149
149
  get sorters() { return sorters; },
150
150
  get filters() { return activeFilters; },
151
151
  });
152
- const query = listResult.query;
152
+ const query = listResult;
153
153
  const deleteResult = useDelete({ get resource() { return resourceName; } });
154
154
  const deleteMutation = deleteResult.mutation;
155
155
 
@@ -623,7 +623,7 @@
623
623
  recordId={id}
624
624
  {field}
625
625
  value={cell.getValue()}
626
- onSave={() => listResult.query.refetch()}
626
+ onSave={() => listResult.refetch()}
627
627
  />
628
628
  {:else if field?.key === primaryKey}
629
629
  <span class="font-mono text-xs">{cell.getValue() ?? '—'}</span>
@@ -22,7 +22,7 @@
22
22
  let shortcutsOpen = $state(false);
23
23
  let mobileMenuOpen = $state(false);
24
24
 
25
- let { children, title = 'Admin', menu, siteUrl }: { children: Snippet; title?: string; menu?: MenuItem[]; siteUrl?: string } = $props();
25
+ let { children, title = 'Admin', menu, siteUrl, routeMode = 'auto' }: { children: Snippet; title?: string; menu?: MenuItem[]; siteUrl?: string; routeMode?: 'hash' | 'path' | 'auto' } = $props();
26
26
 
27
27
  let hasAuth = true;
28
28
  let auth: ReturnType<typeof getAuthProvider> | null = null;
@@ -121,13 +121,13 @@
121
121
  <div class="flex h-screen bg-background" in:fade={{ duration: 200, delay: 50 }}>
122
122
  <!-- Desktop sidebar -->
123
123
  <div class="hidden md:block">
124
- <Sidebar {collapsed} {identity} {title} {menu} onToggle={() => collapsed = !collapsed} onLogout={handleLogout} />
124
+ <Sidebar {collapsed} {identity} {title} {menu} {routeMode} onToggle={() => collapsed = !collapsed} onLogout={handleLogout} />
125
125
  </div>
126
126
 
127
127
  <!-- Mobile sidebar via Sheet -->
128
128
  <Sheet.Root bind:open={mobileMenuOpen} side="left">
129
129
  <div class="md:hidden">
130
- <Sidebar collapsed={false} {identity} {title} {menu} onToggle={() => { mobileMenuOpen = false; }} onLogout={handleLogout} />
130
+ <Sidebar collapsed={false} {identity} {title} {menu} {routeMode} onToggle={() => { mobileMenuOpen = false; }} onLogout={handleLogout} />
131
131
  </div>
132
132
  </Sheet.Root>
133
133
 
@@ -5,6 +5,7 @@ type $$ComponentProps = {
5
5
  title?: string;
6
6
  menu?: MenuItem[];
7
7
  siteUrl?: string;
8
+ routeMode?: 'hash' | 'path' | 'auto';
8
9
  };
9
10
  declare const Layout: import("svelte").Component<$$ComponentProps, {}, "">;
10
11
  type Layout = ReturnType<typeof Layout>;
@@ -35,7 +35,7 @@
35
35
  {/snippet}
36
36
  </PageHeader>
37
37
 
38
- {#if query.query.isLoading}
38
+ {#if query.isLoading}
39
39
  <Card.Root class="overflow-hidden border-border/40 shadow-sm">
40
40
  <Card.Content class="p-0">
41
41
  {#each showFields.slice(0, 6) as _, i}
@@ -46,11 +46,11 @@
46
46
  {/each}
47
47
  </Card.Content>
48
48
  </Card.Root>
49
- {:else if query.query.data?.data}
49
+ {:else if query.data?.data}
50
50
  <Card.Root class="overflow-hidden border-border/40 shadow-sm">
51
51
  <Card.Content class="p-0">
52
52
  {#each showFields as field, i}
53
- {@const value = (query.query.data!.data as Record<string, unknown>)[field.key]}
53
+ {@const value = (query.data!.data as Record<string, unknown>)[field.key]}
54
54
  {@const DisplayComponent = getDisplayComponent(field.type)}
55
55
  <div class="flex flex-col sm:flex-row px-4 sm:px-6 py-3 sm:py-4 {i % 2 === 1 ? 'bg-muted/20' : ''}">
56
56
  <div class="sm:w-1/3 text-xs sm:text-sm font-medium text-muted-foreground mb-1 sm:mb-0">{field.label}</div>
@@ -53,15 +53,17 @@
53
53
  return d;
54
54
  })());
55
55
 
56
+ const validator = $derived(deriveValidator(flatStepsFields));
57
+
56
58
  // ─── useStepsForm: form state + step navigation ──────────────────
57
59
  const form = useStepsForm({
58
60
  get resource() { return resourceName; },
59
61
  get action() { return mode; },
60
62
  get id() { return id; },
61
- defaultValues: defaults,
63
+ get defaultValues() { return defaults; },
62
64
  redirect: 'list',
63
65
  warnWhenUnsavedChanges: true,
64
- validate: deriveValidator(flatStepsFields),
66
+ get validate() { return validator; },
65
67
  });
66
68
 
67
69
  const totalSteps = $derived(steps.length);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/ui",
3
- "version": "0.29.4",
3
+ "version": "0.30.0",
4
4
  "description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -25,7 +25,7 @@
25
25
  "./components/*": "./dist/components/*"
26
26
  },
27
27
  "peerDependencies": {
28
- "@svadmin/core": "^0.20.3",
28
+ "@svadmin/core": "^0.21.0",
29
29
  "@tanstack/svelte-query": "^6.0.0",
30
30
  "highlight.js": "^11.11.1",
31
31
  "isomorphic-dompurify": "^3.7.1",