@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.
- package/dist/components/Avatar.svelte +87 -0
- package/dist/components/Avatar.svelte.d.ts +17 -0
- package/dist/components/Card.svelte +346 -0
- package/dist/components/Card.svelte.d.ts +9 -0
- package/dist/components/CardList.svelte +343 -0
- package/dist/components/CardList.svelte.d.ts +21 -0
- package/dist/components/CardWrapper.svelte +49 -0
- package/dist/components/CardWrapper.svelte.d.ts +15 -0
- package/dist/components/Column.svelte +230 -0
- package/dist/components/Column.svelte.d.ts +24 -0
- package/dist/components/ContextMenu.svelte +111 -0
- package/dist/components/ContextMenu.svelte.d.ts +18 -0
- package/dist/components/DragGhost.svelte +57 -0
- package/dist/components/DragGhost.svelte.d.ts +14 -0
- package/dist/components/Editor.svelte +108 -0
- package/dist/components/Editor.svelte.d.ts +10 -0
- package/dist/components/ExportLayout.svelte +52 -0
- package/dist/components/ExportLayout.svelte.d.ts +15 -0
- package/dist/components/Kanban.svelte +100 -0
- package/dist/components/Kanban.svelte.d.ts +186 -0
- package/dist/components/Layout.svelte +362 -0
- package/dist/components/Layout.svelte.d.ts +19 -0
- package/dist/components/Toolbar.svelte +97 -0
- package/dist/components/Toolbar.svelte.d.ts +12 -0
- package/dist/components/useCardOverlay.svelte.d.ts +24 -0
- package/dist/components/useCardOverlay.svelte.js +60 -0
- package/dist/components/useDrag.svelte.d.ts +22 -0
- package/dist/components/useDrag.svelte.js +16 -0
- package/dist/context.d.ts +3 -0
- package/dist/context.js +3 -0
- package/dist/defaults.d.ts +8 -0
- package/dist/defaults.js +85 -0
- package/dist/directives/dblclick.d.ts +13 -0
- package/dist/directives/dblclick.js +26 -0
- package/dist/directives/drag.d.ts +11 -0
- package/dist/directives/drag.js +183 -0
- package/dist/env.d.ts +9 -0
- package/dist/export/Card.svelte +5 -0
- package/dist/export/Card.svelte.d.ts +11 -0
- package/dist/export/Kanban.svelte +30 -0
- package/dist/export/Kanban.svelte.d.ts +16 -0
- package/dist/export.d.ts +3 -0
- package/dist/export.js +15 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +15 -0
- package/dist/themes/Print.svelte +126 -0
- package/dist/themes/Print.svelte.d.ts +13 -0
- package/dist/themes/PrintBW.svelte +153 -0
- package/dist/themes/PrintBW.svelte.d.ts +13 -0
- package/dist/themes/Willow.svelte +45 -0
- package/dist/themes/Willow.svelte.d.ts +7 -0
- package/dist/themes/WillowDark.svelte +49 -0
- package/dist/themes/WillowDark.svelte.d.ts +7 -0
- package/dist/types.d.ts +86 -0
- package/dist/types.js +1 -0
- package/license.txt +21 -0
- package/package.json +59 -0
- package/readme.md +100 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
<script lang="ts">import { getContext, onDestroy, setContext } from "svelte";
|
|
2
|
+
import { delegateClick, hotkeys, locate, locateID } from "@svar-ui/lib-dom";
|
|
3
|
+
import { Popup } from "@svar-ui/svelte-core";
|
|
4
|
+
import Column from "./Column.svelte";
|
|
5
|
+
import ContextMenu from "./ContextMenu.svelte";
|
|
6
|
+
import DragGhost from "./DragGhost.svelte";
|
|
7
|
+
import { cardDrag } from "../directives/drag.js";
|
|
8
|
+
import { KANBAN_API_CONTEXT, SCROLL_CONTAINER_CONTEXT, DND_CONTEXT } from "../context.js";
|
|
9
|
+
import { useCardOverlay } from "./useCardOverlay.svelte.js";
|
|
10
|
+
let { readonly = false, render, cardContent, cardShape, tooltip, cardPopup, cardCss, columnCss } = $props();
|
|
11
|
+
const columnScroll = $derived(render?.columnScroll ?? true);
|
|
12
|
+
const fixedColumnWidth = $derived(render?.fixedColumnWidth ?? true);
|
|
13
|
+
const virtualizeCards = $derived(render?.virtualizeCards ?? false);
|
|
14
|
+
const virtualizeColumns = $derived(render?.virtualizeColumns ?? false);
|
|
15
|
+
const estimatedCardHeight = $derived(render?.estimatedCardHeight ?? 80);
|
|
16
|
+
const cardOverscan = $derived(render?.cardOverscan ?? 5);
|
|
17
|
+
const columnOverscan = $derived(render?.columnOverscan ?? 1);
|
|
18
|
+
const store = getContext(KANBAN_API_CONTEXT);
|
|
19
|
+
const { viewData } = store.getReactiveState();
|
|
20
|
+
const brandmark = store.getBrandmark();
|
|
21
|
+
const _ = getContext("wx-i18n").getGroup("kanban");
|
|
22
|
+
const dnd = getContext(DND_CONTEXT);
|
|
23
|
+
let root = $state();
|
|
24
|
+
let scroll = $state();
|
|
25
|
+
let visibleColumnIds = $state(new Set());
|
|
26
|
+
let visibilityFrame = 0;
|
|
27
|
+
const columnElements = new Map();
|
|
28
|
+
setContext(SCROLL_CONTAINER_CONTEXT, () => columnScroll ? null : scroll ?? null);
|
|
29
|
+
function sameSet(a, b) {
|
|
30
|
+
if (a.size !== b.size) return false;
|
|
31
|
+
for (const value of a) {
|
|
32
|
+
if (!b.has(value)) return false;
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
function setVisibleColumns(next) {
|
|
37
|
+
if (!sameSet(visibleColumnIds, next)) {
|
|
38
|
+
visibleColumnIds = next;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function updateColumnVisibility() {
|
|
42
|
+
if (!virtualizeColumns || !scroll) {
|
|
43
|
+
setVisibleColumns(new Set());
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const columns = $viewData.columns;
|
|
47
|
+
if (!columns.length) {
|
|
48
|
+
setVisibleColumns(new Set());
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const scrollRect = scroll.getBoundingClientRect();
|
|
52
|
+
let first = -1;
|
|
53
|
+
let last = -1;
|
|
54
|
+
for (let i = 0; i < columns.length; i++) {
|
|
55
|
+
const element = columnElements.get(columns[i].id);
|
|
56
|
+
if (!element) continue;
|
|
57
|
+
const rect = element.getBoundingClientRect();
|
|
58
|
+
if (rect.right >= scrollRect.left && rect.left <= scrollRect.right) {
|
|
59
|
+
if (first === -1) first = i;
|
|
60
|
+
last = i;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (first === -1 || last === -1) {
|
|
64
|
+
setVisibleColumns(new Set());
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const overscan = Math.max(0, Math.floor(columnOverscan || 0));
|
|
68
|
+
first = Math.max(0, first - overscan);
|
|
69
|
+
last = Math.min(columns.length - 1, last + overscan);
|
|
70
|
+
const next = new Set();
|
|
71
|
+
for (let i = first; i <= last; i++) {
|
|
72
|
+
next.add(columns[i].id);
|
|
73
|
+
}
|
|
74
|
+
setVisibleColumns(next);
|
|
75
|
+
}
|
|
76
|
+
function scheduleColumnVisibility(..._tracked) {
|
|
77
|
+
if (visibilityFrame) return;
|
|
78
|
+
visibilityFrame = requestAnimationFrame(() => {
|
|
79
|
+
visibilityFrame = 0;
|
|
80
|
+
updateColumnVisibility();
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function registerColumn(id, element) {
|
|
84
|
+
if (element) columnElements.set(id, element);
|
|
85
|
+
else columnElements.delete(id);
|
|
86
|
+
scheduleColumnVisibility();
|
|
87
|
+
}
|
|
88
|
+
function updateScrollHeightVar() {
|
|
89
|
+
if (!scroll) return;
|
|
90
|
+
scroll.style.setProperty("--wx-kanban-scroll-height", `${scroll.clientHeight}px`);
|
|
91
|
+
}
|
|
92
|
+
function isColumnContentVisible(id) {
|
|
93
|
+
return !virtualizeColumns || visibleColumnIds.size === 0 || visibleColumnIds.has(id);
|
|
94
|
+
}
|
|
95
|
+
function isColumnRequestVisible(id) {
|
|
96
|
+
return !virtualizeColumns || visibleColumnIds.has(id);
|
|
97
|
+
}
|
|
98
|
+
function getCard(id) {
|
|
99
|
+
return store.getState().cards.getById(id);
|
|
100
|
+
}
|
|
101
|
+
const overlay = useCardOverlay(getCard, "right-start");
|
|
102
|
+
const popupExtra = { trackScroll: true };
|
|
103
|
+
function selectCard(id) {
|
|
104
|
+
if (!readonly && id != null) {
|
|
105
|
+
store.exec("select-card", { id });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function handleCardClick(id, ev) {
|
|
109
|
+
// suppress the click that fires right after a pointerup ends a drag
|
|
110
|
+
if (readonly || dnd?.active) return;
|
|
111
|
+
if (cardPopup && id != null) {
|
|
112
|
+
const target = ev?.target;
|
|
113
|
+
const el = target ? locate(target) : null;
|
|
114
|
+
if (el) {
|
|
115
|
+
overlay.handleCardPopup({
|
|
116
|
+
cardId: id,
|
|
117
|
+
element: el
|
|
118
|
+
});
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
selectCard(id);
|
|
123
|
+
}
|
|
124
|
+
function handleCardKey(e) {
|
|
125
|
+
if (readonly) return;
|
|
126
|
+
e?.preventDefault();
|
|
127
|
+
const active = document.activeElement;
|
|
128
|
+
const id = active ? locateID(active) : null;
|
|
129
|
+
if (cardPopup && id != null && active) {
|
|
130
|
+
overlay.handleCardPopup({
|
|
131
|
+
cardId: id,
|
|
132
|
+
element: active
|
|
133
|
+
});
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
selectCard(id);
|
|
137
|
+
}
|
|
138
|
+
const cardMenuConfig = $derived(cardShape?.menu ? typeof cardShape.menu === "object" ? cardShape.menu : {} : null);
|
|
139
|
+
let cardMenu = $state();
|
|
140
|
+
function handleCardMenu(id, ev) {
|
|
141
|
+
if (readonly || id == null || !cardMenu) return;
|
|
142
|
+
ev.stopPropagation();
|
|
143
|
+
cardMenu.show(ev, id);
|
|
144
|
+
}
|
|
145
|
+
function delegateCardClick(node) {
|
|
146
|
+
delegateClick(node, {
|
|
147
|
+
click: handleCardClick,
|
|
148
|
+
menu: handleCardMenu
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
function cardHotkeys(node) {
|
|
152
|
+
const destroy = hotkeys.subscribe((keys) => {
|
|
153
|
+
keys.configure({
|
|
154
|
+
enter: handleCardKey,
|
|
155
|
+
space: handleCardKey
|
|
156
|
+
}, node);
|
|
157
|
+
});
|
|
158
|
+
return { destroy };
|
|
159
|
+
}
|
|
160
|
+
$effect(() => {
|
|
161
|
+
scheduleColumnVisibility(virtualizeColumns, columnOverscan, fixedColumnWidth, $viewData.columns);
|
|
162
|
+
});
|
|
163
|
+
$effect(() => {
|
|
164
|
+
if (!scroll || !virtualizeColumns || typeof ResizeObserver === "undefined") return;
|
|
165
|
+
const observer = new ResizeObserver(() => scheduleColumnVisibility());
|
|
166
|
+
observer.observe(scroll);
|
|
167
|
+
return () => observer.disconnect();
|
|
168
|
+
});
|
|
169
|
+
$effect(() => {
|
|
170
|
+
if (!scroll) return;
|
|
171
|
+
updateScrollHeightVar();
|
|
172
|
+
window.addEventListener("resize", updateScrollHeightVar);
|
|
173
|
+
if (typeof ResizeObserver === "undefined") {
|
|
174
|
+
return () => window.removeEventListener("resize", updateScrollHeightVar);
|
|
175
|
+
}
|
|
176
|
+
const observer = new ResizeObserver(updateScrollHeightVar);
|
|
177
|
+
observer.observe(scroll);
|
|
178
|
+
return () => {
|
|
179
|
+
observer.disconnect();
|
|
180
|
+
window.removeEventListener("resize", updateScrollHeightVar);
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
onDestroy(() => {
|
|
184
|
+
if (visibilityFrame) cancelAnimationFrame(visibilityFrame);
|
|
185
|
+
});
|
|
186
|
+
</script>
|
|
187
|
+
|
|
188
|
+
<div
|
|
189
|
+
class="wx-kanban"
|
|
190
|
+
role="region"
|
|
191
|
+
aria-label={_("Kanban board")}
|
|
192
|
+
bind:this={root}
|
|
193
|
+
>
|
|
194
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
195
|
+
<div
|
|
196
|
+
class="wx-board"
|
|
197
|
+
class:wx-scroll-board={!columnScroll}
|
|
198
|
+
class:wx-layout-flex={!fixedColumnWidth}
|
|
199
|
+
use:cardDrag={{ dnd, store, readonly }}
|
|
200
|
+
use:delegateCardClick
|
|
201
|
+
use:cardHotkeys
|
|
202
|
+
onmousemove={tooltip ? overlay.handleTooltipMove : undefined}
|
|
203
|
+
onmouseleave={tooltip ? overlay.handleTooltipLeave : undefined}
|
|
204
|
+
>
|
|
205
|
+
<div
|
|
206
|
+
class="wx-scroll"
|
|
207
|
+
bind:this={scroll}
|
|
208
|
+
onscroll={scheduleColumnVisibility}
|
|
209
|
+
>
|
|
210
|
+
<div class="wx-content">
|
|
211
|
+
{#each $viewData.columns as column (column.id)}
|
|
212
|
+
<Column
|
|
213
|
+
{column}
|
|
214
|
+
{readonly}
|
|
215
|
+
{cardContent}
|
|
216
|
+
{cardShape}
|
|
217
|
+
contentVisible={isColumnContentVisible(column.id)}
|
|
218
|
+
requestVisible={isColumnRequestVisible(column.id)}
|
|
219
|
+
{virtualizeCards}
|
|
220
|
+
{estimatedCardHeight}
|
|
221
|
+
{cardOverscan}
|
|
222
|
+
{fixedColumnWidth}
|
|
223
|
+
{registerColumn}
|
|
224
|
+
{cardCss}
|
|
225
|
+
{columnCss}
|
|
226
|
+
/>
|
|
227
|
+
{/each}
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
{#if brandmark}
|
|
232
|
+
<a style={brandmark.style} href={brandmark.link} target="_blank">
|
|
233
|
+
{brandmark.text}
|
|
234
|
+
</a>
|
|
235
|
+
{/if}
|
|
236
|
+
<DragGhost {root} {cardContent} {cardShape} />
|
|
237
|
+
|
|
238
|
+
{#if cardMenuConfig}
|
|
239
|
+
<ContextMenu
|
|
240
|
+
options={cardMenuConfig.options}
|
|
241
|
+
filter={cardMenuConfig.filter}
|
|
242
|
+
onclick={cardMenuConfig.onclick}
|
|
243
|
+
bind:this={cardMenu}
|
|
244
|
+
/>
|
|
245
|
+
{/if}
|
|
246
|
+
|
|
247
|
+
{#if overlay.tooltipState && tooltip}
|
|
248
|
+
{@const TooltipCmp = tooltip}
|
|
249
|
+
<div
|
|
250
|
+
class="wx-tooltip"
|
|
251
|
+
style="position:fixed;left:{overlay.mousePos.x +
|
|
252
|
+
12}px;top:{overlay.mousePos.y +
|
|
253
|
+
16}px;z-index:10000;pointer-events:none"
|
|
254
|
+
aria-hidden="true"
|
|
255
|
+
>
|
|
256
|
+
<TooltipCmp card={overlay.tooltipState.card} />
|
|
257
|
+
</div>
|
|
258
|
+
{/if}
|
|
259
|
+
|
|
260
|
+
{#if overlay.cardPopupState && cardPopup}
|
|
261
|
+
{@const CardPopupCmp = cardPopup}
|
|
262
|
+
<Popup
|
|
263
|
+
at={overlay.cardPopupState.at}
|
|
264
|
+
parent={overlay.cardPopupState.element}
|
|
265
|
+
oncancel={overlay.hideCardPopup}
|
|
266
|
+
{...popupExtra}
|
|
267
|
+
>
|
|
268
|
+
<CardPopupCmp
|
|
269
|
+
card={overlay.cardPopupState.card}
|
|
270
|
+
close={overlay.hideCardPopup}
|
|
271
|
+
/>
|
|
272
|
+
</Popup>
|
|
273
|
+
{/if}
|
|
274
|
+
</div>
|
|
275
|
+
|
|
276
|
+
<style>
|
|
277
|
+
.wx-kanban {
|
|
278
|
+
display: flex;
|
|
279
|
+
flex-direction: column;
|
|
280
|
+
height: 100%;
|
|
281
|
+
width: 100%;
|
|
282
|
+
position: relative;
|
|
283
|
+
background: var(--wx-kanban-bg);
|
|
284
|
+
overflow: hidden;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.wx-board {
|
|
288
|
+
flex: 1;
|
|
289
|
+
padding: 12px;
|
|
290
|
+
box-sizing: border-box;
|
|
291
|
+
min-height: 0;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/* the scroll wrapper sits inside the padded board, so the
|
|
295
|
+
padding stays at the edges and never scrolls away */
|
|
296
|
+
.wx-scroll {
|
|
297
|
+
height: 100%;
|
|
298
|
+
overflow-x: auto;
|
|
299
|
+
overflow-y: hidden;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.wx-content {
|
|
303
|
+
display: flex;
|
|
304
|
+
gap: 12px;
|
|
305
|
+
align-items: stretch;
|
|
306
|
+
min-height: 100%;
|
|
307
|
+
box-sizing: border-box;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.wx-board:not(.wx-scroll-board) .wx-content {
|
|
311
|
+
height: 100%;
|
|
312
|
+
min-height: 0;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.wx-board:not(.wx-scroll-board) :global(.wx-column:not(.wx-collapsed)) {
|
|
316
|
+
height: 100%;
|
|
317
|
+
min-height: 0;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.wx-board:not(.wx-scroll-board) :global(.wx-column.wx-collapsed) {
|
|
321
|
+
height: 100%;
|
|
322
|
+
min-height: 0;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/* board-wide vertical scroll (columnScroll: false) */
|
|
326
|
+
.wx-scroll-board .wx-scroll {
|
|
327
|
+
overflow-y: auto;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.wx-scroll-board :global(.wx-column) {
|
|
331
|
+
max-height: none;
|
|
332
|
+
overflow: visible;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.wx-scroll-board :global(.wx-column.wx-collapsed) {
|
|
336
|
+
position: sticky;
|
|
337
|
+
top: 0;
|
|
338
|
+
z-index: 1;
|
|
339
|
+
align-self: flex-start;
|
|
340
|
+
height: var(--wx-kanban-scroll-height, 100%);
|
|
341
|
+
max-height: var(--wx-kanban-scroll-height, 100%);
|
|
342
|
+
overflow: hidden;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.wx-scroll-board :global(.wx-column-cards) {
|
|
346
|
+
overflow: visible;
|
|
347
|
+
flex: none;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.wx-scroll-board :global(.wx-column-header) {
|
|
351
|
+
position: sticky;
|
|
352
|
+
top: 0;
|
|
353
|
+
z-index: 1;
|
|
354
|
+
background: var(--wx-kanban-column-bg);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/* flexible columns sharing viewport width (fixedColumnWidth: false) */
|
|
358
|
+
.wx-layout-flex :global(.wx-column:not(.wx-collapsed)) {
|
|
359
|
+
flex: 1 1 0;
|
|
360
|
+
min-width: 240px;
|
|
361
|
+
}
|
|
362
|
+
</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Component } from "svelte";
|
|
2
|
+
import type { KanbanCard } from "@svar-ui/kanban-store";
|
|
3
|
+
import type { CardShape, RenderConfig, CardCssFn, ColumnCssFn } from "../types.js";
|
|
4
|
+
type Props = {
|
|
5
|
+
cardShape: CardShape;
|
|
6
|
+
readonly?: boolean;
|
|
7
|
+
render?: RenderConfig;
|
|
8
|
+
cardContent?: Component<{
|
|
9
|
+
card: KanbanCard;
|
|
10
|
+
cardShape: CardShape;
|
|
11
|
+
}>;
|
|
12
|
+
tooltip?: any;
|
|
13
|
+
cardPopup?: any;
|
|
14
|
+
cardCss?: CardCssFn;
|
|
15
|
+
columnCss?: ColumnCssFn;
|
|
16
|
+
};
|
|
17
|
+
declare const Layout: Component<Props, {}, "">;
|
|
18
|
+
type Layout = ReturnType<typeof Layout>;
|
|
19
|
+
export default Layout;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<script lang="ts">import { getContext, setContext } from "svelte";
|
|
2
|
+
import { readable } from "svelte/store";
|
|
3
|
+
import { Toolbar } from "@svar-ui/svelte-toolbar";
|
|
4
|
+
import { locale } from "@svar-ui/lib-dom";
|
|
5
|
+
import { en } from "@svar-ui/kanban-locales";
|
|
6
|
+
import { en as coreEn } from "@svar-ui/core-locales";
|
|
7
|
+
import { getToolbarItems } from "@svar-ui/kanban-store";
|
|
8
|
+
const sortOptions = {
|
|
9
|
+
"sort-label-asc": {
|
|
10
|
+
field: "label",
|
|
11
|
+
dir: "asc"
|
|
12
|
+
},
|
|
13
|
+
"sort-label-desc": {
|
|
14
|
+
field: "label",
|
|
15
|
+
dir: "desc"
|
|
16
|
+
},
|
|
17
|
+
"sort-priority-asc": {
|
|
18
|
+
field: "priority",
|
|
19
|
+
dir: "asc"
|
|
20
|
+
},
|
|
21
|
+
"sort-priority-desc": {
|
|
22
|
+
field: "priority",
|
|
23
|
+
dir: "desc"
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
let { api = null, items = [], undo = false, sort = false, add = true } = $props();
|
|
27
|
+
let l = getContext("wx-i18n");
|
|
28
|
+
if (!l) {
|
|
29
|
+
l = locale({
|
|
30
|
+
...en,
|
|
31
|
+
...coreEn
|
|
32
|
+
});
|
|
33
|
+
setContext("wx-i18n", l);
|
|
34
|
+
}
|
|
35
|
+
const _ = l.getGroup("kanban");
|
|
36
|
+
const emptyHistory = readable({
|
|
37
|
+
undo: 0,
|
|
38
|
+
redo: 0
|
|
39
|
+
});
|
|
40
|
+
let history = $derived(api ? api.getReactiveState().history : emptyHistory);
|
|
41
|
+
const historyActions = ["undo", "redo"];
|
|
42
|
+
function defaultHandler(id) {
|
|
43
|
+
if (!api) return;
|
|
44
|
+
if (id === "add-card") {
|
|
45
|
+
api.exec("add-card", {
|
|
46
|
+
card: {},
|
|
47
|
+
edit: true
|
|
48
|
+
});
|
|
49
|
+
} else if (id === "undo" || id === "redo") {
|
|
50
|
+
api.exec(id, {});
|
|
51
|
+
} else if (id === "sort-clear") {
|
|
52
|
+
api.exec("sort-cards", { sort: null });
|
|
53
|
+
} else if (id in sortOptions) {
|
|
54
|
+
api.exec("sort-cards", { sort: sortOptions[id] });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function prepareItem(item) {
|
|
58
|
+
const next = { ...item };
|
|
59
|
+
const id = typeof next.id === "string" ? next.id : "";
|
|
60
|
+
if (next.items) {
|
|
61
|
+
next.items = next.items.map(prepareItem);
|
|
62
|
+
}
|
|
63
|
+
if (next.text) next.text = _(next.text);
|
|
64
|
+
if (next.menuText) next.menuText = _(next.menuText);
|
|
65
|
+
if (next.title) next.title = _(next.title);
|
|
66
|
+
if (historyActions.includes(id)) {
|
|
67
|
+
next.disabled = id === "undo" ? !$history?.undo : !$history?.redo;
|
|
68
|
+
}
|
|
69
|
+
if (!next.handler && id) {
|
|
70
|
+
next.handler = () => defaultHandler(id);
|
|
71
|
+
}
|
|
72
|
+
return next;
|
|
73
|
+
}
|
|
74
|
+
const finalItems = $derived.by(() => {
|
|
75
|
+
const buttons = items.length ? items : getToolbarItems({
|
|
76
|
+
undo,
|
|
77
|
+
sort,
|
|
78
|
+
add
|
|
79
|
+
});
|
|
80
|
+
return buttons.map(prepareItem);
|
|
81
|
+
});
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<div class="wx-root">
|
|
85
|
+
<Toolbar items={finalItems} />
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<style>
|
|
89
|
+
.wx-root {
|
|
90
|
+
display: contents;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* FIXME: separator styles are broken somehow */
|
|
94
|
+
.wx-root :global(.wx-toolbar > div.wx-separator) {
|
|
95
|
+
min-height: auto;
|
|
96
|
+
}
|
|
97
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Toolbar } from "@svar-ui/svelte-toolbar";
|
|
2
|
+
import type { KanbanInstanceApi } from "../types.js";
|
|
3
|
+
type Props = {
|
|
4
|
+
api?: KanbanInstanceApi | null;
|
|
5
|
+
items?: any[];
|
|
6
|
+
add?: boolean;
|
|
7
|
+
undo?: boolean;
|
|
8
|
+
sort?: boolean;
|
|
9
|
+
};
|
|
10
|
+
declare const Toolbar: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type Toolbar = ReturnType<typeof Toolbar>;
|
|
12
|
+
export default Toolbar;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { TPosition } from "@svar-ui/lib-dom";
|
|
2
|
+
import type { CardID, KanbanCard } from "@svar-ui/kanban-store";
|
|
3
|
+
export type CardPopupInfo = {
|
|
4
|
+
cardId: CardID;
|
|
5
|
+
element: HTMLElement;
|
|
6
|
+
};
|
|
7
|
+
export declare function useCardOverlay(getCard: (id: CardID) => KanbanCard | undefined, popupAt?: TPosition): {
|
|
8
|
+
readonly tooltipState: {
|
|
9
|
+
card: KanbanCard;
|
|
10
|
+
} | null;
|
|
11
|
+
readonly mousePos: {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
};
|
|
15
|
+
readonly cardPopupState: {
|
|
16
|
+
card: KanbanCard;
|
|
17
|
+
element: HTMLElement;
|
|
18
|
+
at: TPosition;
|
|
19
|
+
} | null;
|
|
20
|
+
handleTooltipMove: (e: MouseEvent) => void;
|
|
21
|
+
handleTooltipLeave: () => void;
|
|
22
|
+
handleCardPopup: (info: CardPopupInfo | null) => void;
|
|
23
|
+
hideCardPopup: () => void;
|
|
24
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { getID, locate } from "@svar-ui/lib-dom";
|
|
2
|
+
export function useCardOverlay(getCard, popupAt = "right-start") {
|
|
3
|
+
let _tooltipTarget = null;
|
|
4
|
+
let tooltipState = $state(null);
|
|
5
|
+
let mousePos = $state({ x: 0, y: 0 });
|
|
6
|
+
let cardPopupState = $state(null);
|
|
7
|
+
function handleTooltipMove(e) {
|
|
8
|
+
mousePos = { x: e.clientX, y: e.clientY };
|
|
9
|
+
if (cardPopupState || !e.target)
|
|
10
|
+
return;
|
|
11
|
+
const el = locate(e.target) ?? null;
|
|
12
|
+
if (el === _tooltipTarget)
|
|
13
|
+
return;
|
|
14
|
+
_tooltipTarget = el;
|
|
15
|
+
if (!el) {
|
|
16
|
+
tooltipState = null;
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const card = getCard(getID(el));
|
|
20
|
+
tooltipState = card ? { card } : null;
|
|
21
|
+
}
|
|
22
|
+
function handleTooltipLeave() {
|
|
23
|
+
_tooltipTarget = null;
|
|
24
|
+
tooltipState = null;
|
|
25
|
+
}
|
|
26
|
+
function handleCardPopup(info) {
|
|
27
|
+
tooltipState = null;
|
|
28
|
+
_tooltipTarget = null;
|
|
29
|
+
if (!info) {
|
|
30
|
+
cardPopupState = null;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const card = getCard(info.cardId);
|
|
34
|
+
if (card) {
|
|
35
|
+
cardPopupState = {
|
|
36
|
+
card,
|
|
37
|
+
element: info.element,
|
|
38
|
+
at: popupAt,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function hideCardPopup() {
|
|
43
|
+
cardPopupState = null;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
get tooltipState() {
|
|
47
|
+
return tooltipState;
|
|
48
|
+
},
|
|
49
|
+
get mousePos() {
|
|
50
|
+
return mousePos;
|
|
51
|
+
},
|
|
52
|
+
get cardPopupState() {
|
|
53
|
+
return cardPopupState;
|
|
54
|
+
},
|
|
55
|
+
handleTooltipMove,
|
|
56
|
+
handleTooltipLeave,
|
|
57
|
+
handleCardPopup,
|
|
58
|
+
hideCardPopup,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CardID, ColumnID } from "@svar-ui/kanban-store";
|
|
2
|
+
export type DndTarget = {
|
|
3
|
+
column: ColumnID;
|
|
4
|
+
beforeId: CardID | null;
|
|
5
|
+
};
|
|
6
|
+
export declare class DndState {
|
|
7
|
+
active: boolean;
|
|
8
|
+
cardId: CardID | null;
|
|
9
|
+
sourceColumn: ColumnID | null;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
pointer: {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
};
|
|
16
|
+
offset: {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
};
|
|
20
|
+
target: DndTarget | null;
|
|
21
|
+
reset(): void;
|
|
22
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export class DndState {
|
|
2
|
+
active = $state(false);
|
|
3
|
+
cardId = $state(null);
|
|
4
|
+
sourceColumn = $state(null);
|
|
5
|
+
width = $state(0);
|
|
6
|
+
height = $state(0);
|
|
7
|
+
pointer = $state({ x: 0, y: 0 });
|
|
8
|
+
offset = $state({ x: 0, y: 0 });
|
|
9
|
+
target = $state(null);
|
|
10
|
+
reset() {
|
|
11
|
+
this.active = false;
|
|
12
|
+
this.cardId = null;
|
|
13
|
+
this.sourceColumn = null;
|
|
14
|
+
this.target = null;
|
|
15
|
+
}
|
|
16
|
+
}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CardShape, EditorShape } from "./types.js";
|
|
2
|
+
export declare function getCardShape(): CardShape;
|
|
3
|
+
export declare function getPriorityOptions(): {
|
|
4
|
+
id: number;
|
|
5
|
+
label: string;
|
|
6
|
+
css: string;
|
|
7
|
+
}[];
|
|
8
|
+
export declare function getEditorItems(shape?: EditorShape | CardShape): any[];
|