@svadmin/lite 0.25.7 → 0.26.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/README.md +11 -5
- package/dist/components/LiteAuditLog.svelte +102 -36
- package/dist/components/LiteAuditLog.svelte.d.ts +6 -3
- package/dist/components/LiteConcurrentConflictModal.svelte +110 -0
- package/dist/components/LiteConcurrentConflictModal.svelte.d.ts +21 -0
- package/dist/components/LiteCronExpressionBuilder.svelte +78 -0
- package/dist/components/LiteCronExpressionBuilder.svelte.d.ts +17 -0
- package/dist/components/LiteMetricGrid.svelte +143 -0
- package/dist/components/LiteMetricGrid.svelte.d.ts +31 -0
- package/dist/components/LiteMicroAppFrame.svelte +57 -0
- package/dist/components/LiteMicroAppFrame.svelte.d.ts +15 -0
- package/dist/components/LiteOrgChart.svelte +78 -0
- package/dist/components/LiteOrgChart.svelte.d.ts +17 -0
- package/dist/components/LitePresenceAvatarGroup.svelte +118 -15
- package/dist/components/LitePresenceAvatarGroup.svelte.d.ts +8 -3
- package/dist/components/LiteSafeDeleteConfirm.svelte +86 -0
- package/dist/components/LiteSafeDeleteConfirm.svelte.d.ts +21 -0
- package/dist/components/LiteSignaturePad.svelte +3 -1
- package/dist/components/LiteSignaturePad.svelte.d.ts +1 -0
- package/dist/components/LiteTaskCenter.svelte +133 -0
- package/dist/components/LiteTaskCenter.svelte.d.ts +24 -0
- package/dist/components/LiteTour.svelte +4 -0
- package/dist/components/LiteTour.svelte.d.ts +4 -0
- package/dist/components/LiteTrendIndicator.svelte +119 -0
- package/dist/components/LiteTrendIndicator.svelte.d.ts +19 -0
- package/dist/components/LiteVirtualTreeSelect.svelte +92 -0
- package/dist/components/LiteVirtualTreeSelect.svelte.d.ts +20 -0
- package/dist/components/widgets/LiteGaugeChart.svelte +50 -3
- package/dist/components/widgets/LiteGaugeChart.svelte.d.ts +12 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +10 -0
- package/dist/rich-content.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type MetricColumns = 1 | 2 | 3 | 4 | 5 | 6 | 'auto';
|
|
3
|
+
type MetricGap = 'sm' | 'md' | 'lg';
|
|
4
|
+
interface LiteMetricGridItem {
|
|
5
|
+
id?: string;
|
|
6
|
+
label: string;
|
|
7
|
+
value: string | number;
|
|
8
|
+
description?: string;
|
|
9
|
+
trend?: 'up' | 'down' | 'neutral';
|
|
10
|
+
trendValue?: string;
|
|
11
|
+
icon?: string;
|
|
12
|
+
href?: string;
|
|
13
|
+
}
|
|
14
|
+
interface Props {
|
|
15
|
+
items?: LiteMetricGridItem[];
|
|
16
|
+
cols?: MetricColumns;
|
|
17
|
+
gap?: MetricGap;
|
|
18
|
+
title?: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
loading?: boolean;
|
|
21
|
+
skeletonCount?: number;
|
|
22
|
+
empty?: boolean;
|
|
23
|
+
emptyMessage?: string;
|
|
24
|
+
class?: string;
|
|
25
|
+
actions?: Snippet;
|
|
26
|
+
children?: Snippet;
|
|
27
|
+
emptyContent?: Snippet;
|
|
28
|
+
}
|
|
29
|
+
declare const LiteMetricGrid: import("svelte").Component<Props, {}, "">;
|
|
30
|
+
type LiteMetricGrid = ReturnType<typeof LiteMetricGrid>;
|
|
31
|
+
export default LiteMetricGrid;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLIframeAttributes } from 'svelte/elements';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
src: string;
|
|
6
|
+
title?: string;
|
|
7
|
+
height?: string | number;
|
|
8
|
+
sandbox?: string;
|
|
9
|
+
allow?: string;
|
|
10
|
+
referrerpolicy?: HTMLIframeAttributes['referrerpolicy'];
|
|
11
|
+
fallbackHref?: string;
|
|
12
|
+
fallbackLabel?: string;
|
|
13
|
+
class?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
src,
|
|
18
|
+
title = 'Embedded application',
|
|
19
|
+
height = '650px',
|
|
20
|
+
sandbox = 'allow-scripts allow-forms allow-same-origin',
|
|
21
|
+
allow,
|
|
22
|
+
referrerpolicy = 'strict-origin-when-cross-origin',
|
|
23
|
+
fallbackHref,
|
|
24
|
+
fallbackLabel = 'Open in a new tab',
|
|
25
|
+
class: className = '',
|
|
26
|
+
}: Props = $props();
|
|
27
|
+
|
|
28
|
+
const frameHeight = $derived(typeof height === 'number' ? `${height}px` : height);
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<section class="lite-micro-app-frame lite-card {className}" aria-label={title}>
|
|
32
|
+
<header>
|
|
33
|
+
<h2>{title}</h2>
|
|
34
|
+
{#if fallbackHref}<a href={fallbackHref} target="_blank" rel="noopener noreferrer">{fallbackLabel}</a>{/if}
|
|
35
|
+
</header>
|
|
36
|
+
|
|
37
|
+
<iframe
|
|
38
|
+
{src}
|
|
39
|
+
{title}
|
|
40
|
+
{sandbox}
|
|
41
|
+
{allow}
|
|
42
|
+
{referrerpolicy}
|
|
43
|
+
style={`height:${frameHeight};`}
|
|
44
|
+
loading="lazy"
|
|
45
|
+
></iframe>
|
|
46
|
+
<p class="lite-micro-app-help">Embedded messaging, theme sync and automatic height are client-side enhancements.</p>
|
|
47
|
+
</section>
|
|
48
|
+
|
|
49
|
+
<style>
|
|
50
|
+
.lite-micro-app-frame { padding: 12px; }
|
|
51
|
+
.lite-micro-app-frame header { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 8px; }
|
|
52
|
+
.lite-micro-app-frame header a { margin-left: 12px; }
|
|
53
|
+
.lite-micro-app-frame h2 { margin: 0; color: #1d2129; font-size: 15px; }
|
|
54
|
+
.lite-micro-app-frame header a { color: #165dff; font-size: 12px; }
|
|
55
|
+
.lite-micro-app-frame iframe { display: block; width: 100%; border: 1px solid #e5e6eb; border-radius: 4px; background: #fff; }
|
|
56
|
+
.lite-micro-app-help { margin: 6px 0 0; color: #86909c; font-size: 11px; }
|
|
57
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HTMLIframeAttributes } from 'svelte/elements';
|
|
2
|
+
interface Props {
|
|
3
|
+
src: string;
|
|
4
|
+
title?: string;
|
|
5
|
+
height?: string | number;
|
|
6
|
+
sandbox?: string;
|
|
7
|
+
allow?: string;
|
|
8
|
+
referrerpolicy?: HTMLIframeAttributes['referrerpolicy'];
|
|
9
|
+
fallbackHref?: string;
|
|
10
|
+
fallbackLabel?: string;
|
|
11
|
+
class?: string;
|
|
12
|
+
}
|
|
13
|
+
declare const LiteMicroAppFrame: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type LiteMicroAppFrame = ReturnType<typeof LiteMicroAppFrame>;
|
|
15
|
+
export default LiteMicroAppFrame;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export interface LiteOrgNode {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
title?: string;
|
|
6
|
+
department?: string;
|
|
7
|
+
href?: string;
|
|
8
|
+
children?: LiteOrgNode[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
data?: LiteOrgNode | LiteOrgNode[];
|
|
13
|
+
title?: string;
|
|
14
|
+
emptyText?: string;
|
|
15
|
+
class?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
data = [],
|
|
20
|
+
title = 'Organization chart',
|
|
21
|
+
emptyText = 'No organization data',
|
|
22
|
+
class: className = '',
|
|
23
|
+
}: Props = $props();
|
|
24
|
+
|
|
25
|
+
interface FlatNode {
|
|
26
|
+
node: LiteOrgNode;
|
|
27
|
+
depth: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function flatten(nodes: LiteOrgNode[], depth = 0, out: FlatNode[] = []): FlatNode[] {
|
|
31
|
+
for (const node of nodes) {
|
|
32
|
+
out.push({ node, depth });
|
|
33
|
+
if (node.children?.length) flatten(node.children, depth + 1, out);
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const rows = $derived(flatten(Array.isArray(data) ? data : [data]));
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<section class="lite-org-chart lite-card {className}" aria-label={title}>
|
|
42
|
+
<h2>{title}</h2>
|
|
43
|
+
{#if rows.length === 0}
|
|
44
|
+
<p class="lite-org-empty">{emptyText}</p>
|
|
45
|
+
{:else}
|
|
46
|
+
<table class="lite-table">
|
|
47
|
+
<thead>
|
|
48
|
+
<tr>
|
|
49
|
+
<th>Name</th>
|
|
50
|
+
<th>Title</th>
|
|
51
|
+
<th>Department</th>
|
|
52
|
+
</tr>
|
|
53
|
+
</thead>
|
|
54
|
+
<tbody>
|
|
55
|
+
{#each rows as row (row.node.id)}
|
|
56
|
+
<tr data-depth={row.depth}>
|
|
57
|
+
<td>
|
|
58
|
+
<span class="lite-org-indent" style={`padding-left:${row.depth * 16}px`}>
|
|
59
|
+
{#if row.depth > 0}<span class="lite-org-branch" aria-hidden="true">└</span>{/if}
|
|
60
|
+
{#if row.node.href}<a href={row.node.href}>{row.node.name}</a>{:else}{row.node.name}{/if}
|
|
61
|
+
</span>
|
|
62
|
+
</td>
|
|
63
|
+
<td>{row.node.title ?? '—'}</td>
|
|
64
|
+
<td>{row.node.department ?? '—'}</td>
|
|
65
|
+
</tr>
|
|
66
|
+
{/each}
|
|
67
|
+
</tbody>
|
|
68
|
+
</table>
|
|
69
|
+
{/if}
|
|
70
|
+
</section>
|
|
71
|
+
|
|
72
|
+
<style>
|
|
73
|
+
.lite-org-chart { padding: 16px; }
|
|
74
|
+
.lite-org-chart h2 { margin: 0 0 12px; color: #1d2129; font-size: 16px; }
|
|
75
|
+
.lite-org-empty { margin: 0; padding: 20px; color: #86909c; text-align: center; }
|
|
76
|
+
.lite-org-indent { display: inline-block; }
|
|
77
|
+
.lite-org-branch { display: inline-block; margin-right: 4px; color: #86909c; }
|
|
78
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface LiteOrgNode {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
title?: string;
|
|
5
|
+
department?: string;
|
|
6
|
+
href?: string;
|
|
7
|
+
children?: LiteOrgNode[];
|
|
8
|
+
}
|
|
9
|
+
interface Props {
|
|
10
|
+
data?: LiteOrgNode | LiteOrgNode[];
|
|
11
|
+
title?: string;
|
|
12
|
+
emptyText?: string;
|
|
13
|
+
class?: string;
|
|
14
|
+
}
|
|
15
|
+
declare const LiteOrgChart: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
type LiteOrgChart = ReturnType<typeof LiteOrgChart>;
|
|
17
|
+
export default LiteOrgChart;
|
|
@@ -1,55 +1,158 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
interface LitePresenceUser {
|
|
2
|
+
export interface LitePresenceUser {
|
|
3
3
|
id: string;
|
|
4
4
|
name: string;
|
|
5
|
-
|
|
5
|
+
avatar?: string;
|
|
6
|
+
role?: string;
|
|
7
|
+
status?: 'online' | 'viewing' | 'idle' | 'editing';
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
interface Props {
|
|
9
11
|
users?: LitePresenceUser[];
|
|
10
12
|
label?: string;
|
|
11
|
-
|
|
13
|
+
maxVisible?: number;
|
|
14
|
+
maxCount?: number;
|
|
15
|
+
size?: 'sm' | 'md' | 'lg';
|
|
16
|
+
showStatusDot?: boolean;
|
|
12
17
|
class?: string;
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
let {
|
|
16
21
|
users = [],
|
|
17
22
|
label = 'Viewing now',
|
|
18
|
-
|
|
23
|
+
maxVisible,
|
|
24
|
+
maxCount,
|
|
25
|
+
size = 'md',
|
|
26
|
+
showStatusDot = true,
|
|
27
|
+
class: className = '',
|
|
19
28
|
}: Props = $props();
|
|
29
|
+
|
|
30
|
+
const limit = $derived(Math.max(0, maxVisible ?? maxCount ?? 4));
|
|
31
|
+
const visible = $derived(users.slice(0, limit));
|
|
32
|
+
const hidden = $derived(users.slice(limit));
|
|
33
|
+
|
|
34
|
+
function initials(name: string): string {
|
|
35
|
+
return name
|
|
36
|
+
.trim()
|
|
37
|
+
.split(/\s+/)
|
|
38
|
+
.map((part) => part[0] ?? '')
|
|
39
|
+
.join('')
|
|
40
|
+
.slice(0, 2)
|
|
41
|
+
.toUpperCase() || 'U';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function statusLabel(status: LitePresenceUser['status']): string {
|
|
45
|
+
return status ?? 'online';
|
|
46
|
+
}
|
|
20
47
|
</script>
|
|
21
48
|
|
|
22
|
-
<div class="lite-presence-container {className}">
|
|
49
|
+
<div class="lite-presence-container lite-presence-{size} {className}">
|
|
23
50
|
<span class="lite-presence-label">{label}:</span>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
51
|
+
|
|
52
|
+
{#each visible as user (user.id)}
|
|
53
|
+
<span
|
|
54
|
+
class="lite-presence-badge"
|
|
55
|
+
title={`${user.name}${user.role ? ` (${user.role})` : ''} · ${statusLabel(user.status)}`}
|
|
56
|
+
data-presence-user={user.id}
|
|
57
|
+
data-status={statusLabel(user.status)}
|
|
58
|
+
>
|
|
59
|
+
{#if user.avatar}
|
|
60
|
+
<img src={user.avatar} alt="" class="lite-presence-avatar" />
|
|
61
|
+
{:else}
|
|
62
|
+
<span class="lite-presence-initials" aria-hidden="true">{initials(user.name)}</span>
|
|
63
|
+
{/if}
|
|
64
|
+
<span class="lite-presence-name">{user.name}</span>
|
|
65
|
+
{#if showStatusDot && user.status}
|
|
66
|
+
<span class="lite-presence-dot" aria-hidden="true"></span>
|
|
67
|
+
{/if}
|
|
27
68
|
</span>
|
|
28
69
|
{/each}
|
|
70
|
+
|
|
71
|
+
{#if hidden.length > 0}
|
|
72
|
+
<span
|
|
73
|
+
class="lite-presence-overflow"
|
|
74
|
+
title={hidden.map((user) => user.name).join(', ')}
|
|
75
|
+
aria-label={`+${hidden.length} more users`}
|
|
76
|
+
>
|
|
77
|
+
+{hidden.length}
|
|
78
|
+
</span>
|
|
79
|
+
{/if}
|
|
80
|
+
|
|
29
81
|
{#if users.length === 0}
|
|
30
|
-
<span
|
|
82
|
+
<span class="lite-presence-empty">No active viewers</span>
|
|
31
83
|
{/if}
|
|
32
84
|
</div>
|
|
33
85
|
|
|
34
86
|
<style>
|
|
35
87
|
.lite-presence-container {
|
|
36
88
|
display: inline-flex;
|
|
89
|
+
flex-wrap: wrap;
|
|
37
90
|
align-items: center;
|
|
38
91
|
font-size: 12px;
|
|
39
92
|
margin-bottom: 8px;
|
|
40
93
|
}
|
|
41
94
|
.lite-presence-label {
|
|
42
|
-
color: #
|
|
43
|
-
margin-right:
|
|
95
|
+
color: #4e5969;
|
|
96
|
+
margin-right: 2px;
|
|
44
97
|
font-weight: 500;
|
|
45
98
|
}
|
|
46
99
|
.lite-presence-badge {
|
|
100
|
+
position: relative;
|
|
101
|
+
display: inline-flex;
|
|
102
|
+
align-items: center;
|
|
103
|
+
padding: 2px 6px 2px 3px;
|
|
104
|
+
background: #f2f3f5;
|
|
105
|
+
border: 1px solid #e5e6eb;
|
|
106
|
+
border-radius: 999px;
|
|
107
|
+
margin-right: 2px;
|
|
108
|
+
font-size: 11px;
|
|
109
|
+
line-height: 1.4;
|
|
110
|
+
}
|
|
111
|
+
.lite-presence-avatar,
|
|
112
|
+
.lite-presence-initials {
|
|
47
113
|
display: inline-block;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
border-radius: 4px;
|
|
114
|
+
width: 20px;
|
|
115
|
+
height: 20px;
|
|
116
|
+
flex: 0 0 20px;
|
|
52
117
|
margin-right: 4px;
|
|
118
|
+
border-radius: 999px;
|
|
119
|
+
text-align: center;
|
|
120
|
+
line-height: 20px;
|
|
121
|
+
object-fit: cover;
|
|
122
|
+
background: #e8f3ff;
|
|
123
|
+
color: #165dff;
|
|
124
|
+
font-size: 9px;
|
|
125
|
+
font-weight: 700;
|
|
126
|
+
}
|
|
127
|
+
.lite-presence-name { max-width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
128
|
+
.lite-presence-dot {
|
|
129
|
+
margin-left: 4px;
|
|
130
|
+
width: 6px;
|
|
131
|
+
height: 6px;
|
|
132
|
+
border-radius: 999px;
|
|
133
|
+
background: #00b42a;
|
|
134
|
+
box-shadow: 0 0 0 1px #fff;
|
|
135
|
+
}
|
|
136
|
+
.lite-presence-badge[data-status='idle'] .lite-presence-dot { background: #86909c; }
|
|
137
|
+
.lite-presence-badge[data-status='editing'] .lite-presence-dot { background: #ff7d00; }
|
|
138
|
+
.lite-presence-badge[data-status='viewing'] .lite-presence-dot { background: #00b42a; }
|
|
139
|
+
.lite-presence-overflow {
|
|
140
|
+
display: inline-block;
|
|
141
|
+
min-width: 24px;
|
|
142
|
+
line-height: 24px;
|
|
143
|
+
text-align: center;
|
|
144
|
+
height: 24px;
|
|
145
|
+
padding: 0 5px;
|
|
146
|
+
border: 1px solid #e5e6eb;
|
|
147
|
+
border-radius: 999px;
|
|
148
|
+
background: #fff;
|
|
149
|
+
color: #4e5969;
|
|
53
150
|
font-size: 11px;
|
|
151
|
+
font-weight: 600;
|
|
54
152
|
}
|
|
153
|
+
.lite-presence-empty { color: #86909c; font-size: 11px; }
|
|
154
|
+
.lite-presence-sm .lite-presence-avatar,
|
|
155
|
+
.lite-presence-sm .lite-presence-initials { width: 16px; height: 16px; flex-basis: 16px; font-size: 8px; }
|
|
156
|
+
.lite-presence-lg .lite-presence-avatar,
|
|
157
|
+
.lite-presence-lg .lite-presence-initials { width: 26px; height: 26px; flex-basis: 26px; font-size: 10px; }
|
|
55
158
|
</style>
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
interface LitePresenceUser {
|
|
1
|
+
export interface LitePresenceUser {
|
|
2
2
|
id: string;
|
|
3
3
|
name: string;
|
|
4
|
-
|
|
4
|
+
avatar?: string;
|
|
5
|
+
role?: string;
|
|
6
|
+
status?: 'online' | 'viewing' | 'idle' | 'editing';
|
|
5
7
|
}
|
|
6
8
|
interface Props {
|
|
7
9
|
users?: LitePresenceUser[];
|
|
8
10
|
label?: string;
|
|
9
|
-
|
|
11
|
+
maxVisible?: number;
|
|
12
|
+
maxCount?: number;
|
|
13
|
+
size?: 'sm' | 'md' | 'lg';
|
|
14
|
+
showStatusDot?: boolean;
|
|
10
15
|
class?: string;
|
|
11
16
|
}
|
|
12
17
|
declare const LitePresenceAvatarGroup: import("svelte").Component<Props, {}, "">;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export interface LiteDeleteImpact {
|
|
3
|
+
label: string;
|
|
4
|
+
count?: number;
|
|
5
|
+
description?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
title?: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
impactItems?: LiteDeleteImpact[];
|
|
12
|
+
action: string;
|
|
13
|
+
method?: 'POST' | 'post';
|
|
14
|
+
confirmLabel?: string;
|
|
15
|
+
cancelHref?: string;
|
|
16
|
+
cancelLabel?: string;
|
|
17
|
+
confirmationText?: string;
|
|
18
|
+
name?: string;
|
|
19
|
+
class?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
title = 'Delete this record?',
|
|
24
|
+
description = 'This action cannot be undone. Review the impact before continuing.',
|
|
25
|
+
impactItems = [],
|
|
26
|
+
action,
|
|
27
|
+
method = 'POST',
|
|
28
|
+
confirmLabel = 'Delete permanently',
|
|
29
|
+
cancelHref,
|
|
30
|
+
cancelLabel = 'Cancel',
|
|
31
|
+
confirmationText,
|
|
32
|
+
name = 'confirm',
|
|
33
|
+
class: className = '',
|
|
34
|
+
}: Props = $props();
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<section class="lite-safe-delete lite-card {className}" aria-label={title}>
|
|
38
|
+
<header>
|
|
39
|
+
<h2>{title}</h2>
|
|
40
|
+
<p>{description}</p>
|
|
41
|
+
</header>
|
|
42
|
+
|
|
43
|
+
{#if impactItems.length > 0}
|
|
44
|
+
<ul class="lite-safe-delete-impact">
|
|
45
|
+
{#each impactItems as item (item.label)}
|
|
46
|
+
<li>
|
|
47
|
+
<strong>{item.label}</strong>
|
|
48
|
+
{#if typeof item.count === 'number'}<span class="lite-safe-delete-count">{item.count}</span>{/if}
|
|
49
|
+
{#if item.description}<span class="lite-safe-delete-desc">{item.description}</span>{/if}
|
|
50
|
+
</li>
|
|
51
|
+
{/each}
|
|
52
|
+
</ul>
|
|
53
|
+
{/if}
|
|
54
|
+
|
|
55
|
+
<form method={method} action={action}>
|
|
56
|
+
{#if confirmationText}
|
|
57
|
+
<label for="lite-delete-confirmation">
|
|
58
|
+
Type <strong>{confirmationText}</strong> to confirm
|
|
59
|
+
</label>
|
|
60
|
+
<input id="lite-delete-confirmation" name={name} autocomplete="off" required />
|
|
61
|
+
{/if}
|
|
62
|
+
<div class="lite-safe-delete-actions">
|
|
63
|
+
<button type="submit" class="lite-safe-delete-danger">{confirmLabel}</button>
|
|
64
|
+
{#if cancelHref}<a href={cancelHref}>{cancelLabel}</a>{/if}
|
|
65
|
+
</div>
|
|
66
|
+
</form>
|
|
67
|
+
</section>
|
|
68
|
+
|
|
69
|
+
<style>
|
|
70
|
+
.lite-safe-delete { padding: 16px; border-color: #ffd8d2; }
|
|
71
|
+
.lite-safe-delete header h2 { margin: 0; color: #1d2129; font-size: 16px; }
|
|
72
|
+
.lite-safe-delete header p { margin: 6px 0 12px; color: #4e5969; font-size: 12px; line-height: 1.5; }
|
|
73
|
+
.lite-safe-delete-impact { margin: 0 0 12px; padding: 0; list-style: none; }
|
|
74
|
+
.lite-safe-delete-impact li { display: flex; align-items: baseline; padding: 7px 9px; border: 1px solid #ffe4e0; border-radius: 4px; background: #fff7f6; color: #4e5969; font-size: 12px; }
|
|
75
|
+
.lite-safe-delete-impact li > * + * { margin-left: 6px; }
|
|
76
|
+
.lite-safe-delete-impact li + li { margin-top: 6px; }
|
|
77
|
+
.lite-safe-delete-impact strong { color: #1d2129; }
|
|
78
|
+
.lite-safe-delete-count { padding: 1px 5px; border-radius: 999px; background: #f53f3f; color: #fff; font-size: 11px; }
|
|
79
|
+
.lite-safe-delete-desc { color: #86909c; }
|
|
80
|
+
.lite-safe-delete label { display: block; margin-bottom: 5px; color: #4e5969; font-size: 12px; }
|
|
81
|
+
.lite-safe-delete input { width: 100%; box-sizing: border-box; padding: 7px 9px; border: 1px solid #e5e6eb; border-radius: 4px; font: inherit; font-size: 13px; }
|
|
82
|
+
.lite-safe-delete-actions { display: flex; align-items: center; margin-top: 12px; }
|
|
83
|
+
.lite-safe-delete-actions > * + * { margin-left: 12px; }
|
|
84
|
+
.lite-safe-delete-danger { padding: 7px 12px; border: 0; border-radius: 4px; background: #f53f3f; color: #fff; font-size: 12px; cursor: pointer; }
|
|
85
|
+
.lite-safe-delete-actions a { color: #4e5969; font-size: 12px; }
|
|
86
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface LiteDeleteImpact {
|
|
2
|
+
label: string;
|
|
3
|
+
count?: number;
|
|
4
|
+
description?: string;
|
|
5
|
+
}
|
|
6
|
+
interface Props {
|
|
7
|
+
title?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
impactItems?: LiteDeleteImpact[];
|
|
10
|
+
action: string;
|
|
11
|
+
method?: 'POST' | 'post';
|
|
12
|
+
confirmLabel?: string;
|
|
13
|
+
cancelHref?: string;
|
|
14
|
+
cancelLabel?: string;
|
|
15
|
+
confirmationText?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
class?: string;
|
|
18
|
+
}
|
|
19
|
+
declare const LiteSafeDeleteConfirm: import("svelte").Component<Props, {}, "">;
|
|
20
|
+
type LiteSafeDeleteConfirm = ReturnType<typeof LiteSafeDeleteConfirm>;
|
|
21
|
+
export default LiteSafeDeleteConfirm;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
interface Props {
|
|
5
5
|
name?: string;
|
|
6
6
|
value?: string;
|
|
7
|
+
alt?: string;
|
|
7
8
|
label?: string;
|
|
8
9
|
formAction?: string;
|
|
9
10
|
disabled?: boolean;
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
let {
|
|
14
15
|
name = 'signature',
|
|
15
16
|
value = '',
|
|
17
|
+
alt = 'Signature preview',
|
|
16
18
|
label = 'Electronic Signature',
|
|
17
19
|
formAction = '',
|
|
18
20
|
disabled = false,
|
|
@@ -28,7 +30,7 @@
|
|
|
28
30
|
|
|
29
31
|
{#if value}
|
|
30
32
|
<div class="sv-lite-sig-preview">
|
|
31
|
-
<img src={value} alt=
|
|
33
|
+
<img src={value} alt={alt} class="sv-lite-sig-img" />
|
|
32
34
|
</div>
|
|
33
35
|
{:else}
|
|
34
36
|
<div class="sv-lite-sig-empty">
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export type LiteTaskStatus = 'running' | 'pending' | 'completed' | 'failed' | 'cancelled';
|
|
3
|
+
|
|
4
|
+
export interface LiteBackgroundTask {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
status: LiteTaskStatus;
|
|
8
|
+
progress?: number;
|
|
9
|
+
processed?: number;
|
|
10
|
+
total?: number;
|
|
11
|
+
error?: string;
|
|
12
|
+
downloadHref?: string;
|
|
13
|
+
cancelAction?: string;
|
|
14
|
+
retryAction?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
tasks?: LiteBackgroundTask[];
|
|
19
|
+
title?: string;
|
|
20
|
+
emptyText?: string;
|
|
21
|
+
clearAction?: string;
|
|
22
|
+
clearLabel?: string;
|
|
23
|
+
class?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let {
|
|
27
|
+
tasks = [],
|
|
28
|
+
title = 'Background tasks',
|
|
29
|
+
emptyText = 'No background tasks',
|
|
30
|
+
clearAction,
|
|
31
|
+
clearLabel = 'Clear completed',
|
|
32
|
+
class: className = '',
|
|
33
|
+
}: Props = $props();
|
|
34
|
+
|
|
35
|
+
const completedCount = $derived(tasks.filter((task) => task.status === 'completed').length);
|
|
36
|
+
|
|
37
|
+
function statusLabel(status: LiteTaskStatus): string {
|
|
38
|
+
switch (status) {
|
|
39
|
+
case 'running': return 'Running';
|
|
40
|
+
case 'pending': return 'Pending';
|
|
41
|
+
case 'completed': return 'Completed';
|
|
42
|
+
case 'failed': return 'Failed';
|
|
43
|
+
case 'cancelled': return 'Cancelled';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function boundedProgress(task: LiteBackgroundTask): number {
|
|
48
|
+
if (typeof task.progress === 'number') return Math.max(0, Math.min(100, task.progress));
|
|
49
|
+
if (task.total && task.total > 0 && typeof task.processed === 'number') {
|
|
50
|
+
return Math.max(0, Math.min(100, Math.round((task.processed / task.total) * 100)));
|
|
51
|
+
}
|
|
52
|
+
return task.status === 'completed' ? 100 : 0;
|
|
53
|
+
}
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<section class="lite-task-center lite-card {className}" aria-label={title}>
|
|
57
|
+
<header>
|
|
58
|
+
<div>
|
|
59
|
+
<h2>{title}</h2>
|
|
60
|
+
<p>{tasks.length} task{tasks.length === 1 ? '' : 's'}{completedCount ? ` · ${completedCount} completed` : ''}</p>
|
|
61
|
+
</div>
|
|
62
|
+
{#if clearAction && completedCount > 0}
|
|
63
|
+
<form method="POST" action={clearAction}>
|
|
64
|
+
<button type="submit" class="lite-task-secondary">{clearLabel}</button>
|
|
65
|
+
</form>
|
|
66
|
+
{/if}
|
|
67
|
+
</header>
|
|
68
|
+
|
|
69
|
+
{#if tasks.length === 0}
|
|
70
|
+
<p class="lite-task-empty" role="status">{emptyText}</p>
|
|
71
|
+
{:else}
|
|
72
|
+
<table class="lite-table">
|
|
73
|
+
<thead>
|
|
74
|
+
<tr>
|
|
75
|
+
<th>Task</th>
|
|
76
|
+
<th>Status</th>
|
|
77
|
+
<th>Progress</th>
|
|
78
|
+
<th>Actions</th>
|
|
79
|
+
</tr>
|
|
80
|
+
</thead>
|
|
81
|
+
<tbody>
|
|
82
|
+
{#each tasks as task (task.id)}
|
|
83
|
+
<tr data-task-id={task.id} data-status={task.status}>
|
|
84
|
+
<td>
|
|
85
|
+
<strong>{task.title}</strong>
|
|
86
|
+
{#if task.error}<span class="lite-task-error">{task.error}</span>{/if}
|
|
87
|
+
</td>
|
|
88
|
+
<td><span class="lite-task-status" data-status={task.status}>{statusLabel(task.status)}</span></td>
|
|
89
|
+
<td>
|
|
90
|
+
<progress max="100" value={boundedProgress(task)} aria-label={`${task.title} progress`}></progress>
|
|
91
|
+
<span class="lite-task-progress-text">
|
|
92
|
+
{boundedProgress(task)}%{typeof task.processed === 'number' && typeof task.total === 'number'
|
|
93
|
+
? ` · ${task.processed} / ${task.total}`
|
|
94
|
+
: ''}
|
|
95
|
+
</span>
|
|
96
|
+
</td>
|
|
97
|
+
<td class="lite-task-actions">
|
|
98
|
+
{#if task.downloadHref}<a href={task.downloadHref}>Download</a>{/if}
|
|
99
|
+
{#if task.retryAction}
|
|
100
|
+
<form method="POST" action={task.retryAction}><button type="submit" class="lite-task-link">Retry</button></form>
|
|
101
|
+
{/if}
|
|
102
|
+
{#if task.cancelAction && (task.status === 'running' || task.status === 'pending')}
|
|
103
|
+
<form method="POST" action={task.cancelAction}><button type="submit" class="lite-task-link">Cancel</button></form>
|
|
104
|
+
{/if}
|
|
105
|
+
</td>
|
|
106
|
+
</tr>
|
|
107
|
+
{/each}
|
|
108
|
+
</tbody>
|
|
109
|
+
</table>
|
|
110
|
+
{/if}
|
|
111
|
+
</section>
|
|
112
|
+
|
|
113
|
+
<style>
|
|
114
|
+
.lite-task-center { padding: 16px; }
|
|
115
|
+
.lite-task-center header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 12px; }
|
|
116
|
+
.lite-task-center header > form { margin-left: 12px; }
|
|
117
|
+
.lite-task-center header h2 { margin: 0; color: #1d2129; font-size: 16px; }
|
|
118
|
+
.lite-task-center header p { margin: 4px 0 0; color: #4e5969; font-size: 12px; }
|
|
119
|
+
.lite-task-empty { margin: 0; padding: 20px; color: #86909c; text-align: center; }
|
|
120
|
+
.lite-task-error { display: block; margin-top: 3px; color: #f53f3f; font-size: 11px; }
|
|
121
|
+
.lite-task-status { display: inline-block; padding: 2px 6px; border-radius: 999px; background: #f2f3f5; color: #4e5969; font-size: 11px; }
|
|
122
|
+
.lite-task-status[data-status='running'] { background: #e8f3ff; color: #165dff; }
|
|
123
|
+
.lite-task-status[data-status='pending'] { background: #fff7e8; color: #d25f00; }
|
|
124
|
+
.lite-task-status[data-status='completed'] { background: #e8ffea; color: #00b42a; }
|
|
125
|
+
.lite-task-status[data-status='failed'] { background: #ffece8; color: #f53f3f; }
|
|
126
|
+
.lite-task-status[data-status='cancelled'] { background: #f2f3f5; color: #86909c; }
|
|
127
|
+
.lite-task-center progress { width: 100%; height: 6px; }
|
|
128
|
+
.lite-task-progress-text { display: block; margin-top: 3px; color: #86909c; font-size: 11px; }
|
|
129
|
+
.lite-task-actions { white-space: nowrap; }
|
|
130
|
+
.lite-task-actions form { display: inline; }
|
|
131
|
+
.lite-task-link { padding: 0; border: 0; background: none; color: #165dff; font: inherit; font-size: 12px; cursor: pointer; }
|
|
132
|
+
.lite-task-secondary { padding: 5px 9px; border: 1px solid #e5e6eb; border-radius: 4px; background: #fff; color: #4e5969; font-size: 11px; cursor: pointer; }
|
|
133
|
+
</style>
|