@composy/kanban-core 0.0.1 → 0.0.2

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 (147) hide show
  1. package/README.md +171 -37
  2. package/dist/index.css +114 -114
  3. package/dist/index.js +221 -126
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.min.css +1 -1
  6. package/dist/index.min.js +758 -1
  7. package/dist/index.min.js.map +1 -0
  8. package/es/.dts-output-manifest.json +1 -0
  9. package/es/.dts.tsbuildinfo +1 -0
  10. package/es/core/ColumnManager.d.ts +4 -3
  11. package/es/core/ColumnManager.js +43 -18
  12. package/es/core/ColumnManager.js.map +1 -1
  13. package/es/core/DragController.d.ts +1 -2
  14. package/es/core/DragController.js +11 -1
  15. package/es/core/DragController.js.map +1 -1
  16. package/es/core/EventEmitter.d.ts +0 -1
  17. package/es/core/EventEmitter.js +8 -3
  18. package/es/core/EventEmitter.js.map +1 -1
  19. package/es/core/FilterEngine.d.ts +1 -2
  20. package/es/core/FilterEngine.js +10 -5
  21. package/es/core/FilterEngine.js.map +1 -1
  22. package/es/core/KanbanBoard.d.ts +3 -3
  23. package/es/core/KanbanBoard.js +61 -15
  24. package/es/core/KanbanBoard.js.map +1 -1
  25. package/es/core/Renderer.d.ts +2 -3
  26. package/es/core/Renderer.js +11 -7
  27. package/es/core/Renderer.js.map +1 -1
  28. package/es/core/index.d.ts +2 -3
  29. package/es/core/index.js +1 -1
  30. package/es/index.d.ts +3 -4
  31. package/es/index.js +2 -2
  32. package/es/styles/kanban.css +114 -114
  33. package/es/types/index.d.ts +3 -4
  34. package/es/types/index.js +1 -1
  35. package/es/utils/dom.d.ts +0 -1
  36. package/es/utils/dom.js +1 -1
  37. package/es/utils/dom.js.map +1 -1
  38. package/es/utils/id.d.ts +0 -1
  39. package/es/utils/id.js +1 -1
  40. package/es/utils/id.js.map +1 -1
  41. package/es/utils/index.d.ts +1 -2
  42. package/es/utils/index.js +1 -1
  43. package/es/utils/index.js.map +1 -1
  44. package/es/version.d.ts +0 -1
  45. package/es/version.js +1 -1
  46. package/es/version.js.map +1 -1
  47. package/esm/core/ColumnManager.js +43 -18
  48. package/esm/core/ColumnManager.js.map +1 -1
  49. package/esm/core/DragController.js +11 -1
  50. package/esm/core/DragController.js.map +1 -1
  51. package/esm/core/EventEmitter.js +8 -3
  52. package/esm/core/EventEmitter.js.map +1 -1
  53. package/esm/core/FilterEngine.js +10 -5
  54. package/esm/core/FilterEngine.js.map +1 -1
  55. package/esm/core/KanbanBoard.js +63 -17
  56. package/esm/core/KanbanBoard.js.map +1 -1
  57. package/esm/core/Renderer.js +11 -7
  58. package/esm/core/Renderer.js.map +1 -1
  59. package/esm/index.js +4 -4
  60. package/esm/styles/kanban.less +357 -339
  61. package/esm/utils/dom.js +1 -1
  62. package/esm/utils/dom.js.map +1 -1
  63. package/esm/utils/id.js +1 -1
  64. package/esm/utils/id.js.map +1 -1
  65. package/esm/utils/index.js +2 -2
  66. package/esm/utils/index.js.map +1 -1
  67. package/esm/version.js +1 -1
  68. package/esm/version.js.map +1 -1
  69. package/lib/core/ColumnManager.cjs +43 -18
  70. package/lib/core/ColumnManager.cjs.map +1 -1
  71. package/{esm/core/ColumnManager.d.ts → lib/core/ColumnManager.d.cts} +4 -3
  72. package/lib/core/DragController.cjs +11 -1
  73. package/lib/core/DragController.cjs.map +1 -1
  74. package/{esm/core/DragController.d.ts → lib/core/DragController.d.cts} +1 -2
  75. package/lib/core/EventEmitter.cjs +8 -3
  76. package/lib/core/EventEmitter.cjs.map +1 -1
  77. package/lib/core/{EventEmitter.d.ts → EventEmitter.d.cts} +0 -1
  78. package/lib/core/FilterEngine.cjs +10 -5
  79. package/lib/core/FilterEngine.cjs.map +1 -1
  80. package/{esm/core/FilterEngine.d.ts → lib/core/FilterEngine.d.cts} +1 -2
  81. package/lib/core/KanbanBoard.cjs +61 -15
  82. package/lib/core/KanbanBoard.cjs.map +1 -1
  83. package/{esm/core/KanbanBoard.d.ts → lib/core/KanbanBoard.d.cts} +3 -3
  84. package/lib/core/Renderer.cjs +11 -7
  85. package/lib/core/Renderer.cjs.map +1 -1
  86. package/{esm/core/Renderer.d.ts → lib/core/Renderer.d.cts} +2 -3
  87. package/lib/core/index.cjs +1 -1
  88. package/{esm/core/index.d.ts → lib/core/index.d.cts} +2 -3
  89. package/lib/index.cjs +2 -2
  90. package/lib/{index.d.ts → index.d.cts} +3 -4
  91. package/lib/styles/kanban.css +114 -114
  92. package/lib/types/index.cjs +1 -1
  93. package/{esm/types/index.d.ts → lib/types/index.d.cts} +3 -4
  94. package/lib/utils/dom.cjs +1 -1
  95. package/lib/utils/dom.cjs.map +1 -1
  96. package/lib/utils/{dom.d.ts → dom.d.cts} +0 -1
  97. package/lib/utils/id.cjs +1 -1
  98. package/lib/utils/id.cjs.map +1 -1
  99. package/{esm/utils/id.d.ts → lib/utils/id.d.cts} +0 -1
  100. package/lib/utils/index.cjs +1 -1
  101. package/lib/utils/index.cjs.map +1 -1
  102. package/lib/utils/{index.d.ts → index.d.cts} +1 -2
  103. package/lib/version.cjs +1 -1
  104. package/lib/version.cjs.map +1 -1
  105. package/{esm/version.d.ts → lib/version.d.cts} +0 -1
  106. package/package.json +23 -21
  107. package/es/core/ColumnManager.d.ts.map +0 -1
  108. package/es/core/DragController.d.ts.map +0 -1
  109. package/es/core/EventEmitter.d.ts.map +0 -1
  110. package/es/core/FilterEngine.d.ts.map +0 -1
  111. package/es/core/KanbanBoard.d.ts.map +0 -1
  112. package/es/core/Renderer.d.ts.map +0 -1
  113. package/es/core/index.d.ts.map +0 -1
  114. package/es/index.d.ts.map +0 -1
  115. package/es/types/index.d.ts.map +0 -1
  116. package/es/utils/dom.d.ts.map +0 -1
  117. package/es/utils/id.d.ts.map +0 -1
  118. package/es/utils/index.d.ts.map +0 -1
  119. package/es/version.d.ts.map +0 -1
  120. package/esm/core/ColumnManager.d.ts.map +0 -1
  121. package/esm/core/DragController.d.ts.map +0 -1
  122. package/esm/core/EventEmitter.d.ts +0 -35
  123. package/esm/core/EventEmitter.d.ts.map +0 -1
  124. package/esm/core/FilterEngine.d.ts.map +0 -1
  125. package/esm/core/KanbanBoard.d.ts.map +0 -1
  126. package/esm/core/Renderer.d.ts.map +0 -1
  127. package/esm/core/index.d.ts.map +0 -1
  128. package/esm/core/index.js +0 -17
  129. package/esm/index.d.ts +0 -15
  130. package/esm/index.d.ts.map +0 -1
  131. package/esm/types/index.d.ts.map +0 -1
  132. package/esm/types/index.js +0 -10
  133. package/esm/utils/dom.d.ts +0 -24
  134. package/esm/utils/dom.d.ts.map +0 -1
  135. package/esm/utils/id.d.ts.map +0 -1
  136. package/esm/utils/index.d.ts +0 -25
  137. package/esm/utils/index.d.ts.map +0 -1
  138. package/esm/version.d.ts.map +0 -1
  139. package/lib/core/ColumnManager.d.ts +0 -99
  140. package/lib/core/DragController.d.ts +0 -55
  141. package/lib/core/FilterEngine.d.ts +0 -45
  142. package/lib/core/KanbanBoard.d.ts +0 -152
  143. package/lib/core/Renderer.d.ts +0 -59
  144. package/lib/core/index.d.ts +0 -11
  145. package/lib/types/index.d.ts +0 -209
  146. package/lib/utils/id.d.ts +0 -9
  147. package/lib/version.d.ts +0 -6
package/es/utils/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * ***********************************
3
3
  * @composy/kanban-core v0.0.1 *
4
4
  * Built with rollup *
5
- * Build time: 2024-08-17 11:33:25 *
5
+ * Build time: 2024-10-12 17:29:00 *
6
6
  * Build mode: production *
7
7
  * Minified: No *
