@svadmin/ui 0.40.2 → 0.40.4

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.
@@ -429,6 +429,11 @@
429
429
  });
430
430
 
431
431
  const selectedCount = $derived(Object.keys(tableRowSelection.current).length);
432
+
433
+ function isColumnVisible(columnId: string): boolean {
434
+ return tableColumnVisibility.current[columnId] !== false;
435
+ }
436
+
432
437
  const tableView = $derived.by(() => {
433
438
  void tableSorting.current;
434
439
  void tableColumnVisibility.current;
@@ -696,8 +701,9 @@
696
701
  <Table.Root>
697
702
  <Table.Header>
698
703
  {#each tableView.headerGroups as headerGroup, _i (_i)}
704
+ {@const visibleHeaders = headerGroup.headers.filter((header: Header<TableFeatures, BaseRecord, unknown>) => isColumnVisible(header.column.id))}
699
705
  <DraggableHeader
700
- columns={headerGroup.headers.map((header: Header<TableFeatures, BaseRecord, unknown>) => ({ id: header.column.id, header }))}
706
+ columns={visibleHeaders.map((header) => ({ id: header.column.id, header }))}
701
707
  resourceName={resourceName}
702
708
  onReorder={(newOrder) => columnOrderAtom.set(newOrder.map((column) => column.id))}
703
709
  >
@@ -744,11 +750,12 @@
744
750
  {#each tableView.rows as row, _i (_i)}
745
751
  {@const record = row.original}
746
752
  {@const id = record[primaryKey] as string | number}
753
+ {@const visibleCells = row_getVisibleCells(row).filter((cell) => isColumnVisible(cell.column.id))}
747
754
  <ContextMenu.Root>
748
755
  <ContextMenu.Trigger>
749
756
  {#snippet child({ props })}
750
757
  <Table.Row {...props} class="transition-all duration-300 border-b border-border/10 {row_getIsSelected(row) ? 'bg-primary/5' : 'hover:bg-muted/20'}">
751
- {#each row_getVisibleCells(row) as cell, _i (_i)}
758
+ {#each visibleCells as cell, _i (_i)}
752
759
  <Table.Cell>
753
760
  {#if cell.column.id === '_select'}
754
761
  <Checkbox
@@ -840,7 +847,7 @@
840
847
  </ContextMenu.Root>
841
848
  {#if expandedRowRender && rowIsExpanded(row.id)}
842
849
  <Table.Row class="bg-muted/10 border-b border-border/10 transition-all">
843
- <Table.Cell colspan={row_getVisibleCells(row).length}>
850
+ <Table.Cell colspan={visibleCells.length}>
844
851
  {@render expandedRowRender({ record })}
845
852
  </Table.Cell>
846
853
  </Table.Row>
@@ -913,7 +920,7 @@
913
920
  </div>
914
921
  <!-- Card fields -->
915
922
  <div class="space-y-2">
916
- {#each visibleFields.slice(0, 6) as field, _i (_i)}
923
+ {#each visibleFields.filter((field) => isColumnVisible(field.key)).slice(0, 6) as field, _i (_i)}
917
924
  {@const value = record[field.key]}
918
925
  <div class="flex items-start justify-between gap-4">
919
926
  <span class="text-xs font-medium text-muted-foreground shrink-0">{field.label}</span>
@@ -959,11 +966,11 @@
959
966
 
960
967
  <!-- Pagination (shadcn) -->
961
968
  {#if totalPages > 0}
962
- <div class="flex flex-col sm:flex-row items-center justify-between gap-2 text-sm text-muted-foreground">
963
- <div class="flex items-center gap-2">
964
- <span>{i18n.t('common.total', { total: query.data?.total ?? 0 })}</span>
969
+ <div class="flex flex-col sm:flex-row items-center justify-between gap-3 px-1 py-2 text-sm text-muted-foreground">
970
+ <div class="flex shrink-0 items-center gap-3 leading-8">
971
+ <span class="shrink-0 whitespace-nowrap">{i18n.t('common.total', { total: query.data?.total ?? 0 })}</span>
965
972
  <select
966
- class="h-8 w-[70px] px-1 py-1 flex items-center justify-between rounded-md border border-input bg-background text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
973
+ class="h-8 w-[70px] shrink-0 px-1 py-1 flex items-center justify-between rounded-md border border-input bg-background text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
967
974
  value={String(pagination.pageSize ?? 10)}
968
975
  onchange={(e) => {
969
976
  const size = Number((e.target as HTMLSelectElement).value);
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { captureAdminContext, getResources, canAccessAsync } from '@svadmin/core';
2
+ import { captureAdminContext, getResources, canAccessAsync, getAccessControlProvider } from '@svadmin/core';
3
3
  import type { Identity, MenuItem } from '@svadmin/core';
4
4
  import { getPath } from '../router-state.svelte.js';
5
5
  import { useTranslation } from '@svadmin/core/i18n';
@@ -103,6 +103,62 @@
103
103
  items: NavItem[];
104
104
  }
105
105
 
106
+ async function hasCustomMenuAccess(menuItem: MenuItem): Promise<boolean> {
107
+ const resource = menuItem.meta?.resource;
108
+ if (!resource) return true;
109
+
110
+ try {
111
+ const { can } = await canAccessAsync(resource, menuItem.meta?.action ?? 'list');
112
+ return can;
113
+ } catch {
114
+ // Permission-provider failures must not expose protected navigation.
115
+ return false;
116
+ }
117
+ }
118
+
119
+ function isVisibleCustomMenuItem(candidate: MenuItem | null): candidate is MenuItem {
120
+ return candidate !== null;
121
+ }
122
+
123
+ function snapshotCustomMenuItems(menuItems: MenuItem[]): MenuItem[] {
124
+ return menuItems.map((menuItem) => ({
125
+ ...menuItem,
126
+ meta: menuItem.meta ? { ...menuItem.meta } : undefined,
127
+ children: menuItem.children ? snapshotCustomMenuItems(menuItem.children) : undefined,
128
+ }));
129
+ }
130
+
131
+ async function visibleCustomMenuItem(menuItem: MenuItem): Promise<MenuItem | null> {
132
+ if (menuItem.meta?.hidden || !await hasCustomMenuAccess(menuItem)) return null;
133
+ if (!menuItem.children) return menuItem;
134
+
135
+ const children = await visibleCustomMenuItems(menuItem.children);
136
+ if (children.length === 0 && !menuItem.href) return null;
137
+ return { ...menuItem, children };
138
+ }
139
+
140
+ async function visibleCustomMenuItems(menuItems: MenuItem[]): Promise<MenuItem[]> {
141
+ const filteredItems = await Promise.all(menuItems.map(visibleCustomMenuItem));
142
+ return filteredItems.filter(isVisibleCustomMenuItem);
143
+ }
144
+
145
+ let customMenuItems = $state.raw<MenuItem[]>([]);
146
+
147
+ $effect(() => {
148
+ const configuredMenu = menu?.length ? snapshotCustomMenuItems(menu) : [];
149
+ // Provider swaps must invalidate checks nested below public menu groups.
150
+ getAccessControlProvider();
151
+ customMenuItems = [];
152
+ if (configuredMenu.length === 0) return;
153
+
154
+ let cancelled = false;
155
+ void visibleCustomMenuItems(configuredMenu).then((visibleItems) => {
156
+ if (!cancelled) customMenuItems = visibleItems;
157
+ });
158
+
159
+ return () => { cancelled = true; };
160
+ });
161
+
106
162
  let navItems = $state.raw<NavItem[]>([]);
107
163
 
108
164
  $effect(() => {
@@ -239,7 +295,7 @@
239
295
  <nav aria-label="Main menu" class="pb-4" class:px-[10px]={!collapsed} class:px-2={collapsed}>
240
296
  {#if menu && menu.length > 0}
241
297
  <div class="space-y-[2px]">
242
- {#each menu as item, _i (_i)}
298
+ {#each customMenuItems as item (item.name)}
243
299
  <SidebarItem {item} currentPath={path} {collapsed} depth={0} />
244
300
  {/each}
245
301
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/ui",
3
- "version": "0.40.2",
3
+ "version": "0.40.4",
4
4
  "description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -39,11 +39,11 @@
39
39
  "./components/*": "./dist/components/*"
40
40
  },
41
41
  "peerDependencies": {
42
- "@svadmin/core": "^0.34.1",
42
+ "@svadmin/core": "^0.34.2",
43
43
  "@tanstack/svelte-query": "^6.1.38",
44
44
  "highlight.js": "^11.11.1",
45
- "isomorphic-dompurify": "^3.20.0",
46
- "marked": "^18.0.7",
45
+ "isomorphic-dompurify": "^3.22.0",
46
+ "marked": "^18.0.9",
47
47
  "marked-highlight": "^2.2.4",
48
48
  "svelte": "^5.56.8"
49
49
  },
@@ -75,15 +75,15 @@
75
75
  },
76
76
  "dependencies": {
77
77
  "@floating-ui/dom": "^1.8.0",
78
- "@lucide/svelte": "^1.28.0",
79
- "@tanstack/svelte-store": "^0.12.0",
80
- "@tanstack/svelte-table": "9.0.0",
81
- "@tanstack/table-core": "9.0.0",
78
+ "@lucide/svelte": "^1.30.0",
79
+ "@tanstack/svelte-store": "^0.12.1",
80
+ "@tanstack/svelte-table": "9.1.2",
81
+ "@tanstack/table-core": "9.1.2",
82
82
  "bits-ui": "^2.18.1",
83
83
  "clsx": "^2.1.1",
84
84
  "svelte-sonner": "^1.1.1",
85
85
  "tailwind-merge": "^3.6.0",
86
- "tailwind-variants": "^3.3.0"
86
+ "tailwind-variants": "^3.3.1"
87
87
  },
88
88
  "license": "MIT",
89
89
  "author": "zuohuadong",
@@ -97,12 +97,12 @@
97
97
  "url": "https://github.com/zuohuadong/svadmin/issues"
98
98
  },
99
99
  "devDependencies": {
100
- "@happy-dom/global-registrator": "^20.11.1",
101
- "@sveltejs/vite-plugin-svelte": "^7.2.0",
100
+ "@happy-dom/global-registrator": "^20.11.2",
101
+ "@sveltejs/vite-plugin-svelte": "^7.3.0",
102
102
  "@testing-library/dom": "^10.4.1",
103
103
  "@testing-library/svelte": "^5.4.2",
104
- "@testing-library/user-event": "^14.6.1",
105
- "happy-dom": "^20.11.1",
104
+ "@testing-library/user-event": "^14.6.3",
105
+ "happy-dom": "^20.11.2",
106
106
  "vitest": "^4.1.10"
107
107
  },
108
108
  "scripts": {