@svar-ui/svelte-kanban 2.6.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 (58) hide show
  1. package/dist/components/Avatar.svelte +87 -0
  2. package/dist/components/Avatar.svelte.d.ts +17 -0
  3. package/dist/components/Card.svelte +346 -0
  4. package/dist/components/Card.svelte.d.ts +9 -0
  5. package/dist/components/CardList.svelte +343 -0
  6. package/dist/components/CardList.svelte.d.ts +21 -0
  7. package/dist/components/CardWrapper.svelte +49 -0
  8. package/dist/components/CardWrapper.svelte.d.ts +15 -0
  9. package/dist/components/Column.svelte +230 -0
  10. package/dist/components/Column.svelte.d.ts +24 -0
  11. package/dist/components/ContextMenu.svelte +111 -0
  12. package/dist/components/ContextMenu.svelte.d.ts +18 -0
  13. package/dist/components/DragGhost.svelte +57 -0
  14. package/dist/components/DragGhost.svelte.d.ts +14 -0
  15. package/dist/components/Editor.svelte +108 -0
  16. package/dist/components/Editor.svelte.d.ts +10 -0
  17. package/dist/components/ExportLayout.svelte +52 -0
  18. package/dist/components/ExportLayout.svelte.d.ts +15 -0
  19. package/dist/components/Kanban.svelte +100 -0
  20. package/dist/components/Kanban.svelte.d.ts +186 -0
  21. package/dist/components/Layout.svelte +362 -0
  22. package/dist/components/Layout.svelte.d.ts +19 -0
  23. package/dist/components/Toolbar.svelte +97 -0
  24. package/dist/components/Toolbar.svelte.d.ts +12 -0
  25. package/dist/components/useCardOverlay.svelte.d.ts +24 -0
  26. package/dist/components/useCardOverlay.svelte.js +60 -0
  27. package/dist/components/useDrag.svelte.d.ts +22 -0
  28. package/dist/components/useDrag.svelte.js +16 -0
  29. package/dist/context.d.ts +3 -0
  30. package/dist/context.js +3 -0
  31. package/dist/defaults.d.ts +8 -0
  32. package/dist/defaults.js +85 -0
  33. package/dist/directives/dblclick.d.ts +13 -0
  34. package/dist/directives/dblclick.js +26 -0
  35. package/dist/directives/drag.d.ts +11 -0
  36. package/dist/directives/drag.js +183 -0
  37. package/dist/env.d.ts +9 -0
  38. package/dist/export/Card.svelte +5 -0
  39. package/dist/export/Card.svelte.d.ts +11 -0
  40. package/dist/export/Kanban.svelte +30 -0
  41. package/dist/export/Kanban.svelte.d.ts +16 -0
  42. package/dist/export.d.ts +3 -0
  43. package/dist/export.js +15 -0
  44. package/dist/index.d.ts +15 -0
  45. package/dist/index.js +15 -0
  46. package/dist/themes/Print.svelte +126 -0
  47. package/dist/themes/Print.svelte.d.ts +13 -0
  48. package/dist/themes/PrintBW.svelte +153 -0
  49. package/dist/themes/PrintBW.svelte.d.ts +13 -0
  50. package/dist/themes/Willow.svelte +45 -0
  51. package/dist/themes/Willow.svelte.d.ts +7 -0
  52. package/dist/themes/WillowDark.svelte +49 -0
  53. package/dist/themes/WillowDark.svelte.d.ts +7 -0
  54. package/dist/types.d.ts +86 -0
  55. package/dist/types.js +1 -0
  56. package/license.txt +21 -0
  57. package/package.json +59 -0
  58. package/readme.md +100 -0
