@svadmin/ui 0.57.0 → 0.58.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/AICommandBar.svelte +33 -10
  2. package/dist/components/AdminApp.svelte +16 -5
  3. package/dist/components/AutoForm.svelte +4 -4
  4. package/dist/components/AutoTable.svelte +866 -204
  5. package/dist/components/AutoTable.svelte.d.ts +1 -1
  6. package/dist/components/CommandPalette.svelte +39 -17
  7. package/dist/components/ConfirmDialog.svelte +4 -3
  8. package/dist/components/ConfirmDialog.svelte.d.ts +1 -0
  9. package/dist/components/CopilotPanel.svelte +3 -3
  10. package/dist/components/DraggableHeader.svelte +15 -36
  11. package/dist/components/DraggableHeader.svelte.d.ts +13 -4
  12. package/dist/components/EditPage.svelte +4 -4
  13. package/dist/components/ListPage.svelte +4 -12
  14. package/dist/components/ListPage.svelte.d.ts +1 -1
  15. package/dist/components/RecordDetailDrawer.svelte +121 -0
  16. package/dist/components/RecordDetailDrawer.svelte.d.ts +9 -0
  17. package/dist/components/ResourceAccessGuard.svelte +68 -0
  18. package/dist/components/ResourceAccessGuard.svelte.d.ts +12 -0
  19. package/dist/components/ResourceOperationsPage.svelte +8 -3
  20. package/dist/components/ShowPage.svelte +5 -6
  21. package/dist/components/TenantSwitcher.svelte +1 -0
  22. package/dist/components/Toast.svelte +35 -1
  23. package/dist/components/UndoableNotification.svelte +12 -6
  24. package/dist/components/UndoableNotification.svelte.d.ts +2 -0
  25. package/dist/components/buttons/CloneButton.svelte +9 -2
  26. package/dist/components/buttons/CreateButton.svelte +9 -2
  27. package/dist/components/buttons/DeleteButton.svelte +9 -2
  28. package/dist/components/buttons/EditButton.svelte +9 -2
  29. package/dist/components/buttons/ListButton.svelte +1 -1
  30. package/dist/components/buttons/ShowButton.svelte +9 -2
  31. package/dist/components/saved-list-views.d.ts +35 -0
  32. package/dist/components/saved-list-views.js +168 -0
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.js +2 -0
  35. package/package.json +2 -2
@@ -31,6 +31,7 @@
31
31
  <DropdownMenu.Trigger>
32
32
  {#snippet child({ props }: { props: Record<string, unknown> })}
33
33
  <button
34
+ type="button"
34
35
  {...props}
35
36
  class="flex w-full items-center gap-3 rounded-lg px-3 py-2 text-left transition-colors hover:bg-sidebar-accent/50
36
37
  {collapsed ? 'justify-center' : ''}"
@@ -1,8 +1,17 @@
1
1
  <script lang="ts">
2
2
  import { onMount } from 'svelte';
3
3
  import { Toaster, toast as sonner, type ToasterProps } from 'svelte-sonner';
4
- import { getResolvedTheme, getToastQueue, consumeToastQueue, getPromiseQueue, consumePromiseQueue } from '@svadmin/core';
4
+ import {
5
+ consumePromiseQueue,
6
+ consumeToastQueue,
7
+ getPromiseQueue,
8
+ getResolvedTheme,
9
+ getToastQueue,
10
+ getToasts,
11
+ removeToast,
12
+ } from '@svadmin/core';
5
13
  import { registerToastHost, type ToastHostRegistration } from './toast-host.svelte.js';
14
+ import UndoableNotification from './UndoableNotification.svelte';
6
15
 
7
16
  let host = $state<ToastHostRegistration | null>(null);
8
17
  const isActiveHost = $derived(host?.isActive() ?? false);
@@ -56,8 +65,33 @@
56
65
  },
57
66
  }
58
67
  } satisfies ToasterProps);