8
8
  * ***********************************
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/utils/index.ts"],"sourcesContent":["/**\n * @file index.ts\n * @description index 模块\n */\n\n/**\n * Utility functions for @composy/kanban-core\n */\n\nexport { generateId, resetIdCounter } from './id';\nexport { createElement, resolveElement, clearElement, removeElement, insertAt } from './dom';\n\n/**\n * Get element from selector or element\n * @deprecated Use resolveElement instead\n */\n/**\n * getElement 函数\n * @param target - 参数\n * @returns 返回值\n */\n/**\n * getElement - get Element function\n * @param target - 参数说明\n * @returns 返回值说明\n */\nexport function getElement(target: HTMLElement | string): HTMLElement | null {\n if (typeof target === 'string') {\n return document.querySelector(target);\n }\n return target;\n}\n"],"mappings":";;;;;;;;;;;;AA0BO,SAAS,WAAW,QAAkD;CAC3E,IAAI,OAAO,WAAW,UACpB,OAAO,SAAS,cAAc,MAAM;CAEtC,OAAO;AACT"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/utils/index.ts"],"sourcesContent":["/**\r\n * @file index.ts\r\n * @description index 模块\r\n */\r\n\r\n/**\r\n * Utility functions for @composy/kanban-core\r\n */\r\n\r\nexport {\r\n clearElement,\r\n createElement,\r\n insertAt,\r\n removeElement,\r\n resolveElement,\r\n} from './dom'\r\nexport { generateId, resetIdCounter } from './id'\r\n\r\n/**\r\n * Get element from selector or element\r\n * @deprecated Use resolveElement instead\r\n */\r\n/**\r\n * getElement 函数\r\n * @param target - 参数\r\n * @returns 返回值\r\n */\r\n/**\r\n * getElement - get Element function\r\n * @param target - 参数说明\r\n * @returns 返回值说明\r\n */\r\nexport function getElement(target: HTMLElement | string): HTMLElement | null {\r\n if (typeof target === 'string') {\r\n return document.querySelector(target)\r\n }\r\n return target\r\n}\r\n"],"mappings":";;;;;;;;;;;;AAgCO,SAAS,WAAW,QAAkD;CAC3E,IAAI,OAAO,WAAW,UACpB,OAAO,SAAS,cAAc,MAAM;CAEtC,OAAO;AACT"}
package/es/version.d.ts CHANGED
@@ -3,4 +3,3 @@
3
3
  * @description version 模块
4
4
  */
5
5
  export declare const VERSION = "0.1.0";
6
- //# sourceMappingURL=version.d.ts.map
package/es/version.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * ***********************************
3
3
  * @composy/kanban-core v0.0.1 *
4
4
  * Built with rollup *
5
- * Build time: 2024-08-17 11:33:25 *
5
+ * Build time: 2024-10-12 17:29:00 *
6
6
  * Build mode: production *
7
7
  * Minified: No *
8
8
  * ***********************************
package/es/version.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","names":[],"sources":["../src/version.ts"],"sourcesContent":["/**\n * @file version.ts\n * @description version 模块\n */\n\nexport const VERSION = '0.1.0';\n"],"mappings":";;;;;;;;;;AAKO,MAAM,UAAU"}
1
+ {"version":3,"file":"version.js","names":[],"sources":["../src/version.ts"],"sourcesContent":["/**\r\n * @file version.ts\r\n * @description version 模块\r\n */\r\n\r\nexport const VERSION = '0.1.0'\r\n"],"mappings":";;;;;;;;;;AAKO,MAAM,UAAU"}
@@ -2,7 +2,7 @@
2
2
  * ***********************************
3
3
  * @composy/kanban-core v0.0.1 *
4
4
  * Built with rollup *
5
- * Build time: 2024-08-17 11:33:25 *
5
+ * Build time: 2024-10-12 17:29:00 *
6
6
  * Build mode: production *
7
7
  * Minified: No *
8
8
  * ***********************************
