@svadmin/ui 0.78.1 → 0.79.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.
- package/dist/app.css +187 -1
- package/dist/app.theme.css +187 -1
- package/dist/components/AutoTable.svelte +183 -962
- package/dist/components/content/AuditContent.svelte +33 -0
- package/dist/components/content/AuditContent.svelte.d.ts +11 -0
- package/dist/components/content/AuditSection.svelte +46 -0
- package/dist/components/content/AuditSection.svelte.d.ts +18 -0
- package/dist/components/content/ChartContainer.svelte +32 -0
- package/dist/components/content/ChartContainer.svelte.d.ts +15 -0
- package/dist/components/content/CoverageMatrix.svelte +49 -0
- package/dist/components/content/CoverageMatrix.svelte.d.ts +25 -0
- package/dist/components/content/EvidenceCard.svelte +35 -0
- package/dist/components/content/EvidenceCard.svelte.d.ts +14 -0
- package/dist/components/content/FilterToolbar.svelte +24 -4
- package/dist/components/content/FilterToolbar.svelte.d.ts +1 -1
- package/dist/components/content/StatusPill.svelte +16 -0
- package/dist/components/content/StatusPill.svelte.d.ts +9 -0
- package/dist/components/content/audit-state.d.ts +1 -0
- package/dist/components/content/audit-state.js +1 -0
- package/dist/components/content/index.d.ts +9 -0
- package/dist/components/content/index.js +5 -0
- package/dist/components/list-column-preferences.svelte.d.ts +29 -0
- package/dist/components/list-column-preferences.svelte.js +92 -0
- package/dist/components/list-deletion.svelte.d.ts +38 -0
- package/dist/components/list-deletion.svelte.js +111 -0
- package/dist/components/list-selection.svelte.d.ts +31 -0
- package/dist/components/list-selection.svelte.js +100 -0
- package/dist/components/list-state.svelte.d.ts +49 -0
- package/dist/components/list-state.svelte.js +225 -0
- package/dist/components/saved-list-views.svelte.d.ts +42 -0
- package/dist/components/saved-list-views.svelte.js +405 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +5 -0
- package/package.json +1 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
4
|
+
import { useTranslation } from '@svadmin/core/i18n';
|
|
5
|
+
import DataState from './DataState.svelte';
|
|
6
|
+
import { Button } from '../ui/button/index.js';
|
|
7
|
+
import { RefreshCw } from '@lucide/svelte';
|
|
8
|
+
import type { AuditDataState } from './audit-state.js';
|
|
9
|
+
|
|
10
|
+
let { state = 'ready', message, retry, children }: {
|
|
11
|
+
state?: AuditDataState;
|
|
12
|
+
message?: string | undefined;
|
|
13
|
+
retry?: (() => void) | undefined;
|
|
14
|
+
children?: Snippet | undefined;
|
|
15
|
+
} = $props();
|
|
16
|
+
const i18n = useTranslation();
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
{#if state === 'loading' || state === 'empty' || state === 'error' || state === 'forbidden'}
|
|
20
|
+
<DataState {state} {...definedOptions({ title: message, retry: state === 'error' ? retry : undefined })} />
|
|
21
|
+
{:else}
|
|
22
|
+
{#if state === 'partial' || state === 'readonly'}
|
|
23
|
+
<div class="svadmin-audit-notice" data-state={state} role="status">
|
|
24
|
+
<span>{message ?? (state === 'partial'
|
|
25
|
+
? (i18n.locale === 'zh-CN' ? '部分数据暂不可用' : 'Some data is unavailable')
|
|
26
|
+
: (i18n.locale === 'zh-CN' ? '只读' : 'Read only'))}</span>
|
|
27
|
+
{#if state === 'partial' && retry}
|
|
28
|
+
<Button variant="outline" size="sm" onclick={retry}><RefreshCw size={16} aria-hidden="true" />{i18n.t('common.retry')}</Button>
|
|
29
|
+
{/if}
|
|
30
|
+
</div>
|
|
31
|
+
{/if}
|
|
32
|
+
{@render children?.()}
|
|
33
|
+
{/if}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { AuditDataState } from './audit-state.js';
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
state?: AuditDataState;
|
|
5
|
+
message?: string | undefined;
|
|
6
|
+
retry?: (() => void) | undefined;
|
|
7
|
+
children?: Snippet | undefined;
|
|
8
|
+
};
|
|
9
|
+
declare const AuditContent: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
|
+
type AuditContent = ReturnType<typeof AuditContent>;
|
|
11
|
+
export default AuditContent;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { cn } from '../../utils.js';
|
|
4
|
+
import AuditContent from './AuditContent.svelte';
|
|
5
|
+
import type { AuditDataState } from './audit-state.js';
|
|
6
|
+
|
|
7
|
+
export type AuditAccent = 'primary' | 'info' | 'success' | 'warning' | 'danger' | 'neutral';
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
title: string;
|
|
11
|
+
description?: string | undefined;
|
|
12
|
+
eyebrow?: string;
|
|
13
|
+
accent?: AuditAccent;
|
|
14
|
+
actions?: Snippet | undefined;
|
|
15
|
+
children?: Snippet;
|
|
16
|
+
class?: string;
|
|
17
|
+
state?: AuditDataState;
|
|
18
|
+
message?: string | undefined;
|
|
19
|
+
retry?: (() => void) | undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
title,
|
|
24
|
+
description,
|
|
25
|
+
eyebrow,
|
|
26
|
+
accent = 'primary',
|
|
27
|
+
actions,
|
|
28
|
+
children,
|
|
29
|
+
class: className = '',
|
|
30
|
+
state = 'ready',
|
|
31
|
+
message,
|
|
32
|
+
retry,
|
|
33
|
+
}: Props = $props();
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<section aria-label={title} class={cn('svadmin-audit-section', className)} data-svadmin-audit-section data-accent={accent} data-state={state}>
|
|
37
|
+
<header class="svadmin-audit-section__header">
|
|
38
|
+
<div class="svadmin-audit-section__heading">
|
|
39
|
+
{#if eyebrow}<p class="svadmin-audit-section__eyebrow">{eyebrow}</p>{/if}
|
|
40
|
+
<h2>{title}</h2>
|
|
41
|
+
{#if description}<p class="svadmin-audit-section__description">{description}</p>{/if}
|
|
42
|
+
</div>
|
|
43
|
+
{#if actions && state !== 'forbidden'}<div class="svadmin-audit-section__actions">{@render actions()}</div>{/if}
|
|
44
|
+
</header>
|
|
45
|
+
<div class="svadmin-audit-section__body"><AuditContent {state} {message} {retry} {children} /></div>
|
|
46
|
+
</section>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { AuditDataState } from './audit-state.js';
|
|
3
|
+
export type AuditAccent = 'primary' | 'info' | 'success' | 'warning' | 'danger' | 'neutral';
|
|
4
|
+
interface Props {
|
|
5
|
+
title: string;
|
|
6
|
+
description?: string | undefined;
|
|
7
|
+
eyebrow?: string;
|
|
8
|
+
accent?: AuditAccent;
|
|
9
|
+
actions?: Snippet | undefined;
|
|
10
|
+
children?: Snippet;
|
|
11
|
+
class?: string;
|
|
12
|
+
state?: AuditDataState;
|
|
13
|
+
message?: string | undefined;
|
|
14
|
+
retry?: (() => void) | undefined;
|
|
15
|
+
}
|
|
16
|
+
declare const AuditSection: import("svelte").Component<Props, {}, "">;
|
|
17
|
+
type AuditSection = ReturnType<typeof AuditSection>;
|
|
18
|
+
export default AuditSection;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { cn } from '../../utils.js';
|
|
4
|
+
import AuditSection from './AuditSection.svelte';
|
|
5
|
+
import type { AuditDataState } from './audit-state.js';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
title: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
actions?: Snippet;
|
|
11
|
+
children?: Snippet;
|
|
12
|
+
class?: string;
|
|
13
|
+
state?: AuditDataState;
|
|
14
|
+
message?: string | undefined;
|
|
15
|
+
retry?: (() => void) | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
title,
|
|
20
|
+
description,
|
|
21
|
+
actions,
|
|
22
|
+
children,
|
|
23
|
+
class: className = '',
|
|
24
|
+
state = 'ready',
|
|
25
|
+
message,
|
|
26
|
+
retry,
|
|
27
|
+
}: Props = $props();
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<AuditSection {title} {description} {actions} {state} {message} {retry} accent="info" class={cn('svadmin-chart-container', className)}>
|
|
31
|
+
<div class="svadmin-chart-container__body">{@render children?.()}</div>
|
|
32
|
+
</AuditSection>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { AuditDataState } from './audit-state.js';
|
|
3
|
+
interface Props {
|
|
4
|
+
title: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
actions?: Snippet;
|
|
7
|
+
children?: Snippet;
|
|
8
|
+
class?: string;
|
|
9
|
+
state?: AuditDataState;
|
|
10
|
+
message?: string | undefined;
|
|
11
|
+
retry?: (() => void) | undefined;
|
|
12
|
+
}
|
|
13
|
+
declare const ChartContainer: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type ChartContainer = ReturnType<typeof ChartContainer>;
|
|
15
|
+
export default ChartContainer;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../../utils.js';
|
|
3
|
+
import type { AuditAccent } from './AuditSection.svelte';
|
|
4
|
+
import AuditContent from './AuditContent.svelte';
|
|
5
|
+
import type { AuditDataState } from './audit-state.js';
|
|
6
|
+
|
|
7
|
+
export interface CoverageMatrixColumn {
|
|
8
|
+
key: string;
|
|
9
|
+
label: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface CoverageMatrixRow {
|
|
13
|
+
id: string;
|
|
14
|
+
title: string;
|
|
15
|
+
cells: Readonly<Record<string, string | undefined>>;
|
|
16
|
+
accent?: AuditAccent;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
columns: readonly CoverageMatrixColumn[];
|
|
21
|
+
rows: readonly CoverageMatrixRow[];
|
|
22
|
+
class?: string;
|
|
23
|
+
caption: string;
|
|
24
|
+
rowLabel?: string;
|
|
25
|
+
state?: AuditDataState;
|
|
26
|
+
message?: string | undefined;
|
|
27
|
+
retry?: (() => void) | undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let { columns, rows, caption, rowLabel = '', state = 'ready', message, retry, class: className = '' }: Props = $props();
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<AuditContent state={state === 'ready' && rows.length === 0 ? 'empty' : state} {message} {retry}>
|
|
34
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex (命名滚动区域需要键盘访问。) -->
|
|
35
|
+
<div class={cn('svadmin-coverage-matrix', className)} data-svadmin-coverage-matrix role="region" aria-label={caption} tabindex="0">
|
|
36
|
+
<table>
|
|
37
|
+
<caption>{caption}</caption>
|
|
38
|
+
<thead><tr><th scope="col">{rowLabel}</th>{#each columns as column (column.key)}<th scope="col">{column.label}</th>{/each}</tr></thead>
|
|
39
|
+
<tbody>
|
|
40
|
+
{#each rows as row (row.id)}
|
|
41
|
+
<tr data-accent={row.accent ?? 'neutral'}>
|
|
42
|
+
<th scope="row">{row.title}</th>
|
|
43
|
+
{#each columns as column (column.key)}<td>{row.cells[column.key] ?? '—'}</td>{/each}
|
|
44
|
+
</tr>
|
|
45
|
+
{/each}
|
|
46
|
+
</tbody>
|
|
47
|
+
</table>
|
|
48
|
+
</div>
|
|
49
|
+
</AuditContent>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AuditAccent } from './AuditSection.svelte';
|
|
2
|
+
import type { AuditDataState } from './audit-state.js';
|
|
3
|
+
export interface CoverageMatrixColumn {
|
|
4
|
+
key: string;
|
|
5
|
+
label: string;
|
|
6
|
+
}
|
|
7
|
+
export interface CoverageMatrixRow {
|
|
8
|
+
id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
cells: Readonly<Record<string, string | undefined>>;
|
|
11
|
+
accent?: AuditAccent;
|
|
12
|
+
}
|
|
13
|
+
interface Props {
|
|
14
|
+
columns: readonly CoverageMatrixColumn[];
|
|
15
|
+
rows: readonly CoverageMatrixRow[];
|
|
16
|
+
class?: string;
|
|
17
|
+
caption: string;
|
|
18
|
+
rowLabel?: string;
|
|
19
|
+
state?: AuditDataState;
|
|
20
|
+
message?: string | undefined;
|
|
21
|
+
retry?: (() => void) | undefined;
|
|
22
|
+
}
|
|
23
|
+
declare const CoverageMatrix: import("svelte").Component<Props, {}, "">;
|
|
24
|
+
type CoverageMatrix = ReturnType<typeof CoverageMatrix>;
|
|
25
|
+
export default CoverageMatrix;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { cn } from '../../utils.js';
|
|
4
|
+
import StatusPill, { type StatusPillTone } from './StatusPill.svelte';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
title: string;
|
|
8
|
+
status: string;
|
|
9
|
+
statusTone?: StatusPillTone;
|
|
10
|
+
description?: string;
|
|
11
|
+
source?: string;
|
|
12
|
+
children?: Snippet;
|
|
13
|
+
class?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
title,
|
|
18
|
+
status,
|
|
19
|
+
statusTone = 'neutral',
|
|
20
|
+
description,
|
|
21
|
+
source,
|
|
22
|
+
children,
|
|
23
|
+
class: className = '',
|
|
24
|
+
}: Props = $props();
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<article aria-label={title} class={cn('svadmin-evidence-card', className)} data-svadmin-evidence-card data-accent={statusTone}>
|
|
28
|
+
<div class="svadmin-evidence-card__header">
|
|
29
|
+
<h3>{title}</h3>
|
|
30
|
+
<StatusPill label={status} tone={statusTone} />
|
|
31
|
+
</div>
|
|
32
|
+
{#if description}<p class="svadmin-evidence-card__description">{description}</p>{/if}
|
|
33
|
+
{#if children}<div class="svadmin-evidence-card__body">{@render children()}</div>{/if}
|
|
34
|
+
{#if source}<p class="svadmin-evidence-card__source">{source}</p>{/if}
|
|
35
|
+
</article>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import { type StatusPillTone } from './StatusPill.svelte';
|
|
3
|
+
interface Props {
|
|
4
|
+
title: string;
|
|
5
|
+
status: string;
|
|
6
|
+
statusTone?: StatusPillTone;
|
|
7
|
+
description?: string;
|
|
8
|
+
source?: string;
|
|
9
|
+
children?: Snippet;
|
|
10
|
+
class?: string;
|
|
11
|
+
}
|
|
12
|
+
declare const EvidenceCard: import("svelte").Component<Props, {}, "">;
|
|
13
|
+
type EvidenceCard = ReturnType<typeof EvidenceCard>;
|
|
14
|
+
export default EvidenceCard;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type
|
|
2
|
+
import { onMount, type Snippet } from 'svelte';
|
|
3
|
+
import { useTranslation } from '@svadmin/core/i18n';
|
|
4
|
+
import * as Sheet from '../ui/sheet/index.js';
|
|
3
5
|
import { Search, X, SlidersHorizontal, ChevronDown, ChevronUp } from '@lucide/svelte';
|
|
4
6
|
import { Button } from '../ui/button/index.js';
|
|
5
7
|
import { Input } from '../ui/input/index.js';
|
|
@@ -40,6 +42,15 @@
|
|
|
40
42
|
|
|
41
43
|
const isCompact = $derived(density === 'compact');
|
|
42
44
|
const advancedPanelId = `svadmin-filter-toolbar-advanced-${uid}`;
|
|
45
|
+
const i18n = useTranslation();
|
|
46
|
+
let mobile = $state(false);
|
|
47
|
+
onMount(() => {
|
|
48
|
+
const media = window.matchMedia('(max-width: 767px)');
|
|
49
|
+
const update = () => { mobile = media.matches; };
|
|
50
|
+
update();
|
|
51
|
+
media.addEventListener('change', update);
|
|
52
|
+
return () => media.removeEventListener('change', update);
|
|
53
|
+
});
|
|
43
54
|
</script>
|
|
44
55
|
|
|
45
56
|
<div class={cn('svadmin-filter-toolbar', className)} data-density={density} data-svadmin-filter-toolbar data-advanced-open={advanced && advancedOpen ? 'true' : 'false'}>
|
|
@@ -67,12 +78,12 @@
|
|
|
67
78
|
{/if}
|
|
68
79
|
</div>
|
|
69
80
|
{/if}
|
|
70
|
-
{#if filters}
|
|
81
|
+
{#if filters && !mobile}
|
|
71
82
|
<div class="svadmin-filter-toolbar__filters">
|
|
72
83
|
{@render filters()}
|
|
73
84
|
</div>
|
|
74
85
|
{/if}
|
|
75
|
-
{#if advanced}
|
|
86
|
+
{#if advanced || (mobile && filters)}
|
|
76
87
|
<Button
|
|
77
88
|
variant={advancedOpen ? 'secondary' : 'outline'}
|
|
78
89
|
size={isCompact ? 'sm' : 'default'}
|
|
@@ -101,7 +112,16 @@
|
|
|
101
112
|
</div>
|
|
102
113
|
{/if}
|
|
103
114
|
</div>
|
|
104
|
-
{#if
|
|
115
|
+
{#if mobile && (advanced || filters)}
|
|
116
|
+
<Sheet.Root bind:open={advancedOpen} class="svadmin-filter-bottom-sheet" role="dialog" aria-modal="true"
|
|
117
|
+
aria-label={advancedLabel} id={advancedPanelId} closeLabel={i18n.t('common.close')}>
|
|
118
|
+
<Sheet.Header><Sheet.Title>{advancedLabel}</Sheet.Title></Sheet.Header>
|
|
119
|
+
<Sheet.Content>
|
|
120
|
+
{@render filters?.()}
|
|
121
|
+
{@render advanced?.()}
|
|
122
|
+
</Sheet.Content>
|
|
123
|
+
</Sheet.Root>
|
|
124
|
+
{:else if advanced && advancedOpen}
|
|
105
125
|
<div id={advancedPanelId} role="region" aria-label={advancedLabel} class="svadmin-filter-toolbar-advanced" aria-hidden="false">
|
|
106
126
|
{@render advanced()}
|
|
107
127
|
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../../utils.js';
|
|
3
|
+
import StatusBadge from './StatusBadge.svelte';
|
|
4
|
+
|
|
5
|
+
export type StatusPillTone = 'success' | 'warning' | 'danger' | 'info' | 'neutral';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
label: string;
|
|
9
|
+
tone?: StatusPillTone;
|
|
10
|
+
class?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let { label, tone = 'neutral', class: className = '' }: Props = $props();
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<StatusBadge status={tone} {label} class={cn('svadmin-status-pill', className)} />
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type StatusPillTone = 'success' | 'warning' | 'danger' | 'info' | 'neutral';
|
|
2
|
+
interface Props {
|
|
3
|
+
label: string;
|
|
4
|
+
tone?: StatusPillTone;
|
|
5
|
+
class?: string;
|
|
6
|
+
}
|
|
7
|
+
declare const StatusPill: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type StatusPill = ReturnType<typeof StatusPill>;
|
|
9
|
+
export default StatusPill;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type AuditDataState = 'ready' | 'loading' | 'empty' | 'partial' | 'error' | 'forbidden' | 'readonly';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -48,5 +48,14 @@ export { default as FeedbackNotice } from './FeedbackNotice.svelte';
|
|
|
48
48
|
export type { FeedbackNoticeTone, FeedbackNoticePriority } from './FeedbackNotice.svelte';
|
|
49
49
|
export { default as AuditTimeline } from './AuditTimeline.svelte';
|
|
50
50
|
export type { TimelineItem } from './AuditTimeline.svelte';
|
|
51
|
+
export { default as AuditSection } from './AuditSection.svelte';
|
|
52
|
+
export type { AuditAccent } from './AuditSection.svelte';
|
|
53
|
+
export type { AuditDataState } from './audit-state.js';
|
|
54
|
+
export { default as ChartContainer } from './ChartContainer.svelte';
|
|
55
|
+
export { default as CoverageMatrix } from './CoverageMatrix.svelte';
|
|
56
|
+
export type { CoverageMatrixColumn, CoverageMatrixRow } from './CoverageMatrix.svelte';
|
|
57
|
+
export { default as EvidenceCard } from './EvidenceCard.svelte';
|
|
58
|
+
export { default as StatusPill } from './StatusPill.svelte';
|
|
59
|
+
export type { StatusPillTone } from './StatusPill.svelte';
|
|
51
60
|
export { default as MediaThumbnail } from './MediaThumbnail.svelte';
|
|
52
61
|
export type { MediaThumbnailSize, MediaThumbnailFit } from './MediaThumbnail.svelte';
|
|
@@ -33,4 +33,9 @@ export { default as TwoFactorStepper } from './TwoFactorStepper.svelte';
|
|
|
33
33
|
export { default as SystemErrorState } from './SystemErrorState.svelte';
|
|
34
34
|
export { default as FeedbackNotice } from './FeedbackNotice.svelte';
|
|
35
35
|
export { default as AuditTimeline } from './AuditTimeline.svelte';
|
|
36
|
+
export { default as AuditSection } from './AuditSection.svelte';
|
|
37
|
+
export { default as ChartContainer } from './ChartContainer.svelte';
|
|
38
|
+
export { default as CoverageMatrix } from './CoverageMatrix.svelte';
|
|
39
|
+
export { default as EvidenceCard } from './EvidenceCard.svelte';
|
|
40
|
+
export { default as StatusPill } from './StatusPill.svelte';
|
|
36
41
|
export { default as MediaThumbnail } from './MediaThumbnail.svelte';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ColumnVisibilityState } from '@tanstack/svelte-table';
|
|
2
|
+
import type { FieldDefinition } from '@svadmin/core';
|
|
3
|
+
import { type ListPreferenceScope, type SavedListViewState } from './saved-list-views.js';
|
|
4
|
+
type ColumnPreferences = Pick<SavedListViewState, 'columnVisibility' | 'columnOrder'>;
|
|
5
|
+
interface ListColumnPreferencesOptions {
|
|
6
|
+
scope: () => ListPreferenceScope;
|
|
7
|
+
columns: () => Set<string>;
|
|
8
|
+
fields: () => FieldDefinition[];
|
|
9
|
+
isScopeLoaded: () => boolean;
|
|
10
|
+
readPreference: (scope: ListPreferenceScope, key: string, legacyKey: string) => string | null;
|
|
11
|
+
initialViewState: ColumnPreferences | undefined;
|
|
12
|
+
onDirty: () => void;
|
|
13
|
+
}
|
|
14
|
+
export declare function createListColumnPreferences(options: ListColumnPreferencesOptions): {
|
|
15
|
+
visibility: {
|
|
16
|
+
readonly current: NoInfer<ColumnVisibilityState>;
|
|
17
|
+
};
|
|
18
|
+
order: {
|
|
19
|
+
readonly current: NoInfer<string[]>;
|
|
20
|
+
};
|
|
21
|
+
readScope: (scope: ListPreferenceScope) => ColumnPreferences;
|
|
22
|
+
captureState: () => ColumnPreferences;
|
|
23
|
+
applyState: (state: ColumnPreferences) => void;
|
|
24
|
+
setVisibility: (columnId: string, visible: boolean) => void;
|
|
25
|
+
setOrder: (columns: Array<{
|
|
26
|
+
id: string;
|
|
27
|
+
}>) => void;
|
|
28
|
+
};
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { untrack } from 'svelte';
|
|
2
|
+
import { createAtom, useSelector } from '@tanstack/svelte-store';
|
|
3
|
+
import { decodeBaseRecord } from '@svadmin/core/schema';
|
|
4
|
+
import { columnOrderStorageKey, columnVisibilityStorageKey, legacyColumnOrderStorageKey, legacyColumnVisibilityStorageKey, } from './saved-list-views.js';
|
|
5
|
+
export function createListColumnPreferences(options) {
|
|
6
|
+
const visibilityAtom = createAtom(untrack(() => (options.initialViewState?.columnVisibility ?? readVisibility(options.scope()))));
|
|
7
|
+
const orderAtom = createAtom(untrack(() => (options.initialViewState?.columnOrder.length
|
|
8
|
+
? options.initialViewState.columnOrder : readOrder(options.scope()))));
|
|
9
|
+
const visibility = useSelector(visibilityAtom);
|
|
10
|
+
const order = useSelector(orderAtom);
|
|
11
|
+
function readVisibility(scope) {
|
|
12
|
+
const stored = options.readPreference(scope, columnVisibilityStorageKey(scope), legacyColumnVisibilityStorageKey(scope.resourceName));
|
|
13
|
+
if (stored) {
|
|
14
|
+
try {
|
|
15
|
+
const parsed = JSON.parse(stored);
|
|
16
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
17
|
+
const visibility = {};
|
|
18
|
+
for (const [columnId, visible] of Object.entries(decodeBaseRecord(parsed))) {
|
|
19
|
+
if (options.columns().has(columnId) && typeof visible === 'boolean')
|
|
20
|
+
visibility[columnId] = visible;
|
|
21
|
+
}
|
|
22
|
+
return visibility;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch { /* fall through to resource defaults */ }
|
|
26
|
+
}
|
|
27
|
+
const visibility = {};
|
|
28
|
+
for (const field of options.fields()) {
|
|
29
|
+
if (field.showInList === false)
|
|
30
|
+
visibility[field.key] = false;
|
|
31
|
+
}
|
|
32
|
+
return visibility;
|
|
33
|
+
}
|
|
34
|
+
function readOrder(scope) {
|
|
35
|
+
const stored = options.readPreference(scope, columnOrderStorageKey(scope), legacyColumnOrderStorageKey(scope.resourceName));
|
|
36
|
+
if (!stored)
|
|
37
|
+
return [];
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(stored);
|
|
40
|
+
return Array.isArray(parsed)
|
|
41
|
+
? [...new Set(parsed.filter((columnId) => (typeof columnId === 'string' && options.columns().has(columnId))))]
|
|
42
|
+
: [];
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function persistOrder(ids) {
|
|
49
|
+
if (typeof window === 'undefined' || !options.isScopeLoaded())
|
|
50
|
+
return;
|
|
51
|
+
try {
|
|
52
|
+
localStorage.setItem(columnOrderStorageKey(options.scope()), JSON.stringify(ids));
|
|
53
|
+
}
|
|
54
|
+
catch { /* keep in-memory preferences when storage is unavailable */ }
|
|
55
|
+
}
|
|
56
|
+
$effect(() => {
|
|
57
|
+
if (!options.isScopeLoaded())
|
|
58
|
+
return;
|
|
59
|
+
const storageKey = columnVisibilityStorageKey(options.scope());
|
|
60
|
+
if (typeof window !== 'undefined') {
|
|
61
|
+
try {
|
|
62
|
+
localStorage.setItem(storageKey, JSON.stringify(visibility.current));
|
|
63
|
+
}
|
|
64
|
+
catch { /* keep in-memory preferences when storage is unavailable */ }
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
$effect(() => {
|
|
68
|
+
if (!options.isScopeLoaded())
|
|
69
|
+
return;
|
|
70
|
+
persistOrder(order.current);
|
|
71
|
+
});
|
|
72
|
+
function readScope(scope) {
|
|
73
|
+
return { columnVisibility: readVisibility(scope), columnOrder: readOrder(scope) };
|
|
74
|
+
}
|
|
75
|
+
function captureState() {
|
|
76
|
+
return { columnVisibility: { ...visibility.current }, columnOrder: [...order.current] };
|
|
77
|
+
}
|
|
78
|
+
function applyState(state) {
|
|
79
|
+
visibilityAtom.set(state.columnVisibility);
|
|
80
|
+
orderAtom.set(state.columnOrder);
|
|
81
|
+
persistOrder(state.columnOrder);
|
|
82
|
+
}
|
|
83
|
+
function setVisibility(columnId, visible) {
|
|
84
|
+
options.onDirty();
|
|
85
|
+
visibilityAtom.set({ ...visibility.current, [columnId]: visible });
|
|
86
|
+
}
|
|
87
|
+
function setOrder(columns) {
|
|
88
|
+
options.onDirty();
|
|
89
|
+
orderAtom.set(columns.map(column => column.id));
|
|
90
|
+
}
|
|
91
|
+
return { visibility, order, readScope, captureState, applyState, setVisibility, setOrder };
|
|
92
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { createListSelection } from './list-selection.svelte.js';
|
|
2
|
+
interface DeleteRequest {
|
|
3
|
+
ids: (string | number)[];
|
|
4
|
+
batch: boolean;
|
|
5
|
+
}
|
|
6
|
+
interface ListDeletionOptions {
|
|
7
|
+
canRequestSingle: () => boolean;
|
|
8
|
+
canRequestBatch: () => boolean;
|
|
9
|
+
canConfirm: () => boolean;
|
|
10
|
+
permission: () => {
|
|
11
|
+
isLoading: boolean;
|
|
12
|
+
allowed: boolean;
|
|
13
|
+
};
|
|
14
|
+
selection: () => Pick<ReturnType<typeof createListSelection>, 'selectedIds' | 'captureExplicit' | 'restoreExplicit' | 'retainFailedIds'>;
|
|
15
|
+
clearSelection: () => void;
|
|
16
|
+
captureScope: () => () => boolean;
|
|
17
|
+
mutate: (ids: (string | number)[]) => Promise<unknown>;
|
|
18
|
+
reportError: (message: string | null) => void;
|
|
19
|
+
messages: {
|
|
20
|
+
single: () => string;
|
|
21
|
+
batch: (count: number) => string;
|
|
22
|
+
failure: () => string;
|
|
23
|
+
partial: (failed: number, total: number) => string;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export declare function createListDeletion(options: ListDeletionOptions): {
|
|
27
|
+
readonly request: DeleteRequest | null;
|
|
28
|
+
readonly open: boolean;
|
|
29
|
+
readonly pending: boolean;
|
|
30
|
+
readonly message: string;
|
|
31
|
+
requestDelete: (id: string | number) => void;
|
|
32
|
+
requestBatchDelete: () => void;
|
|
33
|
+
confirm: () => Promise<void>;
|
|
34
|
+
cancel: () => void;
|
|
35
|
+
cancelBatchOnCriteriaChange: () => void;
|
|
36
|
+
reset: () => void;
|
|
37
|
+
};
|
|
38
|
+
export {};
|