@composy/kanban-core 0.0.1

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 (145) hide show
  1. package/README.md +37 -0
  2. package/dist/index.css +307 -0
  3. package/dist/index.js +1279 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/index.min.css +1 -0
  6. package/dist/index.min.js +1 -0
  7. package/es/core/ColumnManager.d.ts +99 -0
  8. package/es/core/ColumnManager.d.ts.map +1 -0
  9. package/es/core/ColumnManager.js +285 -0
  10. package/es/core/ColumnManager.js.map +1 -0
  11. package/es/core/DragController.d.ts +55 -0
  12. package/es/core/DragController.d.ts.map +1 -0
  13. package/es/core/DragController.js +225 -0
  14. package/es/core/DragController.js.map +1 -0
  15. package/es/core/EventEmitter.d.ts +35 -0
  16. package/es/core/EventEmitter.d.ts.map +1 -0
  17. package/es/core/EventEmitter.js +68 -0
  18. package/es/core/EventEmitter.js.map +1 -0
  19. package/es/core/FilterEngine.d.ts +45 -0
  20. package/es/core/FilterEngine.d.ts.map +1 -0
  21. package/es/core/FilterEngine.js +127 -0
  22. package/es/core/FilterEngine.js.map +1 -0
  23. package/es/core/KanbanBoard.d.ts +152 -0
  24. package/es/core/KanbanBoard.d.ts.map +1 -0
  25. package/es/core/KanbanBoard.js +332 -0
  26. package/es/core/KanbanBoard.js.map +1 -0
  27. package/es/core/Renderer.d.ts +59 -0
  28. package/es/core/Renderer.d.ts.map +1 -0
  29. package/es/core/Renderer.js +247 -0
  30. package/es/core/Renderer.js.map +1 -0
  31. package/es/core/index.d.ts +11 -0
  32. package/es/core/index.d.ts.map +1 -0
  33. package/es/core/index.js +17 -0
  34. package/es/index.d.ts +15 -0
  35. package/es/index.d.ts.map +1 -0
  36. package/es/index.js +21 -0
  37. package/es/styles/kanban.css +306 -0
  38. package/es/types/index.d.ts +209 -0
  39. package/es/types/index.d.ts.map +1 -0
  40. package/es/types/index.js +10 -0
  41. package/es/utils/dom.d.ts +24 -0
  42. package/es/utils/dom.d.ts.map +1 -0
  43. package/es/utils/dom.js +37 -0
  44. package/es/utils/dom.js.map +1 -0
  45. package/es/utils/id.d.ts +9 -0
  46. package/es/utils/id.d.ts.map +1 -0
  47. package/es/utils/id.js +22 -0
  48. package/es/utils/id.js.map +1 -0
  49. package/es/utils/index.d.ts +25 -0
  50. package/es/utils/index.d.ts.map +1 -0
  51. package/es/utils/index.js +20 -0
  52. package/es/utils/index.js.map +1 -0
  53. package/es/version.d.ts +6 -0
  54. package/es/version.d.ts.map +1 -0
  55. package/es/version.js +15 -0
  56. package/es/version.js.map +1 -0
  57. package/esm/core/ColumnManager.d.ts +99 -0
  58. package/esm/core/ColumnManager.d.ts.map +1 -0
  59. package/esm/core/ColumnManager.js +285 -0
  60. package/esm/core/ColumnManager.js.map +1 -0
  61. package/esm/core/DragController.d.ts +55 -0
  62. package/esm/core/DragController.d.ts.map +1 -0
  63. package/esm/core/DragController.js +225 -0
  64. package/esm/core/DragController.js.map +1 -0
  65. package/esm/core/EventEmitter.d.ts +35 -0
  66. package/esm/core/EventEmitter.d.ts.map +1 -0
  67. package/esm/core/EventEmitter.js +68 -0
  68. package/esm/core/EventEmitter.js.map +1 -0
  69. package/esm/core/FilterEngine.d.ts +45 -0
  70. package/esm/core/FilterEngine.d.ts.map +1 -0
  71. package/esm/core/FilterEngine.js +127 -0
  72. package/esm/core/FilterEngine.js.map +1 -0
  73. package/esm/core/KanbanBoard.d.ts +152 -0
  74. package/esm/core/KanbanBoard.d.ts.map +1 -0
  75. package/esm/core/KanbanBoard.js +332 -0
  76. package/esm/core/KanbanBoard.js.map +1 -0
  77. package/esm/core/Renderer.d.ts +59 -0
  78. package/esm/core/Renderer.d.ts.map +1 -0
  79. package/esm/core/Renderer.js +247 -0
  80. package/esm/core/Renderer.js.map +1 -0
  81. package/esm/core/index.d.ts +11 -0
  82. package/esm/core/index.d.ts.map +1 -0
  83. package/esm/core/index.js +17 -0
  84. package/esm/index.d.ts +15 -0
  85. package/esm/index.d.ts.map +1 -0
  86. package/esm/index.js +21 -0
  87. package/esm/styles/kanban.less +339 -0
  88. package/esm/types/index.d.ts +209 -0
  89. package/esm/types/index.d.ts.map +1 -0
  90. package/esm/types/index.js +10 -0
  91. package/esm/utils/dom.d.ts +24 -0
  92. package/esm/utils/dom.d.ts.map +1 -0
  93. package/esm/utils/dom.js +37 -0
  94. package/esm/utils/dom.js.map +1 -0
  95. package/esm/utils/id.d.ts +9 -0
  96. package/esm/utils/id.d.ts.map +1 -0
  97. package/esm/utils/id.js +22 -0
  98. package/esm/utils/id.js.map +1 -0
  99. package/esm/utils/index.d.ts +25 -0
  100. package/esm/utils/index.d.ts.map +1 -0
  101. package/esm/utils/index.js +20 -0
  102. package/esm/utils/index.js.map +1 -0
  103. package/esm/version.d.ts +6 -0
  104. package/esm/version.d.ts.map +1 -0
  105. package/esm/version.js +15 -0
  106. package/esm/version.js.map +1 -0
  107. package/lib/core/ColumnManager.cjs +286 -0
  108. package/lib/core/ColumnManager.cjs.map +1 -0
  109. package/lib/core/ColumnManager.d.ts +99 -0
  110. package/lib/core/DragController.cjs +226 -0
  111. package/lib/core/DragController.cjs.map +1 -0
  112. package/lib/core/DragController.d.ts +55 -0
  113. package/lib/core/EventEmitter.cjs +69 -0
  114. package/lib/core/EventEmitter.cjs.map +1 -0
  115. package/lib/core/EventEmitter.d.ts +35 -0
  116. package/lib/core/FilterEngine.cjs +128 -0
  117. package/lib/core/FilterEngine.cjs.map +1 -0
  118. package/lib/core/FilterEngine.d.ts +45 -0
  119. package/lib/core/KanbanBoard.cjs +334 -0
  120. package/lib/core/KanbanBoard.cjs.map +1 -0
  121. package/lib/core/KanbanBoard.d.ts +152 -0
  122. package/lib/core/Renderer.cjs +248 -0
  123. package/lib/core/Renderer.cjs.map +1 -0
  124. package/lib/core/Renderer.d.ts +59 -0
  125. package/lib/core/index.cjs +24 -0
  126. package/lib/core/index.d.ts +11 -0
  127. package/lib/index.cjs +37 -0
  128. package/lib/index.d.ts +15 -0
  129. package/lib/package.json +3 -0
  130. package/lib/styles/kanban.css +306 -0
  131. package/lib/types/index.cjs +11 -0
  132. package/lib/types/index.d.ts +209 -0
  133. package/lib/utils/dom.cjs +42 -0
  134. package/lib/utils/dom.cjs.map +1 -0
  135. package/lib/utils/dom.d.ts +24 -0
  136. package/lib/utils/id.cjs +24 -0
  137. package/lib/utils/id.cjs.map +1 -0
  138. package/lib/utils/id.d.ts +9 -0
  139. package/lib/utils/index.cjs +28 -0
  140. package/lib/utils/index.cjs.map +1 -0
  141. package/lib/utils/index.d.ts +25 -0
  142. package/lib/version.cjs +16 -0
  143. package/lib/version.cjs.map +1 -0
  144. package/lib/version.d.ts +6 -0
  145. package/package.json +105 -0
