@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
@@ -0,0 +1,226 @@
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
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
11
+ //#region src/core/DragController.ts
12
+ var DragController = class {
13
+ constructor(events, prefix) {
14
+ this.state = {
15
+ dragging: false,
16
+ cardId: null,
17
+ sourceColumnId: null,
18
+ targetColumnId: null,
19
+ targetIndex: 0,
20
+ placeholder: null
21
+ };
22
+ this.cleanupFns = [];
23
+ this.events = events;
24
+ this.prefix = prefix;
25
+ }
26
+ /**
27
+ * 为卡片元素启用拖拽
28
+ */
29
+ enableCardDrag(cardEl, cardId, columnId) {
30
+ cardEl.setAttribute("draggable", "true");
31
+ cardEl.dataset.cardId = cardId;
32
+ cardEl.dataset.columnId = columnId;
33
+ const onDragStart = (e) => {
34
+ if (!e.dataTransfer) return;
35
+ e.dataTransfer.effectAllowed = "move";
36
+ e.dataTransfer.setData("text/plain", cardId);
37
+ this.state.dragging = true;
38
+ this.state.cardId = cardId;
39
+ this.state.sourceColumnId = columnId;
40
+ cardEl.classList.add(`${this.prefix}-card--dragging`);
41
+ this.events.emit("card:drag-start", {
42
+ cardId,
43
+ columnId
44
+ });
45
+ this.createPlaceholder(cardEl);
46
+ };
47
+ const onDragEnd = () => {
48
+ cardEl.classList.remove(`${this.prefix}-card--dragging`);
49
+ this.removePlaceholder();
50
+ this.state.dragging = false;
51
+ this.state.cardId = null;
52
+ this.state.sourceColumnId = null;
53
+ this.events.emit("card:drag-end", { cardId });
54
+ };
55
+ cardEl.addEventListener("dragstart", onDragStart);
56
+ cardEl.addEventListener("dragend", onDragEnd);
57
+ this.cleanupFns.push(() => {
58
+ cardEl.removeEventListener("dragstart", onDragStart);
59
+ cardEl.removeEventListener("dragend", onDragEnd);
60
+ });
61
+ }
62
+ /**
63
+ * 为列容器启用拖拽接受
64
+ */
65
+ enableColumnDrop(columnBodyEl, columnId) {
66
+ const onDragOver = (e) => {
67
+ e.preventDefault();
68
+ if (!e.dataTransfer) return;
69
+ e.dataTransfer.dropEffect = "move";
70
+ this.state.targetColumnId = columnId;
71
+ const afterElement = this.getDragAfterElement(columnBodyEl, e.clientY);
72
+ const placeholder = this.state.placeholder;
73
+ if (placeholder) if (afterElement) columnBodyEl.insertBefore(placeholder, afterElement);
74
+ else {
75
+ const addBtn = columnBodyEl.querySelector(`.${this.prefix}-add-card`);
76
+ if (addBtn) columnBodyEl.insertBefore(placeholder, addBtn);
77
+ else columnBodyEl.appendChild(placeholder);
78
+ }
79
+ };
80
+ const onDragEnter = (e) => {
81
+ e.preventDefault();
82
+ columnBodyEl.classList.add(`${this.prefix}-column-body--drag-over`);
83
+ };
84
+ const onDragLeave = (e) => {
85
+ const rect = columnBodyEl.getBoundingClientRect();
86
+ 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`);
87
+ };
88
+ const onDrop = (e) => {
89
+ e.preventDefault();
90
+ columnBodyEl.classList.remove(`${this.prefix}-column-body--drag-over`);
91
+ const cardId = this.state.cardId;
92
+ if (!cardId) return;
93
+ const placeholder = this.state.placeholder;
94
+ let dropIndex = 0;
95
+ if (placeholder && placeholder.parentNode === columnBodyEl) {
96
+ const children = [...columnBodyEl.children];
97
+ for (const child of children) {
98
+ if (child === placeholder) break;
99
+ if (child.hasAttribute("data-card-id") && !child.classList.contains(`${this.prefix}-card--dragging`)) dropIndex++;
100
+ }
101
+ } else dropIndex = columnBodyEl.querySelectorAll(`[data-card-id]:not(.${this.prefix}-card--dragging)`).length;
102
+ this.events.emit("card:drop", {
103
+ cardId,
104
+ fromColumnId: this.state.sourceColumnId,
105
+ toColumnId: columnId,
106
+ toIndex: dropIndex
107
+ });
108
+ this.removePlaceholder();
109
+ };
110
+ columnBodyEl.addEventListener("dragover", onDragOver);
111
+ columnBodyEl.addEventListener("dragenter", onDragEnter);
112
+ columnBodyEl.addEventListener("dragleave", onDragLeave);
113
+ columnBodyEl.addEventListener("drop", onDrop);
114
+ this.cleanupFns.push(() => {
115
+ columnBodyEl.removeEventListener("dragover", onDragOver);
116
+ columnBodyEl.removeEventListener("dragenter", onDragEnter);
117
+ columnBodyEl.removeEventListener("dragleave", onDragLeave);
118
+ columnBodyEl.removeEventListener("drop", onDrop);
119
+ });
120
+ }
121
+ /**
122
+ * 为列头部启用列拖拽
123
+ */
124
+ enableColumnDrag(columnEl, handleEl, columnId, boardEl) {
125
+ handleEl.setAttribute("draggable", "true");
126
+ const onDragStart = (e) => {
127
+ if (!e.dataTransfer) return;
128
+ e.dataTransfer.effectAllowed = "move";
129
+ e.dataTransfer.setData("application/kanban-column", columnId);
130
+ columnEl.classList.add(`${this.prefix}-column--dragging`);
131
+ };
132
+ const onDragEnd = () => {
133
+ columnEl.classList.remove(`${this.prefix}-column--dragging`);
134
+ boardEl.querySelectorAll(`.${this.prefix}-column--drag-over`).forEach((el) => {
135
+ el.classList.remove(`${this.prefix}-column--drag-over`);
136
+ });
137
+ };
138
+ const onDragOver = (e) => {
139
+ if (!e.dataTransfer?.types.includes("application/kanban-column")) return;
140
+ e.preventDefault();
141
+ e.dataTransfer.dropEffect = "move";
142
+ columnEl.classList.add(`${this.prefix}-column--drag-over`);
143
+ };
144
+ const onDragLeave = () => {
145
+ columnEl.classList.remove(`${this.prefix}-column--drag-over`);
146
+ };
147
+ const onDrop = (e) => {
148
+ e.preventDefault();
149
+ columnEl.classList.remove(`${this.prefix}-column--drag-over`);
150
+ const fromColumnId = e.dataTransfer?.getData("application/kanban-column");
151
+ if (!fromColumnId || fromColumnId === columnId) return;
152
+ const toIndex = [...boardEl.querySelectorAll(`[data-column-id]`)].indexOf(columnEl);
153
+ this.events.emit("column:drop", {
154
+ fromColumnId,
155
+ toColumnId: columnId,
156
+ toIndex
157
+ });
158
+ };
159
+ handleEl.addEventListener("dragstart", onDragStart);
160
+ handleEl.addEventListener("dragend", onDragEnd);
161
+ columnEl.addEventListener("dragover", onDragOver);
162
+ columnEl.addEventListener("dragleave", onDragLeave);
163
+ columnEl.addEventListener("drop", onDrop);
164
+ this.cleanupFns.push(() => {
165
+ handleEl.removeEventListener("dragstart", onDragStart);
166
+ handleEl.removeEventListener("dragend", onDragEnd);
167
+ columnEl.removeEventListener("dragover", onDragOver);
168
+ columnEl.removeEventListener("dragleave", onDragLeave);
169
+ columnEl.removeEventListener("drop", onDrop);
170
+ });
171
+ }
172
+ /**
173
+ * 获取鼠标位置之后的元素
174
+ */
175
+ getDragAfterElement(container, y) {
176
+ const draggableElements = [...container.querySelectorAll(`[data-card-id]:not(.${this.prefix}-card--dragging):not(.${this.prefix}-placeholder)`)];
177
+ let closestOffset = Number.NEGATIVE_INFINITY;
178
+ let closestElement = null;
179
+ draggableElements.forEach((child) => {
180
+ const box = child.getBoundingClientRect();
181
+ const offset = y - box.top - box.height / 2;
182
+ if (offset < 0 && offset > closestOffset) {
183
+ closestOffset = offset;
184
+ closestElement = child;
185
+ }
186
+ });
187
+ return closestElement;
188
+ }
189
+ /**
190
+ * 创建占位符
191
+ */
192
+ createPlaceholder(cardEl) {
193
+ this.removePlaceholder();
194
+ const placeholder = document.createElement("div");
195
+ placeholder.className = `${this.prefix}-placeholder`;
196
+ placeholder.style.height = `${cardEl.offsetHeight}px`;
197
+ this.state.placeholder = placeholder;
198
+ }
199
+ /**
200
+ * 移除占位符
201
+ */
202
+ removePlaceholder() {
203
+ if (this.state.placeholder) {
204
+ this.state.placeholder.parentNode?.removeChild(this.state.placeholder);
205
+ this.state.placeholder = null;
206
+ }
207
+ }
208
+ /**
209
+ * 获取当前拖拽状态
210
+ */
211
+ getState() {
212
+ return { ...this.state };
213
+ }
214
+ /**
215
+ * 销毁
216
+ */
217
+ destroy() {
218
+ this.cleanupFns.forEach((fn) => fn());
219
+ this.cleanupFns = [];
220
+ this.removePlaceholder();
221
+ }
222
+ };
223
+ //#endregion
224
+ exports.DragController = DragController;
225
+ /*! End of @composy/kanban-core | Powered by @composy/builder */
226
+ //# sourceMappingURL=DragController.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DragController.cjs","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":";;;;;;;;;;;AAiBO,IAAM,iBAAN,MAAqB;CAa1B,YAAY,QAAsB,QAAgB;EAXlD,KAAQ,QAAmB;GACzB,UAAU;GACV,QAAQ;GACR,gBAAgB;GAChB,gBAAgB;GAChB,aAAa;GACb,aAAa;EACf;EAEA,KAAQ,aAA6B,CAAC;EAGpC,KAAK,SAAS;EACd,KAAK,SAAS;CAChB;;;;CAKA,eAAe,QAAqB,QAAgB,UAAwB;EAC1E,OAAO,aAAa,aAAa,MAAM;EACvC,OAAO,QAAQ,SAAS;EACxB,OAAO,QAAQ,WAAW;EAE1B,MAAM,eAAe,MAAiB;GACpC,IAAI,CAAC,EAAE,cAAc;GACrB,EAAE,aAAa,gBAAgB;GAC/B,EAAE,aAAa,QAAQ,cAAc,MAAM;GAE3C,KAAK,MAAM,WAAW;GACtB,KAAK,MAAM,SAAS;GACpB,KAAK,MAAM,iBAAiB;GAE5B,OAAO,UAAU,IAAI,GAAG,KAAK,OAAM,gBAAiB;GACpD,KAAK,OAAO,KAAK,mBAAmB;IAAE;IAAQ;GAAS,CAAC;GAGxD,KAAK,kBAAkB,MAAM;EAC/B;EAEA,MAAM,kBAAkB;GACtB,OAAO,UAAU,OAAO,GAAG,KAAK,OAAM,gBAAiB;GACvD,KAAK,kBAAkB;GACvB,KAAK,MAAM,WAAW;GACtB,KAAK,MAAM,SAAS;GACpB,KAAK,MAAM,iBAAiB;GAC5B,KAAK,OAAO,KAAK,iBAAiB,EAAE,OAAO,CAAC;EAC9C;EAEA,OAAO,iBAAiB,aAAa,WAAW;EAChD,OAAO,iBAAiB,WAAW,SAAS;EAE5C,KAAK,WAAW,WAAW;GACzB,OAAO,oBAAoB,aAAa,WAAW;GACnD,OAAO,oBAAoB,WAAW,SAAS;EACjD,CAAC;CACH;;;;CAKA,iBAAiB,cAA2B,UAAwB;EAClE,MAAM,cAAc,MAAiB;GACnC,EAAE,eAAe;GACjB,IAAI,CAAC,EAAE,cAAc;GACrB,EAAE,aAAa,aAAa;GAE5B,KAAK,MAAM,iBAAiB;GAG5B,MAAM,eAAe,KAAK,oBAAoB,cAAc,EAAE,OAAO;GACrE,MAAM,cAAc,KAAK,MAAM;GAC/B,IAAI,aACF,IAAI,cACF,aAAa,aAAa,aAAa,YAAY;QAC9C;IAEL,MAAM,SAAS,aAAa,cAAc,IAAI,KAAK,OAAM,UAAW;IACpE,IAAI,QACF,aAAa,aAAa,aAAa,MAAM;SAE7C,aAAa,YAAY,WAAW;GAExC;EAEJ;EAEA,MAAM,eAAe,MAAiB;GACpC,EAAE,eAAe;GACjB,aAAa,UAAU,IAAI,GAAG,KAAK,OAAM,wBAAyB;EACpE;EAEA,MAAM,eAAe,MAAiB;GAEpC,MAAM,OAAO,aAAa,sBAAsB;GAChD,IACE,EAAE,UAAU,KAAK,QACjB,EAAE,UAAU,KAAK,SACjB,EAAE,UAAU,KAAK,OACjB,EAAE,UAAU,KAAK,QAEjB,aAAa,UAAU,OAAO,GAAG,KAAK,OAAM,wBAAyB;EAEzE;EAEA,MAAM,UAAU,MAAiB;GAC/B,EAAE,eAAe;GACjB,aAAa,UAAU,OAAO,GAAG,KAAK,OAAM,wBAAyB;GAErE,MAAM,SAAS,KAAK,MAAM;GAC1B,IAAI,CAAC,QAAQ;GAGb,MAAM,cAAc,KAAK,MAAM;GAC/B,IAAI,YAAY;GAEhB,IAAI,eAAe,YAAY,eAAe,cAAc;IAC1D,MAAM,WAAW,CAAC,GAAG,aAAa,QAAQ;IAC1C,KAAA,MAAW,SAAS,UAAU;KAC5B,IAAI,UAAU,aAAa;KAC3B,IACG,MAAsB,aAAa,cAAc,KAClD,CAAE,MAAsB,UAAU,SAAS,GAAG,KAAK,OAAM,gBAAiB,GAE1E;IAEJ;GACF,OAKE,YAHc,aAAa,iBACzB,uBAAuB,KAAK,OAAM,iBAExB,CAAA,CAAM;GAGpB,KAAK,OAAO,KAAK,aAAa;IAC5B;IACA,cAAc,KAAK,MAAM;IACzB,YAAY;IACZ,SAAS;GACX,CAAC;GAED,KAAK,kBAAkB;EACzB;EAEA,aAAa,iBAAiB,YAAY,UAAU;EACpD,aAAa,iBAAiB,aAAa,WAAW;EACtD,aAAa,iBAAiB,aAAa,WAAW;EACtD,aAAa,iBAAiB,QAAQ,MAAM;EAE5C,KAAK,WAAW,WAAW;GACzB,aAAa,oBAAoB,YAAY,UAAU;GACvD,aAAa,oBAAoB,aAAa,WAAW;GACzD,aAAa,oBAAoB,aAAa,WAAW;GACzD,aAAa,oBAAoB,QAAQ,MAAM;EACjD,CAAC;CACH;;;;CAKA,iBACE,UACA,UACA,UACA,SACM;EACN,SAAS,aAAa,aAAa,MAAM;EAEzC,MAAM,eAAe,MAAiB;GACpC,IAAI,CAAC,EAAE,cAAc;GACrB,EAAE,aAAa,gBAAgB;GAC/B,EAAE,aAAa,QAAQ,6BAA6B,QAAQ;GAC5D,SAAS,UAAU,IAAI,GAAG,KAAK,OAAM,kBAAmB;EAC1D;EAEA,MAAM,kBAAkB;GACtB,SAAS,UAAU,OAAO,GAAG,KAAK,OAAM,kBAAmB;GAE3D,QAAQ,iBAAiB,IAAI,KAAK,OAAM,mBAAoB,CAAA,CAAE,SAAQ,OAAM;IAC1E,GAAG,UAAU,OAAO,GAAG,KAAK,OAAM,mBAAoB;GACxD,CAAC;EACH;EAEA,MAAM,cAAc,MAAiB;GACnC,IAAI,CAAC,EAAE,cAAc,MAAM,SAAS,2BAA2B,GAAG;GAClE,EAAE,eAAe;GACjB,EAAE,aAAa,aAAa;GAC5B,SAAS,UAAU,IAAI,GAAG,KAAK,OAAM,mBAAoB;EAC3D;EAEA,MAAM,oBAAoB;GACxB,SAAS,UAAU,OAAO,GAAG,KAAK,OAAM,mBAAoB;EAC9D;EAEA,MAAM,UAAU,MAAiB;GAC/B,EAAE,eAAe;GACjB,SAAS,UAAU,OAAO,GAAG,KAAK,OAAM,mBAAoB;GAC5D,MAAM,eAAe,EAAE,cAAc,QAAQ,2BAA2B;GACxE,IAAI,CAAC,gBAAgB,iBAAiB,UAAU;GAIhD,MAAM,UAAU,CADC,GAAG,QAAQ,iBAAiB,kBAAkB,CAC/C,CAAA,CAAQ,QAAQ,QAAQ;GACxC,KAAK,OAAO,KAAK,eAAe;IAAE;IAAc,YAAY;IAAU;GAAQ,CAAC;EACjF;EAEA,SAAS,iBAAiB,aAAa,WAAW;EAClD,SAAS,iBAAiB,WAAW,SAAS;EAC9C,SAAS,iBAAiB,YAAY,UAAU;EAChD,SAAS,iBAAiB,aAAa,WAAW;EAClD,SAAS,iBAAiB,QAAQ,MAAM;EAExC,KAAK,WAAW,WAAW;GACzB,SAAS,oBAAoB,aAAa,WAAW;GACrD,SAAS,oBAAoB,WAAW,SAAS;GACjD,SAAS,oBAAoB,YAAY,UAAU;GACnD,SAAS,oBAAoB,aAAa,WAAW;GACrD,SAAS,oBAAoB,QAAQ,MAAM;EAC7C,CAAC;CACH;;;;CAKQ,oBAAoB,WAAwB,GAA+B;EACjF,MAAM,oBAAoB,CACxB,GAAG,UAAU,iBACX,uBAAuB,KAAK,OAAM,wBAAyB,KAAK,OAAM,cACxE,CACF;EAEA,IAAI,gBAAgB,OAAO;EAC3B,IAAI,iBAAqC;EAEzC,kBAAkB,SAAQ,UAAS;GACjC,MAAM,MAAM,MAAM,sBAAsB;GACxC,MAAM,SAAS,IAAI,IAAI,MAAM,IAAI,SAAS;GAC1C,IAAI,SAAS,KAAK,SAAS,eAAe;IACxC,gBAAgB;IAChB,iBAAiB;GACnB;EACF,CAAC;EAED,OAAO;CACT;;;;CAKQ,kBAAkB,QAA2B;EACnD,KAAK,kBAAkB;EACvB,MAAM,cAAc,SAAS,cAAc,KAAK;EAChD,YAAY,YAAY,GAAG,KAAK,OAAM;EACtC,YAAY,MAAM,SAAS,GAAG,OAAO,aAAY;EACjD,KAAK,MAAM,cAAc;CAC3B;;;;CAKQ,oBAA0B;EAChC,IAAI,KAAK,MAAM,aAAa;GAC1B,KAAK,MAAM,YAAY,YAAY,YAAY,KAAK,MAAM,WAAW;GACrE,KAAK,MAAM,cAAc;EAC3B;CACF;;;;CAKA,WAAgC;EAC9B,OAAO,EAAE,GAAG,KAAK,MAAM;CACzB;;;;CAKA,UAAgB;EACd,KAAK,WAAW,SAAQ,OAAM,GAAG,CAAC;EAClC,KAAK,aAAa,CAAC;EACnB,KAAK,kBAAkB;CACzB;AACF"}
@@ -0,0 +1,55 @@
1
+ import { EventEmitter } from './EventEmitter';
2
+ /**
3
+ * DragState - Drag State interface
4
+ */
5
+ export interface DragState {
6
+ dragging: boolean;
7
+ cardId: string | null;
8
+ sourceColumnId: string | null;
9
+ targetColumnId: string | null;
10
+ targetIndex: number;
11
+ placeholder: HTMLElement | null;
12
+ }
13
+ /**
14
+ * 拖拽控制器 - HTML5 Drag & Drop API
15
+ */
16
+ export declare class DragController {
17
+ private events;
18
+ private state;
19
+ private prefix;
20
+ private cleanupFns;
21
+ constructor(events: EventEmitter, prefix: string);
22
+ /**
23
+ * 为卡片元素启用拖拽
24
+ */
25
+ enableCardDrag(cardEl: HTMLElement, cardId: string, columnId: string): void;
26
+ /**
27
+ * 为列容器启用拖拽接受
28
+ */
29
+ enableColumnDrop(columnBodyEl: HTMLElement, columnId: string): void;
30
+ /**
31
+ * 为列头部启用列拖拽
32
+ */
33
+ enableColumnDrag(columnEl: HTMLElement, handleEl: HTMLElement, columnId: string, boardEl: HTMLElement): void;
34
+ /**
35
+ * 获取鼠标位置之后的元素
36
+ */
37
+ private getDragAfterElement;
38
+ /**
39
+ * 创建占位符
40
+ */
41
+ private createPlaceholder;
42
+ /**
43
+ * 移除占位符
44
+ */
45
+ private removePlaceholder;
46
+ /**
47
+ * 获取当前拖拽状态
48
+ */
49
+ getState(): Readonly<DragState>;
50
+ /**
51
+ * 销毁
52
+ */
53
+ destroy(): void;
54
+ }
55
+ //# sourceMappingURL=DragController.d.ts.map
@@ -0,0 +1,69 @@
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
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
11
+ //#region src/core/EventEmitter.ts
12
+ var EventEmitter = class {
13
+ constructor() {
14
+ this.listeners = /* @__PURE__ */ new Map();
15
+ }
16
+ /**
17
+ * 监听事件
18
+ */
19
+ on(event, handler) {
20
+ if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
21
+ this.listeners.get(event).add(handler);
22
+ return () => this.off(event, handler);
23
+ }
24
+ /**
25
+ * 监听一次性事件
26
+ */
27
+ once(event, handler) {
28
+ const wrapper = (...args) => {
29
+ this.off(event, wrapper);
30
+ handler(...args);
31
+ };
32
+ return this.on(event, wrapper);
33
+ }
34
+ /**
35
+ * 移除监听器
36
+ */
37
+ off(event, handler) {
38
+ this.listeners.get(event)?.delete(handler);
39
+ }
40
+ /**
41
+ * 触发事件
42
+ */
43
+ emit(event, payload) {
44
+ const handlers = this.listeners.get(event);
45
+ if (handlers) handlers.forEach((handler) => {
46
+ try {
47
+ handler(payload);
48
+ } catch (err) {
49
+ console.error(`[KanbanBoard] Error in event handler for "${event}":`, err);
50
+ }
51
+ });
52
+ }
53
+ /**
54
+ * 移除所有监听器
55
+ */
56
+ removeAll() {
57
+ this.listeners.clear();
58
+ }
59
+ /**
60
+ * 获取监听器数量
61
+ */
62
+ listenerCount(event) {
63
+ return this.listeners.get(event)?.size ?? 0;
64
+ }
65
+ };
66
+ //#endregion
67
+ exports.EventEmitter = EventEmitter;
68
+ /*! End of @composy/kanban-core | Powered by @composy/builder */
69
+ //# sourceMappingURL=EventEmitter.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventEmitter.cjs","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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * 轻量级事件发射器
3
+ */
4
+ export type EventHandler = (...args: unknown[]) => void;
5
+ /**
6
+ * EventEmitter - Event Emitter class
7
+ */
8
+ export declare class EventEmitter {
9
+ private listeners;
10
+ /**
11
+ * 监听事件
12
+ */
13
+ on(event: string, handler: EventHandler): () => void;
14
+ /**
15
+ * 监听一次性事件
16
+ */
17
+ once(event: string, handler: EventHandler): () => void;
18
+ /**
19
+ * 移除监听器
20
+ */
21
+ off(event: string, handler: EventHandler): void;
22
+ /**
23
+ * 触发事件
24
+ */
25
+ emit(event: string, payload?: unknown): void;
26
+ /**
27
+ * 移除所有监听器
28
+ */
29
+ removeAll(): void;
30
+ /**
31
+ * 获取监听器数量
32
+ */
33
+ listenerCount(event: string): number;
34
+ }
35
+ //# sourceMappingURL=EventEmitter.d.ts.map
@@ -0,0 +1,128 @@
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
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
11
+ //#region src/core/FilterEngine.ts
12
+ var FilterEngine = class {
13
+ constructor(events) {
14
+ this.currentCriteria = null;
15
+ /** 被过滤隐藏的卡片 ID 集合 */
16
+ this.hiddenCardIds = /* @__PURE__ */ new Set();
17
+ this.events = events;
18
+ }
19
+ /**
20
+ * 应用筛选条件
21
+ */
22
+ filter(criteria, columns) {
23
+ this.currentCriteria = criteria;
24
+ this.hiddenCardIds.clear();
25
+ const allCards = columns.flatMap((col) => col.cards);
26
+ for (const card of allCards) if (!this.matchCriteria(card, criteria)) this.hiddenCardIds.add(card.id);
27
+ this.events.emit("filter:change", { criteria });
28
+ return new Set(this.hiddenCardIds);
29
+ }
30
+ /**
31
+ * 搜索卡片
32
+ */
33
+ search(keyword, columns) {
34
+ this.events.emit("search:change", { keyword });
35
+ if (!keyword.trim()) {
36
+ this.clearFilter();
37
+ return columns.flatMap((col) => col.cards);
38
+ }
39
+ const lowerKeyword = keyword.toLowerCase();
40
+ const allCards = columns.flatMap((col) => col.cards);
41
+ const matched = [];
42
+ this.hiddenCardIds.clear();
43
+ for (const card of allCards) {
44
+ const titleMatch = card.title.toLowerCase().includes(lowerKeyword);
45
+ const descMatch = card.description?.toLowerCase().includes(lowerKeyword) ?? false;
46
+ const labelMatch = card.labels?.some((l) => l.name.toLowerCase().includes(lowerKeyword)) ?? false;
47
+ const assigneeMatch = card.assignees?.some((a) => a.toLowerCase().includes(lowerKeyword)) ?? false;
48
+ if (titleMatch || descMatch || labelMatch || assigneeMatch) matched.push(card);
49
+ else this.hiddenCardIds.add(card.id);
50
+ }
51
+ return matched;
52
+ }
53
+ /**
54
+ * 清除筛选
55
+ */
56
+ clearFilter() {
57
+ this.currentCriteria = null;
58
+ this.hiddenCardIds.clear();
59
+ this.events.emit("filter:clear", void 0);
60
+ }
61
+ /**
62
+ * 检查卡片是否可见
63
+ */
64
+ isCardVisible(cardId) {
65
+ return !this.hiddenCardIds.has(cardId);
66
+ }
67
+ /**
68
+ * 获取隐藏的卡片 ID 集合
69
+ */
70
+ getHiddenCardIds() {
71
+ return new Set(this.hiddenCardIds);
72
+ }
73
+ /**
74
+ * 获取当前筛选条件
75
+ */
76
+ getCurrentCriteria() {
77
+ return this.currentCriteria;
78
+ }
79
+ /**
80
+ * 匹配筛选条件
81
+ */
82
+ matchCriteria(card, criteria) {
83
+ if (criteria.keyword) {
84
+ const kw = criteria.keyword.toLowerCase();
85
+ const titleMatch = card.title.toLowerCase().includes(kw);
86
+ const descMatch = card.description?.toLowerCase().includes(kw) ?? false;
87
+ if (!titleMatch && !descMatch) return false;
88
+ }
89
+ if (criteria.labelIds?.length) {
90
+ const cardLabelIds = card.labels?.map((l) => l.id) ?? [];
91
+ if (!criteria.labelIds.some((id) => cardLabelIds.includes(id))) return false;
92
+ }
93
+ if (criteria.assignees?.length) {
94
+ if (!criteria.assignees.some((a) => card.assignees?.includes(a))) return false;
95
+ }
96
+ if (criteria.priorities?.length) {
97
+ if (!card.priority || !criteria.priorities.includes(card.priority)) return false;
98
+ }
99
+ if (criteria.columnIds?.length) {
100
+ if (!criteria.columnIds.includes(card.columnId)) return false;
101
+ }
102
+ if (criteria.dueDateRange) {
103
+ if (!card.dueDate) return false;
104
+ const dueDate = new Date(card.dueDate).getTime();
105
+ if (criteria.dueDateRange.start) {
106
+ if (dueDate < new Date(criteria.dueDateRange.start).getTime()) return false;
107
+ }
108
+ if (criteria.dueDateRange.end) {
109
+ if (dueDate > new Date(criteria.dueDateRange.end).getTime()) return false;
110
+ }
111
+ }
112
+ if (criteria.custom) {
113
+ if (!criteria.custom(card)) return false;
114
+ }
115
+ return true;
116
+ }
117
+ /**
118
+ * 销毁
119
+ */
120
+ destroy() {
121
+ this.hiddenCardIds.clear();
122
+ this.currentCriteria = null;
123
+ }
124
+ };
125
+ //#endregion
126
+ exports.FilterEngine = FilterEngine;
127
+ /*! End of @composy/kanban-core | Powered by @composy/builder */
128
+ //# sourceMappingURL=FilterEngine.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterEngine.cjs","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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"}
@@ -0,0 +1,45 @@
1
+ import type { Card, Column, FilterCriteria } from '../types';
2
+ import { EventEmitter } from './EventEmitter';
3
+ /**
4
+ * 筛选/搜索引擎
5
+ */
6
+ export declare class FilterEngine {
7
+ private events;
8
+ private currentCriteria;
9
+ /** 被过滤隐藏的卡片 ID 集合 */
10
+ private hiddenCardIds;
11
+ constructor(events: EventEmitter);
12
+ /**
13
+ * 应用筛选条件
14
+ */
15
+ filter(criteria: FilterCriteria, columns: Column[]): Set<string>;
16
+ /**
17
+ * 搜索卡片
18
+ */
19
+ search(keyword: string, columns: Column[]): Card[];
20
+ /**
21
+ * 清除筛选
22
+ */
23
+ clearFilter(): void;
24
+ /**
25
+ * 检查卡片是否可见
26
+ */
27
+ isCardVisible(cardId: string): boolean;
28
+ /**
29
+ * 获取隐藏的卡片 ID 集合
30
+ */
31
+ getHiddenCardIds(): Set<string>;
32
+ /**
33
+ * 获取当前筛选条件
34
+ */
35
+ getCurrentCriteria(): FilterCriteria | null;
36
+ /**
37
+ * 匹配筛选条件
38
+ */
39
+ private matchCriteria;
40
+ /**
41
+ * 销毁
42
+ */
43
+ destroy(): void;
44
+ }
45
+ //# sourceMappingURL=FilterEngine.d.ts.map