@svadmin/ui 0.92.0 → 0.93.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.
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { Button } from './ui/button/index.js';
|
|
4
4
|
import { Checkbox } from './ui/checkbox/index.js';
|
|
5
5
|
import * as Popover from './ui/popover/index.js';
|
|
6
|
-
import { SlidersHorizontal, RotateCcw, ArrowUp, ArrowDown, Search } from '@lucide/svelte';
|
|
6
|
+
import { SlidersHorizontal, RotateCcw, ArrowUp, ArrowDown, Search, Lock, Pin, PinOff } from '@lucide/svelte';
|
|
7
7
|
import { cn } from '../utils.js';
|
|
8
8
|
|
|
9
9
|
export interface ColumnItem {
|
|
@@ -11,12 +11,21 @@
|
|
|
11
11
|
label: string;
|
|
12
12
|
visible: boolean;
|
|
13
13
|
fixed?: 'left' | 'right';
|
|
14
|
+
/** 业务必显列。隐藏、预设和已保存偏好都不能把它关掉。 */
|
|
15
|
+
required?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ColumnPreset {
|
|
19
|
+
id: string;
|
|
20
|
+
label: string;
|
|
21
|
+
visibleKeys: string[];
|
|
14
22
|
}
|
|
15
23
|
|
|
16
24
|
interface Props {
|
|
17
25
|
columns: ColumnItem[];
|
|
18
26
|
onchange?: (columns: ColumnItem[]) => void;
|
|
19
27
|
onreset?: () => void;
|
|
28
|
+
presets?: ColumnPreset[];
|
|
20
29
|
storageKey?: string;
|
|
21
30
|
title?: string;
|
|
22
31
|
triggerVariant?: 'outline' | 'ghost' | 'secondary';
|
|
@@ -28,6 +37,7 @@
|
|
|
28
37
|
columns = $bindable([]),
|
|
29
38
|
onchange,
|
|
30
39
|
onreset,
|
|
40
|
+
presets = [],
|
|
31
41
|
storageKey,
|
|
32
42
|
title,
|
|
33
43
|
triggerVariant = 'outline',
|
|
@@ -41,6 +51,44 @@
|
|
|
41
51
|
|
|
42
52
|
// Snapshot initial configuration for reset
|
|
43
53
|
let initialSnapshot = $state<ColumnItem[]>(JSON.parse(JSON.stringify(columns)));
|
|
54
|
+
let storageHydrated = false;
|
|
55
|
+
|
|
56
|
+
// storageKey 之前只写不读;挂载后按 key 合并可见性、固定位和顺序,未知列仍按新定义保留。
|
|
57
|
+
$effect(() => {
|
|
58
|
+
if (storageHydrated || !storageKey || typeof window === 'undefined') return;
|
|
59
|
+
storageHydrated = true;
|
|
60
|
+
try {
|
|
61
|
+
const raw = window.localStorage.getItem(storageKey);
|
|
62
|
+
if (!raw) return;
|
|
63
|
+
const parsed: unknown = JSON.parse(raw);
|
|
64
|
+
if (!Array.isArray(parsed)) return;
|
|
65
|
+
const stored = new Map(parsed
|
|
66
|
+
.filter((entry): entry is Record<string, unknown> => !!entry && typeof entry === 'object')
|
|
67
|
+
.filter((entry) => typeof entry['key'] === 'string')
|
|
68
|
+
.map((entry) => [entry['key'] as string, entry]));
|
|
69
|
+
const next = columns.map((column): ColumnItem => {
|
|
70
|
+
const saved = stored.get(column.key);
|
|
71
|
+
if (!saved) return column;
|
|
72
|
+
const savedFixed = saved['fixed'];
|
|
73
|
+
const fixed = savedFixed === 'left' ? 'left' : savedFixed === 'right' ? 'right' : undefined;
|
|
74
|
+
const visible = column.required === true || saved['visible'] !== false;
|
|
75
|
+
if (fixed === undefined) return { ...column, visible };
|
|
76
|
+
return { ...column, visible, fixed };
|
|
77
|
+
});
|
|
78
|
+
const order = parsed
|
|
79
|
+
.map((entry) => (entry && typeof entry === 'object' ? (entry as Record<string, unknown>)['key'] : undefined))
|
|
80
|
+
.filter((key): key is string => typeof key === 'string');
|
|
81
|
+
next.sort((left, right) => {
|
|
82
|
+
const leftIndex = order.indexOf(left.key);
|
|
83
|
+
const rightIndex = order.indexOf(right.key);
|
|
84
|
+
return (leftIndex < 0 ? Number.MAX_SAFE_INTEGER : leftIndex)
|
|
85
|
+
- (rightIndex < 0 ? Number.MAX_SAFE_INTEGER : rightIndex);
|
|
86
|
+
});
|
|
87
|
+
columns = next;
|
|
88
|
+
} catch {
|
|
89
|
+
// 存储损坏时保持调用方传入的资源默认列。
|
|
90
|
+
}
|
|
91
|
+
});
|
|
44
92
|
|
|
45
93
|
const displayTitle = $derived(title ?? i18n.t('common.columnSettings', { defaultValue: 'Columns' }));
|
|
46
94
|
const resetLabel = $derived(i18n.t('common.reset', { defaultValue: 'Reset' }));
|
|
@@ -65,13 +113,48 @@
|
|
|
65
113
|
}
|
|
66
114
|
|
|
67
115
|
function toggleColumn(key: string) {
|
|
116
|
+
const target = columns.find((column) => column.key === key);
|
|
117
|
+
if (!target || target.required) return;
|
|
118
|
+
if (target.visible && columns.filter((column) => column.visible).length <= 1) return;
|
|
68
119
|
columns = columns.map((c) => (c.key === key ? { ...c, visible: !c.visible } : c));
|
|
69
120
|
savePreference(columns);
|
|
70
121
|
onchange?.(columns);
|
|
71
122
|
}
|
|
72
123
|
|
|
73
124
|
function toggleAll(checked: boolean) {
|
|
74
|
-
|
|
125
|
+
const hasRequired = columns.some((column) => column.required);
|
|
126
|
+
if (!checked && !hasRequired) {
|
|
127
|
+
// 至少保留一列,避免空表头无法自愈;保留第一列作为最小可见集。
|
|
128
|
+
columns = columns.map((column, index) => ({ ...column, visible: index === 0 }));
|
|
129
|
+
} else {
|
|
130
|
+
columns = columns.map((c) => (c.required ? { ...c, visible: true } : { ...c, visible: checked }));
|
|
131
|
+
}
|
|
132
|
+
savePreference(columns);
|
|
133
|
+
onchange?.(columns);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function toggleFixed(key: string) {
|
|
137
|
+
columns = columns.map((column) => {
|
|
138
|
+
if (column.key !== key) return column;
|
|
139
|
+
if (column.fixed) {
|
|
140
|
+
const { fixed: _fixed, ...rest } = column;
|
|
141
|
+
return rest;
|
|
142
|
+
}
|
|
143
|
+
return { ...column, fixed: 'left' as const };
|
|
144
|
+
});
|
|
145
|
+
savePreference(columns);
|
|
146
|
+
onchange?.(columns);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function applyPreset(preset: ColumnPreset) {
|
|
150
|
+
const visibleKeys = new Set(preset.visibleKeys);
|
|
151
|
+
columns = columns.map((column) => ({
|
|
152
|
+
...column,
|
|
153
|
+
visible: column.required === true || visibleKeys.has(column.key),
|
|
154
|
+
}));
|
|
155
|
+
if (!columns.some((column) => column.visible) && columns[0]) {
|
|
156
|
+
columns = columns.map((column, index) => ({ ...column, visible: index === 0 }));
|
|
157
|
+
}
|
|
75
158
|
savePreference(columns);
|
|
76
159
|
onchange?.(columns);
|
|
77
160
|
}
|
|
@@ -155,6 +238,20 @@
|
|
|
155
238
|
</div>
|
|
156
239
|
{/if}
|
|
157
240
|
|
|
241
|
+
{#if presets.length > 0}
|
|
242
|
+
<div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-77a2a20e90d4 svadmin-u-359090c2d529">
|
|
243
|
+
{#each presets as preset (preset.id)}
|
|
244
|
+
<Button
|
|
245
|
+
type="button"
|
|
246
|
+
variant="outline"
|
|
247
|
+
size="sm"
|
|
248
|
+
data-column-preset={preset.id}
|
|
249
|
+
onclick={() => applyPreset(preset)}
|
|
250
|
+
>{preset.label}</Button>
|
|
251
|
+
{/each}
|
|
252
|
+
</div>
|
|
253
|
+
{/if}
|
|
254
|
+
|
|
158
255
|
<!-- Column list with ordering -->
|
|
159
256
|
<div class="svadmin-u-50d0d216a2f8 svadmin-u-67d7e383dca6 svadmin-u-92bf82f493b1 svadmin-u-da7c36cd8867 svadmin-u-eda955402ba6">
|
|
160
257
|
{#each filteredColumns as col (col.key)}
|
|
@@ -163,14 +260,32 @@
|
|
|
163
260
|
<label class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-77a2a20e90d4 svadmin-u-34516836730d svadmin-u-7f6912283f11 svadmin-u-f283ea9bea0e svadmin-u-36e579c0b41c svadmin-u-aa2c13a5e1b4">
|
|
164
261
|
<Checkbox
|
|
165
262
|
checked={col.visible}
|
|
263
|
+
disabled={col.required}
|
|
264
|
+
aria-disabled={col.required ? 'true' : undefined}
|
|
265
|
+
data-column-required={col.required ? col.key : undefined}
|
|
166
266
|
onCheckedChange={() => toggleColumn(col.key)}
|
|
167
267
|
/>
|
|
168
268
|
<span class="svadmin-u-f283ea9bea0e svadmin-u-d4108abe6359 {col.visible ? '' : 'svadmin-u-bfa603190748 line-through svadmin-u-f2868c227fcd'}">
|
|
169
269
|
{col.label}
|
|
170
270
|
</span>
|
|
271
|
+
{#if col.required}
|
|
272
|
+
<Lock class="svadmin-u-6a60c09e6aaa svadmin-u-9cea05671a29" aria-label="Required column" data-column-required={col.key} />
|
|
273
|
+
{/if}
|
|
171
274
|
</label>
|
|
172
275
|
|
|
173
|
-
<div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-a3899220f90e
|
|
276
|
+
<div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-a3899220f90e">
|
|
277
|
+
<Button
|
|
278
|
+
variant="ghost"
|
|
279
|
+
size="icon"
|
|
280
|
+
class="svadmin-u-cd0d9c512cdc svadmin-u-72470489ff4e svadmin-u-bfa603190748 svadmin-u-ea7b2e9e070e"
|
|
281
|
+
aria-label={col.fixed ? 'Cancel fixed column' : 'Fix column left'}
|
|
282
|
+
aria-pressed={Boolean(col.fixed)}
|
|
283
|
+
data-column-fixed={col.fixed ?? 'none'}
|
|
284
|
+
onclick={() => toggleFixed(col.key)}
|
|
285
|
+
>
|
|
286
|
+
{#if col.fixed}<PinOff class="svadmin-u-6a60c09e6aaa svadmin-u-9cea05671a29" />{:else}<Pin class="svadmin-u-6a60c09e6aaa svadmin-u-9cea05671a29" />{/if}
|
|
287
|
+
</Button>
|
|
288
|
+
<div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-a3899220f90e svadmin-u-7065497e1ca0 svadmin-u-181f3d6c9821 svadmin-u-67d6184a0024">
|
|
174
289
|
<Button
|
|
175
290
|
variant="ghost"
|
|
176
291
|
size="icon"
|
|
@@ -191,6 +306,7 @@
|
|
|
191
306
|
>
|
|
192
307
|
<ArrowDown class="svadmin-u-6a60c09e6aaa svadmin-u-9cea05671a29" />
|
|
193
308
|
</Button>
|
|
309
|
+
</div>
|
|
194
310
|
</div>
|
|
195
311
|
</div>
|
|
196
312
|
{/each}
|
|
@@ -3,11 +3,19 @@ export interface ColumnItem {
|
|
|
3
3
|
label: string;
|
|
4
4
|
visible: boolean;
|
|
5
5
|
fixed?: 'left' | 'right';
|
|
6
|
+
/** 业务必显列。隐藏、预设和已保存偏好都不能把它关掉。 */
|
|
7
|
+
required?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface ColumnPreset {
|
|
10
|
+
id: string;
|
|
11
|
+
label: string;
|
|
12
|
+
visibleKeys: string[];
|
|
6
13
|
}
|
|
7
14
|
interface Props {
|
|
8
15
|
columns: ColumnItem[];
|
|
9
16
|
onchange?: (columns: ColumnItem[]) => void;
|
|
10
17
|
onreset?: () => void;
|
|
18
|
+
presets?: ColumnPreset[];
|
|
11
19
|
storageKey?: string;
|
|
12
20
|
title?: string;
|
|
13
21
|
triggerVariant?: 'outline' | 'ghost' | 'secondary';
|
package/dist/index.d.ts
CHANGED
|
@@ -92,7 +92,7 @@ export { default as FilterBuilder } from './components/FilterBuilder.svelte';
|
|
|
92
92
|
export type { FilterRuleItem } from './components/FilterBuilder.svelte';
|
|
93
93
|
export { default as Watermark } from './components/Watermark.svelte';
|
|
94
94
|
export { default as ColumnSettings } from './components/ColumnSettings.svelte';
|
|
95
|
-
export type { ColumnItem } from './components/ColumnSettings.svelte';
|
|
95
|
+
export type { ColumnItem, ColumnPreset } from './components/ColumnSettings.svelte';
|
|
96
96
|
export { default as UnsavedChangesPrompt } from './components/UnsavedChangesPrompt.svelte';
|
|
97
97
|
export { default as ImportWizard } from './components/ImportWizard.svelte';
|
|
98
98
|
export type { ImportPreviewFailure, ImportPreviewResult } from '@svadmin/core';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.93.0",
|
|
4
4
|
"description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"@codemirror/lang-json": "^6.0.0",
|
|
89
89
|
"@codemirror/lang-markdown": "^6.0.0",
|
|
90
90
|
"@codemirror/lang-sql": "^6.0.0",
|
|
91
|
-
"@svadmin/core": "^0.66.
|
|
91
|
+
"@svadmin/core": "^0.66.2",
|
|
92
92
|
"@tanstack/svelte-query": "^6.1.48",
|
|
93
93
|
"codemirror": "^6.0.0",
|
|
94
94
|
"isomorphic-dompurify": "^3.23.0",
|
|
@@ -139,8 +139,8 @@
|
|
|
139
139
|
}
|
|
140
140
|
},
|
|
141
141
|
"dependencies": {
|
|
142
|
-
"@svadmin/devtools": "^0.3.
|
|
143
|
-
"@svadmin/devtools-contract": "^0.3.
|
|
142
|
+
"@svadmin/devtools": "^0.3.9",
|
|
143
|
+
"@svadmin/devtools-contract": "^0.3.1",
|
|
144
144
|
"@vibeunion/devtools-devframe": "^0.1.0",
|
|
145
145
|
"typebox": "^1.3.34",
|
|
146
146
|
"@floating-ui/dom": "^1.8.0",
|
|
@@ -177,7 +177,7 @@
|
|
|
177
177
|
"@codemirror/lang-markdown": "^6.5.2",
|
|
178
178
|
"@codemirror/lang-sql": "^6.10.0",
|
|
179
179
|
"@happy-dom/global-registrator": "^20.11.12",
|
|
180
|
-
"@svadmin/ai-elements": "^0.12.
|
|
180
|
+
"@svadmin/ai-elements": "^0.12.6",
|
|
181
181
|
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
182
182
|
"@tailwindcss/cli": "4.3.3",
|
|
183
183
|
"@testing-library/dom": "^10.4.1",
|