@@ -0,0 +1,87 @@
1
+ <script lang="ts">let { value, size = 24, limit } = $props();
2
+ const users = $derived(Array.isArray(value) ? value : value ? [value] : []);
3
+ const safeLimit = $derived(typeof limit === "number" && Number.isFinite(limit) ? Math.max(0, Math.floor(limit)) : users.length);
4
+ const visibleUsers = $derived(users.slice(0, safeLimit));
5
+ const overflowCount = $derived(Math.max(0, users.length - visibleUsers.length));
6
+ const fontSize = $derived(Math.max(10, Math.round(size * .42)));
7
+ const stackStyle = $derived(`--wx-avatar-size:${size}px;--wx-avatar-font-size:${fontSize}px;`);
8
+ function getLabel(user) {
9
+ return user.label ?? user.name ?? "";
10
+ }
11
+ function getInitials(user) {
12
+ const label = getLabel(user).trim();
13
+ if (!label) return "";
14
+ const words = label.split(/\s+/);
15
+ return (words[0][0] + (words[1]?.[0] ?? "")).toUpperCase().slice(0, 2);
16
+ }
17
+ function getImage(user) {
18
+ return user.img ?? user.avatar;
19
+ }
20
+ export {};
21
+ </script>
22
+
23
+ {#if users.length > 0}
24
+ <div class="wx-avatars" style={stackStyle}>
25
+ {#each visibleUsers as user (user.id)}
26
+ {@const label = getLabel(user)}
27
+ {@const image = getImage(user)}
28
+ <span
29
+ class="wx-avatar {user.css ?? ''}"
30
+ title={label || undefined}
31
+ aria-label={label || undefined}
32
+ >
33
+ {#if image}
34
+ <img class="wx-image" src={image} alt="" loading="lazy" />
35
+ {:else}
36
+ {getInitials(user)}
37
+ {/if}
38
+ </span>
39
+ {/each}
40
+ {#if overflowCount > 0}
41
+ <span class="wx-avatar wx-more">+{overflowCount}</span>
42
+ {/if}
43
+ </div>
44
+ {/if}
45
+
46
+ <style>
47
+ .wx-avatars {
48
+ display: inline-flex;
49
+ align-items: center;
50
+ min-width: 0;
51
+ }
52
+
53
+ .wx-avatar {
54
+ display: inline-flex;
55
+ align-items: center;
56
+ justify-content: center;
57
+ width: var(--wx-avatar-size);
58
+ height: var(--wx-avatar-size);
59
+ margin-left: calc(var(--wx-avatar-size) * -0.25);
60
+ border: 2px solid var(--wx-kanban-card-bg);
61
+ border-radius: 50%;
62
+ background: var(--wx-kanban-avatar-bg);
63
+ color: var(--wx-color-font);
64
+ font-size: var(--wx-avatar-font-size);
65
+ font-weight: var(--wx-font-weight-md);
66
+ line-height: 1;
67
+ overflow: hidden;
68
+ text-transform: uppercase;
69
+ user-select: none;
70
+ }
71
+
72
+ .wx-avatar:first-child {
73
+ margin-left: 0;
74
+ }
75
+
76
+ .wx-image {
77
+ display: block;
78
+ width: 100%;
79
+ height: 100%;
80
+ object-fit: cover;
81
+ }
82
+
83
+ .wx-more {
84
+ font-size: calc(var(--wx-avatar-font-size) * 0.85);
85
+ text-transform: none;
86
+ }
87
+ </style>
@@ -0,0 +1,17 @@
1
+ import type { CardID } from "@svar-ui/kanban-store";
2
+ type AvatarUser = {
3
+ id: CardID;
4
+ label?: string;
5
+ name?: string;
6
+ img?: string;
7
+ avatar?: string;
8
+ css?: string;
9
+ };
10
+ type Props = {
11
+ value?: AvatarUser | AvatarUser[] | null;
12
+ size?: number;
13
+ limit?: number;
14
+ };
15
+ declare const Avatar: import("svelte").Component<Props, {}, "">;
16
+ type Avatar = ReturnType<typeof Avatar>;
17
+ export default Avatar;
@@ -0,0 +1,346 @@
1
+ <script lang="ts">import { getContext } from "svelte";
2
+ import Avatar from "./Avatar.svelte";
3
+ import { getPriorityOptions } from "../defaults.js";
4
+ let { card, cardShape } = $props();
5
+ const _ = getContext("wx-i18n").getGroup("kanban");
6
+ function countOf(v) {
7
+ if (typeof v === "number") return v;
8
+ if (Array.isArray(v)) return v.length;
9
+ return 0;
10
+ }
11
+ function configOf(shape) {
12
+ return typeof shape === "object" && shape !== null ? shape : undefined;
13
+ }
14
+ function itemID(value) {
15
+ if (typeof value === "string" || typeof value === "number") return value;
16
+ const id = value?.id;
17
+ if (typeof id === "string" || typeof id === "number") return id;
18
+ return null;
19
+ }
20
+ function fallbackLabel(value) {
21
+ const id = itemID(value);
22
+ if (id != null) return String(id);
23
+ return String(value?.label ?? value?.name ?? "");
24
+ }
25
+ function findItem(collection, id) {
26
+ return collection?.find((item) => item.id === id);
27
+ }
28
+ function resolveItem(value, collection) {
29
+ const id = itemID(value);
30
+ if (id == null) return null;
31
+ const match = findItem(collection, id);
32
+ if (match) return match;
33
+ return {
34
+ id,
35
+ label: fallbackLabel(value)
36
+ };
37
+ }
38
+ function resolveItems(values, collection, max) {
39
+ if (!Array.isArray(values)) return [];
40
+ const items = values.map((value) => resolveItem(value, collection)).filter((item) => item !== null);
41
+ return typeof max === "number" && Number.isFinite(max) ? items.slice(0, Math.max(0, max)) : items;
42
+ }
43
+ function toDate(value) {
44
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;
45
+ if (typeof value === "string" || typeof value === "number") {
46
+ const d = new Date(value);
47
+ return Number.isNaN(d.getTime()) ? null : d;
48
+ }
49
+ return null;
50
+ }
51
+ function pad2(n) {
52
+ return n < 10 ? "0" + n : String(n);
53
+ }
54
+ function formatDeadline(value, format) {
55
+ const d = toDate(value);
56
+ if (!d) return null;
57
+ if (!format) return d.toLocaleDateString();
58
+ return format.replace(/YYYY/g, String(d.getFullYear())).replace(/MM/g, pad2(d.getMonth() + 1)).replace(/DD/g, pad2(d.getDate())).replace(/HH/g, pad2(d.getHours())).replace(/mm/g, pad2(d.getMinutes()));
59
+ }
60
+ let priorityConfig = $derived(configOf(cardShape.priority));
61
+ let tagConfig = $derived(configOf(cardShape.tags));
62
+ let userConfig = $derived(configOf(cardShape.users));
63
+ let deadlineConfig = $derived(configOf(cardShape.deadline));
64
+ let progressConfig = $derived(configOf(cardShape.progress));
65
+ let progressPercent = $derived(Math.round(Math.max(0, Math.min(1, card.progress ?? 0)) * 100));
66
+ let priority = $derived(cardShape.priority ? resolveItem(card.priority, priorityConfig?.data ?? getPriorityOptions()) : null);
67
+ let tags = $derived(cardShape.tags ? resolveItems(card.tags, tagConfig?.data, tagConfig?.max) : []);
68
+ let users = $derived(cardShape.users ? resolveItems(card.users, userConfig?.data, userConfig?.max) : []);
69
+ let avatarUsers = $derived(users.map((u) => ({
70
+ id: u.id,
71
+ name: u.label,
72
+ avatar: u.img
73
+ })));
74
+ let deadline = $derived(cardShape.deadline ? formatDeadline(card.deadline, deadlineConfig?.format) : null);
75
+ </script>
76
+
77
+ {#if card.cover && cardShape.cover}
78
+ <div
79
+ class="wx-cover"
80
+ style="background-image: url({card.cover});"
81
+ ></div>
82
+ {/if}
83
+
84
+
85
+ {#if priority || deadline}
86
+ <div class="wx-header">
87
+ {#if priority}
88
+ <span class="wx-priority {priority.css ?? ''}"
89
+ >{_(priority.label)}</span
90
+ >
91
+ {/if}
92
+ {#if deadline}
93
+ <span class="wx-deadline">{deadline}</span>
94
+ {/if}
95
+ {#if cardShape.menu}
96
+ <button
97
+ type="button"
98
+ class="wx-menu"
99
+ data-action="menu"
100
+ aria-label={_("Card menu")}
101
+ >
102
+ <i class="wx-icon wxi-dots-h"></i>
103
+ </button>
104
+ {/if}
105
+ </div>
106
+ {/if}
107
+
108
+
109
+ <div class="wx-body">
110
+ <div class="wx-title-row">
111
+ {#if card.label}
112
+ <div class="wx-title">
113
+ {card.label}
114
+
115
+ {#if cardShape.menu && !priority && !deadline}
116
+ <button
117
+ type="button"
118
+ class="wx-menu"
119
+ data-action="menu"
120
+ aria-label={_("Card menu")}
121
+ >
122
+ <i class="wx-icon wxi-dots-h"></i>
123
+ </button>
124
+ {/if}
125
+ </div>
126
+ {/if}
127
+ </div>
128
+ {#if card.description && cardShape.description}
129
+ <p class="wx-description">{card.description}</p>
130
+ {/if}
131
+ {#if tags.length > 0}
132
+ <div class="wx-tags">
133
+ {#each tags as tag (tag.id)}
134
+ <span class="wx-tag {tag.css ?? ''}">{tag.label}</span>
135
+ {/each}
136
+ </div>
137
+ {/if}
138
+ {#if card.progress > 0 && cardShape.progress}
139
+ <div class="wx-progress-row">
140
+ <div class="wx-progress" aria-label={_("Progress")}>
141
+ <div
142
+ class="wx-progress-fill"
143
+ style="width: {progressPercent}%;"
144
+ ></div>
145
+ </div>
146
+ {#if progressConfig?.showLabel}
147
+ <span class="wx-progress-label">{progressPercent}%</span>
148
+ {/if}
149
+ </div>
150
+ {/if}
151
+ </div>
152
+
153
+
154
+ {#if users.length > 0 || (countOf(card.attachments) > 0 && cardShape.attachments) || (countOf(card.comments) > 0 && cardShape.comments)}
155
+ <div class="wx-footer">
156
+ {#if avatarUsers.length > 0}
157
+ <Avatar value={avatarUsers} size={24} />
158
+ {/if}
159
+ <div class="wx-counters">
160
+ {#if countOf(card.attachments) > 0 && cardShape.attachments}
161
+ <span class="wx-counter" aria-label={_("Attachments")}>
162
+ <i class="wx-icon wxi-paperclip"></i>
163
+ {countOf(card.attachments)}
164
+ </span>
165
+ {/if}
166
+ {#if countOf(card.comments) > 0 && cardShape.comments}
167
+ <span class="wx-counter" aria-label={_("Comments")}>
168
+ <i class="wx-icon wxi-message"></i>
169
+ {countOf(card.comments)}
170
+ </span>
171
+ {/if}
172
+ </div>
173
+ </div>
174
+ {/if}
175
+
176
+ <style>
177
+ .wx-cover {
178
+ margin: -12px;
179
+ margin-bottom: 0;
180
+ height: 80px;
181
+ background-size: cover;
182
+ background-position: center;
183
+ border-top-left-radius: var(--wx-border-radius);
184
+ border-top-right-radius: var(--wx-border-radius);
185
+ }
186
+
187
+ .wx-header {
188
+ display: flex;
189
+ align-items: center;
190
+ justify-content: space-between;
191
+ gap: 6px;
192
+ font-size: var(--wx-font-size-sm);
193
+ }
194
+
195
+ .wx-priority {
196
+ background: var(--wx-kanban-tag-bg);
197
+ border-radius: var(--wx-border-radius);
198
+ padding: 1px 6px;
199
+ font-weight: var(--wx-font-weight-md);
200
+ }
201
+
202
+ .wx-header :global(.wx-card-priority-low) {
203
+ background: var(--wx-kanban-priority-low-bg);
204
+ color: var(--wx-kanban-priority-low-color);
205
+ }
206
+
207
+ .wx-header :global(.wx-card-priority-medium) {
208
+ background: var(--wx-kanban-priority-medium-bg);
209
+ color: var(--wx-kanban-priority-medium-color);
210
+ }
211
+
212
+ .wx-header :global(.wx-card-priority-high) {
213
+ background: var(--wx-kanban-priority-high-bg);
214
+ color: var(--wx-kanban-priority-high-color);
215
+ }
216
+
217
+ .wx-deadline {
218
+ margin-left: auto;
219
+ color: var(--wx-color-font-alt);
220
+ }
221
+
222
+ .wx-menu {
223
+ display: inline-flex;
224
+ align-items: center;
225
+ justify-content: center;
226
+ background: none;
227
+ border: none;
228
+ padding: 0;
229
+ width: 22px;
230
+ height: 22px;
231
+ border-radius: var(--wx-icon-border-radius);
232
+ cursor: pointer;
233
+ color: var(--wx-color-font-alt);
234
+ font-size: 16px;
235
+ }
236
+
237
+ .wx-title .wx-menu {
238
+ float: right;
239
+ }
240
+
241
+ .wx-menu:hover {
242
+ background: var(--wx-background-hover);
243
+ color: var(--wx-color-font);
244
+ }
245
+
246
+ .wx-menu:focus-visible {
247
+ outline: none;
248
+ border: 1px solid var(--wx-color-primary);
249
+ }
250
+
251
+ .wx-body {
252
+ display: flex;
253
+ flex-direction: column;
254
+ gap: 4px;
255
+ }
256
+
257
+ .wx-title {
258
+ font-weight: var(--wx-font-weight-md);
259
+ overflow: hidden;
260
+ display: -webkit-box;
261
+ -webkit-line-clamp: 2;
262
+ line-clamp: 2;
263
+ -webkit-box-orient: vertical;
264
+ }
265
+
266
+ .wx-description {
267
+ margin: 0;
268
+ color: var(--wx-color-font-alt);
269
+ font-size: 13px;
270
+ overflow: hidden;
271
+ display: -webkit-box;
272
+ -webkit-line-clamp: 2;
273
+ line-clamp: 2;
274
+ -webkit-box-orient: vertical;
275
+ margin-bottom: 4px;
276
+ }
277
+
278
+ .wx-tags {
279
+ display: flex;
280
+ flex-wrap: wrap;
281
+ gap: 4px;
282
+ margin-bottom: 4px;
283
+ }
284
+
285
+ .wx-tag {
286
+ background: var(--wx-kanban-tag-bg);
287
+ border-radius: 10px;
288
+ padding: 1px 8px;
289
+ font-size: 11px;
290
+ color: var(--wx-color-font-alt);
291
+ }
292
+
293
+ .wx-progress-row {
294
+ display: flex;
295
+ align-items: center;
296
+ gap: 6px;
297
+ }
298
+
299
+ .wx-progress {
300
+ flex: 1;
301
+ height: 4px;
302
+ background: var(--wx-kanban-progress-bg);
303
+ border-radius: 2px;
304
+ overflow: hidden;
305
+ }
306
+
307
+ .wx-progress-label {
308
+ font-size: 11px;
309
+ color: var(--wx-color-font-alt);
310
+ min-width: 32px;
311
+ text-align: right;
312
+ }
313
+
314
+ .wx-progress-fill {
315
+ height: 100%;
316
+ background: var(--wx-kanban-progress-fill);
317
+ }
318
+
319
+ .wx-footer {
320
+ display: flex;
321
+ justify-content: space-between;
322
+ align-items: center;
323
+ margin-top: 2px;
324
+ }
325
+
326
+ .wx-counters {
327
+ display: flex;
328
+ gap: 12px;
329
+ font-size: var(--wx-font-size-sm);
330
+ color: var(--wx-color-font-alt);
331
+ margin-top: -5px;
332
+ margin-left: auto;
333
+ }
334
+
335
+ .wx-counter {
336
+ display: inline-flex;
337
+ align-items: center;
338
+ gap: 2px;
339
+ }
340
+
341
+ .wx-counter .wx-icon {
342
+ font-size: 16px;
343
+ margin-top: 7px;
344
+ margin-bottom: 5px;
345
+ }
346
+ </style>
@@ -0,0 +1,9 @@
1
+ import type { KanbanCard } from "@svar-ui/kanban-store";
2
+ import type { CardShape } from "../types.js";
3
+ type Props = {
4
+ card: KanbanCard;
5
+ cardShape: CardShape;
6
+ };
7
+ declare const Card: import("svelte").Component<Props, {}, "">;
8
+ type Card = ReturnType<typeof Card>;
9
+ export default Card;