68
+
69
+ function undo(id: number, callback?: () => void): void {
70
+ removeToast(id);
71
+ callback?.();
72
+ }
73
+
74
+ function commit(id: number, callback?: () => void): void {
75
+ removeToast(id);
76
+ callback?.();
77
+ }
59
78
  </script>
60
79
 
61
80
  {#if isActiveHost}
62
81
  <Toaster {...toasterProps} />
82
+ <div
83
+ class="pointer-events-none fixed bottom-4 left-4 right-4 z-[100] flex flex-col-reverse gap-2 sm:bottom-6 sm:left-1/2 sm:right-auto sm:w-[min(32rem,calc(100vw-3rem))] sm:-translate-x-1/2"
84
+ data-svadmin-undo-stack
85
+ >
86
+ {#each getToasts().slice(-3) as item (item.id)}
87
+ <UndoableNotification
88
+ message={item.message}
89
+ duration={item.duration}
90
+ embedded
91
+ managedExternally
92
+ onUndo={() => undo(item.id, item.onUndo)}
93
+ onTimeout={() => commit(item.id, item.onTimeout)}
94
+ />
95
+ {/each}
96
+ </div>
63
97
  {/if}
@@ -8,9 +8,11 @@
8
8
 
9
9
  const i18n = useTranslation();
10
10
 
11
- let { message, duration = 5000, onUndo, onTimeout } = $props<{
11
+ let { message, duration = 5000, embedded = false, managedExternally = false, onUndo, onTimeout } = $props<{
12
12
  message: string;
13
13
  duration?: number;
14
+ embedded?: boolean;
15
+ managedExternally?: boolean;
14
16
  onUndo: () => void;
15
17
  onTimeout: () => void;
16
18
  }>();
@@ -27,7 +29,7 @@
27
29
  if (remaining <= 0) {
28
30
  remaining = 0;
29
31
  clearInterval(interval);
30
- if (!dismissed) {
32
+ if (!dismissed && !managedExternally) {
31
33
  dismissed = true;
32
34
  onTimeout();
33
35
  }
@@ -51,17 +53,21 @@
51
53
 
52
54
  {#if !dismissed}
53
55
  <div
54
- class="fixed bottom-6 left-1/2 -translate-x-1/2 z-[100] min-w-80 max-w-lg rounded-lg border bg-card shadow-lg overflow-hidden"
56
+ class={embedded
57
+ ? 'pointer-events-auto relative w-full overflow-hidden rounded-lg border bg-card shadow-lg'
58
+ : 'fixed bottom-4 left-4 right-4 z-[100] overflow-hidden rounded-lg border bg-card shadow-lg sm:bottom-6 sm:left-1/2 sm:right-auto sm:w-[min(32rem,calc(100vw-3rem))] sm:-translate-x-1/2'}
59
+ role="status"
60
+ aria-live="polite"
55
61
  transition:slide={{ duration: 250 }}
56
62
  >
57
- <div class="flex items-center justify-between px-4 py-3 gap-3">
58
- <p class="text-sm text-foreground">{message}</p>
63
+ <div class="flex items-center justify-between gap-3 px-4 py-3">
64
+ <p class="min-w-0 break-words text-sm text-foreground">{message}</p>
59
65
  <div class="flex items-center gap-1 flex-shrink-0">
60
66
  <Button variant="ghost" size="sm" onclick={handleUndo} class="font-semibold text-primary">
61
67
  <Undo2 class="h-3.5 w-3.5 mr-1" />
62
68
  {i18n.t('common.undo')}
63
69
  </Button>
64
- <Button variant="ghost" size="icon" class="h-7 w-7" onclick={handleDismiss}>
70
+ <Button variant="ghost" size="icon" class="h-7 w-7" onclick={handleDismiss} aria-label={i18n.t('common.close')}>
65
71
  <X class="h-3.5 w-3.5" />
66
72
  </Button>
67
73
  </div>
@@ -1,6 +1,8 @@
1
1
  type $$ComponentProps = {
2
2
  message: string;
3
3
  duration?: number;
4
+ embedded?: boolean;
5
+ managedExternally?: boolean;
4
6
  onUndo: () => void;
5
7
  onTimeout: () => void;
6
8
  };
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import { useNavigation, useCan, useTranslation } from '@svadmin/core';
3
+ import { getResource, useNavigation, useCan, useTranslation } from '@svadmin/core';
4
4
  import { Button } from '../ui/button/index.js';
5
5
  import { Copy } from '@lucide/svelte';
6
6
  import type { ButtonAccessControl } from './access-control';
@@ -27,6 +27,13 @@
27
27
  }>();
28
28
 
29
29
  const nav = useNavigation();
30
+ const resourceDefinition = $derived.by(() => {
31
+ try {
32
+ return getResource(resource);
33
+ } catch {
34
+ return null;
35
+ }
36
+ });
30
37
  const can = useCan(() => ({
31
38
  resource,
32
39
  action: 'create',
@@ -34,8 +41,8 @@
34
41
  meta: accessControl?.meta,
35
42
  queryOptions: { enabled: accessControl?.enabled ?? true }
36
43
  }));
37
- const hidden = $derived(accessControl?.hideIfUnauthorized && !can.allowed);
38
44
  const displayText = $derived(label ?? i18n.t('common.clone'));
45
+ const hidden = $derived(resourceDefinition?.canCreate === false || (accessControl?.hideIfUnauthorized && !can.allowed));
39
46
  </script>
40
47
 
41
48
  {#if !hidden}
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import { useNavigation, useCan, useTranslation } from '@svadmin/core';
3
+ import { getResource, useNavigation, useCan, useTranslation } from '@svadmin/core';
4
4
  import { Button } from '../ui/button/index.js';
5
5
  import { Plus } from '@lucide/svelte';
6
6
  import type { ButtonAccessControl } from './access-control';
@@ -24,6 +24,13 @@
24
24
  }>();
25
25
 
26
26
  const nav = useNavigation();
27
+ const resourceDefinition = $derived.by(() => {
28
+ try {
29
+ return getResource(resource);
30
+ } catch {
31
+ return null;
32
+ }
33
+ });
27
34
  const can = useCan(() => ({
28
35
  resource,
29
36
  action: 'create',
@@ -31,8 +38,8 @@
31
38
  meta: accessControl?.meta,
32
39
  queryOptions: { enabled: accessControl?.enabled ?? true }
33
40
  }));
34
- const hidden = $derived(accessControl?.hideIfUnauthorized && !can.allowed);
35
41
  const displayText = $derived(label ?? i18n.t('common.create'));
42
+ const hidden = $derived(resourceDefinition?.canCreate === false || (accessControl?.hideIfUnauthorized && !can.allowed));
36
43
  </script>
37
44
 
38
45
  {#if !hidden}
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import { useDelete, useCan, useTranslation } from '@svadmin/core';
3
+ import { getResource, useDelete, useCan, useTranslation } from '@svadmin/core';
4
4
  import { Button } from '../ui/button/index.js';
5
5
  import { Trash2 } from '@lucide/svelte';
6
6
  import type { ButtonAccessControl } from './access-control';
@@ -31,6 +31,13 @@
31
31
  }>();
32
32
 
33
33
  const deleteMut = useDelete({ get resource() { return resource; }, get mutationMode() { return undoable ? 'undoable' as const : 'pessimistic' as const; } });
34
+ const resourceDefinition = $derived.by(() => {
35
+ try {
36
+ return getResource(resource);
37
+ } catch {
38
+ return null;
39
+ }
40
+ });
34
41
  const can = useCan(() => ({
35
42
  resource,
36
43
  action: 'delete',
@@ -38,8 +45,8 @@
38
45
  meta: accessControl?.meta,
39
46
  queryOptions: { enabled: accessControl?.enabled ?? true }
40
47
  }));
41
- const hidden = $derived(accessControl?.hideIfUnauthorized && !can.allowed);
42
48
  const displayText = $derived(label ?? i18n.t('common.delete'));
49
+ const hidden = $derived(resourceDefinition?.canDelete === false || (accessControl?.hideIfUnauthorized && !can.allowed));
43
50
  let confirming = $state(false);
44
51
 
45
52
  async function handleDelete() {
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import { useNavigation, useCan, useTranslation } from '@svadmin/core';
3
+ import { getResource, useNavigation, useCan, useTranslation } from '@svadmin/core';
4
4
  import { Button } from '../ui/button/index.js';
5
5
  import { Pencil } from '@lucide/svelte';
6
6
  import type { ButtonAccessControl } from './access-control';
@@ -27,6 +27,13 @@
27
27
  }>();
28
28
 
29
29
  const nav = useNavigation();
30
+ const resourceDefinition = $derived.by(() => {
31
+ try {
32
+ return getResource(resource);
33
+ } catch {
34
+ return null;
35
+ }
36
+ });
30
37
  const can = useCan(() => ({
31
38
  resource,
32
39
  action: 'edit',
@@ -34,8 +41,8 @@
34
41
  meta: accessControl?.meta,
35
42
  queryOptions: { enabled: accessControl?.enabled ?? true }
36
43
  }));
37
- const hidden = $derived(accessControl?.hideIfUnauthorized && !can.allowed);
38
44
  const displayText = $derived(label ?? i18n.t('common.edit'));
45
+ const hidden = $derived(resourceDefinition?.canEdit === false || (accessControl?.hideIfUnauthorized && !can.allowed));
39
46
  </script>
40
47
 
41
48
  {#if !hidden}
@@ -19,7 +19,7 @@
19
19
  const res = $derived((() => {
20
20
  try { return getResource(resourceName); } catch { return null; }
21
21
  })());
22
- const displayLabel = $derived(label ?? res?.label ?? i18n.t('common.list') ?? resourceName);
22
+ const displayLabel = $derived(label ?? (hideText ? i18n.t('common.backToList') : (res?.label ?? i18n.t('common.list') ?? resourceName)));
23
23
 
24
24
  function navigateToList() {
25
25
  const navigate = () => nav.list(resourceName);
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import { useNavigation, useCan, useTranslation } from '@svadmin/core';
3
+ import { getResource, useNavigation, useCan, useTranslation } from '@svadmin/core';
4
4
  import { Button } from '../ui/button/index.js';
5
5
  import { Eye } from '@lucide/svelte';
6
6
  import type { ButtonAccessControl } from './access-control';
@@ -29,6 +29,13 @@
29
29
  }>();
30
30
 
31
31
  const nav = useNavigation();
32
+ const resourceDefinition = $derived.by(() => {
33
+ try {
34
+ return getResource(resource);
35
+ } catch {
36
+ return null;
37
+ }
38
+ });
32
39
  const can = useCan(() => ({
33
40
  resource,
34
41
  action: 'show',
@@ -36,7 +43,7 @@
36
43
  meta: accessControl?.meta,
37
44
  queryOptions: { enabled: accessControl?.enabled ?? true }
38
45
  }));
39
- const hidden = $derived(accessControl?.hideIfUnauthorized && !can.allowed);
46
+ const hidden = $derived(resourceDefinition?.canShow === false || (accessControl?.hideIfUnauthorized && !can.allowed));
40
47
  const displayText = $derived(label ?? i18n.t('common.detail'));
41
48
 
42
49
  function navigateToRecord() {
@@ -0,0 +1,35 @@
1
+ import type { Filter, Sort } from '@svadmin/core';
2
+ export interface SavedListViewState {
3
+ search: string;
4
+ filters: Filter[];
5
+ sorters: Sort[];
6
+ pagination: {
7
+ current: number;
8
+ pageSize: number;
9
+ };
10
+ columnVisibility: Record<string, boolean>;
11
+ columnOrder: string[];
12
+ }
13
+ export interface SavedListView {
14
+ id: string;
15
+ name: string;
16
+ state: SavedListViewState;
17
+ }
18
+ export interface ListPreferenceScope {
19
+ resourceName: string;
20
+ providerName: string;
21
+ tenantIdentity?: string | number;
22
+ }
23
+ export declare function readSavedListViews(raw: string | null, allowedColumnIds: Set<string>): SavedListView[];
24
+ export declare function serializeSavedListViews(views: SavedListView[]): string;
25
+ export declare function cloneSavedListViewState(state: SavedListViewState): SavedListViewState;
26
+ export declare function listPreferenceScopeId(scope: ListPreferenceScope): string;
27
+ export declare function canMigrateLegacyListPreferences(scope: ListPreferenceScope): boolean;
28
+ export declare function savedListViewsStorageKey(scope: ListPreferenceScope): string;
29
+ export declare function activeSavedListViewStorageKey(scope: ListPreferenceScope): string;
30
+ export declare function columnVisibilityStorageKey(scope: ListPreferenceScope): string;
31
+ export declare function columnOrderStorageKey(scope: ListPreferenceScope): string;
32
+ export declare function legacySavedListViewsStorageKey(resourceName: string): string;
33
+ export declare function legacyActiveSavedListViewStorageKey(resourceName: string): string;
34
+ export declare function legacyColumnVisibilityStorageKey(resourceName: string): string;
35
+ export declare function legacyColumnOrderStorageKey(resourceName: string): string;
@@ -0,0 +1,168 @@
1
+ const STORAGE_VERSION = 1;
2
+ const MAX_SAVED_VIEWS = 25;
3
+ const MAX_FILTER_NODES = 200;
4
+ const CRUD_OPERATORS = new Set([
5
+ 'eq', 'ne', 'lt', 'gt', 'lte', 'gte',
6
+ 'contains', 'ncontains', 'startswith', 'endswith',
7
+ 'in', 'nin', 'null', 'nnull', 'between', 'nbetween',
8
+ ]);
9
+ function isRecord(value) {
10
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
11
+ }
12
+ function isPositiveInteger(value, max) {
13
+ return typeof value === 'number' && Number.isInteger(value) && value > 0 && value <= max;
14
+ }
15
+ function parseFilter(value, allowedFieldIds, budget, depth = 0) {
16
+ if (depth > 20 || budget.remaining-- <= 0 || !isRecord(value))
17
+ return undefined;
18
+ if (typeof value.field === 'string') {
19
+ if (!allowedFieldIds.has(value.field) || typeof value.operator !== 'string' || !CRUD_OPERATORS.has(value.operator) || !('value' in value)) {
20
+ return undefined;
21
+ }
22
+ return {
23
+ field: value.field,
24
+ operator: value.operator,
25
+ value: value.value,
26
+ };
27
+ }
28
+ if ((value.operator !== 'and' && value.operator !== 'or') || !Array.isArray(value.value))
29
+ return undefined;
30
+ const children = value.value.map((entry) => parseFilter(entry, allowedFieldIds, budget, depth + 1));
31
+ if (children.some((entry) => entry === undefined))
32
+ return undefined;
33
+ return { operator: value.operator, value: children };
34
+ }
35
+ function parseSorters(value, allowedFieldIds) {
36
+ const sorters = [];
37
+ for (const sorter of value) {
38
+ if (!isRecord(sorter) || typeof sorter.field !== 'string' || !allowedFieldIds.has(sorter.field))
39
+ return undefined;
40
+ if (sorter.order !== 'asc' && sorter.order !== 'desc')
41
+ return undefined;
42
+ sorters.push({ field: sorter.field, order: sorter.order });
43
+ }
44
+ return sorters;
45
+ }
46
+ function parseColumnVisibility(value, allowedColumnIds) {
47
+ if (!isRecord(value))
48
+ return undefined;
49
+ const visibility = {};
50
+ for (const [columnId, visible] of Object.entries(value)) {
51
+ if (!allowedColumnIds.has(columnId) || typeof visible !== 'boolean')
52
+ continue;
53
+ visibility[columnId] = visible;
54
+ }
55
+ return visibility;
56
+ }
57
+ function parseColumnOrder(value, allowedColumnIds) {
58
+ if (!Array.isArray(value))
59
+ return undefined;
60
+ return [...new Set(value.filter((columnId) => (typeof columnId === 'string' && allowedColumnIds.has(columnId))))];
61
+ }
62
+ function parseState(value, allowedColumnIds) {
63
+ if (!isRecord(value))
64
+ return undefined;
65
+ if (typeof value.search !== 'string' || value.search.length > 1_000)
66
+ return undefined;
67
+ if (!Array.isArray(value.filters) || !Array.isArray(value.sorters))
68
+ return undefined;
69
+ if (!isRecord(value.pagination) || !isPositiveInteger(value.pagination.current, 1_000_000) || !isPositiveInteger(value.pagination.pageSize, 1_000)) {
70
+ return undefined;
71
+ }
72
+ const allowedFieldIds = new Set([...allowedColumnIds].filter((columnId) => !columnId.startsWith('_')));
73
+ const filterBudget = { remaining: MAX_FILTER_NODES };
74
+ const filters = value.filters.map((entry) => parseFilter(entry, allowedFieldIds, filterBudget));
75
+ if (filters.some((entry) => entry === undefined))
76
+ return undefined;
77
+ const sorters = parseSorters(value.sorters, allowedFieldIds);
78
+ const columnVisibility = parseColumnVisibility(value.columnVisibility, allowedColumnIds);
79
+ const columnOrder = parseColumnOrder(value.columnOrder, allowedColumnIds);
80
+ if (!sorters || !columnVisibility || !columnOrder)
81
+ return undefined;
82
+ return {
83
+ search: value.search,
84
+ filters: filters,
85
+ sorters,
86
+ pagination: {
87
+ current: value.pagination.current,
88
+ pageSize: value.pagination.pageSize,
89
+ },
90
+ columnVisibility,
91
+ columnOrder,
92
+ };
93
+ }
94
+ export function readSavedListViews(raw, allowedColumnIds) {
95
+ if (!raw)
96
+ return [];
97
+ try {
98
+ const parsed = JSON.parse(raw);
99
+ if (!isRecord(parsed) || parsed.version !== STORAGE_VERSION || !Array.isArray(parsed.views))
100
+ return [];
101
+ const views = [];
102
+ const seenIds = new Set();
103
+ for (const entry of parsed.views.slice(0, MAX_SAVED_VIEWS)) {
104
+ if (!isRecord(entry) || typeof entry.id !== 'string' || !entry.id.trim() || entry.id.length > 120)
105
+ continue;
106
+ if (seenIds.has(entry.id))
107
+ continue;
108
+ if (typeof entry.name !== 'string' || !entry.name.trim() || entry.name.length > 60)
109
+ continue;
110
+ const state = parseState(entry.state, allowedColumnIds);
111
+ if (!state)
112
+ continue;
113
+ seenIds.add(entry.id);
114
+ views.push({ id: entry.id, name: entry.name.trim(), state });
115
+ }
116
+ return views;
117
+ }
118
+ catch (error) {
119
+ if (error instanceof SyntaxError)
120
+ return [];
121
+ throw error;
122
+ }
123
+ }
124
+ export function serializeSavedListViews(views) {
125
+ return JSON.stringify({ version: STORAGE_VERSION, views: views.slice(0, MAX_SAVED_VIEWS) });
126
+ }
127
+ export function cloneSavedListViewState(state) {
128
+ return JSON.parse(JSON.stringify(state));
129
+ }
130
+ function encodeScopeValue(value) {
131
+ if (value === undefined)
132
+ return 'u';
133
+ return `${typeof value === 'number' ? 'n' : 's'}:${encodeURIComponent(String(value))}`;
134
+ }
135
+ export function listPreferenceScopeId(scope) {
136
+ return [
137
+ `r:${encodeURIComponent(scope.resourceName)}`,
138
+ `p:${encodeURIComponent(scope.providerName)}`,
139
+ `t:${encodeScopeValue(scope.tenantIdentity)}`,
140
+ ].join('|');
141
+ }
142
+ export function canMigrateLegacyListPreferences(scope) {
143
+ return scope.providerName === 'default' && scope.tenantIdentity === undefined;
144
+ }
145
+ export function savedListViewsStorageKey(scope) {
146
+ return `svadmin-list-views-v2-${listPreferenceScopeId(scope)}`;
147
+ }
148
+ export function activeSavedListViewStorageKey(scope) {
149
+ return `svadmin-list-view-active-v2-${listPreferenceScopeId(scope)}`;
150
+ }
151
+ export function columnVisibilityStorageKey(scope) {
152
+ return `svadmin-columns-v2-${listPreferenceScopeId(scope)}`;
153
+ }
154
+ export function columnOrderStorageKey(scope) {
155
+ return `svadmin-colorder-v2-${listPreferenceScopeId(scope)}`;
156
+ }
157
+ export function legacySavedListViewsStorageKey(resourceName) {
158
+ return `svadmin-list-views-${resourceName}`;
159
+ }
160
+ export function legacyActiveSavedListViewStorageKey(resourceName) {
161
+ return `svadmin-list-view-active-${resourceName}`;
162
+ }
163
+ export function legacyColumnVisibilityStorageKey(resourceName) {
164
+ return `svadmin-columns-${resourceName}`;
165
+ }
166
+ export function legacyColumnOrderStorageKey(resourceName) {
167
+ return `svadmin-colorder-${resourceName}`;
168
+ }
package/dist/index.d.ts CHANGED
@@ -15,6 +15,8 @@ export { default as EmptyState } from './components/EmptyState.svelte';
15
15
  export { default as StatsCard } from './components/StatsCard.svelte';
16
16
  export { default as PageHeader } from './components/PageHeader.svelte';
17
17
  export { default as ResourceOperationsPage } from './components/ResourceOperationsPage.svelte';
18
+ export { default as RecordDetailDrawer } from './components/RecordDetailDrawer.svelte';
19
+ export { default as ResourceAccessGuard } from './components/ResourceAccessGuard.svelte';
18
20
  export { default as LoginPage } from './components/LoginPage.svelte';
19
21
  export { default as AuthPageShell } from './components/AuthPageShell.svelte';
20
22
  export { default as RegisterPage } from './components/RegisterPage.svelte';
package/dist/index.js CHANGED
@@ -18,6 +18,8 @@ export { default as EmptyState } from './components/EmptyState.svelte';
18
18
  export { default as StatsCard } from './components/StatsCard.svelte';
19
19
  export { default as PageHeader } from './components/PageHeader.svelte';
20
20
  export { default as ResourceOperationsPage } from './components/ResourceOperationsPage.svelte';
21
+ export { default as RecordDetailDrawer } from './components/RecordDetailDrawer.svelte';
22
+ export { default as ResourceAccessGuard } from './components/ResourceAccessGuard.svelte';
21
23
  export { default as LoginPage } from './components/LoginPage.svelte';
22
24
  export { default as AuthPageShell } from './components/AuthPageShell.svelte';
23
25
  export { default as RegisterPage } from './components/RegisterPage.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/ui",
3
- "version": "0.57.0",
3
+ "version": "0.58.0",
4
4
  "description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -39,7 +39,7 @@
39
39
  "./components/*": "./dist/components/*"
40
40
  },
41
41
  "peerDependencies": {
42
- "@svadmin/core": "^0.41.1",
42
+ "@svadmin/core": "^0.42.0",
43
43
  "@tanstack/svelte-query": "^6.1.38",
44
44
  "highlight.js": "^11.11.1",
45
45
  "isomorphic-dompurify": "^3.22.0",