@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,343 @@
1
+ <script lang="ts">import { getContext, onDestroy } from "svelte";
2
+ import { setID } from "@svar-ui/lib-dom";
3
+ import CardWrapper from "./CardWrapper.svelte";
4
+ import { KANBAN_API_CONTEXT, DND_CONTEXT, SCROLL_CONTAINER_CONTEXT } from "../context.js";
5
+ import { DndState } from "./useDrag.svelte.js";
6
+ import { dblclick } from "../directives/dblclick.js";
7
+ let { column, readonly = false, cardContent, cardShape, contentVisible, virtualizeCards, estimatedCardHeight, cardOverscan, fixedColumnWidth, cardCss } = $props();
8
+ function getCardExtraCss(card) {
9
+ return cardCss ? cardCss(card, column) ?? "" : "";
10
+ }
11
+ const store = getContext(KANBAN_API_CONTEXT);
12
+ const dnd = getContext(DND_CONTEXT);
13
+ const getScrollContainer = getContext(SCROLL_CONTAINER_CONTEXT);
14
+ const columnAccessor = $derived(store.getState().columnAccessor);
15
+ let container = $state();
16
+ let range = $state({
17
+ start: 0,
18
+ end: -1,
19
+ top: 0,
20
+ bottom: 0,
21
+ total: 0
22
+ });
23
+ let cardGap = $state(8);
24
+ let frame = 0;
25
+ let previousVirtualizeCards = false;
26
+ const heightCache = new Map();
27
+ const measuredNodes = new Map();
28
+ let cardObserver;
29
+ const isDropColumn = $derived(dnd?.active && dnd.target?.column === column.id);
30
+ const renderStart = $derived(virtualizeCards ? range.start : 0);
31
+ const renderEnd = $derived(virtualizeCards ? range.end : column.cards.length - 1);
32
+ const renderedCards = $derived(contentVisible ? column.cards.slice(renderStart, Math.max(renderStart, renderEnd + 1)) : []);
33
+ const hiddenHeight = $derived(range.total || estimateTotalHeight(column.cards.length));
34
+ const topSpacerHeight = $derived(range.start > 0 ? Math.max(0, range.top - cardGap) : 0);
35
+ const bottomSpacerHeight = $derived(range.bottom > 0 ? Math.max(0, range.bottom - cardGap) : 0);
36
+ const afterRenderedBeforeId = $derived(contentVisible && virtualizeCards && renderEnd >= 0 && renderEnd < column.cards.length - 1 ? column.cards[renderEnd + 1]?.id : undefined);
37
+ const trailingPlaceholder = $derived(isDropColumn && (dnd.target.beforeId == null || dnd.target.beforeId === afterRenderedBeforeId));
38
+ function estimateTotalHeight(count) {
39
+ if (!count) return 0;
40
+ const height = Math.max(1, estimatedCardHeight || 1);
41
+ return count * height + Math.max(0, count - 1) * cardGap;
42
+ }
43
+ function readGap() {
44
+ if (!container) return;
45
+ const styles = getComputedStyle(container);
46
+ const configuredGap = styles.getPropertyValue("--wx-card-gap").trim();
47
+ const raw = configuredGap || styles.rowGap || styles.gap;
48
+ const next = Number.parseFloat(raw);
49
+ cardGap = Number.isFinite(next) ? next : 8;
50
+ }
51
+ function getCardHeight(card) {
52
+ return card.id != null ? heightCache.get(card.id) ?? Math.max(1, estimatedCardHeight || 1) : Math.max(1, estimatedCardHeight || 1);
53
+ }
54
+ function buildOffsets() {
55
+ const offsets = [0];
56
+ let total = 0;
57
+ for (let i = 0; i < column.cards.length; i++) {
58
+ total += getCardHeight(column.cards[i]);
59
+ if (i < column.cards.length - 1) total += cardGap;
60
+ offsets.push(total);
61
+ }
62
+ return {
63
+ offsets,
64
+ total
65
+ };
66
+ }
67
+ function upperBound(values, value) {
68
+ let low = 0;
69
+ let high = values.length;
70
+ while (low < high) {
71
+ const mid = Math.floor((low + high) / 2);
72
+ if (values[mid] <= value) low = mid + 1;
73
+ else high = mid;
74
+ }
75
+ return low;
76
+ }
77
+ function recalculate() {
78
+ if (!container) return;
79
+ readGap();
80
+ if (!contentVisible || !virtualizeCards) {
81
+ range = {
82
+ start: 0,
83
+ end: contentVisible ? column.cards.length - 1 : -1,
84
+ top: 0,
85
+ bottom: 0,
86
+ total: estimateTotalHeight(column.cards.length)
87
+ };
88
+ return;
89
+ }
90
+ const count = column.cards.length;
91
+ if (!count) {
92
+ range = {
93
+ start: 0,
94
+ end: -1,
95
+ top: 0,
96
+ bottom: 0,
97
+ total: 0
98
+ };
99
+ return;
100
+ }
101
+ const { offsets, total } = buildOffsets();
102
+ const boardScroll = getScrollContainer?.() ?? null;
103
+ let viewportTop = 0;
104
+ let viewportBottom = 0;
105
+ if (boardScroll) {
106
+ const boardRect = boardScroll.getBoundingClientRect();
107
+ const containerRect = container.getBoundingClientRect();
108
+ viewportTop = Math.max(0, boardRect.top - containerRect.top);
109
+ viewportBottom = Math.min(total, boardRect.bottom - containerRect.top);
110
+ } else {
111
+ viewportTop = container.scrollTop;
112
+ viewportBottom = viewportTop + container.clientHeight;
113
+ }
114
+ const safeOverscan = Math.max(0, Math.floor(cardOverscan || 0));
115
+ let start = Math.max(0, upperBound(offsets, viewportTop) - 1);
116
+ let end = Math.max(start, upperBound(offsets, viewportBottom) - 1);
117
+ start = Math.max(0, start - safeOverscan);
118
+ end = Math.min(count - 1, end + safeOverscan);
119
+ range = {
120
+ start,
121
+ end,
122
+ top: offsets[start],
123
+ bottom: Math.max(0, total - offsets[end + 1]),
124
+ total
125
+ };
126
+ }
127
+ function scheduleRecalculate(..._tracked) {
128
+ if (frame) return;
129
+ frame = requestAnimationFrame(() => {
130
+ frame = 0;
131
+ recalculate();
132
+ });
133
+ }
134
+ function pruneHeightCache() {
135
+ const ids = new Set(column.cards.map((card) => card.id));
136
+ for (const id of heightCache.keys()) {
137
+ if (!ids.has(id)) heightCache.delete(id);
138
+ }
139
+ while (heightCache.size > 1e4) {
140
+ const first = heightCache.keys().next().value;
141
+ if (first === undefined) break;
142
+ heightCache.delete(first);
143
+ }
144
+ }
145
+ function ensureCardObserver() {
146
+ if (cardObserver || typeof ResizeObserver === "undefined") return;
147
+ cardObserver = new ResizeObserver((entries) => {
148
+ let changed = false;
149
+ for (const entry of entries) {
150
+ const id = measuredNodes.get(entry.target);
151
+ if (id == null) continue;
152
+ const height = Math.ceil(entry.target.offsetHeight);
153
+ if (height > 0 && heightCache.get(id) !== height) {
154
+ heightCache.delete(id);
155
+ heightCache.set(id, height);
156
+ changed = true;
157
+ }
158
+ }
159
+ if (changed) scheduleRecalculate();
160
+ });
161
+ }
162
+ function measureCard(node, card) {
163
+ if (card.id != null) {
164
+ ensureCardObserver();
165
+ measuredNodes.set(node, card.id);
166
+ cardObserver?.observe(node);
167
+ }
168
+ return {
169
+ update(next) {
170
+ cardObserver?.unobserve(node);
171
+ if (next.id != null) {
172
+ measuredNodes.set(node, next.id);
173
+ cardObserver?.observe(node);
174
+ } else {
175
+ measuredNodes.delete(node);
176
+ }
177
+ },
178
+ destroy() {
179
+ cardObserver?.unobserve(node);
180
+ measuredNodes.delete(node);
181
+ }
182
+ };
183
+ }
184
+ $effect(() => {
185
+ if (virtualizeCards && !previousVirtualizeCards) {
186
+ heightCache.clear();
187
+ }
188
+ previousVirtualizeCards = virtualizeCards;
189
+ pruneHeightCache();
190
+ scheduleRecalculate(column.cards, contentVisible, estimatedCardHeight, cardOverscan);
191
+ });
192
+ $effect(() => {
193
+ if (!container || !contentVisible || !virtualizeCards) return;
194
+ const scrollElement = getScrollContainer?.() ?? container;
195
+ const onScroll = () => scheduleRecalculate();
196
+ scrollElement.addEventListener("scroll", onScroll, { passive: true });
197
+ window.addEventListener("resize", onScroll);
198
+ scheduleRecalculate();
199
+ return () => {
200
+ scrollElement.removeEventListener("scroll", onScroll);
201
+ window.removeEventListener("resize", onScroll);
202
+ };
203
+ });
204
+ $effect(() => {
205
+ if (!container || !contentVisible || !virtualizeCards || fixedColumnWidth || typeof ResizeObserver === "undefined") {
206
+ return;
207
+ }
208
+ let width = container.clientWidth;
209
+ const observer = new ResizeObserver(() => {
210
+ const next = container?.clientWidth ?? 0;
211
+ if (next && next !== width) {
212
+ width = next;
213
+ heightCache.clear();
214
+ scheduleRecalculate();
215
+ }
216
+ });
217
+ observer.observe(container);
218
+ return () => observer.disconnect();
219
+ });
220
+ onDestroy(() => {
221
+ if (frame) cancelAnimationFrame(frame);
222
+ cardObserver?.disconnect();
223
+ });
224
+ </script>
225
+
226
+ <div
227
+ class="wx-column-cards"
228
+ data-kanban-column-cards={setID(column.id)}
229
+ data-kanban-render-start={renderStart}
230
+ data-kanban-render-end={renderEnd}
231
+ data-kanban-card-count={column.cards.length}
232
+ data-kanban-after-rendered-before-id={afterRenderedBeforeId == null
233
+ ? undefined
234
+ : setID(afterRenderedBeforeId)}
235
+ bind:this={container}
236
+ use:dblclick={{
237
+ store,
238
+ column: column.id,
239
+ columnAccessor,
240
+ readonly,
241
+ }}
242
+ >
243
+ {#if contentVisible}
244
+ {#if virtualizeCards}
245
+ {#if topSpacerHeight > 0}
246
+ <div
247
+ class="wx-virtual-spacer"
248
+ style:height="{topSpacerHeight}px"
249
+ ></div>
250
+ {/if}
251
+ {#each renderedCards as cardItem (cardItem.id)}
252
+ {#if isDropColumn && dnd.target!.beforeId === cardItem.id}
253
+ <div
254
+ class="wx-drop-placeholder"
255
+ style:height="{dnd.height}px"
256
+ ></div>
257
+ {/if}
258
+ {@const extraCss = getCardExtraCss(cardItem)}
259
+ <div
260
+ class="wx-card-row {cardItem.css ?? ''} {extraCss}"
261
+ data-kanban-card-id={cardItem.id == null
262
+ ? undefined
263
+ : setID(cardItem.id)}
264
+ use:measureCard={cardItem}
265
+ class:wx-dragging={dnd?.active &&
266
+ dnd.cardId === cardItem.id}
267
+ >
268
+ <CardWrapper card={cardItem} {cardContent} {cardShape} {extraCss} />
269
+ </div>
270
+ {/each}
271
+ {#if trailingPlaceholder}
272
+ <div
273
+ class="wx-drop-placeholder"
274
+ style:height="{dnd.height}px"
275
+ ></div>
276
+ {/if}
277
+ {#if bottomSpacerHeight > 0}
278
+ <div
279
+ class="wx-virtual-spacer"
280
+ style:height="{bottomSpacerHeight}px"
281
+ ></div>
282
+ {/if}
283
+ {:else}
284
+ {#each column.cards as cardItem (cardItem.id)}
285
+ {#if isDropColumn && dnd.target!.beforeId === cardItem.id}
286
+ <div
287
+ class="wx-drop-placeholder"
288
+ style:height="{dnd.height}px"
289
+ ></div>
290
+ {/if}
291
+ {@const extraCss = getCardExtraCss(cardItem)}
292
+ <div
293
+ class="wx-card-row {cardItem.css ?? ''} {extraCss}"
294
+ data-kanban-card-id={cardItem.id == null
295
+ ? undefined
296
+ : setID(cardItem.id)}
297
+ class:wx-dragging={dnd?.active &&
298
+ dnd.cardId === cardItem.id}
299
+ >
300
+ <CardWrapper card={cardItem} {cardContent} {cardShape} {extraCss} />
301
+ </div>
302
+ {/each}
303
+ {#if trailingPlaceholder}
304
+ <div
305
+ class="wx-drop-placeholder"
306
+ style:height="{dnd.height}px"
307
+ ></div>
308
+ {/if}
309
+ {/if}
310
+ {:else if hiddenHeight > 0}
311
+ <div
312
+ class="wx-virtual-spacer"
313
+ style:height="{hiddenHeight}px"
314
+ ></div>
315
+ {/if}
316
+ </div>
317
+
318
+ <style>
319
+ .wx-column-cards {
320
+ display: flex;
321
+ flex-direction: column;
322
+ gap: 8px;
323
+ padding: 8px;
324
+ overflow-y: auto;
325
+ flex: 1;
326
+ min-height: 0;
327
+ }
328
+
329
+ .wx-column-cards > :global(div) {
330
+ flex-shrink: 0;
331
+ }
332
+
333
+ .wx-drop-placeholder {
334
+ border: 1px dashed var(--wx-kanban-border-color);
335
+ border-radius: var(--wx-border-radius);
336
+ background: var(--wx-kanban-drop-placeholder-bg);
337
+ box-sizing: border-box;
338
+ }
339
+
340
+ .wx-dragging {
341
+ display: none;
342
+ }
343
+ </style>
@@ -0,0 +1,21 @@
1
+ import type { Component } from "svelte";
2
+ import type { ColumnView, KanbanCard } from "@svar-ui/kanban-store";
3
+ import type { CardShape, CardCssFn } from "../types.js";
4
+ type Props = {
5
+ column: ColumnView;
6
+ readonly?: boolean;
7
+ cardContent?: Component<{
8
+ card: KanbanCard;
9
+ cardShape: CardShape;
10
+ }>;
11
+ cardShape: CardShape;
12
+ contentVisible: boolean;
13
+ virtualizeCards: boolean;
14
+ estimatedCardHeight: number;
15
+ cardOverscan: number;
16
+ fixedColumnWidth: boolean;
17
+ cardCss?: CardCssFn;
18
+ };
19
+ declare const CardList: Component<Props, {}, "">;
20
+ type CardList = ReturnType<typeof CardList>;
21
+ export default CardList;
@@ -0,0 +1,49 @@
1
+ <script lang="ts">import { getContext } from "svelte";
2
+ import { setID } from "@svar-ui/lib-dom";
3
+ import Card from "./Card.svelte";
4
+ const { cardContent: CardContent, card, cardShape, extraCss = "" } = $props();
5
+ const _ = getContext("wx-i18n").getGroup("kanban");
6
+ </script>
7
+
8
+ <!-- svelte-ignore a11y_no_noninteractive_element_to_interactive_role -->
9
+ <article
10
+ class="wx-card {card.css ?? ''} {extraCss}"
11
+ data-id={card.id == null ? undefined : setID(card.id)}
12
+ role="button"
13
+ tabindex="0"
14
+ aria-label={card.label ?? `${_("Card")} ${card.id}`}
15
+ >
16
+ {#if CardContent}
17
+ <CardContent
18
+ {card}
19
+ {cardShape}
20
+ ></CardContent>
21
+ {:else}
22
+ <Card card={card} {cardShape} />
23
+ {/if}
24
+ </article>
25
+
26
+ <style>
27
+ .wx-card {
28
+ display: flex;
29
+ flex-direction: column;
30
+ gap: 6px;
31
+ padding: 10px;
32
+ background: var(--wx-kanban-card-bg);
33
+ border-radius: var(--wx-border-radius);
34
+ box-shadow: var(--wx-kanban-card-shadow);
35
+ cursor: pointer;
36
+ border-top: 3px solid transparent;
37
+ touch-action: none;
38
+ user-select: none;
39
+ }
40
+
41
+ .wx-card:hover {
42
+ box-shadow: var(--wx-kanban-card-shadow-hover);
43
+ }
44
+
45
+ .wx-card:focus-visible {
46
+ outline: 2px solid var(--wx-color-primary);
47
+ outline-offset: 1px;
48
+ }
49
+ </style>
@@ -0,0 +1,15 @@
1
+ import type { Component } from "svelte";
2
+ import type { KanbanCard } from "@svar-ui/kanban-store";
3
+ import type { CardShape } from "../types.js";
4
+ type Props = {
5
+ cardContent?: Component<{
6
+ card: KanbanCard;
7
+ cardShape: CardShape;
8
+ }>;
9
+ card: KanbanCard;
10
+ cardShape: CardShape;
11
+ extraCss?: string;
12
+ };
13
+ declare const CardWrapper: Component<Props, {}, "">;
14
+ type CardWrapper = ReturnType<typeof CardWrapper>;
15
+ export default CardWrapper;
@@ -0,0 +1,230 @@
1
+ <script lang="ts">import { getContext } from "svelte";
2
+ import CardList from "./CardList.svelte";
3
+ import { createColumnCard } from "../directives/dblclick.js";
4
+ import { KANBAN_API_CONTEXT } from "../context.js";
5
+ let { column, readonly = false, cardContent, cardShape, contentVisible, requestVisible, virtualizeCards, estimatedCardHeight, cardOverscan, fixedColumnWidth, registerColumn, cardCss, columnCss } = $props();
6
+ const dynamicColumnCss = $derived(columnCss ? columnCss(column.cards, column) ?? "" : "");
7
+ const store = getContext(KANBAN_API_CONTEXT);
8
+ const columnAccessor = $derived(store.getState().columnAccessor);
9
+ const _ = getContext("wx-i18n").getGroup("kanban");
10
+ let root = $state();
11
+ const cardLimitVisible = $derived(typeof column.cardLimit === "number" || column.cardLimit === true);
12
+ const cardLimitNumber = $derived(typeof column.cardLimit === "number" ? column.cardLimit : null);
13
+ const addCardVisible = $derived(column.addCard !== false && !readonly);
14
+ function toggleCollapsed() {
15
+ store.exec("update-column", {
16
+ id: column.id,
17
+ column: { collapsed: !column.collapsed }
18
+ });
19
+ }
20
+ function addCard() {
21
+ if (!addCardVisible) return;
22
+ const card = createColumnCard({}, columnAccessor, column.id);
23
+ store.exec("add-card", {
24
+ card,
25
+ edit: true
26
+ });
27
+ }
28
+ $effect(() => {
29
+ if (!registerColumn || !root) return;
30
+ const id = column.id;
31
+ registerColumn(id, root);
32
+ return () => registerColumn(id, null);
33
+ });
34
+ </script>
35
+
36
+ <section
37
+ class="wx-column {column.css ?? ''} {dynamicColumnCss}"
38
+ class:wx-collapsed={column.collapsed}
39
+ class:wx-over-limit={column.overLimit}
40
+ bind:this={root}
41
+ >
42
+ {#if column.collapsed}
43
+ <button
44
+ type="button"
45
+ class="wx-expand"
46
+ onclick={toggleCollapsed}
47
+ aria-label={_("Expand column")}
48
+ >
49
+ <i class="wx-icon wxi-angle-right"></i>
50
+ </button>
51
+ <div class="wx-body">
52
+ <h3 class="wx-title">
53
+ <span>{column.label}</span>
54
+ {#if cardLimitVisible}
55
+ <span class="wx-count" class:wx-over={column.overLimit}>
56
+ {column.cards.length}{#if cardLimitNumber != null}/{cardLimitNumber}{/if}
57
+ </span>
58
+ {/if}
59
+ </h3>
60
+ </div>
61
+ {:else}
62
+ <header class="wx-column-header">
63
+ <button
64
+ type="button"
65
+ class="wx-toggle"
66
+ onclick={toggleCollapsed}
67
+ aria-label={_("Collapse column")}
68
+ >
69
+ <i class="wx-icon wxi-angle-left"></i>
70
+ </button>
71
+ <h3 class="wx-title">{column.label}</h3>
72
+ {#if cardLimitVisible}
73
+ <span class="wx-count" class:wx-over={column.overLimit}>
74
+ {column.cards.length}{#if cardLimitNumber != null}/{cardLimitNumber}{/if}
75
+ </span>
76
+ {/if}
77
+ {#if addCardVisible}
78
+ <button
79
+ type="button"
80
+ class="wx-add"
81
+ onclick={addCard}
82
+ aria-label="{_('Add card to')} {column.label}"
83
+ >
84
+ <i class="wx-icon wxi-plus"></i>
85
+ </button>
86
+ {/if}
87
+ </header>
88
+ <CardList
89
+ {column}
90
+ {readonly}
91
+ {cardContent}
92
+ {cardShape}
93
+ {contentVisible}
94
+ {virtualizeCards}
95
+ {estimatedCardHeight}
96
+ {cardOverscan}
97
+ {fixedColumnWidth}
98
+ {cardCss}
99
+ />
100
+ {/if}
101
+ </section>
102
+
103
+ <style>
104
+ .wx-icon {
105
+ font-size: 24px;
106
+ margin-top: 5px;
107
+ }
108
+
109
+ .wx-column {
110
+ display: flex;
111
+ flex-direction: column;
112
+ flex: 0 0 280px;
113
+ min-width: 280px;
114
+ max-height: 100%;
115
+ background: var(--wx-kanban-column-bg);
116
+ border-radius: var(--wx-radius-major);
117
+ overflow: hidden;
118
+ }
119
+
120
+ .wx-collapsed {
121
+ flex: 0 0 40px;
122
+ min-width: 40px;
123
+ max-width: 40px;
124
+ align-items: center;
125
+ }
126
+
127
+ .wx-over-limit .wx-column-header,
128
+ .wx-over-limit .wx-body {
129
+ background: var(--wx-kanban-column-over-limit-bg);
130
+ }
131
+
132
+ .wx-column-header {
133
+ display: flex;
134
+ align-items: center;
135
+ gap: 8px;
136
+ padding: 10px 12px;
137
+ border-bottom: 1px solid var(--wx-kanban-border-color);
138
+ }
139
+
140
+ .wx-title {
141
+ flex: 1;
142
+ margin: 0;
143
+ font-weight: var(--wx-font-weight-md);
144
+ overflow: hidden;
145
+ text-overflow: ellipsis;
146
+ white-space: nowrap;
147
+ }
148
+
149
+ .wx-count {
150
+ font-size: var(--wx-font-size-sm);
151
+ color: var(--wx-color-font-alt);
152
+ }
153
+
154
+ .wx-over {
155
+ color: var(--wx-color-danger);
156
+ font-weight: var(--wx-font-weight-md);
157
+ }
158
+
159
+ .wx-add,
160
+ .wx-toggle {
161
+ display: inline-flex;
162
+ align-items: center;
163
+ justify-content: center;
164
+ background: none;
165
+ border: none;
166
+ border-radius: var(--wx-icon-border-radius);
167
+ padding: 0;
168
+ width: 18px;
169
+ height: 18px;
170
+ cursor: pointer;
171
+ font-size: var(--wx-font-size-sm);
172
+ color: var(--wx-color-font-alt);
173
+ }
174
+
175
+ .wx-add:hover,
176
+ .wx-toggle:hover {
177
+ background: var(--wx-background-hover);
178
+ }
179
+
180
+ .wx-add:focus,
181
+ .wx-toggle:focus {
182
+ outline: none;
183
+ border: 1px solid var(--wx-color-primary);
184
+ }
185
+
186
+ .wx-expand {
187
+ display: inline-flex;
188
+ align-items: center;
189
+ justify-content: center;
190
+ background: none;
191
+ border: none;
192
+ padding: 0;
193
+ width: 100%;
194
+ height: 36px;
195
+ flex: 0 0 36px;
196
+ cursor: pointer;
197
+ font-size: var(--wx-font-size-sm);
198
+ color: var(--wx-color-font-alt);
199
+ }
200
+
201
+ .wx-body {
202
+ position: relative;
203
+ flex: 1;
204
+ width: 100%;
205
+ min-height: 0;
206
+ }
207
+
208
+ .wx-collapsed .wx-title {
209
+ position: absolute;
210
+ left: 50%;
211
+ bottom: 0px;
212
+ display: flex;
213
+ align-items: center;
214
+ gap: 8px;
215
+ max-width: calc(var(--wx-kanban-scroll-height, 100vh) - 96px);
216
+ transform: rotate(-90deg);
217
+ transform-origin: left center;
218
+ }
219
+
220
+ .wx-collapsed .wx-title > span:first-child {
221
+ min-width: 0;
222
+ overflow: hidden;
223
+ text-overflow: ellipsis;
224
+ }
225
+
226
+ .wx-collapsed .wx-count {
227
+ flex: 0 0 auto;
228
+ font-weight: var(--wx-font-weight);
229
+ }
230
+ </style>
@@ -0,0 +1,24 @@
1
+ import type { Component } from "svelte";
2
+ import type { ColumnID, ColumnView, KanbanCard } from "@svar-ui/kanban-store";
3
+ import type { CardShape, CardCssFn, ColumnCssFn } from "../types.js";
4
+ type Props = {
5
+ column: ColumnView;
6
+ readonly?: boolean;
7
+ cardContent?: Component<{
8
+ card: KanbanCard;
9
+ cardShape: CardShape;
10
+ }>;
11
+ cardShape: CardShape;
12
+ contentVisible: boolean;
13
+ requestVisible: boolean;
14
+ virtualizeCards: boolean;
15
+ estimatedCardHeight: number;
16
+ cardOverscan: number;
17
+ fixedColumnWidth: boolean;
18
+ registerColumn?: (id: ColumnID, element: HTMLElement | null) => void;
19
+ cardCss?: CardCssFn;
20
+ columnCss?: ColumnCssFn;
21
+ };
22
+ declare const Column: Component<Props, {}, "">;
23
+ type Column = ReturnType<typeof Column>;
24
+ export default Column;