@@ -19,15 +19,25 @@ var ColumnManager = class {
19
19
  */
20
20
  init(columns) {
21
21
  this.columns = columns.map((col, index) => this.normalizeColumn(col, index));
22
+ this.sortColumns();
22
23
  }
23
24
  /**
24
25
  * 标准化列数据
25
26
  */
26
27
  normalizeColumn(partial, index) {
28
+ const id = partial.id || generateId("col");
29
+ const cards = (partial.cards ?? []).map((card, cardIndex) => this.normalizeCard({
30
+ ...card,
31
+ columnId: id
32
+ }, id, cardIndex));
33
+ cards.sort((a, b) => a.order - b.order);
34
+ cards.forEach((card, cardIndex) => {
35
+ card.order = cardIndex;
36
+ });
27
37
  return {
28
- id: partial.id || generateId("col"),
29
- title: partial.title || `Column ${index + 1}`,
30
- cards: (partial.cards || []).map((card, cardIndex) => this.normalizeCard(card, partial.id || "", cardIndex)),
38
+ id,
39
+ title: partial.title ?? `Column ${index + 1}`,
40
+ cards,
31
41
  wipLimit: partial.wipLimit,
32
42
  collapsed: partial.collapsed ?? false,
33
43
  color: partial.color,
@@ -40,16 +50,16 @@ var ColumnManager = class {
40
50
  normalizeCard(partial, columnId, index) {
41
51
  return {
42
52
  id: partial.id || generateId("card"),
43
- title: partial.title || "",
53
+ title: partial.title ?? "",
44
54
  description: partial.description,
45
- labels: partial.labels || [],
46
- assignees: partial.assignees || [],
55
+ labels: partial.labels?.map((label) => ({ ...label })) ?? [],
56
+ assignees: partial.assignees ? [...partial.assignees] : [],
47
57
  dueDate: partial.dueDate,
48
58
  priority: partial.priority,
49
59
  order: partial.order ?? index,
50
- columnId: partial.columnId || columnId,
60
+ columnId: partial.columnId ?? columnId,
51
61
  data: partial.data,
52
- createdAt: partial.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
62
+ createdAt: partial.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
53
63
  updatedAt: partial.updatedAt
54
64
  };
55
65
  }
@@ -70,11 +80,8 @@ var ColumnManager = class {
70
80
  */
71
81
  addColumn(partial) {
72
82
  const column = this.normalizeColumn(partial, this.columns.length);
73
- column.cards = column.cards.map((card) => ({
74
- ...card,
75
- columnId: column.id
76
- }));
77
83
  this.columns.push(column);
84
+ this.sortColumns();
78
85
  this.events.emit("column:add", { column });
79
86
  return column;
80
87
  }
@@ -96,6 +103,12 @@ var ColumnManager = class {
96
103
  const column = this.columns.find((c) => c.id === id);
97
104
  if (!column) return void 0;
98
105
  Object.assign(column, changes, { id });
106
+ if (changes.cards) {
107
+ column.cards = changes.cards.map((card, index) => this.normalizeCard(card, id, index));
108
+ column.cards.sort((a, b) => a.order - b.order);
109
+ this.reorderCards(column);
110
+ }
111
+ if (changes.order !== void 0) this.sortColumns();
99
112
  this.events.emit("column:update", {
100
113
  column,
101
114
  changes
@@ -106,15 +119,17 @@ var ColumnManager = class {
106
119
  * 移动列
107
120
  */
108
121
  moveColumn(id, toIndex) {
122
+ this.sortColumns();
109
123
  const fromIndex = this.columns.findIndex((c) => c.id === id);
110
124
  if (fromIndex === -1) return;
111
125
  const [column] = this.columns.splice(fromIndex, 1);
112
- this.columns.splice(toIndex, 0, column);
126
+ const targetIndex = this.clampIndex(toIndex, this.columns.length);
127
+ this.columns.splice(targetIndex, 0, column);
113
128
  this.reorderColumns();
114
129
  this.events.emit("column:move", {
115
130
  column,
116
131
  fromIndex,
117
- toIndex
132
+ toIndex: targetIndex
118
133
  });
119
134
  }
120
135
  /**
@@ -226,14 +241,15 @@ var ColumnManager = class {
226
241
  }
227
242
  const [card] = fromColumn.cards.splice(cardIndex, 1);
228
243
  card.columnId = toColumnId;
229
- toColumn.cards.splice(toIndex, 0, card);
244
+ const targetIndex = this.clampIndex(toIndex, toColumn.cards.length);
245
+ toColumn.cards.splice(targetIndex, 0, card);
230
246
  this.reorderCards(fromColumn);
231
247
  this.reorderCards(toColumn);
232
248
  this.events.emit("card:move", {
233
249
  card,
234
250
  fromColumnId: fromColumn.id,
235
251
  toColumnId,
236
- toIndex
252
+ toIndex: targetIndex
237
253
  });
238
254
  }
239
255
  /**
@@ -270,7 +286,8 @@ var ColumnManager = class {
270
286
  * 设置列数据(用于导入)
271
287
  */
272
288
  setColumns(columns) {
273
- this.columns = columns;
289
+ this.columns = columns.map((column, index) => this.normalizeColumn(column, index));
290
+ this.sortColumns();
274
291
  }
275
292
  /**
276
293
  * 清空所有数据
@@ -278,6 +295,14 @@ var ColumnManager = class {
278
295
  clear() {
279
296
  this.columns = [];
280
297
  }
298
+ clampIndex(index, length) {
299
+ if (!Number.isFinite(index)) return length;
300
+ return Math.min(Math.max(Math.trunc(index), 0), length);
301
+ }
302
+ sortColumns() {
303
+ this.columns.sort((a, b) => a.order - b.order);
304
+ this.reorderColumns();
305
+ }
281
306
  };
282
307
  //#endregion
283
308
  export { ColumnManager };
@@ -1 +1 @@
1
- {"version":3,"file":"ColumnManager.js","names":[],"sources":["../../src/core/ColumnManager.ts"],"sourcesContent":["import type { Column, Card } from '../types';\nimport { generateId } from '../utils/id';\nimport { EventEmitter } from './EventEmitter';\n\n/**\n * 列管理器\n */\nexport class ColumnManager {\n private columns: Column[] = [];\n private events: EventEmitter;\n\n constructor(events: EventEmitter) {\n this.events = events;\n }\n\n /**\n * 初始化列\n */\n init(columns: Partial<Column>[]): void {\n this.columns = columns.map((col, index) => this.normalizeColumn(col, index));\n }\n\n /**\n * 标准化列数据\n */\n private normalizeColumn(partial: Partial<Column>, index: number): Column {\n return {\n id: partial.id || generateId('col'),\n title: partial.title || `Column ${index + 1}`,\n cards: (partial.cards || []).map((card, cardIndex) =>\n this.normalizeCard(card, partial.id || '', cardIndex),\n ),\n wipLimit: partial.wipLimit,\n collapsed: partial.collapsed ?? false,\n color: partial.color,\n order: partial.order ?? index,\n };\n }\n\n /**\n * 标准化卡片数据\n */\n private normalizeCard(partial: Partial<Card>, columnId: string, index: number): Card {\n const id = partial.id || generateId('card');\n return {\n id,\n title: partial.title || '',\n description: partial.description,\n labels: partial.labels || [],\n assignees: partial.assignees || [],\n dueDate: partial.dueDate,\n priority: partial.priority,\n order: partial.order ?? index,\n columnId: partial.columnId || columnId,\n data: partial.data,\n createdAt: partial.createdAt || new Date().toISOString(),\n updatedAt: partial.updatedAt,\n };\n }\n\n /**\n * 获取所有列\n */\n getColumns(): Column[] {\n return [...this.columns].sort((a, b) => a.order - b.order);\n }\n\n /**\n * 获取单列\n */\n getColumn(id: string): Column | undefined {\n return this.columns.find(c => c.id === id);\n }\n\n /**\n * 添加列\n */\n addColumn(partial: Partial<Column>): Column {\n const column = this.normalizeColumn(partial, this.columns.length);\n // 确保新列的 cards 关联 columnId\n column.cards = column.cards.map(card => ({ ...card, columnId: column.id }));\n this.columns.push(column);\n this.events.emit('column:add', { column });\n return column;\n }\n\n /**\n * 移除列\n */\n removeColumn(id: string): Column | undefined {\n const index = this.columns.findIndex(c => c.id === id);\n if (index === -1) return undefined;\n const [column] = this.columns.splice(index, 1);\n this.reorderColumns();\n this.events.emit('column:remove', { column });\n return column;\n }\n\n /**\n * 更新列\n */\n updateColumn(id: string, changes: Partial<Column>): Column | undefined {\n const column = this.columns.find(c => c.id === id);\n if (!column) return undefined;\n Object.assign(column, changes, { id }); // 不允许更改 id\n this.events.emit('column:update', { column, changes });\n return column;\n }\n\n /**\n * 移动列\n */\n moveColumn(id: string, toIndex: number): void {\n const fromIndex = this.columns.findIndex(c => c.id === id);\n if (fromIndex === -1) return;\n const [column] = this.columns.splice(fromIndex, 1);\n this.columns.splice(toIndex, 0, column);\n this.reorderColumns();\n this.events.emit('column:move', { column, fromIndex, toIndex });\n }\n\n /**\n * 折叠/展开列\n */\n toggleCollapse(id: string): void {\n const column = this.columns.find(c => c.id === id);\n if (!column) return;\n column.collapsed = !column.collapsed;\n this.events.emit('column:collapse', { column, collapsed: column.collapsed });\n }\n\n /**\n * 获取列中的卡片\n */\n getCards(columnId: string): Card[] {\n const column = this.columns.find(c => c.id === columnId);\n return column ? [...column.cards].sort((a, b) => a.order - b.order) : [];\n }\n\n /**\n * 添加卡片到列\n */\n addCard(columnId: string, partial: Partial<Card>): Card | undefined {\n const column = this.columns.find(c => c.id === columnId);\n if (!column) return undefined;\n\n // 检查 WIP 限制\n if (column.wipLimit && column.cards.length >= column.wipLimit) {\n this.events.emit('error', {\n error: new Error(`Column \"${column.title}\" reached WIP limit (${column.wipLimit})`),\n });\n return undefined;\n }\n\n const card = this.normalizeCard(\n { ...partial, columnId },\n columnId,\n column.cards.length,\n );\n column.cards.push(card);\n this.events.emit('card:add', { card, columnId });\n return card;\n }\n\n /**\n * 移除卡片\n */\n removeCard(cardId: string): Card | undefined {\n for (const column of this.columns) {\n const index = column.cards.findIndex(c => c.id === cardId);\n if (index !== -1) {\n const [card] = column.cards.splice(index, 1);\n this.reorderCards(column);\n this.events.emit('card:remove', { card, columnId: column.id });\n return card;\n }\n }\n return undefined;\n }\n\n /**\n * 更新卡片\n */\n updateCard(cardId: string, changes: Partial<Card>): Card | undefined {\n for (const column of this.columns) {\n const card = column.cards.find(c => c.id === cardId);\n if (card) {\n Object.assign(card, changes, { id: cardId, columnId: column.id });\n card.updatedAt = new Date().toISOString();\n this.events.emit('card:update', { card, changes });\n return card;\n }\n }\n return undefined;\n }\n\n /**\n * 获取单张卡片\n */\n getCard(cardId: string): Card | undefined {\n for (const column of this.columns) {\n const card = column.cards.find(c => c.id === cardId);\n if (card) return card;\n }\n return undefined;\n }\n\n /**\n * 移动卡片\n */\n moveCard(cardId: string, toColumnId: string, toIndex: number): void {\n let fromColumn: Column | undefined;\n let cardIndex = -1;\n\n for (const column of this.columns) {\n const idx = column.cards.findIndex(c => c.id === cardId);\n if (idx !== -1) {\n fromColumn = column;\n cardIndex = idx;\n break;\n }\n }\n\n if (!fromColumn || cardIndex === -1) return;\n\n const toColumn = this.columns.find(c => c.id === toColumnId);\n if (!toColumn) return;\n\n // 检查 WIP 限制(跨列移动时)\n if (\n fromColumn.id !== toColumnId &&\n toColumn.wipLimit &&\n toColumn.cards.length >= toColumn.wipLimit\n ) {\n this.events.emit('error', {\n error: new Error(`Column \"${toColumn.title}\" reached WIP limit (${toColumn.wipLimit})`),\n });\n return;\n }\n\n const [card] = fromColumn.cards.splice(cardIndex, 1);\n card.columnId = toColumnId;\n toColumn.cards.splice(toIndex, 0, card);\n\n this.reorderCards(fromColumn);\n this.reorderCards(toColumn);\n\n this.events.emit('card:move', {\n card,\n fromColumnId: fromColumn.id,\n toColumnId,\n toIndex,\n });\n }\n\n /**\n * 获取所有卡片\n */\n getAllCards(): Card[] {\n const cards: Card[] = [];\n for (const column of this.columns) {\n cards.push(...column.cards);\n }\n return cards;\n }\n\n /**\n * 获取卡片总数\n */\n getCardCount(): number {\n return this.columns.reduce((sum, col) => sum + col.cards.length, 0);\n }\n\n /**\n * 重新排序列\n */\n private reorderColumns(): void {\n this.columns.forEach((col, index) => {\n col.order = index;\n });\n }\n\n /**\n * 重新排序列中的卡片\n */\n private reorderCards(column: Column): void {\n column.cards.forEach((card, index) => {\n card.order = index;\n });\n }\n\n /**\n * 设置列数据(用于导入)\n */\n setColumns(columns: Column[]): void {\n this.columns = columns;\n }\n\n /**\n * 清空所有数据\n */\n clear(): void {\n this.columns = [];\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ColumnManager.js","names":[],"sources":["../../src/core/ColumnManager.ts"],"sourcesContent":["import type { Card, Column } from '../types'\r\nimport type { EventEmitter } from './EventEmitter'\r\nimport { generateId } from '../utils/id'\r\n\r\n/**\r\n * 列管理器\r\n */\r\nexport class ColumnManager {\r\n private columns: Column[] = []\r\n private events: EventEmitter\r\n\r\n constructor(events: EventEmitter) {\r\n this.events = events\r\n }\r\n\r\n /**\r\n * 初始化列\r\n */\r\n init(columns: Partial<Column>[]): void {\r\n this.columns = columns.map((col, index) => this.normalizeColumn(col, index))\r\n this.sortColumns()\r\n }\r\n\r\n /**\r\n * 标准化列数据\r\n */\r\n private normalizeColumn(partial: Partial<Column>, index: number): Column {\r\n const id = partial.id || generateId('col')\r\n const cards = (partial.cards ?? []).map((card, cardIndex) =>\r\n this.normalizeCard({ ...card, columnId: id }, id, cardIndex)\r\n )\r\n cards.sort((a, b) => a.order - b.order)\r\n cards.forEach((card, cardIndex) => {\r\n card.order = cardIndex\r\n })\r\n return {\r\n id,\r\n title: partial.title ?? `Column ${index + 1}`,\r\n cards,\r\n wipLimit: partial.wipLimit,\r\n collapsed: partial.collapsed ?? false,\r\n color: partial.color,\r\n order: partial.order ?? index,\r\n }\r\n }\r\n\r\n /**\r\n * 标准化卡片数据\r\n */\r\n private normalizeCard(\r\n partial: Partial<Card>,\r\n columnId: string,\r\n index: number\r\n ): Card {\r\n const id = partial.id || generateId('card')\r\n return {\r\n id,\r\n title: partial.title ?? '',\r\n description: partial.description,\r\n labels: partial.labels?.map(label => ({ ...label })) ?? [],\r\n assignees: partial.assignees ? [...partial.assignees] : [],\r\n dueDate: partial.dueDate,\r\n priority: partial.priority,\r\n order: partial.order ?? index,\r\n columnId: partial.columnId ?? columnId,\r\n data: partial.data,\r\n createdAt: partial.createdAt ?? new Date().toISOString(),\r\n updatedAt: partial.updatedAt,\r\n }\r\n }\r\n\r\n /**\r\n * 获取所有列\r\n */\r\n getColumns(): Column[] {\r\n return [...this.columns].sort((a, b) => a.order - b.order)\r\n }\r\n\r\n /**\r\n * 获取单列\r\n */\r\n getColumn(id: string): Column | undefined {\r\n return this.columns.find(c => c.id === id)\r\n }\r\n\r\n /**\r\n * 添加列\r\n */\r\n addColumn(partial: Partial<Column>): Column {\r\n const column = this.normalizeColumn(partial, this.columns.length)\r\n this.columns.push(column)\r\n this.sortColumns()\r\n this.events.emit('column:add', { column })\r\n return column\r\n }\r\n\r\n /**\r\n * 移除列\r\n */\r\n removeColumn(id: string): Column | undefined {\r\n const index = this.columns.findIndex(c => c.id === id)\r\n if (index === -1) return undefined\r\n const [column] = this.columns.splice(index, 1)\r\n this.reorderColumns()\r\n this.events.emit('column:remove', { column })\r\n return column\r\n }\r\n\r\n /**\r\n * 更新列\r\n */\r\n updateColumn(id: string, changes: Partial<Column>): Column | undefined {\r\n const column = this.columns.find(c => c.id === id)\r\n if (!column) return undefined\r\n Object.assign(column, changes, { id }) // 不允许更改 id\r\n if (changes.cards) {\r\n column.cards = changes.cards.map((card, index) =>\r\n this.normalizeCard(card, id, index)\r\n )\r\n column.cards.sort((a, b) => a.order - b.order)\r\n this.reorderCards(column)\r\n }\r\n if (changes.order !== undefined) this.sortColumns()\r\n this.events.emit('column:update', { column, changes })\r\n return column\r\n }\r\n\r\n /**\r\n * 移动列\r\n */\r\n moveColumn(id: string, toIndex: number): void {\r\n this.sortColumns()\r\n const fromIndex = this.columns.findIndex(c => c.id === id)\r\n if (fromIndex === -1) return\r\n const [column] = this.columns.splice(fromIndex, 1)\r\n const targetIndex = this.clampIndex(toIndex, this.columns.length)\r\n this.columns.splice(targetIndex, 0, column)\r\n this.reorderColumns()\r\n this.events.emit('column:move', { column, fromIndex, toIndex: targetIndex })\r\n }\r\n\r\n /**\r\n * 折叠/展开列\r\n */\r\n toggleCollapse(id: string): void {\r\n const column = this.columns.find(c => c.id === id)\r\n if (!column) return\r\n column.collapsed = !column.collapsed\r\n this.events.emit('column:collapse', { column, collapsed: column.collapsed })\r\n }\r\n\r\n /**\r\n * 获取列中的卡片\r\n */\r\n getCards(columnId: string): Card[] {\r\n const column = this.columns.find(c => c.id === columnId)\r\n return column ? [...column.cards].sort((a, b) => a.order - b.order) : []\r\n }\r\n\r\n /**\r\n * 添加卡片到列\r\n */\r\n addCard(columnId: string, partial: Partial<Card>): Card | undefined {\r\n const column = this.columns.find(c => c.id === columnId)\r\n if (!column) return undefined\r\n\r\n // 检查 WIP 限制\r\n if (column.wipLimit && column.cards.length >= column.wipLimit) {\r\n this.events.emit('error', {\r\n error: new Error(\r\n `Column \"${column.title}\" reached WIP limit (${column.wipLimit})`\r\n ),\r\n })\r\n return undefined\r\n }\r\n\r\n const card = this.normalizeCard(\r\n { ...partial, columnId },\r\n columnId,\r\n column.cards.length\r\n )\r\n column.cards.push(card)\r\n this.events.emit('card:add', { card, columnId })\r\n return card\r\n }\r\n\r\n /**\r\n * 移除卡片\r\n */\r\n removeCard(cardId: string): Card | undefined {\r\n for (const column of this.columns) {\r\n const index = column.cards.findIndex(c => c.id === cardId)\r\n if (index !== -1) {\r\n const [card] = column.cards.splice(index, 1)\r\n this.reorderCards(column)\r\n this.events.emit('card:remove', { card, columnId: column.id })\r\n return card\r\n }\r\n }\r\n return undefined\r\n }\r\n\r\n /**\r\n * 更新卡片\r\n */\r\n updateCard(cardId: string, changes: Partial<Card>): Card | undefined {\r\n for (const column of this.columns) {\r\n const card = column.cards.find(c => c.id === cardId)\r\n if (card) {\r\n Object.assign(card, changes, { id: cardId, columnId: column.id })\r\n card.updatedAt = new Date().toISOString()\r\n this.events.emit('card:update', { card, changes })\r\n return card\r\n }\r\n }\r\n return undefined\r\n }\r\n\r\n /**\r\n * 获取单张卡片\r\n */\r\n getCard(cardId: string): Card | undefined {\r\n for (const column of this.columns) {\r\n const card = column.cards.find(c => c.id === cardId)\r\n if (card) return card\r\n }\r\n return undefined\r\n }\r\n\r\n /**\r\n * 移动卡片\r\n */\r\n moveCard(cardId: string, toColumnId: string, toIndex: number): void {\r\n let fromColumn: Column | undefined\r\n let cardIndex = -1\r\n\r\n for (const column of this.columns) {\r\n const idx = column.cards.findIndex(c => c.id === cardId)\r\n if (idx !== -1) {\r\n fromColumn = column\r\n cardIndex = idx\r\n break\r\n }\r\n }\r\n\r\n if (!fromColumn || cardIndex === -1) return\r\n\r\n const toColumn = this.columns.find(c => c.id === toColumnId)\r\n if (!toColumn) return\r\n\r\n // 检查 WIP 限制(跨列移动时)\r\n if (\r\n fromColumn.id !== toColumnId &&\r\n toColumn.wipLimit &&\r\n toColumn.cards.length >= toColumn.wipLimit\r\n ) {\r\n this.events.emit('error', {\r\n error: new Error(\r\n `Column \"${toColumn.title}\" reached WIP limit (${toColumn.wipLimit})`\r\n ),\r\n })\r\n return\r\n }\r\n\r\n const [card] = fromColumn.cards.splice(cardIndex, 1)\r\n card.columnId = toColumnId\r\n const targetIndex = this.clampIndex(toIndex, toColumn.cards.length)\r\n toColumn.cards.splice(targetIndex, 0, card)\r\n\r\n this.reorderCards(fromColumn)\r\n this.reorderCards(toColumn)\r\n\r\n this.events.emit('card:move', {\r\n card,\r\n fromColumnId: fromColumn.id,\r\n toColumnId,\r\n toIndex: targetIndex,\r\n })\r\n }\r\n\r\n /**\r\n * 获取所有卡片\r\n */\r\n getAllCards(): Card[] {\r\n const cards: Card[] = []\r\n for (const column of this.columns) {\r\n cards.push(...column.cards)\r\n }\r\n return cards\r\n }\r\n\r\n /**\r\n * 获取卡片总数\r\n */\r\n getCardCount(): number {\r\n return this.columns.reduce((sum, col) => sum + col.cards.length, 0)\r\n }\r\n\r\n /**\r\n * 重新排序列\r\n */\r\n private reorderColumns(): void {\r\n this.columns.forEach((col, index) => {\r\n col.order = index\r\n })\r\n }\r\n\r\n /**\r\n * 重新排序列中的卡片\r\n */\r\n private reorderCards(column: Column): void {\r\n column.cards.forEach((card, index) => {\r\n card.order = index\r\n })\r\n }\r\n\r\n /**\r\n * 设置列数据(用于导入)\r\n */\r\n setColumns(columns: Column[]): void {\r\n this.columns = columns.map((column, index) =>\r\n this.normalizeColumn(column, index)\r\n )\r\n this.sortColumns()\r\n }\r\n\r\n /**\r\n * 清空所有数据\r\n */\r\n clear(): void {\r\n this.columns = []\r\n }\r\n\r\n private clampIndex(index: number, length: number): number {\r\n if (!Number.isFinite(index)) return length\r\n return Math.min(Math.max(Math.trunc(index), 0), length)\r\n }\r\n\r\n private sortColumns(): void {\r\n this.columns.sort((a, b) => a.order - b.order)\r\n this.reorderColumns()\r\n }\r\n}\r\n"],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
  * ***********************************
3
3
  * @composy/kanban-core v0.0.1 *
4
4
  * Built with rollup *
5
- * Build time: 2024-08-17 11:33:25 *
5
+ * Build time: 2024-10-12 17:29:00 *
6
6
  * Build mode: production *
7
7
  * Minified: No *
8
8
  * ***********************************
@@ -36,6 +36,8 @@ var DragController = class {
36
36
  this.state.dragging = true;
37
37
  this.state.cardId = cardId;
38
38
  this.state.sourceColumnId = columnId;
39
+ this.state.targetColumnId = columnId;
40
+ this.state.targetIndex = 0;
39
41
  cardEl.classList.add(`${this.prefix}-card--dragging`);
40
42
  this.events.emit("card:drag-start", {
41
43
  cardId,
@@ -49,6 +51,8 @@ var DragController = class {
49
51
  this.state.dragging = false;
50
52
  this.state.cardId = null;
51
53
  this.state.sourceColumnId = null;
54
+ this.state.targetColumnId = null;
55
+ this.state.targetIndex = 0;
52
56
  this.events.emit("card:drag-end", { cardId });
53
57
  };
54
58
  cardEl.addEventListener("dragstart", onDragStart);
@@ -98,6 +102,7 @@ var DragController = class {
98
102
  if (child.hasAttribute("data-card-id") && !child.classList.contains(`${this.prefix}-card--dragging`)) dropIndex++;
99
103
  }
100
104
  } else dropIndex = columnBodyEl.querySelectorAll(`[data-card-id]:not(.${this.prefix}-card--dragging)`).length;
105
+ this.state.targetIndex = dropIndex;
101
106
  this.events.emit("card:drop", {
102
107
  cardId,
103
108
  fromColumnId: this.state.sourceColumnId,
@@ -217,6 +222,11 @@ var DragController = class {
217
222
  this.cleanupFns.forEach((fn) => fn());
218
223
  this.cleanupFns = [];
219
224
  this.removePlaceholder();
225
+ this.state.dragging = false;
226
+ this.state.cardId = null;
227
+ this.state.sourceColumnId = null;
228
+ this.state.targetColumnId = null;
229
+ this.state.targetIndex = 0;
220
230
  }
221
231
  };
222
232
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"DragController.js","names":[],"sources":["../../src/core/DragController.ts"],"sourcesContent":["import { EventEmitter } from './EventEmitter';\n\n/**\n * DragState - Drag State interface\n */\nexport interface DragState {\n dragging: boolean;\n cardId: string | null;\n sourceColumnId: string | null;\n targetColumnId: string | null;\n targetIndex: number;\n placeholder: HTMLElement | null;\n}\n\n/**\n * 拖拽控制器 - HTML5 Drag & Drop API\n */\nexport class DragController {\n private events: EventEmitter;\n private state: DragState = {\n dragging: false,\n cardId: null,\n sourceColumnId: null,\n targetColumnId: null,\n targetIndex: 0,\n placeholder: null,\n };\n private prefix: string;\n private cleanupFns: (() => void)[] = [];\n\n constructor(events: EventEmitter, prefix: string) {\n this.events = events;\n this.prefix = prefix;\n }\n\n /**\n * 为卡片元素启用拖拽\n */\n enableCardDrag(cardEl: HTMLElement, cardId: string, columnId: string): void {\n cardEl.setAttribute('draggable', 'true');\n cardEl.dataset.cardId = cardId;\n cardEl.dataset.columnId = columnId;\n\n const onDragStart = (e: DragEvent) => {\n if (!e.dataTransfer) return;\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', cardId);\n\n this.state.dragging = true;\n this.state.cardId = cardId;\n this.state.sourceColumnId = columnId;\n\n cardEl.classList.add(`${this.prefix}-card--dragging`);\n this.events.emit('card:drag-start', { cardId, columnId });\n\n // 创建占位符\n this.createPlaceholder(cardEl);\n };\n\n const onDragEnd = () => {\n cardEl.classList.remove(`${this.prefix}-card--dragging`);\n this.removePlaceholder();\n this.state.dragging = false;\n this.state.cardId = null;\n this.state.sourceColumnId = null;\n this.events.emit('card:drag-end', { cardId });\n };\n\n cardEl.addEventListener('dragstart', onDragStart);\n cardEl.addEventListener('dragend', onDragEnd);\n\n this.cleanupFns.push(() => {\n cardEl.removeEventListener('dragstart', onDragStart);\n cardEl.removeEventListener('dragend', onDragEnd);\n });\n }\n\n /**\n * 为列容器启用拖拽接受\n */\n enableColumnDrop(columnBodyEl: HTMLElement, columnId: string): void {\n const onDragOver = (e: DragEvent) => {\n e.preventDefault();\n if (!e.dataTransfer) return;\n e.dataTransfer.dropEffect = 'move';\n\n this.state.targetColumnId = columnId;\n\n // 计算插入位置\n const afterElement = this.getDragAfterElement(columnBodyEl, e.clientY);\n const placeholder = this.state.placeholder;\n if (placeholder) {\n if (afterElement) {\n columnBodyEl.insertBefore(placeholder, afterElement);\n } else {\n // 插入到最后一张卡片之后,但在“添加卡片”按钮之前\n const addBtn = columnBodyEl.querySelector(`.${this.prefix}-add-card`);\n if (addBtn) {\n columnBodyEl.insertBefore(placeholder, addBtn);\n } else {\n columnBodyEl.appendChild(placeholder);\n }\n }\n }\n };\n\n const onDragEnter = (e: DragEvent) => {\n e.preventDefault();\n columnBodyEl.classList.add(`${this.prefix}-column-body--drag-over`);\n };\n\n const onDragLeave = (e: DragEvent) => {\n // 检查是否真的离开了列\n const rect = columnBodyEl.getBoundingClientRect();\n if (\n e.clientX < rect.left ||\n e.clientX > rect.right ||\n e.clientY < rect.top ||\n e.clientY > rect.bottom\n ) {\n columnBodyEl.classList.remove(`${this.prefix}-column-body--drag-over`);\n }\n };\n\n const onDrop = (e: DragEvent) => {\n e.preventDefault();\n columnBodyEl.classList.remove(`${this.prefix}-column-body--drag-over`);\n\n const cardId = this.state.cardId;\n if (!cardId) return;\n\n // 计算 drop 位置:只统计占位符前面的可见卡片数量\n const placeholder = this.state.placeholder;\n let dropIndex = 0;\n\n if (placeholder && placeholder.parentNode === columnBodyEl) {\n const children = [...columnBodyEl.children];\n for (const child of children) {\n if (child === placeholder) break;\n if (\n (child as HTMLElement).hasAttribute('data-card-id') &&\n !(child as HTMLElement).classList.contains(`${this.prefix}-card--dragging`)\n ) {\n dropIndex++;\n }\n }\n } else {\n // 占位符不在当前列时,放到末尾\n const cards = columnBodyEl.querySelectorAll(\n `[data-card-id]:not(.${this.prefix}-card--dragging)`,\n );\n dropIndex = cards.length;\n }\n\n this.events.emit('card:drop', {\n cardId,\n fromColumnId: this.state.sourceColumnId,\n toColumnId: columnId,\n toIndex: dropIndex,\n });\n\n this.removePlaceholder();\n };\n\n columnBodyEl.addEventListener('dragover', onDragOver);\n columnBodyEl.addEventListener('dragenter', onDragEnter);\n columnBodyEl.addEventListener('dragleave', onDragLeave);\n columnBodyEl.addEventListener('drop', onDrop);\n\n this.cleanupFns.push(() => {\n columnBodyEl.removeEventListener('dragover', onDragOver);\n columnBodyEl.removeEventListener('dragenter', onDragEnter);\n columnBodyEl.removeEventListener('dragleave', onDragLeave);\n columnBodyEl.removeEventListener('drop', onDrop);\n });\n }\n\n /**\n * 为列头部启用列拖拽\n */\n enableColumnDrag(\n columnEl: HTMLElement,\n handleEl: HTMLElement,\n columnId: string,\n boardEl: HTMLElement,\n ): void {\n handleEl.setAttribute('draggable', 'true');\n\n const onDragStart = (e: DragEvent) => {\n if (!e.dataTransfer) return;\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('application/kanban-column', columnId);\n columnEl.classList.add(`${this.prefix}-column--dragging`);\n };\n\n const onDragEnd = () => {\n columnEl.classList.remove(`${this.prefix}-column--dragging`);\n // 清除所有列的 drag-over 状态\n boardEl.querySelectorAll(`.${this.prefix}-column--drag-over`).forEach(el => {\n el.classList.remove(`${this.prefix}-column--drag-over`);\n });\n };\n\n const onDragOver = (e: DragEvent) => {\n if (!e.dataTransfer?.types.includes('application/kanban-column')) return;\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n columnEl.classList.add(`${this.prefix}-column--drag-over`);\n };\n\n const onDragLeave = () => {\n columnEl.classList.remove(`${this.prefix}-column--drag-over`);\n };\n\n const onDrop = (e: DragEvent) => {\n e.preventDefault();\n columnEl.classList.remove(`${this.prefix}-column--drag-over`);\n const fromColumnId = e.dataTransfer?.getData('application/kanban-column');\n if (!fromColumnId || fromColumnId === columnId) return;\n\n // 计算目标位置\n const columns = [...boardEl.querySelectorAll(`[data-column-id]`)];\n const toIndex = columns.indexOf(columnEl);\n this.events.emit('column:drop', { fromColumnId, toColumnId: columnId, toIndex });\n };\n\n handleEl.addEventListener('dragstart', onDragStart);\n handleEl.addEventListener('dragend', onDragEnd);\n columnEl.addEventListener('dragover', onDragOver);\n columnEl.addEventListener('dragleave', onDragLeave);\n columnEl.addEventListener('drop', onDrop);\n\n this.cleanupFns.push(() => {\n handleEl.removeEventListener('dragstart', onDragStart);\n handleEl.removeEventListener('dragend', onDragEnd);\n columnEl.removeEventListener('dragover', onDragOver);\n columnEl.removeEventListener('dragleave', onDragLeave);\n columnEl.removeEventListener('drop', onDrop);\n });\n }\n\n /**\n * 获取鼠标位置之后的元素\n */\n private getDragAfterElement(container: HTMLElement, y: number): HTMLElement | null {\n const draggableElements = [\n ...container.querySelectorAll(\n `[data-card-id]:not(.${this.prefix}-card--dragging):not(.${this.prefix}-placeholder)`,\n ),\n ] as HTMLElement[];\n\n let closestOffset = Number.NEGATIVE_INFINITY;\n let closestElement: HTMLElement | null = null;\n\n draggableElements.forEach(child => {\n const box = child.getBoundingClientRect();\n const offset = y - box.top - box.height / 2;\n if (offset < 0 && offset > closestOffset) {\n closestOffset = offset;\n closestElement = child;\n }\n });\n\n return closestElement;\n }\n\n /**\n * 创建占位符\n */\n private createPlaceholder(cardEl: HTMLElement): void {\n this.removePlaceholder();\n const placeholder = document.createElement('div');\n placeholder.className = `${this.prefix}-placeholder`;\n placeholder.style.height = `${cardEl.offsetHeight}px`;\n this.state.placeholder = placeholder;\n }\n\n /**\n * 移除占位符\n */\n private removePlaceholder(): void {\n if (this.state.placeholder) {\n this.state.placeholder.parentNode?.removeChild(this.state.placeholder);\n this.state.placeholder = null;\n }\n }\n\n /**\n * 获取当前拖拽状态\n */\n getState(): Readonly<DragState> {\n return { ...this.state };\n }\n\n /**\n * 销毁\n */\n destroy(): void {\n this.cleanupFns.forEach(fn => fn());\n this.cleanupFns = [];\n this.removePlaceholder();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DragController.js","names":[],"sources":["../../src/core/DragController.ts"],"sourcesContent":["import type { EventEmitter } from './EventEmitter'\r\n\r\n/**\r\n * DragState - Drag State interface\r\n */\r\nexport interface DragState {\r\n dragging: boolean\r\n cardId: string | null\r\n sourceColumnId: string | null\r\n targetColumnId: string | null\r\n targetIndex: number\r\n placeholder: HTMLElement | null\r\n}\r\n\r\n/**\r\n * 拖拽控制器 - HTML5 Drag & Drop API\r\n */\r\nexport class DragController {\r\n private events: EventEmitter\r\n private state: DragState = {\r\n dragging: false,\r\n cardId: null,\r\n sourceColumnId: null,\r\n targetColumnId: null,\r\n targetIndex: 0,\r\n placeholder: null,\r\n }\r\n private prefix: string\r\n private cleanupFns: (() => void)[] = []\r\n\r\n constructor(events: EventEmitter, prefix: string) {\r\n this.events = events\r\n this.prefix = prefix\r\n }\r\n\r\n /**\r\n * 为卡片元素启用拖拽\r\n */\r\n enableCardDrag(cardEl: HTMLElement, cardId: string, columnId: string): void {\r\n cardEl.setAttribute('draggable', 'true')\r\n cardEl.dataset.cardId = cardId\r\n cardEl.dataset.columnId = columnId\r\n\r\n const onDragStart = (e: DragEvent) => {\r\n if (!e.dataTransfer) return\r\n e.dataTransfer.effectAllowed = 'move'\r\n e.dataTransfer.setData('text/plain', cardId)\r\n\r\n this.state.dragging = true\r\n this.state.cardId = cardId\r\n this.state.sourceColumnId = columnId\r\n this.state.targetColumnId = columnId\r\n this.state.targetIndex = 0\r\n\r\n cardEl.classList.add(`${this.prefix}-card--dragging`)\r\n this.events.emit('card:drag-start', { cardId, columnId })\r\n\r\n // 创建占位符\r\n this.createPlaceholder(cardEl)\r\n }\r\n\r\n const onDragEnd = () => {\r\n cardEl.classList.remove(`${this.prefix}-card--dragging`)\r\n this.removePlaceholder()\r\n this.state.dragging = false\r\n this.state.cardId = null\r\n this.state.sourceColumnId = null\r\n this.state.targetColumnId = null\r\n this.state.targetIndex = 0\r\n this.events.emit('card:drag-end', { cardId })\r\n }\r\n\r\n cardEl.addEventListener('dragstart', onDragStart)\r\n cardEl.addEventListener('dragend', onDragEnd)\r\n\r\n this.cleanupFns.push(() => {\r\n cardEl.removeEventListener('dragstart', onDragStart)\r\n cardEl.removeEventListener('dragend', onDragEnd)\r\n })\r\n }\r\n\r\n /**\r\n * 为列容器启用拖拽接受\r\n */\r\n enableColumnDrop(columnBodyEl: HTMLElement, columnId: string): void {\r\n const onDragOver = (e: DragEvent) => {\r\n e.preventDefault()\r\n if (!e.dataTransfer) return\r\n e.dataTransfer.dropEffect = 'move'\r\n\r\n this.state.targetColumnId = columnId\r\n\r\n // 计算插入位置\r\n const afterElement = this.getDragAfterElement(columnBodyEl, e.clientY)\r\n const placeholder = this.state.placeholder\r\n if (placeholder) {\r\n if (afterElement) {\r\n columnBodyEl.insertBefore(placeholder, afterElement)\r\n } else {\r\n // 插入到最后一张卡片之后,但在“添加卡片”按钮之前\r\n const addBtn = columnBodyEl.querySelector(`.${this.prefix}-add-card`)\r\n if (addBtn) {\r\n columnBodyEl.insertBefore(placeholder, addBtn)\r\n } else {\r\n columnBodyEl.appendChild(placeholder)\r\n }\r\n }\r\n }\r\n }\r\n\r\n const onDragEnter = (e: DragEvent) => {\r\n e.preventDefault()\r\n columnBodyEl.classList.add(`${this.prefix}-column-body--drag-over`)\r\n }\r\n\r\n const onDragLeave = (e: DragEvent) => {\r\n // 检查是否真的离开了列\r\n const rect = columnBodyEl.getBoundingClientRect()\r\n if (\r\n e.clientX < rect.left ||\r\n e.clientX > rect.right ||\r\n e.clientY < rect.top ||\r\n e.clientY > rect.bottom\r\n ) {\r\n columnBodyEl.classList.remove(`${this.prefix}-column-body--drag-over`)\r\n }\r\n }\r\n\r\n const onDrop = (e: DragEvent) => {\r\n e.preventDefault()\r\n columnBodyEl.classList.remove(`${this.prefix}-column-body--drag-over`)\r\n\r\n const cardId = this.state.cardId\r\n if (!cardId) return\r\n\r\n // 计算 drop 位置:只统计占位符前面的可见卡片数量\r\n const placeholder = this.state.placeholder\r\n let dropIndex = 0\r\n\r\n if (placeholder && placeholder.parentNode === columnBodyEl) {\r\n const children = [...columnBodyEl.children]\r\n for (const child of children) {\r\n if (child === placeholder) break\r\n if (\r\n (child as HTMLElement).hasAttribute('data-card-id') &&\r\n !(child as HTMLElement).classList.contains(\r\n `${this.prefix}-card--dragging`\r\n )\r\n ) {\r\n dropIndex++\r\n }\r\n }\r\n } else {\r\n // 占位符不在当前列时,放到末尾\r\n const cards = columnBodyEl.querySelectorAll(\r\n `[data-card-id]:not(.${this.prefix}-card--dragging)`\r\n )\r\n dropIndex = cards.length\r\n }\r\n\r\n this.state.targetIndex = dropIndex\r\n\r\n this.events.emit('card:drop', {\r\n cardId,\r\n fromColumnId: this.state.sourceColumnId,\r\n toColumnId: columnId,\r\n toIndex: dropIndex,\r\n })\r\n\r\n this.removePlaceholder()\r\n }\r\n\r\n columnBodyEl.addEventListener('dragover', onDragOver)\r\n columnBodyEl.addEventListener('dragenter', onDragEnter)\r\n columnBodyEl.addEventListener('dragleave', onDragLeave)\r\n columnBodyEl.addEventListener('drop', onDrop)\r\n\r\n this.cleanupFns.push(() => {\r\n columnBodyEl.removeEventListener('dragover', onDragOver)\r\n columnBodyEl.removeEventListener('dragenter', onDragEnter)\r\n columnBodyEl.removeEventListener('dragleave', onDragLeave)\r\n columnBodyEl.removeEventListener('drop', onDrop)\r\n })\r\n }\r\n\r\n /**\r\n * 为列头部启用列拖拽\r\n */\r\n enableColumnDrag(\r\n columnEl: HTMLElement,\r\n handleEl: HTMLElement,\r\n columnId: string,\r\n boardEl: HTMLElement\r\n ): void {\r\n handleEl.setAttribute('draggable', 'true')\r\n\r\n const onDragStart = (e: DragEvent) => {\r\n if (!e.dataTransfer) return\r\n e.dataTransfer.effectAllowed = 'move'\r\n e.dataTransfer.setData('application/kanban-column', columnId)\r\n columnEl.classList.add(`${this.prefix}-column--dragging`)\r\n }\r\n\r\n const onDragEnd = () => {\r\n columnEl.classList.remove(`${this.prefix}-column--dragging`)\r\n // 清除所有列的 drag-over 状态\r\n boardEl\r\n .querySelectorAll(`.${this.prefix}-column--drag-over`)\r\n .forEach(el => {\r\n el.classList.remove(`${this.prefix}-column--drag-over`)\r\n })\r\n }\r\n\r\n const onDragOver = (e: DragEvent) => {\r\n if (!e.dataTransfer?.types.includes('application/kanban-column')) return\r\n e.preventDefault()\r\n e.dataTransfer.dropEffect = 'move'\r\n columnEl.classList.add(`${this.prefix}-column--drag-over`)\r\n }\r\n\r\n const onDragLeave = () => {\r\n columnEl.classList.remove(`${this.prefix}-column--drag-over`)\r\n }\r\n\r\n const onDrop = (e: DragEvent) => {\r\n e.preventDefault()\r\n columnEl.classList.remove(`${this.prefix}-column--drag-over`)\r\n const fromColumnId = e.dataTransfer?.getData('application/kanban-column')\r\n if (!fromColumnId || fromColumnId === columnId) return\r\n\r\n // 计算目标位置\r\n const columns = [...boardEl.querySelectorAll(`[data-column-id]`)]\r\n const toIndex = columns.indexOf(columnEl)\r\n this.events.emit('column:drop', {\r\n fromColumnId,\r\n toColumnId: columnId,\r\n toIndex,\r\n })\r\n }\r\n\r\n handleEl.addEventListener('dragstart', onDragStart)\r\n handleEl.addEventListener('dragend', onDragEnd)\r\n columnEl.addEventListener('dragover', onDragOver)\r\n columnEl.addEventListener('dragleave', onDragLeave)\r\n columnEl.addEventListener('drop', onDrop)\r\n\r\n this.cleanupFns.push(() => {\r\n handleEl.removeEventListener('dragstart', onDragStart)\r\n handleEl.removeEventListener('dragend', onDragEnd)\r\n columnEl.removeEventListener('dragover', onDragOver)\r\n columnEl.removeEventListener('dragleave', onDragLeave)\r\n columnEl.removeEventListener('drop', onDrop)\r\n })\r\n }\r\n\r\n /**\r\n * 获取鼠标位置之后的元素\r\n */\r\n private getDragAfterElement(\r\n container: HTMLElement,\r\n y: number\r\n ): HTMLElement | null {\r\n const draggableElements = [\r\n ...container.querySelectorAll(\r\n `[data-card-id]:not(.${this.prefix}-card--dragging):not(.${this.prefix}-placeholder)`\r\n ),\r\n ] as HTMLElement[]\r\n\r\n let closestOffset = Number.NEGATIVE_INFINITY\r\n let closestElement: HTMLElement | null = null\r\n\r\n draggableElements.forEach(child => {\r\n const box = child.getBoundingClientRect()\r\n const offset = y - box.top - box.height / 2\r\n if (offset < 0 && offset > closestOffset) {\r\n closestOffset = offset\r\n closestElement = child\r\n }\r\n })\r\n\r\n return closestElement\r\n }\r\n\r\n /**\r\n * 创建占位符\r\n */\r\n private createPlaceholder(cardEl: HTMLElement): void {\r\n this.removePlaceholder()\r\n const placeholder = document.createElement('div')\r\n placeholder.className = `${this.prefix}-placeholder`\r\n placeholder.style.height = `${cardEl.offsetHeight}px`\r\n this.state.placeholder = placeholder\r\n }\r\n\r\n /**\r\n * 移除占位符\r\n */\r\n private removePlaceholder(): void {\r\n if (this.state.placeholder) {\r\n this.state.placeholder.parentNode?.removeChild(this.state.placeholder)\r\n this.state.placeholder = null\r\n }\r\n }\r\n\r\n /**\r\n * 获取当前拖拽状态\r\n */\r\n getState(): Readonly<DragState> {\r\n return { ...this.state }\r\n }\r\n\r\n /**\r\n * 销毁\r\n */\r\n destroy(): void {\r\n this.cleanupFns.forEach(fn => fn())\r\n this.cleanupFns = []\r\n this.removePlaceholder()\r\n this.state.dragging = false\r\n this.state.cardId = null\r\n this.state.sourceColumnId = null\r\n this.state.targetColumnId = null\r\n this.state.targetIndex = 0\r\n }\r\n}\r\n"],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
  * ***********************************
3
3
  * @composy/kanban-core v0.0.1 *
4
4
  * Built with rollup *
5
- * Build time: 2024-08-17 11:33:25 *
5
+ * Build time: 2024-10-12 17:29:00 *
6
6
  * Build mode: production *
7
7
  * Minified: No *
8
8
  * ***********************************
@@ -17,7 +17,9 @@ var EventEmitter = class {
17
17
  */
18
18
  on(event, handler) {
19
19
  if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
20
- this.listeners.get(event).add(handler);
20
+ const handlers = this.listeners.get(event);
21
+ if (!handlers) return () => this.off(event, handler);
22
+ handlers.add(handler);
21
23
  return () => this.off(event, handler);
22
24
  }
23
25
  /**
@@ -34,7 +36,10 @@ var EventEmitter = class {
34
36
  * 移除监听器
35
37
  */
36
38
  off(event, handler) {
37
- this.listeners.get(event)?.delete(handler);
39
+ const handlers = this.listeners.get(event);
40
+ if (!handlers) return;
41
+ handlers.delete(handler);
42
+ if (handlers.size === 0) this.listeners.delete(event);
38
43
  }
39
44
  /**
40
45
  * 触发事件
@@ -1 +1 @@
1
- {"version":3,"file":"EventEmitter.js","names":[],"sources":["../../src/core/EventEmitter.ts"],"sourcesContent":["/**\n * 轻量级事件发射器\n */\nexport type EventHandler = (...args: unknown[]) => void;\n\n/**\n * EventEmitter - Event Emitter class\n */\nexport class EventEmitter {\n private listeners = new Map<string, Set<EventHandler>>();\n\n /**\n * 监听事件\n */\n on(event: string, handler: EventHandler): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(handler);\n return () => this.off(event, handler);\n }\n\n /**\n * 监听一次性事件\n */\n once(event: string, handler: EventHandler): () => void {\n const wrapper: EventHandler = (...args) => {\n this.off(event, wrapper);\n handler(...args);\n };\n return this.on(event, wrapper);\n }\n\n /**\n * 移除监听器\n */\n off(event: string, handler: EventHandler): void {\n this.listeners.get(event)?.delete(handler);\n }\n\n /**\n * 触发事件\n */\n emit(event: string, payload?: unknown): void {\n const handlers = this.listeners.get(event);\n if (handlers) {\n handlers.forEach(handler => {\n try {\n handler(payload);\n } catch (err) {\n console.error(`[KanbanBoard] Error in event handler for \"${event}\":`, err);\n }\n });\n }\n }\n\n /**\n * 移除所有监听器\n */\n removeAll(): void {\n this.listeners.clear();\n }\n\n /**\n * 获取监听器数量\n */\n listenerCount(event: string): number {\n return this.listeners.get(event)?.size ?? 0;\n }\n}\n"],"mappings":";;;;;;;;;;AAQO,IAAM,eAAN,MAAmB;CAAnB,cAAA;EACL,KAAQ,4BAAY,IAAI,IAA+B;CAAA;;;;CAKvD,GAAG,OAAe,SAAmC;EACnD,IAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAC3B,KAAK,UAAU,IAAI,uBAAO,IAAI,IAAI,CAAC;EAErC,KAAK,UAAU,IAAI,KAAK,CAAA,CAAG,IAAI,OAAO;EACtC,aAAa,KAAK,IAAI,OAAO,OAAO;CACtC;;;;CAKA,KAAK,OAAe,SAAmC;EACrD,MAAM,WAAwB,GAAI,SAAS;GACzC,KAAK,IAAI,OAAO,OAAO;GACvB,QAAQ,GAAG,IAAI;EACjB;EACA,OAAO,KAAK,GAAG,OAAO,OAAO;CAC/B;;;;CAKA,IAAI,OAAe,SAA6B;EAC9C,KAAK,UAAU,IAAI,KAAK,CAAA,EAAG,OAAO,OAAO;CAC3C;;;;CAKA,KAAK,OAAe,SAAyB;EAC3C,MAAM,WAAW,KAAK,UAAU,IAAI,KAAK;EACzC,IAAI,UACF,SAAS,SAAQ,YAAW;GAC1B,IAAI;IACF,QAAQ,OAAO;GACjB,SAAS,KAAK;IACZ,QAAQ,MAAM,6CAA6C,MAAK,KAAM,GAAG;GAC3E;EACF,CAAC;CAEL;;;;CAKA,YAAkB;EAChB,KAAK,UAAU,MAAM;CACvB;;;;CAKA,cAAc,OAAuB;EACnC,OAAO,KAAK,UAAU,IAAI,KAAK,CAAA,EAAG,QAAQ;CAC5C;AACF"}
1
+ {"version":3,"file":"EventEmitter.js","names":[],"sources":["../../src/core/EventEmitter.ts"],"sourcesContent":["/**\r\n * 轻量级事件发射器\r\n */\r\nexport type EventHandler = (...args: unknown[]) => void\r\n\r\n/**\r\n * EventEmitter - Event Emitter class\r\n */\r\nexport class EventEmitter {\r\n private listeners = new Map<string, Set<EventHandler>>()\r\n\r\n /**\r\n * 监听事件\r\n */\r\n on(event: string, handler: EventHandler): () => void {\r\n if (!this.listeners.has(event)) {\r\n this.listeners.set(event, new Set())\r\n }\r\n const handlers = this.listeners.get(event)\r\n if (!handlers) return () => this.off(event, handler)\r\n handlers.add(handler)\r\n return () => this.off(event, handler)\r\n }\r\n\r\n /**\r\n * 监听一次性事件\r\n */\r\n once(event: string, handler: EventHandler): () => void {\r\n const wrapper: EventHandler = (...args) => {\r\n this.off(event, wrapper)\r\n handler(...args)\r\n }\r\n return this.on(event, wrapper)\r\n }\r\n\r\n /**\r\n * 移除监听器\r\n */\r\n off(event: string, handler: EventHandler): void {\r\n const handlers = this.listeners.get(event)\r\n if (!handlers) return\r\n handlers.delete(handler)\r\n if (handlers.size === 0) this.listeners.delete(event)\r\n }\r\n\r\n /**\r\n * 触发事件\r\n */\r\n emit(event: string, payload?: unknown): void {\r\n const handlers = this.listeners.get(event)\r\n if (handlers) {\r\n handlers.forEach(handler => {\r\n try {\r\n handler(payload)\r\n } catch (err) {\r\n console.error(\r\n `[KanbanBoard] Error in event handler for \"${event}\":`,\r\n err\r\n )\r\n }\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * 移除所有监听器\r\n */\r\n removeAll(): void {\r\n this.listeners.clear()\r\n }\r\n\r\n /**\r\n * 获取监听器数量\r\n */\r\n listenerCount(event: string): number {\r\n return this.listeners.get(event)?.size ?? 0\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;AAQO,IAAM,eAAN,MAAmB;CAAnB,cAAA;EACL,KAAQ,4BAAY,IAAI,IAA+B;CAAA;;;;CAKvD,GAAG,OAAe,SAAmC;EACnD,IAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAC3B,KAAK,UAAU,IAAI,uBAAO,IAAI,IAAI,CAAC;EAErC,MAAM,WAAW,KAAK,UAAU,IAAI,KAAK;EACzC,IAAI,CAAC,UAAU,aAAa,KAAK,IAAI,OAAO,OAAO;EACnD,SAAS,IAAI,OAAO;EACpB,aAAa,KAAK,IAAI,OAAO,OAAO;CACtC;;;;CAKA,KAAK,OAAe,SAAmC;EACrD,MAAM,WAAwB,GAAI,SAAS;GACzC,KAAK,IAAI,OAAO,OAAO;GACvB,QAAQ,GAAG,IAAI;EACjB;EACA,OAAO,KAAK,GAAG,OAAO,OAAO;CAC/B;;;;CAKA,IAAI,OAAe,SAA6B;EAC9C,MAAM,WAAW,KAAK,UAAU,IAAI,KAAK;EACzC,IAAI,CAAC,UAAU;EACf,SAAS,OAAO,OAAO;EACvB,IAAI,SAAS,SAAS,GAAG,KAAK,UAAU,OAAO,KAAK;CACtD;;;;CAKA,KAAK,OAAe,SAAyB;EAC3C,MAAM,WAAW,KAAK,UAAU,IAAI,KAAK;EACzC,IAAI,UACF,SAAS,SAAQ,YAAW;GAC1B,IAAI;IACF,QAAQ,OAAO;GACjB,SAAS,KAAK;IACZ,QAAQ,MACN,6CAA6C,MAAK,KAClD,GACF;GACF;EACF,CAAC;CAEL;;;;CAKA,YAAkB;EAChB,KAAK,UAAU,MAAM;CACvB;;;;CAKA,cAAc,OAAuB;EACnC,OAAO,KAAK,UAAU,IAAI,KAAK,CAAA,EAAG,QAAQ;CAC5C;AACF"}
@@ -2,7 +2,7 @@
2
2
  * ***********************************
3
3
  * @composy/kanban-core v0.0.1 *
4
4
  * Built with rollup *
5
- * Build time: 2024-08-17 11:33:25 *
5
+ * Build time: 2024-10-12 17:29:00 *
6
6
  * Build mode: production *
7
7
  * Minified: No *
8
8
  * ***********************************
@@ -31,11 +31,13 @@ var FilterEngine = class {
31
31
  */
32
32
  search(keyword, columns) {
33
33
  this.events.emit("search:change", { keyword });
34
- if (!keyword.trim()) {
34
+ const normalizedKeyword = keyword.trim();
35
+ this.currentCriteria = normalizedKeyword ? { keyword: normalizedKeyword } : null;
36
+ if (!normalizedKeyword) {
35
37
  this.clearFilter();
36
38
  return columns.flatMap((col) => col.cards);
37
39
  }
38
- const lowerKeyword = keyword.toLowerCase();
40
+ const lowerKeyword = normalizedKeyword.toLowerCase();
39
41
  const allCards = columns.flatMap((col) => col.cards);
40
42
  const matched = [];
41
43
  this.hiddenCardIds.clear();
@@ -101,11 +103,14 @@ var FilterEngine = class {
101
103
  if (criteria.dueDateRange) {
102
104
  if (!card.dueDate) return false;
103
105
  const dueDate = new Date(card.dueDate).getTime();
106
+ if (Number.isNaN(dueDate)) return false;
104
107
  if (criteria.dueDateRange.start) {
105
- if (dueDate < new Date(criteria.dueDateRange.start).getTime()) return false;
108
+ const start = new Date(criteria.dueDateRange.start).getTime();
109
+ if (Number.isNaN(start) || dueDate < start) return false;
106
110
  }
107
111
  if (criteria.dueDateRange.end) {
108
- if (dueDate > new Date(criteria.dueDateRange.end).getTime()) return false;
112
+ const end = new Date(criteria.dueDateRange.end).getTime();
113
+ if (Number.isNaN(end) || dueDate > end) return false;
109
114
  }
110
115
  }
111
116
  if (criteria.custom) {
@@ -1 +1 @@
1
- {"version":3,"file":"FilterEngine.js","names":[],"sources":["../../src/core/FilterEngine.ts"],"sourcesContent":["import type { Card, Column, FilterCriteria } from '../types';\nimport { EventEmitter } from './EventEmitter';\n\n/**\n * 筛选/搜索引擎\n */\nexport class FilterEngine {\n private events: EventEmitter;\n private currentCriteria: FilterCriteria | null = null;\n /** 被过滤隐藏的卡片 ID 集合 */\n private hiddenCardIds = new Set<string>();\n\n constructor(events: EventEmitter) {\n this.events = events;\n }\n\n /**\n * 应用筛选条件\n */\n filter(criteria: FilterCriteria, columns: Column[]): Set<string> {\n this.currentCriteria = criteria;\n this.hiddenCardIds.clear();\n\n const allCards = columns.flatMap(col => col.cards);\n\n for (const card of allCards) {\n if (!this.matchCriteria(card, criteria)) {\n this.hiddenCardIds.add(card.id);\n }\n }\n\n this.events.emit('filter:change', { criteria });\n return new Set(this.hiddenCardIds);\n }\n\n /**\n * 搜索卡片\n */\n search(keyword: string, columns: Column[]): Card[] {\n this.events.emit('search:change', { keyword });\n\n if (!keyword.trim()) {\n this.clearFilter();\n return columns.flatMap(col => col.cards);\n }\n\n const lowerKeyword = keyword.toLowerCase();\n const allCards = columns.flatMap(col => col.cards);\n const matched: Card[] = [];\n\n this.hiddenCardIds.clear();\n\n for (const card of allCards) {\n const titleMatch = card.title.toLowerCase().includes(lowerKeyword);\n const descMatch = card.description?.toLowerCase().includes(lowerKeyword) ?? false;\n const labelMatch = card.labels?.some(l => l.name.toLowerCase().includes(lowerKeyword)) ?? false;\n const assigneeMatch = card.assignees?.some(a => a.toLowerCase().includes(lowerKeyword)) ?? false;\n\n if (titleMatch || descMatch || labelMatch || assigneeMatch) {\n matched.push(card);\n } else {\n this.hiddenCardIds.add(card.id);\n }\n }\n\n return matched;\n }\n\n /**\n * 清除筛选\n */\n clearFilter(): void {\n this.currentCriteria = null;\n this.hiddenCardIds.clear();\n this.events.emit('filter:clear', undefined);\n }\n\n /**\n * 检查卡片是否可见\n */\n isCardVisible(cardId: string): boolean {\n return !this.hiddenCardIds.has(cardId);\n }\n\n /**\n * 获取隐藏的卡片 ID 集合\n */\n getHiddenCardIds(): Set<string> {\n return new Set(this.hiddenCardIds);\n }\n\n /**\n * 获取当前筛选条件\n */\n getCurrentCriteria(): FilterCriteria | null {\n return this.currentCriteria;\n }\n\n /**\n * 匹配筛选条件\n */\n private matchCriteria(card: Card, criteria: FilterCriteria): boolean {\n // 关键词\n if (criteria.keyword) {\n const kw = criteria.keyword.toLowerCase();\n const titleMatch = card.title.toLowerCase().includes(kw);\n const descMatch = card.description?.toLowerCase().includes(kw) ?? false;\n if (!titleMatch && !descMatch) return false;\n }\n\n // 标签\n if (criteria.labelIds?.length) {\n const cardLabelIds = card.labels?.map(l => l.id) ?? [];\n if (!criteria.labelIds.some(id => cardLabelIds.includes(id))) return false;\n }\n\n // 成员\n if (criteria.assignees?.length) {\n if (!criteria.assignees.some(a => card.assignees?.includes(a))) return false;\n }\n\n // 优先级\n if (criteria.priorities?.length) {\n if (!card.priority || !criteria.priorities.includes(card.priority)) return false;\n }\n\n // 列\n if (criteria.columnIds?.length) {\n if (!criteria.columnIds.includes(card.columnId)) return false;\n }\n\n // 截止日期\n if (criteria.dueDateRange) {\n if (!card.dueDate) return false;\n const dueDate = new Date(card.dueDate).getTime();\n if (criteria.dueDateRange.start) {\n if (dueDate < new Date(criteria.dueDateRange.start).getTime()) return false;\n }\n if (criteria.dueDateRange.end) {\n if (dueDate > new Date(criteria.dueDateRange.end).getTime()) return false;\n }\n }\n\n // 自定义函数\n if (criteria.custom) {\n if (!criteria.custom(card)) return false;\n }\n\n return true;\n }\n\n /**\n * 销毁\n */\n destroy(): void {\n this.hiddenCardIds.clear();\n this.currentCriteria = null;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"FilterEngine.js","names":[],"sources":["../../src/core/FilterEngine.ts"],"sourcesContent":["import type { Card, Column, FilterCriteria } from '../types'\r\nimport type { EventEmitter } from './EventEmitter'\r\n\r\n/**\r\n * 筛选/搜索引擎\r\n */\r\nexport class FilterEngine {\r\n private events: EventEmitter\r\n private currentCriteria: FilterCriteria | null = null\r\n /** 被过滤隐藏的卡片 ID 集合 */\r\n private hiddenCardIds = new Set<string>()\r\n\r\n constructor(events: EventEmitter) {\r\n this.events = events\r\n }\r\n\r\n /**\r\n * 应用筛选条件\r\n */\r\n filter(criteria: FilterCriteria, columns: Column[]): Set<string> {\r\n this.currentCriteria = criteria\r\n this.hiddenCardIds.clear()\r\n\r\n const allCards = columns.flatMap(col => col.cards)\r\n\r\n for (const card of allCards) {\r\n if (!this.matchCriteria(card, criteria)) {\r\n this.hiddenCardIds.add(card.id)\r\n }\r\n }\r\n\r\n this.events.emit('filter:change', { criteria })\r\n return new Set(this.hiddenCardIds)\r\n }\r\n\r\n /**\r\n * 搜索卡片\r\n */\r\n search(keyword: string, columns: Column[]): Card[] {\r\n this.events.emit('search:change', { keyword })\r\n const normalizedKeyword = keyword.trim()\r\n this.currentCriteria = normalizedKeyword\r\n ? { keyword: normalizedKeyword }\r\n : null\r\n\r\n if (!normalizedKeyword) {\r\n this.clearFilter()\r\n return columns.flatMap(col => col.cards)\r\n }\r\n\r\n const lowerKeyword = normalizedKeyword.toLowerCase()\r\n const allCards = columns.flatMap(col => col.cards)\r\n const matched: Card[] = []\r\n\r\n this.hiddenCardIds.clear()\r\n\r\n for (const card of allCards) {\r\n const titleMatch = card.title.toLowerCase().includes(lowerKeyword)\r\n const descMatch =\r\n card.description?.toLowerCase().includes(lowerKeyword) ?? false\r\n const labelMatch =\r\n card.labels?.some(l => l.name.toLowerCase().includes(lowerKeyword)) ??\r\n false\r\n const assigneeMatch =\r\n card.assignees?.some(a => a.toLowerCase().includes(lowerKeyword)) ??\r\n false\r\n\r\n if (titleMatch || descMatch || labelMatch || assigneeMatch) {\r\n matched.push(card)\r\n } else {\r\n this.hiddenCardIds.add(card.id)\r\n }\r\n }\r\n\r\n return matched\r\n }\r\n\r\n /**\r\n * 清除筛选\r\n */\r\n clearFilter(): void {\r\n this.currentCriteria = null\r\n this.hiddenCardIds.clear()\r\n this.events.emit('filter:clear', undefined)\r\n }\r\n\r\n /**\r\n * 检查卡片是否可见\r\n */\r\n isCardVisible(cardId: string): boolean {\r\n return !this.hiddenCardIds.has(cardId)\r\n }\r\n\r\n /**\r\n * 获取隐藏的卡片 ID 集合\r\n */\r\n getHiddenCardIds(): Set<string> {\r\n return new Set(this.hiddenCardIds)\r\n }\r\n\r\n /**\r\n * 获取当前筛选条件\r\n */\r\n getCurrentCriteria(): FilterCriteria | null {\r\n return this.currentCriteria\r\n }\r\n\r\n /**\r\n * 匹配筛选条件\r\n */\r\n private matchCriteria(card: Card, criteria: FilterCriteria): boolean {\r\n // 关键词\r\n if (criteria.keyword) {\r\n const kw = criteria.keyword.toLowerCase()\r\n const titleMatch = card.title.toLowerCase().includes(kw)\r\n const descMatch = card.description?.toLowerCase().includes(kw) ?? false\r\n if (!titleMatch && !descMatch) return false\r\n }\r\n\r\n // 标签\r\n if (criteria.labelIds?.length) {\r\n const cardLabelIds = card.labels?.map(l => l.id) ?? []\r\n if (!criteria.labelIds.some(id => cardLabelIds.includes(id))) return false\r\n }\r\n\r\n // 成员\r\n if (criteria.assignees?.length) {\r\n if (!criteria.assignees.some(a => card.assignees?.includes(a)))\r\n return false\r\n }\r\n\r\n // 优先级\r\n if (criteria.priorities?.length) {\r\n if (!card.priority || !criteria.priorities.includes(card.priority))\r\n return false\r\n }\r\n\r\n // 列\r\n if (criteria.columnIds?.length) {\r\n if (!criteria.columnIds.includes(card.columnId)) return false\r\n }\r\n\r\n // 截止日期\r\n if (criteria.dueDateRange) {\r\n if (!card.dueDate) return false\r\n const dueDate = new Date(card.dueDate).getTime()\r\n if (Number.isNaN(dueDate)) return false\r\n if (criteria.dueDateRange.start) {\r\n const start = new Date(criteria.dueDateRange.start).getTime()\r\n if (Number.isNaN(start) || dueDate < start) return false\r\n }\r\n if (criteria.dueDateRange.end) {\r\n const end = new Date(criteria.dueDateRange.end).getTime()\r\n if (Number.isNaN(end) || dueDate > end) return false\r\n }\r\n }\r\n\r\n // 自定义函数\r\n if (criteria.custom) {\r\n if (!criteria.custom(card)) return false\r\n }\r\n\r\n return true\r\n }\r\n\r\n /**\r\n * 销毁\r\n */\r\n destroy(): void {\r\n this.hiddenCardIds.clear()\r\n this.currentCriteria = null\r\n }\r\n}\r\n"],"mappings":"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"}