package/dist/index.js ADDED
@@ -0,0 +1,1279 @@
1
+ /*!
2
+ * ***********************************
3
+ * @composy/kanban-core v0.0.1 *
4
+ * Built with rollup *
5
+ * Build time: 2024-08-17 11:33:25 *
6
+ * Build mode: production *
7
+ * Minified: No *
8
+ * ***********************************
9
+ */
10
+ (function(global, factory) {
11
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.LDesignKanban = {}));
12
+ })(this, function(exports) {
13
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
14
+ //#region src/utils/dom.ts
15
+ function createElement(tag, className, attributes) {
16
+ const el = document.createElement(tag);
17
+ if (className) el.className = className;
18
+ if (attributes) Object.entries(attributes).forEach(([key, value]) => {
19
+ el.setAttribute(key, value);
20
+ });
21
+ return el;
22
+ }
23
+ function resolveElement(target) {
24
+ if (typeof target === "string") return document.querySelector(target);
25
+ return target;
26
+ }
27
+ function clearElement(el) {
28
+ el.innerHTML = "";
29
+ }
30
+ function removeElement(el) {
31
+ el?.parentNode?.removeChild(el);
32
+ }
33
+ function insertAt(parent, child, index) {
34
+ const ref = parent.children[index];
35
+ if (ref) parent.insertBefore(child, ref);
36
+ else parent.appendChild(child);
37
+ }
38
+ //#endregion
39
+ //#region src/core/EventEmitter.ts
40
+ var EventEmitter = class {
41
+ constructor() {
42
+ this.listeners = /* @__PURE__ */ new Map();
43
+ }
44
+ /**
45
+ * 监听事件
46
+ */
47
+ on(event, handler) {
48
+ if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
49
+ this.listeners.get(event).add(handler);
50
+ return () => this.off(event, handler);
51
+ }
52
+ /**
53
+ * 监听一次性事件
54
+ */
55
+ once(event, handler) {
56
+ const wrapper = (...args) => {
57
+ this.off(event, wrapper);
58
+ handler(...args);
59
+ };
60
+ return this.on(event, wrapper);
61
+ }
62
+ /**
63
+ * 移除监听器
64
+ */
65
+ off(event, handler) {
66
+ this.listeners.get(event)?.delete(handler);
67
+ }
68
+ /**
69
+ * 触发事件
70
+ */
71
+ emit(event, payload) {
72
+ const handlers = this.listeners.get(event);
73
+ if (handlers) handlers.forEach((handler) => {
74
+ try {
75
+ handler(payload);
76
+ } catch (err) {
77
+ console.error(`[KanbanBoard] Error in event handler for "${event}":`, err);
78
+ }
79
+ });
80
+ }
81
+ /**
82
+ * 移除所有监听器
83
+ */
84
+ removeAll() {
85
+ this.listeners.clear();
86
+ }
87
+ /**
88
+ * 获取监听器数量
89
+ */
90
+ listenerCount(event) {
91
+ return this.listeners.get(event)?.size ?? 0;
92
+ }
93
+ };
94
+ //#endregion
95
+ //#region src/utils/id.ts
96
+ let counter = 0;
97
+ function generateId(prefix = "kb") {
98
+ counter++;
99
+ return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 6)}_${counter}`;
100
+ }
101
+ function resetIdCounter() {
102
+ counter = 0;
103
+ }
104
+ //#endregion
105
+ //#region src/core/ColumnManager.ts
106
+ var ColumnManager = class {
107
+ constructor(events) {
108
+ this.columns = [];
109
+ this.events = events;
110
+ }
111
+ /**
112
+ * 初始化列
113
+ */
114
+ init(columns) {
115
+ this.columns = columns.map((col, index) => this.normalizeColumn(col, index));
116
+ }
117
+ /**
118
+ * 标准化列数据
119
+ */
120
+ normalizeColumn(partial, index) {
121
+ return {
122
+ id: partial.id || generateId("col"),
123
+ title: partial.title || `Column ${index + 1}`,
124
+ cards: (partial.cards || []).map((card, cardIndex) => this.normalizeCard(card, partial.id || "", cardIndex)),
125
+ wipLimit: partial.wipLimit,
126
+ collapsed: partial.collapsed ?? false,
127
+ color: partial.color,
128
+ order: partial.order ?? index
129
+ };
130
+ }
131
+ /**
132
+ * 标准化卡片数据
133
+ */
134
+ normalizeCard(partial, columnId, index) {
135
+ return {
136
+ id: partial.id || generateId("card"),
137
+ title: partial.title || "",
138
+ description: partial.description,
139
+ labels: partial.labels || [],
140
+ assignees: partial.assignees || [],
141
+ dueDate: partial.dueDate,
142
+ priority: partial.priority,
143
+ order: partial.order ?? index,
144
+ columnId: partial.columnId || columnId,
145
+ data: partial.data,
146
+ createdAt: partial.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
147
+ updatedAt: partial.updatedAt
148
+ };
149
+ }
150
+ /**
151
+ * 获取所有列
152
+ */
153
+ getColumns() {
154
+ return [...this.columns].sort((a, b) => a.order - b.order);
155
+ }
156
+ /**
157
+ * 获取单列
158
+ */
159
+ getColumn(id) {
160
+ return this.columns.find((c) => c.id === id);
161
+ }
162
+ /**
163
+ * 添加列
164
+ */
165
+ addColumn(partial) {
166
+ const column = this.normalizeColumn(partial, this.columns.length);
167
+ column.cards = column.cards.map((card) => ({
168
+ ...card,
169
+ columnId: column.id
170
+ }));
171
+ this.columns.push(column);
172
+ this.events.emit("column:add", { column });
173
+ return column;
174
+ }
175
+ /**
176
+ * 移除列
177
+ */
178
+ removeColumn(id) {
179
+ const index = this.columns.findIndex((c) => c.id === id);
180
+ if (index === -1) return void 0;
181
+ const [column] = this.columns.splice(index, 1);
182
+ this.reorderColumns();
183
+ this.events.emit("column:remove", { column });
184
+ return column;
185
+ }
186
+ /**
187
+ * 更新列
188
+ */
189
+ updateColumn(id, changes) {
190
+ const column = this.columns.find((c) => c.id === id);
191
+ if (!column) return void 0;
192
+ Object.assign(column, changes, { id });
193
+ this.events.emit("column:update", {
194
+ column,
195
+ changes
196
+ });
197
+ return column;
198
+ }
199
+ /**
200
+ * 移动列
201
+ */
202
+ moveColumn(id, toIndex) {
203
+ const fromIndex = this.columns.findIndex((c) => c.id === id);
204
+ if (fromIndex === -1) return;
205
+ const [column] = this.columns.splice(fromIndex, 1);
206
+ this.columns.splice(toIndex, 0, column);
207
+ this.reorderColumns();
208
+ this.events.emit("column:move", {
209
+ column,
210
+ fromIndex,
211
+ toIndex
212
+ });
213
+ }
214
+ /**
215
+ * 折叠/展开列
216
+ */
217
+ toggleCollapse(id) {
218
+ const column = this.columns.find((c) => c.id === id);
219
+ if (!column) return;
220
+ column.collapsed = !column.collapsed;
221
+ this.events.emit("column:collapse", {
222
+ column,
223
+ collapsed: column.collapsed
224
+ });
225
+ }
226
+ /**
227
+ * 获取列中的卡片
228
+ */
229
+ getCards(columnId) {
230
+ const column = this.columns.find((c) => c.id === columnId);
231
+ return column ? [...column.cards].sort((a, b) => a.order - b.order) : [];
232
+ }
233
+ /**
234
+ * 添加卡片到列
235
+ */
236
+ addCard(columnId, partial) {
237
+ const column = this.columns.find((c) => c.id === columnId);
238
+ if (!column) return void 0;
239
+ if (column.wipLimit && column.cards.length >= column.wipLimit) {
240
+ this.events.emit("error", { error: /* @__PURE__ */ new Error(`Column "${column.title}" reached WIP limit (${column.wipLimit})`) });
241
+ return;
242
+ }
243
+ const card = this.normalizeCard({
244
+ ...partial,
245
+ columnId
246
+ }, columnId, column.cards.length);
247
+ column.cards.push(card);
248
+ this.events.emit("card:add", {
249
+ card,
250
+ columnId
251
+ });
252
+ return card;
253
+ }
254
+ /**
255
+ * 移除卡片
256
+ */
257
+ removeCard(cardId) {
258
+ for (const column of this.columns) {
259
+ const index = column.cards.findIndex((c) => c.id === cardId);
260
+ if (index !== -1) {
261
+ const [card] = column.cards.splice(index, 1);
262
+ this.reorderCards(column);
263
+ this.events.emit("card:remove", {
264
+ card,
265
+ columnId: column.id
266
+ });
267
+ return card;
268
+ }
269
+ }
270
+ }
271
+ /**
272
+ * 更新卡片
273
+ */
274
+ updateCard(cardId, changes) {
275
+ for (const column of this.columns) {
276
+ const card = column.cards.find((c) => c.id === cardId);
277
+ if (card) {
278
+ Object.assign(card, changes, {
279
+ id: cardId,
280
+ columnId: column.id
281
+ });
282
+ card.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
283
+ this.events.emit("card:update", {
284
+ card,
285
+ changes
286
+ });
287
+ return card;
288
+ }
289
+ }
290
+ }
291
+ /**
292
+ * 获取单张卡片
293
+ */
294
+ getCard(cardId) {
295
+ for (const column of this.columns) {
296
+ const card = column.cards.find((c) => c.id === cardId);
297
+ if (card) return card;
298
+ }
299
+ }
300
+ /**
301
+ * 移动卡片
302
+ */
303
+ moveCard(cardId, toColumnId, toIndex) {
304
+ let fromColumn;
305
+ let cardIndex = -1;
306
+ for (const column of this.columns) {
307
+ const idx = column.cards.findIndex((c) => c.id === cardId);
308
+ if (idx !== -1) {
309
+ fromColumn = column;
310
+ cardIndex = idx;
311
+ break;
312
+ }
313
+ }
314
+ if (!fromColumn || cardIndex === -1) return;
315
+ const toColumn = this.columns.find((c) => c.id === toColumnId);
316
+ if (!toColumn) return;
317
+ if (fromColumn.id !== toColumnId && toColumn.wipLimit && toColumn.cards.length >= toColumn.wipLimit) {
318
+ this.events.emit("error", { error: /* @__PURE__ */ new Error(`Column "${toColumn.title}" reached WIP limit (${toColumn.wipLimit})`) });
319
+ return;
320
+ }
321
+ const [card] = fromColumn.cards.splice(cardIndex, 1);
322
+ card.columnId = toColumnId;
323
+ toColumn.cards.splice(toIndex, 0, card);
324
+ this.reorderCards(fromColumn);
325
+ this.reorderCards(toColumn);
326
+ this.events.emit("card:move", {
327
+ card,
328
+ fromColumnId: fromColumn.id,
329
+ toColumnId,
330
+ toIndex
331
+ });
332
+ }
333
+ /**
334
+ * 获取所有卡片
335
+ */
336
+ getAllCards() {
337
+ const cards = [];
338
+ for (const column of this.columns) cards.push(...column.cards);
339
+ return cards;
340
+ }
341
+ /**
342
+ * 获取卡片总数
343
+ */
344
+ getCardCount() {
345
+ return this.columns.reduce((sum, col) => sum + col.cards.length, 0);
346
+ }
347
+ /**
348
+ * 重新排序列
349
+ */
350
+ reorderColumns() {
351
+ this.columns.forEach((col, index) => {
352
+ col.order = index;
353
+ });
354
+ }
355
+ /**
356
+ * 重新排序列中的卡片
357
+ */
358
+ reorderCards(column) {
359
+ column.cards.forEach((card, index) => {
360
+ card.order = index;
361
+ });
362
+ }
363
+ /**
364
+ * 设置列数据(用于导入)
365
+ */
366
+ setColumns(columns) {
367
+ this.columns = columns;
368
+ }
369
+ /**
370
+ * 清空所有数据
371
+ */
372
+ clear() {
373
+ this.columns = [];
374
+ }
375
+ };
376
+ //#endregion
377
+ //#region src/core/DragController.ts
378
+ var DragController = class {
379
+ constructor(events, prefix) {
380
+ this.state = {
381
+ dragging: false,
382
+ cardId: null,
383
+ sourceColumnId: null,
384
+ targetColumnId: null,
385
+ targetIndex: 0,
386
+ placeholder: null
387
+ };
388
+ this.cleanupFns = [];
389
+ this.events = events;
390
+ this.prefix = prefix;
391
+ }
392
+ /**
393
+ * 为卡片元素启用拖拽
394
+ */
395
+ enableCardDrag(cardEl, cardId, columnId) {
396
+ cardEl.setAttribute("draggable", "true");
397
+ cardEl.dataset.cardId = cardId;
398
+ cardEl.dataset.columnId = columnId;
399
+ const onDragStart = (e) => {
400
+ if (!e.dataTransfer) return;
401
+ e.dataTransfer.effectAllowed = "move";
402
+ e.dataTransfer.setData("text/plain", cardId);
403
+ this.state.dragging = true;
404
+ this.state.cardId = cardId;
405
+ this.state.sourceColumnId = columnId;
406
+ cardEl.classList.add(`${this.prefix}-card--dragging`);
407
+ this.events.emit("card:drag-start", {
408
+ cardId,
409
+ columnId
410
+ });
411
+ this.createPlaceholder(cardEl);
412
+ };
413
+ const onDragEnd = () => {
414
+ cardEl.classList.remove(`${this.prefix}-card--dragging`);
415
+ this.removePlaceholder();
416
+ this.state.dragging = false;
417
+ this.state.cardId = null;
418
+ this.state.sourceColumnId = null;
419
+ this.events.emit("card:drag-end", { cardId });
420
+ };
421
+ cardEl.addEventListener("dragstart", onDragStart);
422
+ cardEl.addEventListener("dragend", onDragEnd);
423
+ this.cleanupFns.push(() => {
424
+ cardEl.removeEventListener("dragstart", onDragStart);
425
+ cardEl.removeEventListener("dragend", onDragEnd);
426
+ });
427
+ }
428
+ /**
429
+ * 为列容器启用拖拽接受
430
+ */
431
+ enableColumnDrop(columnBodyEl, columnId) {
432
+ const onDragOver = (e) => {
433
+ e.preventDefault();
434
+ if (!e.dataTransfer) return;
435
+ e.dataTransfer.dropEffect = "move";
436
+ this.state.targetColumnId = columnId;
437
+ const afterElement = this.getDragAfterElement(columnBodyEl, e.clientY);
438
+ const placeholder = this.state.placeholder;
439
+ if (placeholder) if (afterElement) columnBodyEl.insertBefore(placeholder, afterElement);
440
+ else {
441
+ const addBtn = columnBodyEl.querySelector(`.${this.prefix}-add-card`);
442
+ if (addBtn) columnBodyEl.insertBefore(placeholder, addBtn);
443
+ else columnBodyEl.appendChild(placeholder);
444
+ }
445
+ };
446
+ const onDragEnter = (e) => {
447
+ e.preventDefault();
448
+ columnBodyEl.classList.add(`${this.prefix}-column-body--drag-over`);
449
+ };
450
+ const onDragLeave = (e) => {
451
+ const rect = columnBodyEl.getBoundingClientRect();
452
+ if (e.clientX < rect.left || e.clientX > rect.right || e.clientY < rect.top || e.clientY > rect.bottom) columnBodyEl.classList.remove(`${this.prefix}-column-body--drag-over`);
453
+ };
454
+ const onDrop = (e) => {
455
+ e.preventDefault();
456
+ columnBodyEl.classList.remove(`${this.prefix}-column-body--drag-over`);
457
+ const cardId = this.state.cardId;
458
+ if (!cardId) return;
459
+ const placeholder = this.state.placeholder;
460
+ let dropIndex = 0;
461
+ if (placeholder && placeholder.parentNode === columnBodyEl) {
462
+ const children = [...columnBodyEl.children];
463
+ for (const child of children) {
464
+ if (child === placeholder) break;
465
+ if (child.hasAttribute("data-card-id") && !child.classList.contains(`${this.prefix}-card--dragging`)) dropIndex++;
466
+ }
467
+ } else dropIndex = columnBodyEl.querySelectorAll(`[data-card-id]:not(.${this.prefix}-card--dragging)`).length;
468
+ this.events.emit("card:drop", {
469
+ cardId,
470
+ fromColumnId: this.state.sourceColumnId,
471
+ toColumnId: columnId,
472
+ toIndex: dropIndex
473
+ });
474
+ this.removePlaceholder();
475
+ };
476
+ columnBodyEl.addEventListener("dragover", onDragOver);
477
+ columnBodyEl.addEventListener("dragenter", onDragEnter);
478
+ columnBodyEl.addEventListener("dragleave", onDragLeave);
479
+ columnBodyEl.addEventListener("drop", onDrop);
480
+ this.cleanupFns.push(() => {
481
+ columnBodyEl.removeEventListener("dragover", onDragOver);
482
+ columnBodyEl.removeEventListener("dragenter", onDragEnter);
483
+ columnBodyEl.removeEventListener("dragleave", onDragLeave);
484
+ columnBodyEl.removeEventListener("drop", onDrop);
485
+ });
486
+ }
487
+ /**
488
+ * 为列头部启用列拖拽
489
+ */
490
+ enableColumnDrag(columnEl, handleEl, columnId, boardEl) {
491
+ handleEl.setAttribute("draggable", "true");
492
+ const onDragStart = (e) => {
493
+ if (!e.dataTransfer) return;
494
+ e.dataTransfer.effectAllowed = "move";
495
+ e.dataTransfer.setData("application/kanban-column", columnId);
496
+ columnEl.classList.add(`${this.prefix}-column--dragging`);
497
+ };
498
+ const onDragEnd = () => {
499
+ columnEl.classList.remove(`${this.prefix}-column--dragging`);
500
+ boardEl.querySelectorAll(`.${this.prefix}-column--drag-over`).forEach((el) => {
501
+ el.classList.remove(`${this.prefix}-column--drag-over`);
502
+ });
503
+ };
504
+ const onDragOver = (e) => {
505
+ if (!e.dataTransfer?.types.includes("application/kanban-column")) return;
506
+ e.preventDefault();
507
+ e.dataTransfer.dropEffect = "move";
508
+ columnEl.classList.add(`${this.prefix}-column--drag-over`);
509
+ };
510
+ const onDragLeave = () => {
511
+ columnEl.classList.remove(`${this.prefix}-column--drag-over`);
512
+ };
513
+ const onDrop = (e) => {
514
+ e.preventDefault();
515
+ columnEl.classList.remove(`${this.prefix}-column--drag-over`);
516
+ const fromColumnId = e.dataTransfer?.getData("application/kanban-column");
517
+ if (!fromColumnId || fromColumnId === columnId) return;
518
+ const toIndex = [...boardEl.querySelectorAll(`[data-column-id]`)].indexOf(columnEl);
519
+ this.events.emit("column:drop", {
520
+ fromColumnId,
521
+ toColumnId: columnId,
522
+ toIndex
523
+ });
524
+ };
525
+ handleEl.addEventListener("dragstart", onDragStart);
526
+ handleEl.addEventListener("dragend", onDragEnd);
527
+ columnEl.addEventListener("dragover", onDragOver);
528
+ columnEl.addEventListener("dragleave", onDragLeave);
529
+ columnEl.addEventListener("drop", onDrop);
530
+ this.cleanupFns.push(() => {
531
+ handleEl.removeEventListener("dragstart", onDragStart);
532
+ handleEl.removeEventListener("dragend", onDragEnd);
533
+ columnEl.removeEventListener("dragover", onDragOver);
534
+ columnEl.removeEventListener("dragleave", onDragLeave);
535
+ columnEl.removeEventListener("drop", onDrop);
536
+ });
537
+ }
538
+ /**
539
+ * 获取鼠标位置之后的元素
540
+ */
541
+ getDragAfterElement(container, y) {
542
+ const draggableElements = [...container.querySelectorAll(`[data-card-id]:not(.${this.prefix}-card--dragging):not(.${this.prefix}-placeholder)`)];
543
+ let closestOffset = Number.NEGATIVE_INFINITY;
544
+ let closestElement = null;
545
+ draggableElements.forEach((child) => {
546
+ const box = child.getBoundingClientRect();
547
+ const offset = y - box.top - box.height / 2;
548
+ if (offset < 0 && offset > closestOffset) {
549
+ closestOffset = offset;
550
+ closestElement = child;
551
+ }
552
+ });
553
+ return closestElement;
554
+ }
555
+ /**
556
+ * 创建占位符
557
+ */
558
+ createPlaceholder(cardEl) {
559
+ this.removePlaceholder();
560
+ const placeholder = document.createElement("div");
561
+ placeholder.className = `${this.prefix}-placeholder`;
562
+ placeholder.style.height = `${cardEl.offsetHeight}px`;
563
+ this.state.placeholder = placeholder;
564
+ }
565
+ /**
566
+ * 移除占位符
567
+ */
568
+ removePlaceholder() {
569
+ if (this.state.placeholder) {
570
+ this.state.placeholder.parentNode?.removeChild(this.state.placeholder);
571
+ this.state.placeholder = null;
572
+ }
573
+ }
574
+ /**
575
+ * 获取当前拖拽状态
576
+ */
577
+ getState() {
578
+ return { ...this.state };
579
+ }
580
+ /**
581
+ * 销毁
582
+ */
583
+ destroy() {
584
+ this.cleanupFns.forEach((fn) => fn());
585
+ this.cleanupFns = [];
586
+ this.removePlaceholder();
587
+ }
588
+ };
589
+ //#endregion
590
+ //#region src/core/FilterEngine.ts
591
+ var FilterEngine = class {
592
+ constructor(events) {
593
+ this.currentCriteria = null;
594
+ /** 被过滤隐藏的卡片 ID 集合 */
595
+ this.hiddenCardIds = /* @__PURE__ */ new Set();
596
+ this.events = events;
597
+ }
598
+ /**
599
+ * 应用筛选条件
600
+ */
601
+ filter(criteria, columns) {
602
+ this.currentCriteria = criteria;
603
+ this.hiddenCardIds.clear();
604
+ const allCards = columns.flatMap((col) => col.cards);
605
+ for (const card of allCards) if (!this.matchCriteria(card, criteria)) this.hiddenCardIds.add(card.id);
606
+ this.events.emit("filter:change", { criteria });
607
+ return new Set(this.hiddenCardIds);
608
+ }
609
+ /**
610
+ * 搜索卡片
611
+ */
612
+ search(keyword, columns) {
613
+ this.events.emit("search:change", { keyword });
614
+ if (!keyword.trim()) {
615
+ this.clearFilter();
616
+ return columns.flatMap((col) => col.cards);
617
+ }
618
+ const lowerKeyword = keyword.toLowerCase();
619
+ const allCards = columns.flatMap((col) => col.cards);
620
+ const matched = [];
621
+ this.hiddenCardIds.clear();
622
+ for (const card of allCards) {
623
+ const titleMatch = card.title.toLowerCase().includes(lowerKeyword);
624
+ const descMatch = card.description?.toLowerCase().includes(lowerKeyword) ?? false;
625
+ const labelMatch = card.labels?.some((l) => l.name.toLowerCase().includes(lowerKeyword)) ?? false;
626
+ const assigneeMatch = card.assignees?.some((a) => a.toLowerCase().includes(lowerKeyword)) ?? false;
627
+ if (titleMatch || descMatch || labelMatch || assigneeMatch) matched.push(card);
628
+ else this.hiddenCardIds.add(card.id);
629
+ }
630
+ return matched;
631
+ }
632
+ /**
633
+ * 清除筛选
634
+ */
635
+ clearFilter() {
636
+ this.currentCriteria = null;
637
+ this.hiddenCardIds.clear();
638
+ this.events.emit("filter:clear", void 0);
639
+ }
640
+ /**
641
+ * 检查卡片是否可见
642
+ */
643
+ isCardVisible(cardId) {
644
+ return !this.hiddenCardIds.has(cardId);
645
+ }
646
+ /**
647
+ * 获取隐藏的卡片 ID 集合
648
+ */
649
+ getHiddenCardIds() {
650
+ return new Set(this.hiddenCardIds);
651
+ }
652
+ /**
653
+ * 获取当前筛选条件
654
+ */
655
+ getCurrentCriteria() {
656
+ return this.currentCriteria;
657
+ }
658
+ /**
659
+ * 匹配筛选条件
660
+ */
661
+ matchCriteria(card, criteria) {
662
+ if (criteria.keyword) {
663
+ const kw = criteria.keyword.toLowerCase();
664
+ const titleMatch = card.title.toLowerCase().includes(kw);
665
+ const descMatch = card.description?.toLowerCase().includes(kw) ?? false;
666
+ if (!titleMatch && !descMatch) return false;
667
+ }
668
+ if (criteria.labelIds?.length) {
669
+ const cardLabelIds = card.labels?.map((l) => l.id) ?? [];
670
+ if (!criteria.labelIds.some((id) => cardLabelIds.includes(id))) return false;
671
+ }
672
+ if (criteria.assignees?.length) {
673
+ if (!criteria.assignees.some((a) => card.assignees?.includes(a))) return false;
674
+ }
675
+ if (criteria.priorities?.length) {
676
+ if (!card.priority || !criteria.priorities.includes(card.priority)) return false;
677
+ }
678
+ if (criteria.columnIds?.length) {
679
+ if (!criteria.columnIds.includes(card.columnId)) return false;
680
+ }
681
+ if (criteria.dueDateRange) {
682
+ if (!card.dueDate) return false;
683
+ const dueDate = new Date(card.dueDate).getTime();
684
+ if (criteria.dueDateRange.start) {
685
+ if (dueDate < new Date(criteria.dueDateRange.start).getTime()) return false;
686
+ }
687
+ if (criteria.dueDateRange.end) {
688
+ if (dueDate > new Date(criteria.dueDateRange.end).getTime()) return false;
689
+ }
690
+ }
691
+ if (criteria.custom) {
692
+ if (!criteria.custom(card)) return false;
693
+ }
694
+ return true;
695
+ }
696
+ /**
697
+ * 销毁
698
+ */
699
+ destroy() {
700
+ this.hiddenCardIds.clear();
701
+ this.currentCriteria = null;
702
+ }
703
+ };
704
+ //#endregion
705
+ //#region src/core/Renderer.ts
706
+ var Renderer = class {
707
+ constructor(container, events, dragController, options) {
708
+ this.boardEl = null;
709
+ this.columnElements = /* @__PURE__ */ new Map();
710
+ this.columnBodyElements = /* @__PURE__ */ new Map();
711
+ this.cardElements = /* @__PURE__ */ new Map();
712
+ this.container = container;
713
+ this.events = events;
714
+ this.dragController = dragController;
715
+ this.options = options;
716
+ this.prefix = options.classPrefix || "kanban";
717
+ }
718
+ /**
719
+ * 渲染整个看板
720
+ */
721
+ render(columns, hiddenCardIds) {
722
+ this.cleanup();
723
+ this.container.querySelectorAll("[data-kanban-board]").forEach((el) => el.remove());
724
+ const p = this.prefix;
725
+ this.boardEl = createElement("div", `${p}-board`);
726
+ this.boardEl.setAttribute("data-kanban-board", "");
727
+ const columnsContainer = createElement("div", `${p}-columns`);
728
+ for (const column of columns) {
729
+ const colEl = this.renderColumn(column, hiddenCardIds);
730
+ columnsContainer.appendChild(colEl);
731
+ }
732
+ if (this.options.showAddColumn !== false) {
733
+ const addColBtn = createElement("div", `${p}-add-column`);
734
+ addColBtn.innerHTML = `<button class="${p}-add-column-btn">+ \u6DFB\u52A0\u5217</button>`;
735
+ addColBtn.querySelector("button").addEventListener("click", () => {
736
+ this.events.emit("ui:add-column");
737
+ });
738
+ columnsContainer.appendChild(addColBtn);
739
+ }
740
+ this.boardEl.appendChild(columnsContainer);
741
+ this.container.appendChild(this.boardEl);
742
+ }
743
+ /**
744
+ * 渲染单个列
745
+ */
746
+ renderColumn(column, hiddenCardIds) {
747
+ const p = this.prefix;
748
+ const colEl = createElement("div", `${p}-column`, { "data-column-id": column.id });
749
+ if (column.collapsed) colEl.classList.add(`${p}-column--collapsed`);
750
+ const header = this.renderColumnHeader(column);
751
+ colEl.appendChild(header);
752
+ const body = createElement("div", `${p}-column-body`);
753
+ body.dataset.columnId = column.id;
754
+ if (!column.collapsed) {
755
+ const visibleCards = column.cards.filter((card) => !hiddenCardIds || !hiddenCardIds.has(card.id)).sort((a, b) => a.order - b.order);
756
+ for (const card of visibleCards) {
757
+ const cardEl = this.renderCard(card, column.id);
758
+ body.appendChild(cardEl);
759
+ }
760
+ if (this.options.showAddCard !== false) {
761
+ const addBtn = createElement("div", `${p}-add-card`);
762
+ addBtn.innerHTML = `<button class="${p}-add-card-btn">+ \u6DFB\u52A0\u5361\u7247</button>`;
763
+ addBtn.querySelector("button").addEventListener("click", () => {
764
+ this.events.emit("ui:add-card", { columnId: column.id });
765
+ });
766
+ body.appendChild(addBtn);
767
+ }
768
+ if (this.options.draggable !== false) this.dragController.enableColumnDrop(body, column.id);
769
+ }
770
+ colEl.appendChild(body);
771
+ if (this.options.columnDraggable !== false && this.boardEl) {
772
+ const dragHandle = header.querySelector(`.${p}-column-drag-handle`);
773
+ if (dragHandle) this.dragController.enableColumnDrag(colEl, dragHandle, column.id, this.boardEl);
774
+ }
775
+ this.columnElements.set(column.id, colEl);
776
+ this.columnBodyElements.set(column.id, body);
777
+ return colEl;
778
+ }
779
+ /**
780
+ * 渲染列头部
781
+ */
782
+ renderColumnHeader(column) {
783
+ const p = this.prefix;
784
+ const header = createElement("div", `${p}-column-header`);
785
+ if (this.options.columnHeaderRenderer) {
786
+ this.options.columnHeaderRenderer(column, header);
787
+ return header;
788
+ }
789
+ if (this.options.columnDraggable !== false) {
790
+ const handle = createElement("span", `${p}-column-drag-handle`);
791
+ handle.innerHTML = "&#8942;&#8942;";
792
+ handle.title = "拖拽排序";
793
+ header.appendChild(handle);
794
+ }
795
+ if (column.color) {
796
+ const indicator = createElement("span", `${p}-column-color`);
797
+ indicator.style.backgroundColor = column.color;
798
+ header.appendChild(indicator);
799
+ }
800
+ const titleEl = createElement("span", `${p}-column-title`);
801
+ titleEl.textContent = column.title;
802
+ header.appendChild(titleEl);
803
+ if (this.options.showCardCount !== false) {
804
+ const count = createElement("span", `${p}-column-count`);
805
+ const wipText = column.wipLimit ? `/${column.wipLimit}` : "";
806
+ count.textContent = `${column.cards.length}${wipText}`;
807
+ if (column.wipLimit && column.cards.length >= column.wipLimit) count.classList.add(`${p}-column-count--limit`);
808
+ header.appendChild(count);
809
+ }
810
+ const collapseBtn = createElement("button", `${p}-column-collapse-btn`);
811
+ collapseBtn.innerHTML = column.collapsed ? "&#9654;" : "&#9660;";
812
+ collapseBtn.title = column.collapsed ? "展开" : "折叠";
813
+ collapseBtn.addEventListener("click", () => {
814
+ this.events.emit("ui:toggle-collapse", { columnId: column.id });
815
+ });
816
+ header.appendChild(collapseBtn);
817
+ if (this.options.showColumnActions !== false) {
818
+ const actions = createElement("button", `${p}-column-actions-btn`);
819
+ actions.innerHTML = "&#8943;";
820
+ actions.title = "更多操作";
821
+ actions.addEventListener("click", (e) => {
822
+ e.stopPropagation();
823
+ this.events.emit("ui:column-actions", {
824
+ columnId: column.id,
825
+ event: e
826
+ });
827
+ });
828
+ header.appendChild(actions);
829
+ }
830
+ return header;
831
+ }
832
+ /**
833
+ * 渲染单张卡片
834
+ */
835
+ renderCard(card, columnId) {
836
+ const p = this.prefix;
837
+ const cardEl = createElement("div", `${p}-card`, { "data-card-id": card.id });
838
+ if (this.options.cardRenderer) this.options.cardRenderer(card, cardEl);
839
+ else this.renderDefaultCard(card, cardEl);
840
+ if (this.options.draggable !== false) this.dragController.enableCardDrag(cardEl, card.id, columnId);
841
+ cardEl.addEventListener("click", () => {
842
+ this.events.emit("card:click", { card });
843
+ });
844
+ this.cardElements.set(card.id, cardEl);
845
+ return cardEl;
846
+ }
847
+ /**
848
+ * 默认卡片渲染
849
+ */
850
+ renderDefaultCard(card, cardEl) {
851
+ const p = this.prefix;
852
+ if (card.priority) {
853
+ const priorityEl = createElement("div", `${p}-card-priority ${p}-card-priority--${card.priority}`);
854
+ cardEl.appendChild(priorityEl);
855
+ }
856
+ if (card.labels?.length) {
857
+ const labelsEl = createElement("div", `${p}-card-labels`);
858
+ for (const label of card.labels) {
859
+ const labelEl = createElement("span", `${p}-card-label`);
860
+ labelEl.style.backgroundColor = label.color;
861
+ labelEl.textContent = label.name;
862
+ labelsEl.appendChild(labelEl);
863
+ }
864
+ cardEl.appendChild(labelsEl);
865
+ }
866
+ const titleEl = createElement("div", `${p}-card-title`);
867
+ titleEl.textContent = card.title;
868
+ cardEl.appendChild(titleEl);
869
+ if (card.description) {
870
+ const descEl = createElement("div", `${p}-card-desc`);
871
+ descEl.textContent = card.description;
872
+ cardEl.appendChild(descEl);
873
+ }
874
+ if (card.assignees?.length || card.dueDate) {
875
+ const footer = createElement("div", `${p}-card-footer`);
876
+ if (card.assignees?.length) {
877
+ const assigneesEl = createElement("div", `${p}-card-assignees`);
878
+ for (const assignee of card.assignees.slice(0, 3)) {
879
+ const avatar = createElement("span", `${p}-card-avatar`);
880
+ avatar.textContent = assignee.charAt(0).toUpperCase();
881
+ avatar.title = assignee;
882
+ assigneesEl.appendChild(avatar);
883
+ }
884
+ if (card.assignees.length > 3) {
885
+ const more = createElement("span", `${p}-card-avatar-more`);
886
+ more.textContent = `+${card.assignees.length - 3}`;
887
+ assigneesEl.appendChild(more);
888
+ }
889
+ footer.appendChild(assigneesEl);
890
+ }
891
+ if (card.dueDate) {
892
+ const dueEl = createElement("span", `${p}-card-due`);
893
+ const date = new Date(card.dueDate);
894
+ dueEl.textContent = `${date.getMonth() + 1}/${date.getDate()}`;
895
+ if (date < /* @__PURE__ */ new Date()) dueEl.classList.add(`${p}-card-due--overdue`);
896
+ footer.appendChild(dueEl);
897
+ }
898
+ cardEl.appendChild(footer);
899
+ }
900
+ }
901
+ /**
902
+ * 获取列元素
903
+ */
904
+ getColumnElement(columnId) {
905
+ return this.columnElements.get(columnId);
906
+ }
907
+ /**
908
+ * 获取卡片元素
909
+ */
910
+ getCardElement(cardId) {
911
+ return this.cardElements.get(cardId);
912
+ }
913
+ /**
914
+ * 获取看板根元素
915
+ */
916
+ getBoardElement() {
917
+ return this.boardEl;
918
+ }
919
+ /**
920
+ * 清理 DOM
921
+ */
922
+ cleanup() {
923
+ this.columnElements.clear();
924
+ this.columnBodyElements.clear();
925
+ this.cardElements.clear();
926
+ if (this.boardEl) {
927
+ this.boardEl.remove();
928
+ this.boardEl = null;
929
+ }
930
+ }
931
+ /**
932
+ * 销毁
933
+ */
934
+ destroy() {
935
+ this.cleanup();
936
+ }
937
+ };
938
+ //#endregion
939
+ //#region src/version.ts
940
+ const VERSION = "0.1.0";
941
+ //#endregion
942
+ //#region src/core/KanbanBoard.ts
943
+ var KanbanBoard = class {
944
+ constructor(options) {
945
+ this.destroyed = false;
946
+ this.labels = [];
947
+ this.renderTimer = null;
948
+ this.options = {
949
+ draggable: true,
950
+ columnDraggable: true,
951
+ crossColumnDrag: true,
952
+ searchable: true,
953
+ showAddCard: true,
954
+ showAddColumn: true,
955
+ showColumnActions: true,
956
+ showCardCount: true,
957
+ classPrefix: "kanban",
958
+ ...options
959
+ };
960
+ const el = resolveElement(options.container);
961
+ if (!el) throw new Error("[KanbanBoard] Container element not found");
962
+ this.container = el;
963
+ this.events = new EventEmitter();
964
+ this.columnManager = new ColumnManager(this.events);
965
+ this.dragController = new DragController(this.events, this.options.classPrefix || "kanban");
966
+ this.filterEngine = new FilterEngine(this.events);
967
+ this.renderer = new Renderer(this.container, this.events, this.dragController, this.options);
968
+ this.labels = options.labels || [];
969
+ this.init();
970
+ }
971
+ init() {
972
+ if (this.options.columns?.length) this.columnManager.init(this.options.columns);
973
+ this.bindInternalEvents();
974
+ this.render();
975
+ this.events.emit("ready", { instance: this });
976
+ this.options.onReady?.(this);
977
+ if (this.options.debug) console.log("[KanbanBoard] Initialized", {
978
+ columns: this.columnManager.getColumns().length,
979
+ cards: this.columnManager.getCardCount()
980
+ });
981
+ }
982
+ /**
983
+ * 绑定内部事件
984
+ */
985
+ bindInternalEvents() {
986
+ this.events.on("card:drop", (payload) => {
987
+ this.columnManager.moveCard(payload.cardId, payload.toColumnId, payload.toIndex);
988
+ this.options.onCardMove?.(this.columnManager.getCard(payload.cardId), payload.fromColumnId, payload.toColumnId, payload.toIndex);
989
+ this.scheduleRender();
990
+ });
991
+ this.events.on("column:drop", (payload) => {
992
+ const fromIndex = this.columnManager.getColumns().findIndex((c) => c.id === payload.fromColumnId);
993
+ this.columnManager.moveColumn(payload.fromColumnId, payload.toIndex);
994
+ const col = this.columnManager.getColumn(payload.fromColumnId);
995
+ if (col) this.options.onColumnMove?.(col, fromIndex, payload.toIndex);
996
+ this.scheduleRender();
997
+ });
998
+ this.events.on("ui:toggle-collapse", (payload) => {
999
+ this.columnManager.toggleCollapse(payload.columnId);
1000
+ const col = this.columnManager.getColumn(payload.columnId);
1001
+ if (col) this.options.onColumnCollapse?.(col, col.collapsed ?? false);
1002
+ this.render();
1003
+ });
1004
+ this.events.on("card:add", (p) => this.options.onCardAdd?.(p.card, p.columnId));
1005
+ this.events.on("card:remove", (p) => this.options.onCardRemove?.(p.card, p.columnId));
1006
+ this.events.on("card:click", (p) => this.options.onCardClick?.(p.card));
1007
+ this.events.on("card:update", (p) => this.options.onCardUpdate?.(p.card));
1008
+ this.events.on("column:add", (p) => this.options.onColumnAdd?.(p.column));
1009
+ this.events.on("column:remove", (p) => this.options.onColumnRemove?.(p.column));
1010
+ this.events.on("error", (p) => this.options.onError?.(p.error));
1011
+ }
1012
+ /**
1013
+ * 添加列
1014
+ */
1015
+ addColumn(partial) {
1016
+ const column = this.columnManager.addColumn(partial);
1017
+ this.render();
1018
+ return column;
1019
+ }
1020
+ /**
1021
+ * 移除列
1022
+ */
1023
+ removeColumn(id) {
1024
+ const column = this.columnManager.removeColumn(id);
1025
+ if (column) this.render();
1026
+ return column;
1027
+ }
1028
+ /**
1029
+ * 更新列
1030
+ */
1031
+ updateColumn(id, changes) {
1032
+ const column = this.columnManager.updateColumn(id, changes);
1033
+ if (column) this.render();
1034
+ return column;
1035
+ }
1036
+ /**
1037
+ * 移动列
1038
+ */
1039
+ moveColumn(id, toIndex) {
1040
+ this.columnManager.moveColumn(id, toIndex);
1041
+ this.render();
1042
+ }
1043
+ /**
1044
+ * 折叠/展开列
1045
+ */
1046
+ toggleColumnCollapse(id) {
1047
+ this.columnManager.toggleCollapse(id);
1048
+ this.render();
1049
+ }
1050
+ /**
1051
+ * 获取所有列
1052
+ */
1053
+ getColumns() {
1054
+ return this.columnManager.getColumns();
1055
+ }
1056
+ /**
1057
+ * 获取单列
1058
+ */
1059
+ getColumn(id) {
1060
+ return this.columnManager.getColumn(id);
1061
+ }
1062
+ /**
1063
+ * 添加卡片
1064
+ */
1065
+ addCard(columnId, partial) {
1066
+ const card = this.columnManager.addCard(columnId, partial);
1067
+ if (card) this.render();
1068
+ return card;
1069
+ }
1070
+ /**
1071
+ * 移除卡片
1072
+ */
1073
+ removeCard(cardId) {
1074
+ const card = this.columnManager.removeCard(cardId);
1075
+ if (card) this.render();
1076
+ return card;
1077
+ }
1078
+ /**
1079
+ * 更新卡片
1080
+ */
1081
+ updateCard(cardId, changes) {
1082
+ const card = this.columnManager.updateCard(cardId, changes);
1083
+ if (card) this.render();
1084
+ return card;
1085
+ }
1086
+ /**
1087
+ * 移动卡片
1088
+ */
1089
+ moveCard(cardId, toColumnId, toIndex) {
1090
+ this.columnManager.moveCard(cardId, toColumnId, toIndex);
1091
+ this.render();
1092
+ }
1093
+ /**
1094
+ * 获取单张卡片
1095
+ */
1096
+ getCard(cardId) {
1097
+ return this.columnManager.getCard(cardId);
1098
+ }
1099
+ /**
1100
+ * 获取所有卡片
1101
+ */
1102
+ getAllCards() {
1103
+ return this.columnManager.getAllCards();
1104
+ }
1105
+ /**
1106
+ * 获取卡片总数
1107
+ */
1108
+ getCardCount() {
1109
+ return this.columnManager.getCardCount();
1110
+ }
1111
+ /**
1112
+ * 筛选卡片
1113
+ */
1114
+ filter(criteria) {
1115
+ this.filterEngine.filter(criteria, this.columnManager.getColumns());
1116
+ this.render();
1117
+ }
1118
+ /**
1119
+ * 搜索卡片
1120
+ */
1121
+ search(keyword) {
1122
+ const result = this.filterEngine.search(keyword, this.columnManager.getColumns());
1123
+ this.render();
1124
+ return result;
1125
+ }
1126
+ /**
1127
+ * 清除筛选
1128
+ */
1129
+ clearFilter() {
1130
+ this.filterEngine.clearFilter();
1131
+ this.render();
1132
+ }
1133
+ /**
1134
+ * 导出看板数据
1135
+ */
1136
+ exportData() {
1137
+ const data = {
1138
+ version: VERSION,
1139
+ title: this.options.title || "Kanban Board",
1140
+ columns: this.columnManager.getColumns(),
1141
+ labels: this.labels,
1142
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString()
1143
+ };
1144
+ this.events.emit("data:export", { board: data });
1145
+ return data;
1146
+ }
1147
+ /**
1148
+ * 导入看板数据
1149
+ */
1150
+ importData(data) {
1151
+ this.columnManager.setColumns(data.columns);
1152
+ this.labels = data.labels || [];
1153
+ this.filterEngine.clearFilter();
1154
+ this.events.emit("data:import", { board: data });
1155
+ this.render();
1156
+ }
1157
+ /**
1158
+ * 监听事件
1159
+ */
1160
+ on(event, handler) {
1161
+ return this.events.on(event, handler);
1162
+ }
1163
+ /**
1164
+ * 监听一次性事件
1165
+ */
1166
+ once(event, handler) {
1167
+ return this.events.once(event, handler);
1168
+ }
1169
+ /**
1170
+ * 移除事件监听
1171
+ */
1172
+ off(event, handler) {
1173
+ this.events.off(event, handler);
1174
+ }
1175
+ /**
1176
+ * 获取所有标签
1177
+ */
1178
+ getLabels() {
1179
+ return [...this.labels];
1180
+ }
1181
+ /**
1182
+ * 添加标签
1183
+ */
1184
+ addLabel(label) {
1185
+ this.labels.push(label);
1186
+ }
1187
+ /**
1188
+ * 移除标签
1189
+ */
1190
+ removeLabel(id) {
1191
+ this.labels = this.labels.filter((l) => l.id !== id);
1192
+ }
1193
+ /**
1194
+ * 延迟渲染(合并多次调用,等待浏览器拖拽操作完成)
1195
+ */
1196
+ scheduleRender() {
1197
+ if (this.renderTimer !== null) return;
1198
+ this.renderTimer = setTimeout(() => {
1199
+ this.renderTimer = null;
1200
+ this.render();
1201
+ }, 0);
1202
+ }
1203
+ /**
1204
+ * 重新渲染
1205
+ */
1206
+ render() {
1207
+ if (this.destroyed) return;
1208
+ this.dragController.destroy();
1209
+ this.dragController = new DragController(this.events, this.options.classPrefix || "kanban");
1210
+ this.renderer = new Renderer(this.container, this.events, this.dragController, this.options);
1211
+ this.renderer.render(this.columnManager.getColumns(), this.filterEngine.getHiddenCardIds());
1212
+ }
1213
+ /**
1214
+ * 获取看板根元素
1215
+ */
1216
+ getBoardElement() {
1217
+ return this.renderer.getBoardElement();
1218
+ }
1219
+ /**
1220
+ * 获取配置
1221
+ */
1222
+ getOptions() {
1223
+ return { ...this.options };
1224
+ }
1225
+ /**
1226
+ * 销毁实例
1227
+ */
1228
+ destroy() {
1229
+ if (this.destroyed) return;
1230
+ this.destroyed = true;
1231
+ if (this.renderTimer !== null) {
1232
+ clearTimeout(this.renderTimer);
1233
+ this.renderTimer = null;
1234
+ }
1235
+ this.dragController.destroy();
1236
+ this.filterEngine.destroy();
1237
+ this.renderer.destroy();
1238
+ this.columnManager.clear();
1239
+ this.events.emit("destroy", void 0);
1240
+ this.options.onDestroy?.();
1241
+ this.events.removeAll();
1242
+ if (this.options.debug) console.log("[KanbanBoard] Destroyed");
1243
+ }
1244
+ /**
1245
+ * 是否已销毁
1246
+ */
1247
+ isDestroyed() {
1248
+ return this.destroyed;
1249
+ }
1250
+ };
1251
+ function createKanbanBoard(options) {
1252
+ return new KanbanBoard(options);
1253
+ }
1254
+ //#endregion
1255
+ //#region src/utils/index.ts
1256
+ function getElement(target) {
1257
+ if (typeof target === "string") return document.querySelector(target);
1258
+ return target;
1259
+ }
1260
+ //#endregion
1261
+ exports.ColumnManager = ColumnManager;
1262
+ exports.DragController = DragController;
1263
+ exports.EventEmitter = EventEmitter;
1264
+ exports.FilterEngine = FilterEngine;
1265
+ exports.KanbanBoard = KanbanBoard;
1266
+ exports.Renderer = Renderer;
1267
+ exports.VERSION = VERSION;
1268
+ exports.clearElement = clearElement;
1269
+ exports.createElement = createElement;
1270
+ exports.createKanbanBoard = createKanbanBoard;
1271
+ exports.generateId = generateId;
1272
+ exports.getElement = getElement;
1273
+ exports.insertAt = insertAt;
1274
+ exports.removeElement = removeElement;
1275
+ exports.resetIdCounter = resetIdCounter;
1276
+ exports.resolveElement = resolveElement;
1277
+ });
1278
+ /*! End of @composy/kanban-core | Powered by @composy/builder */
1279
+ //# sourceMappingURL=index.js.map