@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.
- package/README.md +37 -0
- package/dist/index.css +307 -0
- package/dist/index.js +1279 -0
- package/dist/index.js.map +1 -0
- package/dist/index.min.css +1 -0
- package/dist/index.min.js +1 -0
- package/es/core/ColumnManager.d.ts +99 -0
- package/es/core/ColumnManager.d.ts.map +1 -0
- package/es/core/ColumnManager.js +285 -0
- package/es/core/ColumnManager.js.map +1 -0
- package/es/core/DragController.d.ts +55 -0
- package/es/core/DragController.d.ts.map +1 -0
- package/es/core/DragController.js +225 -0
- package/es/core/DragController.js.map +1 -0
- package/es/core/EventEmitter.d.ts +35 -0
- package/es/core/EventEmitter.d.ts.map +1 -0
- package/es/core/EventEmitter.js +68 -0
- package/es/core/EventEmitter.js.map +1 -0
- package/es/core/FilterEngine.d.ts +45 -0
- package/es/core/FilterEngine.d.ts.map +1 -0
- package/es/core/FilterEngine.js +127 -0
- package/es/core/FilterEngine.js.map +1 -0
- package/es/core/KanbanBoard.d.ts +152 -0
- package/es/core/KanbanBoard.d.ts.map +1 -0
- package/es/core/KanbanBoard.js +332 -0
- package/es/core/KanbanBoard.js.map +1 -0
- package/es/core/Renderer.d.ts +59 -0
- package/es/core/Renderer.d.ts.map +1 -0
- package/es/core/Renderer.js +247 -0
- package/es/core/Renderer.js.map +1 -0
- package/es/core/index.d.ts +11 -0
- package/es/core/index.d.ts.map +1 -0
- package/es/core/index.js +17 -0
- package/es/index.d.ts +15 -0
- package/es/index.d.ts.map +1 -0
- package/es/index.js +21 -0
- package/es/styles/kanban.css +306 -0
- package/es/types/index.d.ts +209 -0
- package/es/types/index.d.ts.map +1 -0
- package/es/types/index.js +10 -0
- package/es/utils/dom.d.ts +24 -0
- package/es/utils/dom.d.ts.map +1 -0
- package/es/utils/dom.js +37 -0
- package/es/utils/dom.js.map +1 -0
- package/es/utils/id.d.ts +9 -0
- package/es/utils/id.d.ts.map +1 -0
- package/es/utils/id.js +22 -0
- package/es/utils/id.js.map +1 -0
- package/es/utils/index.d.ts +25 -0
- package/es/utils/index.d.ts.map +1 -0
- package/es/utils/index.js +20 -0
- package/es/utils/index.js.map +1 -0
- package/es/version.d.ts +6 -0
- package/es/version.d.ts.map +1 -0
- package/es/version.js +15 -0
- package/es/version.js.map +1 -0
- package/esm/core/ColumnManager.d.ts +99 -0
- package/esm/core/ColumnManager.d.ts.map +1 -0
- package/esm/core/ColumnManager.js +285 -0
- package/esm/core/ColumnManager.js.map +1 -0
- package/esm/core/DragController.d.ts +55 -0
- package/esm/core/DragController.d.ts.map +1 -0
- package/esm/core/DragController.js +225 -0
- package/esm/core/DragController.js.map +1 -0
- package/esm/core/EventEmitter.d.ts +35 -0
- package/esm/core/EventEmitter.d.ts.map +1 -0
- package/esm/core/EventEmitter.js +68 -0
- package/esm/core/EventEmitter.js.map +1 -0
- package/esm/core/FilterEngine.d.ts +45 -0
- package/esm/core/FilterEngine.d.ts.map +1 -0
- package/esm/core/FilterEngine.js +127 -0
- package/esm/core/FilterEngine.js.map +1 -0
- package/esm/core/KanbanBoard.d.ts +152 -0
- package/esm/core/KanbanBoard.d.ts.map +1 -0
- package/esm/core/KanbanBoard.js +332 -0
- package/esm/core/KanbanBoard.js.map +1 -0
- package/esm/core/Renderer.d.ts +59 -0
- package/esm/core/Renderer.d.ts.map +1 -0
- package/esm/core/Renderer.js +247 -0
- package/esm/core/Renderer.js.map +1 -0
- package/esm/core/index.d.ts +11 -0
- package/esm/core/index.d.ts.map +1 -0
- package/esm/core/index.js +17 -0
- package/esm/index.d.ts +15 -0
- package/esm/index.d.ts.map +1 -0
- package/esm/index.js +21 -0
- package/esm/styles/kanban.less +339 -0
- package/esm/types/index.d.ts +209 -0
- package/esm/types/index.d.ts.map +1 -0
- package/esm/types/index.js +10 -0
- package/esm/utils/dom.d.ts +24 -0
- package/esm/utils/dom.d.ts.map +1 -0
- package/esm/utils/dom.js +37 -0
- package/esm/utils/dom.js.map +1 -0
- package/esm/utils/id.d.ts +9 -0
- package/esm/utils/id.d.ts.map +1 -0
- package/esm/utils/id.js +22 -0
- package/esm/utils/id.js.map +1 -0
- package/esm/utils/index.d.ts +25 -0
- package/esm/utils/index.d.ts.map +1 -0
- package/esm/utils/index.js +20 -0
- package/esm/utils/index.js.map +1 -0
- package/esm/version.d.ts +6 -0
- package/esm/version.d.ts.map +1 -0
- package/esm/version.js +15 -0
- package/esm/version.js.map +1 -0
- package/lib/core/ColumnManager.cjs +286 -0
- package/lib/core/ColumnManager.cjs.map +1 -0
- package/lib/core/ColumnManager.d.ts +99 -0
- package/lib/core/DragController.cjs +226 -0
- package/lib/core/DragController.cjs.map +1 -0
- package/lib/core/DragController.d.ts +55 -0
- package/lib/core/EventEmitter.cjs +69 -0
- package/lib/core/EventEmitter.cjs.map +1 -0
- package/lib/core/EventEmitter.d.ts +35 -0
- package/lib/core/FilterEngine.cjs +128 -0
- package/lib/core/FilterEngine.cjs.map +1 -0
- package/lib/core/FilterEngine.d.ts +45 -0
- package/lib/core/KanbanBoard.cjs +334 -0
- package/lib/core/KanbanBoard.cjs.map +1 -0
- package/lib/core/KanbanBoard.d.ts +152 -0
- package/lib/core/Renderer.cjs +248 -0
- package/lib/core/Renderer.cjs.map +1 -0
- package/lib/core/Renderer.d.ts +59 -0
- package/lib/core/index.cjs +24 -0
- package/lib/core/index.d.ts +11 -0
- package/lib/index.cjs +37 -0
- package/lib/index.d.ts +15 -0
- package/lib/package.json +3 -0
- package/lib/styles/kanban.css +306 -0
- package/lib/types/index.cjs +11 -0
- package/lib/types/index.d.ts +209 -0
- package/lib/utils/dom.cjs +42 -0
- package/lib/utils/dom.cjs.map +1 -0
- package/lib/utils/dom.d.ts +24 -0
- package/lib/utils/id.cjs +24 -0
- package/lib/utils/id.cjs.map +1 -0
- package/lib/utils/id.d.ts +9 -0
- package/lib/utils/index.cjs +28 -0
- package/lib/utils/index.cjs.map +1 -0
- package/lib/utils/index.d.ts +25 -0
- package/lib/version.cjs +16 -0
- package/lib/version.cjs.map +1 -0
- package/lib/version.d.ts +6 -0
- package/package.json +105 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/utils/dom.ts","../src/core/EventEmitter.ts","../src/utils/id.ts","../src/core/ColumnManager.ts","../src/core/DragController.ts","../src/core/FilterEngine.ts","../src/core/Renderer.ts","../src/version.ts","../src/core/KanbanBoard.ts","../src/utils/index.ts"],"sourcesContent":["/**\n * DOM 工具函数\n */\n\n/**\n * 创建 DOM 元素\n */\nexport function createElement<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n className?: string,\n attributes?: Record<string, string>,\n): HTMLElementTagNameMap[K] {\n const el = document.createElement(tag);\n if (className) el.className = className;\n if (attributes) {\n Object.entries(attributes).forEach(([key, value]) => {\n el.setAttribute(key, value);\n });\n }\n return el;\n}\n\n/**\n * 从选择器或元素获取 HTMLElement\n */\nexport function resolveElement(target: HTMLElement | string): HTMLElement | null {\n if (typeof target === 'string') {\n return document.querySelector(target);\n }\n return target;\n}\n\n/**\n * 清空元素内容\n */\nexport function clearElement(el: HTMLElement): void {\n el.innerHTML = '';\n}\n\n/**\n * 安全移除元素\n */\nexport function removeElement(el: HTMLElement | null): void {\n el?.parentNode?.removeChild(el);\n}\n\n/**\n * 插入元素到指定位置\n */\nexport function insertAt(parent: HTMLElement, child: HTMLElement, index: number): void {\n const ref = parent.children[index] as HTMLElement | undefined;\n if (ref) {\n parent.insertBefore(child, ref);\n } else {\n parent.appendChild(child);\n }\n}\n","/**\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","let counter = 0;\n\n/**\n * 生成唯一 ID\n */\nexport function generateId(prefix = 'kb'): string {\n counter++;\n const timestamp = Date.now().toString(36);\n const random = Math.random().toString(36).substring(2, 6);\n return `${prefix}_${timestamp}_${random}_${counter}`;\n}\n\n/**\n * 重置计数器(仅测试用)\n */\nexport function resetIdCounter(): void {\n counter = 0;\n}\n","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","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","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","import type { Column, Card, KanbanOptions } from '../types';\nimport { createElement } from '../utils/dom';\nimport { EventEmitter } from './EventEmitter';\nimport { DragController } from './DragController';\n\n/**\n * DOM 渲染器\n */\nexport class Renderer {\n private container: HTMLElement;\n private boardEl: HTMLElement | null = null;\n private events: EventEmitter;\n private dragController: DragController;\n private options: KanbanOptions;\n private prefix: string;\n private columnElements = new Map<string, HTMLElement>();\n private columnBodyElements = new Map<string, HTMLElement>();\n private cardElements = new Map<string, HTMLElement>();\n\n constructor(\n container: HTMLElement,\n events: EventEmitter,\n dragController: DragController,\n options: KanbanOptions,\n ) {\n this.container = container;\n this.events = events;\n this.dragController = dragController;\n this.options = options;\n this.prefix = options.classPrefix || 'kanban';\n }\n\n /**\n * 渲染整个看板\n */\n render(columns: Column[], hiddenCardIds?: Set<string>): void {\n this.cleanup();\n\n // 清除容器中残留的旧看板元素(HMR / 重复渲染保护)\n const staleBoards = this.container.querySelectorAll('[data-kanban-board]');\n staleBoards.forEach(el => el.remove());\n\n const p = this.prefix;\n this.boardEl = createElement('div', `${p}-board`);\n this.boardEl.setAttribute('data-kanban-board', '');\n\n // 列容器\n const columnsContainer = createElement('div', `${p}-columns`);\n\n for (const column of columns) {\n const colEl = this.renderColumn(column, hiddenCardIds);\n columnsContainer.appendChild(colEl);\n }\n\n // 添加列按钮\n if (this.options.showAddColumn !== false) {\n const addColBtn = createElement('div', `${p}-add-column`);\n addColBtn.innerHTML = `<button class=\"${p}-add-column-btn\">+ 添加列</button>`;\n addColBtn.querySelector('button')!.addEventListener('click', () => {\n this.events.emit('ui:add-column');\n });\n columnsContainer.appendChild(addColBtn);\n }\n\n this.boardEl.appendChild(columnsContainer);\n this.container.appendChild(this.boardEl);\n }\n\n /**\n * 渲染单个列\n */\n private renderColumn(column: Column, hiddenCardIds?: Set<string>): HTMLElement {\n const p = this.prefix;\n const colEl = createElement('div', `${p}-column`, { 'data-column-id': column.id });\n if (column.collapsed) colEl.classList.add(`${p}-column--collapsed`);\n\n // 列头部\n const header = this.renderColumnHeader(column);\n colEl.appendChild(header);\n\n // 列内容\n const body = createElement('div', `${p}-column-body`);\n body.dataset.columnId = column.id;\n\n if (!column.collapsed) {\n const visibleCards = column.cards\n .filter(card => !hiddenCardIds || !hiddenCardIds.has(card.id))\n .sort((a, b) => a.order - b.order);\n\n for (const card of visibleCards) {\n const cardEl = this.renderCard(card, column.id);\n body.appendChild(cardEl);\n }\n\n // 添加卡片按钮\n if (this.options.showAddCard !== false) {\n const addBtn = createElement('div', `${p}-add-card`);\n addBtn.innerHTML = `<button class=\"${p}-add-card-btn\">+ 添加卡片</button>`;\n addBtn.querySelector('button')!.addEventListener('click', () => {\n this.events.emit('ui:add-card', { columnId: column.id });\n });\n body.appendChild(addBtn);\n }\n\n // 启用列内拖拽放置\n if (this.options.draggable !== false) {\n this.dragController.enableColumnDrop(body, column.id);\n }\n }\n\n colEl.appendChild(body);\n\n // 列拖拽\n if (this.options.columnDraggable !== false && this.boardEl) {\n const dragHandle = header.querySelector(`.${p}-column-drag-handle`) as HTMLElement;\n if (dragHandle) {\n this.dragController.enableColumnDrag(colEl, dragHandle, column.id, this.boardEl!);\n }\n }\n\n this.columnElements.set(column.id, colEl);\n this.columnBodyElements.set(column.id, body);\n\n return colEl;\n }\n\n /**\n * 渲染列头部\n */\n private renderColumnHeader(column: Column): HTMLElement {\n const p = this.prefix;\n const header = createElement('div', `${p}-column-header`);\n\n if (this.options.columnHeaderRenderer) {\n this.options.columnHeaderRenderer(column, header);\n return header;\n }\n\n // 拖拽手柄\n if (this.options.columnDraggable !== false) {\n const handle = createElement('span', `${p}-column-drag-handle`);\n handle.innerHTML = '⋮⋮';\n handle.title = '拖拽排序';\n header.appendChild(handle);\n }\n\n // 颜色指示器\n if (column.color) {\n const indicator = createElement('span', `${p}-column-color`);\n indicator.style.backgroundColor = column.color;\n header.appendChild(indicator);\n }\n\n // 标题\n const titleEl = createElement('span', `${p}-column-title`);\n titleEl.textContent = column.title;\n header.appendChild(titleEl);\n\n // 卡片计数\n if (this.options.showCardCount !== false) {\n const count = createElement('span', `${p}-column-count`);\n const wipText = column.wipLimit ? `/${column.wipLimit}` : '';\n count.textContent = `${column.cards.length}${wipText}`;\n if (column.wipLimit && column.cards.length >= column.wipLimit) {\n count.classList.add(`${p}-column-count--limit`);\n }\n header.appendChild(count);\n }\n\n // 折叠按钮\n const collapseBtn = createElement('button', `${p}-column-collapse-btn`);\n collapseBtn.innerHTML = column.collapsed ? '▶' : '▼';\n collapseBtn.title = column.collapsed ? '展开' : '折叠';\n collapseBtn.addEventListener('click', () => {\n this.events.emit('ui:toggle-collapse', { columnId: column.id });\n });\n header.appendChild(collapseBtn);\n\n // 操作菜单\n if (this.options.showColumnActions !== false) {\n const actions = createElement('button', `${p}-column-actions-btn`);\n actions.innerHTML = '⋯';\n actions.title = '更多操作';\n actions.addEventListener('click', (e) => {\n e.stopPropagation();\n this.events.emit('ui:column-actions', { columnId: column.id, event: e });\n });\n header.appendChild(actions);\n }\n\n return header;\n }\n\n /**\n * 渲染单张卡片\n */\n private renderCard(card: Card, columnId: string): HTMLElement {\n const p = this.prefix;\n const cardEl = createElement('div', `${p}-card`, { 'data-card-id': card.id });\n\n if (this.options.cardRenderer) {\n this.options.cardRenderer(card, cardEl);\n } else {\n this.renderDefaultCard(card, cardEl);\n }\n\n // 启用卡片拖拽\n if (this.options.draggable !== false) {\n this.dragController.enableCardDrag(cardEl, card.id, columnId);\n }\n\n // 点击事件\n cardEl.addEventListener('click', () => {\n this.events.emit('card:click', { card });\n });\n\n this.cardElements.set(card.id, cardEl);\n return cardEl;\n }\n\n /**\n * 默认卡片渲染\n */\n private renderDefaultCard(card: Card, cardEl: HTMLElement): void {\n const p = this.prefix;\n\n // 优先级指示器\n if (card.priority) {\n const priorityEl = createElement('div', `${p}-card-priority ${p}-card-priority--${card.priority}`);\n cardEl.appendChild(priorityEl);\n }\n\n // 标签\n if (card.labels?.length) {\n const labelsEl = createElement('div', `${p}-card-labels`);\n for (const label of card.labels) {\n const labelEl = createElement('span', `${p}-card-label`);\n labelEl.style.backgroundColor = label.color;\n labelEl.textContent = label.name;\n labelsEl.appendChild(labelEl);\n }\n cardEl.appendChild(labelsEl);\n }\n\n // 标题\n const titleEl = createElement('div', `${p}-card-title`);\n titleEl.textContent = card.title;\n cardEl.appendChild(titleEl);\n\n // 描述\n if (card.description) {\n const descEl = createElement('div', `${p}-card-desc`);\n descEl.textContent = card.description;\n cardEl.appendChild(descEl);\n }\n\n // 底部信息栏\n const hasFooter = card.assignees?.length || card.dueDate;\n if (hasFooter) {\n const footer = createElement('div', `${p}-card-footer`);\n\n if (card.assignees?.length) {\n const assigneesEl = createElement('div', `${p}-card-assignees`);\n for (const assignee of card.assignees.slice(0, 3)) {\n const avatar = createElement('span', `${p}-card-avatar`);\n avatar.textContent = assignee.charAt(0).toUpperCase();\n avatar.title = assignee;\n assigneesEl.appendChild(avatar);\n }\n if (card.assignees.length > 3) {\n const more = createElement('span', `${p}-card-avatar-more`);\n more.textContent = `+${card.assignees.length - 3}`;\n assigneesEl.appendChild(more);\n }\n footer.appendChild(assigneesEl);\n }\n\n if (card.dueDate) {\n const dueEl = createElement('span', `${p}-card-due`);\n const date = new Date(card.dueDate);\n dueEl.textContent = `${date.getMonth() + 1}/${date.getDate()}`;\n const now = new Date();\n if (date < now) dueEl.classList.add(`${p}-card-due--overdue`);\n footer.appendChild(dueEl);\n }\n\n cardEl.appendChild(footer);\n }\n }\n\n /**\n * 获取列元素\n */\n getColumnElement(columnId: string): HTMLElement | undefined {\n return this.columnElements.get(columnId);\n }\n\n /**\n * 获取卡片元素\n */\n getCardElement(cardId: string): HTMLElement | undefined {\n return this.cardElements.get(cardId);\n }\n\n /**\n * 获取看板根元素\n */\n getBoardElement(): HTMLElement | null {\n return this.boardEl;\n }\n\n /**\n * 清理 DOM\n */\n private cleanup(): void {\n this.columnElements.clear();\n this.columnBodyElements.clear();\n this.cardElements.clear();\n if (this.boardEl) {\n this.boardEl.remove();\n this.boardEl = null;\n }\n }\n\n /**\n * 销毁\n */\n destroy(): void {\n this.cleanup();\n }\n}\n","/**\n * @file version.ts\n * @description version 模块\n */\n\nexport const VERSION = '0.1.0';\n","import type { KanbanOptions, Column, Card, FilterCriteria, BoardData, Label } from '../types';\nimport { resolveElement } from '../utils/dom';\nimport { EventEmitter, type EventHandler } from './EventEmitter';\nimport { ColumnManager } from './ColumnManager';\nimport { DragController } from './DragController';\nimport { FilterEngine } from './FilterEngine';\nimport { Renderer } from './Renderer';\nimport { VERSION } from '../version';\n\n/**\n * KanbanBoard - 看板视图库,支持拖拽卡片与列管理\n */\nexport class KanbanBoard {\n private container: HTMLElement;\n private options: KanbanOptions;\n private destroyed = false;\n\n readonly events: EventEmitter;\n private columnManager: ColumnManager;\n private dragController: DragController;\n private filterEngine: FilterEngine;\n private renderer: Renderer;\n private labels: Label[] = [];\n\n constructor(options: KanbanOptions) {\n this.options = {\n draggable: true,\n columnDraggable: true,\n crossColumnDrag: true,\n searchable: true,\n showAddCard: true,\n showAddColumn: true,\n showColumnActions: true,\n showCardCount: true,\n classPrefix: 'kanban',\n ...options,\n };\n\n const el = resolveElement(options.container);\n if (!el) {\n throw new Error('[KanbanBoard] Container element not found');\n }\n this.container = el;\n\n // 初始化子系统\n this.events = new EventEmitter();\n this.columnManager = new ColumnManager(this.events);\n this.dragController = new DragController(this.events, this.options.classPrefix || 'kanban');\n this.filterEngine = new FilterEngine(this.events);\n this.renderer = new Renderer(this.container, this.events, this.dragController, this.options);\n\n // 初始化标签\n this.labels = options.labels || [];\n\n this.init();\n }\n\n private init(): void {\n // 初始化列数据\n if (this.options.columns?.length) {\n this.columnManager.init(this.options.columns as Partial<Column>[]);\n }\n\n // 绑定内部事件\n this.bindInternalEvents();\n\n // 首次渲染\n this.render();\n\n // 触发 ready\n this.events.emit('ready', { instance: this });\n this.options.onReady?.(this);\n\n if (this.options.debug) {\n console.log('[KanbanBoard] Initialized', {\n columns: this.columnManager.getColumns().length,\n cards: this.columnManager.getCardCount(),\n });\n }\n }\n\n /**\n * 绑定内部事件\n */\n private bindInternalEvents(): void {\n // 卡片拖拽放置\n this.events.on('card:drop', (payload: unknown) => {\n this.columnManager.moveCard(payload.cardId, payload.toColumnId, payload.toIndex);\n this.options.onCardMove?.(this.columnManager.getCard(payload.cardId)!, payload.fromColumnId, payload.toColumnId, payload.toIndex);\n // 延迟渲染,等待浏览器完成拖拽操作(dragend 事件触发后再重建 DOM)\n this.scheduleRender();\n });\n\n // 列拖拽放置\n this.events.on('column:drop', (payload: unknown) => {\n const columns = this.columnManager.getColumns();\n const fromIndex = columns.findIndex(c => c.id === payload.fromColumnId);\n this.columnManager.moveColumn(payload.fromColumnId, payload.toIndex);\n const col = this.columnManager.getColumn(payload.fromColumnId);\n if (col) this.options.onColumnMove?.(col, fromIndex, payload.toIndex);\n this.scheduleRender();\n });\n\n // UI 事件 - 折叠列\n this.events.on('ui:toggle-collapse', (payload: unknown) => {\n this.columnManager.toggleCollapse(payload.columnId);\n const col = this.columnManager.getColumn(payload.columnId);\n if (col) this.options.onColumnCollapse?.(col, col.collapsed ?? false);\n this.render();\n });\n\n // 回调转发\n this.events.on('card:add', (p: unknown) => this.options.onCardAdd?.(p.card, p.columnId));\n this.events.on('card:remove', (p: unknown) => this.options.onCardRemove?.(p.card, p.columnId));\n this.events.on('card:click', (p: unknown) => this.options.onCardClick?.(p.card));\n this.events.on('card:update', (p: unknown) => this.options.onCardUpdate?.(p.card));\n this.events.on('column:add', (p: unknown) => this.options.onColumnAdd?.(p.column));\n this.events.on('column:remove', (p: unknown) => this.options.onColumnRemove?.(p.column));\n this.events.on('error', (p: unknown) => this.options.onError?.(p.error));\n }\n\n // ============================================================\n // Column API\n // ============================================================\n\n /**\n * 添加列\n */\n addColumn(partial: Partial<Column>): Column {\n const column = this.columnManager.addColumn(partial);\n this.render();\n return column;\n }\n\n /**\n * 移除列\n */\n removeColumn(id: string): Column | undefined {\n const column = this.columnManager.removeColumn(id);\n if (column) this.render();\n return column;\n }\n\n /**\n * 更新列\n */\n updateColumn(id: string, changes: Partial<Column>): Column | undefined {\n const column = this.columnManager.updateColumn(id, changes);\n if (column) this.render();\n return column;\n }\n\n /**\n * 移动列\n */\n moveColumn(id: string, toIndex: number): void {\n this.columnManager.moveColumn(id, toIndex);\n this.render();\n }\n\n /**\n * 折叠/展开列\n */\n toggleColumnCollapse(id: string): void {\n this.columnManager.toggleCollapse(id);\n this.render();\n }\n\n /**\n * 获取所有列\n */\n getColumns(): Column[] {\n return this.columnManager.getColumns();\n }\n\n /**\n * 获取单列\n */\n getColumn(id: string): Column | undefined {\n return this.columnManager.getColumn(id);\n }\n\n // ============================================================\n // Card API\n // ============================================================\n\n /**\n * 添加卡片\n */\n addCard(columnId: string, partial: Partial<Card>): Card | undefined {\n const card = this.columnManager.addCard(columnId, partial);\n if (card) this.render();\n return card;\n }\n\n /**\n * 移除卡片\n */\n removeCard(cardId: string): Card | undefined {\n const card = this.columnManager.removeCard(cardId);\n if (card) this.render();\n return card;\n }\n\n /**\n * 更新卡片\n */\n updateCard(cardId: string, changes: Partial<Card>): Card | undefined {\n const card = this.columnManager.updateCard(cardId, changes);\n if (card) this.render();\n return card;\n }\n\n /**\n * 移动卡片\n */\n moveCard(cardId: string, toColumnId: string, toIndex: number): void {\n this.columnManager.moveCard(cardId, toColumnId, toIndex);\n this.render();\n }\n\n /**\n * 获取单张卡片\n */\n getCard(cardId: string): Card | undefined {\n return this.columnManager.getCard(cardId);\n }\n\n /**\n * 获取所有卡片\n */\n getAllCards(): Card[] {\n return this.columnManager.getAllCards();\n }\n\n /**\n * 获取卡片总数\n */\n getCardCount(): number {\n return this.columnManager.getCardCount();\n }\n\n // ============================================================\n // Filter / Search API\n // ============================================================\n\n /**\n * 筛选卡片\n */\n filter(criteria: FilterCriteria): void {\n this.filterEngine.filter(criteria, this.columnManager.getColumns());\n this.render();\n }\n\n /**\n * 搜索卡片\n */\n search(keyword: string): Card[] {\n const result = this.filterEngine.search(keyword, this.columnManager.getColumns());\n this.render();\n return result;\n }\n\n /**\n * 清除筛选\n */\n clearFilter(): void {\n this.filterEngine.clearFilter();\n this.render();\n }\n\n // ============================================================\n // Import / Export API\n // ============================================================\n\n /**\n * 导出看板数据\n */\n exportData(): BoardData {\n const data: BoardData = {\n version: VERSION,\n title: this.options.title || 'Kanban Board',\n columns: this.columnManager.getColumns(),\n labels: this.labels,\n exportedAt: new Date().toISOString(),\n };\n this.events.emit('data:export', { board: data });\n return data;\n }\n\n /**\n * 导入看板数据\n */\n importData(data: BoardData): void {\n this.columnManager.setColumns(data.columns);\n this.labels = data.labels || [];\n this.filterEngine.clearFilter();\n this.events.emit('data:import', { board: data });\n this.render();\n }\n\n // ============================================================\n // Event API\n // ============================================================\n\n /**\n * 监听事件\n */\n on(event: string, handler: EventHandler): () => void {\n return this.events.on(event, handler);\n }\n\n /**\n * 监听一次性事件\n */\n once(event: string, handler: EventHandler): () => void {\n return this.events.once(event, handler);\n }\n\n /**\n * 移除事件监听\n */\n off(event: string, handler: EventHandler): void {\n this.events.off(event, handler);\n }\n\n // ============================================================\n // Label API\n // ============================================================\n\n /**\n * 获取所有标签\n */\n getLabels(): Label[] {\n return [...this.labels];\n }\n\n /**\n * 添加标签\n */\n addLabel(label: Label): void {\n this.labels.push(label);\n }\n\n /**\n * 移除标签\n */\n removeLabel(id: string): void {\n this.labels = this.labels.filter(l => l.id !== id);\n }\n\n // ============================================================\n // Render\n // ============================================================\n\n private renderTimer: ReturnType<typeof setTimeout> | null = null;\n\n /**\n * 延迟渲染(合并多次调用,等待浏览器拖拽操作完成)\n */\n private scheduleRender(): void {\n if (this.renderTimer !== null) return;\n this.renderTimer = setTimeout(() => {\n this.renderTimer = null;\n this.render();\n }, 0);\n }\n\n /**\n * 重新渲染\n */\n render(): void {\n if (this.destroyed) return;\n this.dragController.destroy();\n this.dragController = new DragController(this.events, this.options.classPrefix || 'kanban');\n this.renderer = new Renderer(this.container, this.events, this.dragController, this.options);\n this.renderer.render(\n this.columnManager.getColumns(),\n this.filterEngine.getHiddenCardIds(),\n );\n }\n\n /**\n * 获取看板根元素\n */\n getBoardElement(): HTMLElement | null {\n return this.renderer.getBoardElement();\n }\n\n /**\n * 获取配置\n */\n getOptions(): Readonly<KanbanOptions> {\n return { ...this.options };\n }\n\n // ============================================================\n // Lifecycle\n // ============================================================\n\n /**\n * 销毁实例\n */\n destroy(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n\n if (this.renderTimer !== null) {\n clearTimeout(this.renderTimer);\n this.renderTimer = null;\n }\n\n this.dragController.destroy();\n this.filterEngine.destroy();\n this.renderer.destroy();\n this.columnManager.clear();\n\n this.events.emit('destroy', undefined);\n this.options.onDestroy?.();\n this.events.removeAll();\n\n if (this.options.debug) {\n console.log('[KanbanBoard] Destroyed');\n }\n }\n\n /**\n * 是否已销毁\n */\n isDestroyed(): boolean {\n return this.destroyed;\n }\n}\n\n/**\n * 创建看板实例(工厂函数)\n */\nexport function createKanbanBoard(options: KanbanOptions): KanbanBoard {\n return new KanbanBoard(options);\n}\n","/**\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":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.kanban-board{width:100%;min-height:400px;overflow-x:auto;overflow-y:hidden;max-width:100%}.kanban-columns{display:flex;gap:16px;padding:8px;min-height:100%;align-items:flex-start}.kanban-column{flex:0 0 280px;max-width:280px;background:#f4f5f7;border-radius:8px;display:flex;flex-direction:column;max-height:calc(100vh - 200px);transition:opacity .2s,box-shadow .2s}.kanban-column--collapsed{flex:0 0 48px;max-width:48px;min-height:200px}.kanban-column--dragging{opacity:.5}.kanban-column--drag-over{box-shadow:0 0 0 2px #4c9aff}.kanban-column-header{display:flex;align-items:center;gap:6px;padding:10px 12px;border-bottom:1px solid #e1e4e8;-webkit-user-select:none;-moz-user-select:none;user-select:none}.kanban-column-drag-handle{cursor:grab;color:#a0a0a0;font-size:12px;letter-spacing:-3px;line-height:1;padding:2px 0}.kanban-column-drag-handle:active{cursor:grabbing}.kanban-column-color{width:12px;height:12px;border-radius:3px;flex-shrink:0}.kanban-column-title{font-size:14px;font-weight:600;color:#172b4d;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kanban-column-count{font-size:12px;color:#6b778c;background:#dfe1e6;border-radius:10px;padding:1px 8px;flex-shrink:0}.kanban-column-count--limit{background:#ffebe6;color:#bf2600}.kanban-column-actions-btn,.kanban-column-collapse-btn{background:0 0;border:none;cursor:pointer;color:#6b778c;font-size:12px;padding:2px 4px;border-radius:3px;line-height:1}.kanban-column-actions-btn:hover,.kanban-column-collapse-btn:hover{background:#dfe1e6;color:#172b4d}.kanban-column-body{flex:1;overflow-y:auto;padding:8px;min-height:40px}.kanban-column-body--drag-over{background:#e3f2fd;border-radius:0 0 8px 8px}.kanban-placeholder{background:#e3f2fd;border:2px dashed #90caf9;border-radius:6px;margin-bottom:8px;min-height:40px;transition:height .15s}.kanban-card{background:#fff;border-radius:6px;padding:10px 12px;margin-bottom:8px;cursor:pointer;box-shadow:0 1px 2px rgba(0,0,0,.08);transition:box-shadow .2s,transform .15s,opacity .2s;border:1px solid transparent;position:relative}.kanban-card:hover{box-shadow:0 2px 8px rgba(0,0,0,.12);border-color:#e1e4e8}.kanban-card--dragging{opacity:.4;transform:rotate(2deg)}.kanban-card-priority{position:absolute;left:0;top:0;bottom:0;width:3px;border-radius:6px 0 0 6px}.kanban-card-priority--low{background:#36b37e}.kanban-card-priority--medium{background:#ffab00}.kanban-card-priority--high{background:#ff5630}.kanban-card-priority--urgent{background:#bf2600}.kanban-card-labels{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:6px}.kanban-card-label{display:inline-block;padding:1px 8px;border-radius:3px;font-size:11px;color:#fff;font-weight:500;line-height:1.6}.kanban-card-title{font-size:14px;font-weight:500;color:#172b4d;line-height:1.4;word-break:break-word}.kanban-card-desc{font-size:12px;color:#6b778c;margin-top:4px;line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.kanban-card-footer{display:flex;align-items:center;justify-content:space-between;margin-top:8px;gap:8px}.kanban-card-assignees{display:flex;gap:-4px}.kanban-card-avatar{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;background:#dfe1e6;color:#42526e;font-size:11px;font-weight:600;border:2px solid #fff;margin-left:-4px}.kanban-card-avatar:first-child{margin-left:0}.kanban-card-avatar-more{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;background:#c1c7d0;color:#42526e;font-size:10px;font-weight:600;border:2px solid #fff;margin-left:-4px}.kanban-card-due{font-size:12px;color:#6b778c;display:flex;align-items:center;gap:4px}.kanban-card-due--overdue{color:#ff5630;font-weight:600}.kanban-add-card{padding:0 4px}.kanban-add-card-btn{width:100%;padding:6px 8px;background:0 0;border:none;border-radius:6px;color:#6b778c;font-size:13px;cursor:pointer;text-align:left;transition:background .15s,color .15s}.kanban-add-card-btn:hover{background:#dfe1e6;color:#172b4d}.kanban-add-column{flex:0 0 280px}.kanban-add-column-btn{width:100%;padding:10px;background:rgba(0,0,0,.04);border:2px dashed #d0d4d9;border-radius:8px;color:#6b778c;font-size:14px;cursor:pointer;transition:background .15s,border-color .15s,color .15s}.kanban-add-column-btn:hover{background:rgba(0,0,0,.08);border-color:#a5adba;color:#172b4d}.kanban-column-body::-webkit-scrollbar{width:6px}.kanban-column-body::-webkit-scrollbar-track{background:0 0}.kanban-column-body::-webkit-scrollbar-thumb{background:#c1c7d0;border-radius:3px}.kanban-column-body::-webkit-scrollbar-thumb:hover{background:#a5adba}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(global,factory){typeof exports==="object"&&typeof module!=="undefined"?factory(exports):typeof define==="function"&&define.amd?define(["exports"],factory):(global=typeof globalThis!=="undefined"?globalThis:global||self,factory(global.LDesignKanban={}));})(this,function(exports){Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function createElement(tag,className,attributes){const el=document.createElement(tag);if(className)el.className=className;if(attributes)Object.entries(attributes).forEach(([key,value])=>{el.setAttribute(key,value);});return el;}function resolveElement(target){if(typeof target==="string")return document.querySelector(target);return target;}function clearElement(el){el.innerHTML="";}function removeElement(el){el?.parentNode?.removeChild(el);}function insertAt(parent,child,index){const ref=parent.children[index];if(ref)parent.insertBefore(child,ref);else parent.appendChild(child);}var EventEmitter=class{constructor(){this.listeners=new Map();}on(event,handler){if(!this.listeners.has(event))this.listeners.set(event,new Set());this.listeners.get(event).add(handler);return()=>this.off(event,handler);}once(event,handler){const wrapper=(...args)=>{this.off(event,wrapper);handler(...args);};return this.on(event,wrapper);}off(event,handler){this.listeners.get(event)?.delete(handler);}emit(event,payload){const handlers=this.listeners.get(event);if(handlers)handlers.forEach((handler)=>{try{handler(payload);}catch(err){console.error(`[KanbanBoard]Error in event handler for "${event}":`,err);}});}removeAll(){this.listeners.clear();}listenerCount(event){return this.listeners.get(event)?.size??0;}};let counter=0;function generateId(prefix="kb"){counter++;return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).substring(2,6)}_${counter}`;}function resetIdCounter(){counter=0;}var ColumnManager=class{constructor(events){this.columns=[];this.events=events;}init(columns){this.columns=columns.map((col,index)=>this.normalizeColumn(col,index));}normalizeColumn(partial,index){return{id:partial.id||generateId("col"),title:partial.title||`Column ${index+1}`,cards:(partial.cards||[]).map((card,cardIndex)=>this.normalizeCard(card,partial.id||"",cardIndex)),wipLimit:partial.wipLimit,collapsed:partial.collapsed??false,color:partial.color,order:partial.order??index};}normalizeCard(partial,columnId,index){return{id:partial.id||generateId("card"),title:partial.title||"",description:partial.description,labels:partial.labels||[],assignees:partial.assignees||[],dueDate:partial.dueDate,priority:partial.priority,order:partial.order??index,columnId:partial.columnId||columnId,data:partial.data,createdAt:partial.createdAt||(new Date()).toISOString(),updatedAt:partial.updatedAt};}getColumns(){return[...this.columns].sort((a,b)=>a.order-b.order);}getColumn(id){return this.columns.find((c)=>c.id===id);}addColumn(partial){const column=this.normalizeColumn(partial,this.columns.length);column.cards=column.cards.map((card)=>({...card,columnId:column.id}));this.columns.push(column);this.events.emit("column:add",{column});return column;}removeColumn(id){const index=this.columns.findIndex((c)=>c.id===id);if(index===-1)return void 0;const[column]=this.columns.splice(index,1);this.reorderColumns();this.events.emit("column:remove",{column});return column;}updateColumn(id,changes){const column=this.columns.find((c)=>c.id===id);if(!column)return void 0;Object.assign(column,changes,{id});this.events.emit("column:update",{column,changes});return column;}moveColumn(id,toIndex){const fromIndex=this.columns.findIndex((c)=>c.id===id);if(fromIndex===-1)return;const[column]=this.columns.splice(fromIndex,1);this.columns.splice(toIndex,0,column);this.reorderColumns();this.events.emit("column:move",{column,fromIndex,toIndex});}toggleCollapse(id){const column=this.columns.find((c)=>c.id===id);if(!column)return;column.collapsed=!column.collapsed;this.events.emit("column:collapse",{column,collapsed:column.collapsed});}getCards(columnId){const column=this.columns.find((c)=>c.id===columnId);return column?[...column.cards].sort((a,b)=>a.order-b.order):[];}addCard(columnId,partial){const column=this.columns.find((c)=>c.id===columnId);if(!column)return void 0;if(column.wipLimit&&column.cards.length>=column.wipLimit){this.events.emit("error",{error:new Error(`Column "${column.title}" reached WIP limit(${column.wipLimit})`)});return;}const card=this.normalizeCard({...partial,columnId},columnId,column.cards.length);column.cards.push(card);this.events.emit("card:add",{card,columnId});return card;}removeCard(cardId){for(const column of this.columns){const index=column.cards.findIndex((c)=>c.id===cardId);if(index!==-1){const[card]=column.cards.splice(index,1);this.reorderCards(column);this.events.emit("card:remove",{card,columnId:column.id});return card;}}}updateCard(cardId,changes){for(const column of this.columns){const card=column.cards.find((c)=>c.id===cardId);if(card){Object.assign(card,changes,{id:cardId,columnId:column.id});card.updatedAt=(new Date()).toISOString();this.events.emit("card:update",{card,changes});return card;}}}getCard(cardId){for(const column of this.columns){const card=column.cards.find((c)=>c.id===cardId);if(card)return card;}}moveCard(cardId,toColumnId,toIndex){let fromColumn;let cardIndex=-1;for(const column of this.columns){const idx=column.cards.findIndex((c)=>c.id===cardId);if(idx!==-1){fromColumn=column;cardIndex=idx;break;}}if(!fromColumn||cardIndex===-1)return;const toColumn=this.columns.find((c)=>c.id===toColumnId);if(!toColumn)return;if(fromColumn.id!==toColumnId&&toColumn.wipLimit&&toColumn.cards.length>=toColumn.wipLimit){this.events.emit("error",{error:new Error(`Column "${toColumn.title}" reached WIP limit(${toColumn.wipLimit})`)});return;}const[card]=fromColumn.cards.splice(cardIndex,1);card.columnId=toColumnId;toColumn.cards.splice(toIndex,0,card);this.reorderCards(fromColumn);this.reorderCards(toColumn);this.events.emit("card:move",{card,fromColumnId:fromColumn.id,toColumnId,toIndex});}getAllCards(){const cards=[];for(const column of this.columns)cards.push(...column.cards);return cards;}getCardCount(){return this.columns.reduce((sum,col)=>sum+col.cards.length,0);}reorderColumns(){this.columns.forEach((col,index)=>{col.order=index;});}reorderCards(column){column.cards.forEach((card,index)=>{card.order=index;});}setColumns(columns){this.columns=columns;}clear(){this.columns=[];}};var DragController=class{constructor(events,prefix){this.state={dragging:false,cardId:null,sourceColumnId:null,targetColumnId:null,targetIndex:0,placeholder:null};this.cleanupFns=[];this.events=events;this.prefix=prefix;}enableCardDrag(cardEl,cardId,columnId){cardEl.setAttribute("draggable","true");cardEl.dataset.cardId=cardId;cardEl.dataset.columnId=columnId;const onDragStart=(e)=>{if(!e.dataTransfer)return;e.dataTransfer.effectAllowed="move";e.dataTransfer.setData("text/plain",cardId);this.state.dragging=true;this.state.cardId=cardId;this.state.sourceColumnId=columnId;cardEl.classList.add(`${this.prefix}-card--dragging`);this.events.emit("card:drag-start",{cardId,columnId});this.createPlaceholder(cardEl);};const onDragEnd=()=>{cardEl.classList.remove(`${this.prefix}-card--dragging`);this.removePlaceholder();this.state.dragging=false;this.state.cardId=null;this.state.sourceColumnId=null;this.events.emit("card:drag-end",{cardId});};cardEl.addEventListener("dragstart",onDragStart);cardEl.addEventListener("dragend",onDragEnd);this.cleanupFns.push(()=>{cardEl.removeEventListener("dragstart",onDragStart);cardEl.removeEventListener("dragend",onDragEnd);});}enableColumnDrop(columnBodyEl,columnId){const onDragOver=(e)=>{e.preventDefault();if(!e.dataTransfer)return;e.dataTransfer.dropEffect="move";this.state.targetColumnId=columnId;const afterElement=this.getDragAfterElement(columnBodyEl,e.clientY);const placeholder=this.state.placeholder;if(placeholder)if(afterElement)columnBodyEl.insertBefore(placeholder,afterElement);else{const addBtn=columnBodyEl.querySelector(`.${this.prefix}-add-card`);if(addBtn)columnBodyEl.insertBefore(placeholder,addBtn);else columnBodyEl.appendChild(placeholder);}};const onDragEnter=(e)=>{e.preventDefault();columnBodyEl.classList.add(`${this.prefix}-column-body--drag-over`);};const onDragLeave=(e)=>{const rect=columnBodyEl.getBoundingClientRect();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`);};const onDrop=(e)=>{e.preventDefault();columnBodyEl.classList.remove(`${this.prefix}-column-body--drag-over`);const cardId=this.state.cardId;if(!cardId)return;const placeholder=this.state.placeholder;let dropIndex=0;if(placeholder&&placeholder.parentNode===columnBodyEl){const children=[...columnBodyEl.children];for(const child of children){if(child===placeholder)break;if(child.hasAttribute("data-card-id")&&!child.classList.contains(`${this.prefix}-card--dragging`))dropIndex++;}}else dropIndex=columnBodyEl.querySelectorAll(`[data-card-id]:not(.${this.prefix}-card--dragging)`).length;this.events.emit("card:drop",{cardId,fromColumnId:this.state.sourceColumnId,toColumnId:columnId,toIndex:dropIndex});this.removePlaceholder();};columnBodyEl.addEventListener("dragover",onDragOver);columnBodyEl.addEventListener("dragenter",onDragEnter);columnBodyEl.addEventListener("dragleave",onDragLeave);columnBodyEl.addEventListener("drop",onDrop);this.cleanupFns.push(()=>{columnBodyEl.removeEventListener("dragover",onDragOver);columnBodyEl.removeEventListener("dragenter",onDragEnter);columnBodyEl.removeEventListener("dragleave",onDragLeave);columnBodyEl.removeEventListener("drop",onDrop);});}enableColumnDrag(columnEl,handleEl,columnId,boardEl){handleEl.setAttribute("draggable","true");const onDragStart=(e)=>{if(!e.dataTransfer)return;e.dataTransfer.effectAllowed="move";e.dataTransfer.setData("application/kanban-column",columnId);columnEl.classList.add(`${this.prefix}-column--dragging`);};const onDragEnd=()=>{columnEl.classList.remove(`${this.prefix}-column--dragging`);boardEl.querySelectorAll(`.${this.prefix}-column--drag-over`).forEach((el)=>{el.classList.remove(`${this.prefix}-column--drag-over`);});};const onDragOver=(e)=>{if(!e.dataTransfer?.types.includes("application/kanban-column"))return;e.preventDefault();e.dataTransfer.dropEffect="move";columnEl.classList.add(`${this.prefix}-column--drag-over`);};const onDragLeave=()=>{columnEl.classList.remove(`${this.prefix}-column--drag-over`);};const onDrop=(e)=>{e.preventDefault();columnEl.classList.remove(`${this.prefix}-column--drag-over`);const fromColumnId=e.dataTransfer?.getData("application/kanban-column");if(!fromColumnId||fromColumnId===columnId)return;const toIndex=[...boardEl.querySelectorAll(`[data-column-id]`)].indexOf(columnEl);this.events.emit("column:drop",{fromColumnId,toColumnId:columnId,toIndex});};handleEl.addEventListener("dragstart",onDragStart);handleEl.addEventListener("dragend",onDragEnd);columnEl.addEventListener("dragover",onDragOver);columnEl.addEventListener("dragleave",onDragLeave);columnEl.addEventListener("drop",onDrop);this.cleanupFns.push(()=>{handleEl.removeEventListener("dragstart",onDragStart);handleEl.removeEventListener("dragend",onDragEnd);columnEl.removeEventListener("dragover",onDragOver);columnEl.removeEventListener("dragleave",onDragLeave);columnEl.removeEventListener("drop",onDrop);});}getDragAfterElement(container,y){const draggableElements=[...container.querySelectorAll(`[data-card-id]:not(.${this.prefix}-card--dragging):not(.${this.prefix}-placeholder)`)];let closestOffset=Number.NEGATIVE_INFINITY;let closestElement=null;draggableElements.forEach((child)=>{const box=child.getBoundingClientRect();const offset=y-box.top-box.height/2;if(offset<0&&offset>closestOffset){closestOffset=offset;closestElement=child;}});return closestElement;}createPlaceholder(cardEl){this.removePlaceholder();const placeholder=document.createElement("div");placeholder.className=`${this.prefix}-placeholder`;placeholder.style.height=`${cardEl.offsetHeight}px`;this.state.placeholder=placeholder;}removePlaceholder(){if(this.state.placeholder){this.state.placeholder.parentNode?.removeChild(this.state.placeholder);this.state.placeholder=null;}}getState(){return{...this.state};}destroy(){this.cleanupFns.forEach((fn)=>fn());this.cleanupFns=[];this.removePlaceholder();}};var FilterEngine=class{constructor(events){this.currentCriteria=null;this.hiddenCardIds=new Set();this.events=events;}filter(criteria,columns){this.currentCriteria=criteria;this.hiddenCardIds.clear();const allCards=columns.flatMap((col)=>col.cards);for(const card of allCards)if(!this.matchCriteria(card,criteria))this.hiddenCardIds.add(card.id);this.events.emit("filter:change",{criteria});return new Set(this.hiddenCardIds);}search(keyword,columns){this.events.emit("search:change",{keyword});if(!keyword.trim()){this.clearFilter();return columns.flatMap((col)=>col.cards);}const lowerKeyword=keyword.toLowerCase();const allCards=columns.flatMap((col)=>col.cards);const matched=[];this.hiddenCardIds.clear();for(const card of allCards){const titleMatch=card.title.toLowerCase().includes(lowerKeyword);const descMatch=card.description?.toLowerCase().includes(lowerKeyword)??false;const labelMatch=card.labels?.some((l)=>l.name.toLowerCase().includes(lowerKeyword))??false;const assigneeMatch=card.assignees?.some((a)=>a.toLowerCase().includes(lowerKeyword))??false;if(titleMatch||descMatch||labelMatch||assigneeMatch)matched.push(card);else this.hiddenCardIds.add(card.id);}return matched;}clearFilter(){this.currentCriteria=null;this.hiddenCardIds.clear();this.events.emit("filter:clear",void 0);}isCardVisible(cardId){return!this.hiddenCardIds.has(cardId);}getHiddenCardIds(){return new Set(this.hiddenCardIds);}getCurrentCriteria(){return this.currentCriteria;}matchCriteria(card,criteria){if(criteria.keyword){const kw=criteria.keyword.toLowerCase();const titleMatch=card.title.toLowerCase().includes(kw);const descMatch=card.description?.toLowerCase().includes(kw)??false;if(!titleMatch&&!descMatch)return false;}if(criteria.labelIds?.length){const cardLabelIds=card.labels?.map((l)=>l.id)??[];if(!criteria.labelIds.some((id)=>cardLabelIds.includes(id)))return false;}if(criteria.assignees?.length){if(!criteria.assignees.some((a)=>card.assignees?.includes(a)))return false;}if(criteria.priorities?.length){if(!card.priority||!criteria.priorities.includes(card.priority))return false;}if(criteria.columnIds?.length){if(!criteria.columnIds.includes(card.columnId))return false;}if(criteria.dueDateRange){if(!card.dueDate)return false;const dueDate=new Date(card.dueDate).getTime();if(criteria.dueDateRange.start){if(dueDate<new Date(criteria.dueDateRange.start).getTime())return false;}if(criteria.dueDateRange.end){if(dueDate>new Date(criteria.dueDateRange.end).getTime())return false;}}if(criteria.custom){if(!criteria.custom(card))return false;}return true;}destroy(){this.hiddenCardIds.clear();this.currentCriteria=null;}};var Renderer=class{constructor(container,events,dragController,options){this.boardEl=null;this.columnElements=new Map();this.columnBodyElements=new Map();this.cardElements=new Map();this.container=container;this.events=events;this.dragController=dragController;this.options=options;this.prefix=options.classPrefix||"kanban";}render(columns,hiddenCardIds){this.cleanup();this.container.querySelectorAll("[data-kanban-board]").forEach((el)=>el.remove());const p=this.prefix;this.boardEl=createElement("div",`${p}-board`);this.boardEl.setAttribute("data-kanban-board","");const columnsContainer=createElement("div",`${p}-columns`);for(const column of columns){const colEl=this.renderColumn(column,hiddenCardIds);columnsContainer.appendChild(colEl);}if(this.options.showAddColumn!==false){const addColBtn=createElement("div",`${p}-add-column`);addColBtn.innerHTML=`<button class="${p}-add-column-btn">+\u6DFB\u52A0\u5217</button>`;addColBtn.querySelector("button").addEventListener("click",()=>{this.events.emit("ui:add-column");});columnsContainer.appendChild(addColBtn);}this.boardEl.appendChild(columnsContainer);this.container.appendChild(this.boardEl);}renderColumn(column,hiddenCardIds){const p=this.prefix;const colEl=createElement("div",`${p}-column`,{"data-column-id":column.id});if(column.collapsed)colEl.classList.add(`${p}-column--collapsed`);const header=this.renderColumnHeader(column);colEl.appendChild(header);const body=createElement("div",`${p}-column-body`);body.dataset.columnId=column.id;if(!column.collapsed){const visibleCards=column.cards.filter((card)=>!hiddenCardIds||!hiddenCardIds.has(card.id)).sort((a,b)=>a.order-b.order);for(const card of visibleCards){const cardEl=this.renderCard(card,column.id);body.appendChild(cardEl);}if(this.options.showAddCard!==false){const addBtn=createElement("div",`${p}-add-card`);addBtn.innerHTML=`<button class="${p}-add-card-btn">+\u6DFB\u52A0\u5361\u7247</button>`;addBtn.querySelector("button").addEventListener("click",()=>{this.events.emit("ui:add-card",{columnId:column.id});});body.appendChild(addBtn);}if(this.options.draggable!==false)this.dragController.enableColumnDrop(body,column.id);}colEl.appendChild(body);if(this.options.columnDraggable!==false&&this.boardEl){const dragHandle=header.querySelector(`.${p}-column-drag-handle`);if(dragHandle)this.dragController.enableColumnDrag(colEl,dragHandle,column.id,this.boardEl);}this.columnElements.set(column.id,colEl);this.columnBodyElements.set(column.id,body);return colEl;}renderColumnHeader(column){const p=this.prefix;const header=createElement("div",`${p}-column-header`);if(this.options.columnHeaderRenderer){this.options.columnHeaderRenderer(column,header);return header;}if(this.options.columnDraggable!==false){const handle=createElement("span",`${p}-column-drag-handle`);handle.innerHTML="⋮⋮";handle.title="拖拽排序";header.appendChild(handle);}if(column.color){const indicator=createElement("span",`${p}-column-color`);indicator.style.backgroundColor=column.color;header.appendChild(indicator);}const titleEl=createElement("span",`${p}-column-title`);titleEl.textContent=column.title;header.appendChild(titleEl);if(this.options.showCardCount!==false){const count=createElement("span",`${p}-column-count`);const wipText=column.wipLimit?`/${column.wipLimit}`:"";count.textContent=`${column.cards.length}${wipText}`;if(column.wipLimit&&column.cards.length>=column.wipLimit)count.classList.add(`${p}-column-count--limit`);header.appendChild(count);}const collapseBtn=createElement("button",`${p}-column-collapse-btn`);collapseBtn.innerHTML=column.collapsed?"▶":"▼";collapseBtn.title=column.collapsed?"展开":"折叠";collapseBtn.addEventListener("click",()=>{this.events.emit("ui:toggle-collapse",{columnId:column.id});});header.appendChild(collapseBtn);if(this.options.showColumnActions!==false){const actions=createElement("button",`${p}-column-actions-btn`);actions.innerHTML="⋯";actions.title="更多操作";actions.addEventListener("click",(e)=>{e.stopPropagation();this.events.emit("ui:column-actions",{columnId:column.id,event:e});});header.appendChild(actions);}return header;}renderCard(card,columnId){const p=this.prefix;const cardEl=createElement("div",`${p}-card`,{"data-card-id":card.id});if(this.options.cardRenderer)this.options.cardRenderer(card,cardEl);else this.renderDefaultCard(card,cardEl);if(this.options.draggable!==false)this.dragController.enableCardDrag(cardEl,card.id,columnId);cardEl.addEventListener("click",()=>{this.events.emit("card:click",{card});});this.cardElements.set(card.id,cardEl);return cardEl;}renderDefaultCard(card,cardEl){const p=this.prefix;if(card.priority){const priorityEl=createElement("div",`${p}-card-priority ${p}-card-priority--${card.priority}`);cardEl.appendChild(priorityEl);}if(card.labels?.length){const labelsEl=createElement("div",`${p}-card-labels`);for(const label of card.labels){const labelEl=createElement("span",`${p}-card-label`);labelEl.style.backgroundColor=label.color;labelEl.textContent=label.name;labelsEl.appendChild(labelEl);}cardEl.appendChild(labelsEl);}const titleEl=createElement("div",`${p}-card-title`);titleEl.textContent=card.title;cardEl.appendChild(titleEl);if(card.description){const descEl=createElement("div",`${p}-card-desc`);descEl.textContent=card.description;cardEl.appendChild(descEl);}if(card.assignees?.length||card.dueDate){const footer=createElement("div",`${p}-card-footer`);if(card.assignees?.length){const assigneesEl=createElement("div",`${p}-card-assignees`);for(const assignee of card.assignees.slice(0,3)){const avatar=createElement("span",`${p}-card-avatar`);avatar.textContent=assignee.charAt(0).toUpperCase();avatar.title=assignee;assigneesEl.appendChild(avatar);}if(card.assignees.length>3){const more=createElement("span",`${p}-card-avatar-more`);more.textContent=`+${card.assignees.length-3}`;assigneesEl.appendChild(more);}footer.appendChild(assigneesEl);}if(card.dueDate){const dueEl=createElement("span",`${p}-card-due`);const date=new Date(card.dueDate);dueEl.textContent=`${date.getMonth()+1}/${date.getDate()}`;if(date<new Date())dueEl.classList.add(`${p}-card-due--overdue`);footer.appendChild(dueEl);}cardEl.appendChild(footer);}}getColumnElement(columnId){return this.columnElements.get(columnId);}getCardElement(cardId){return this.cardElements.get(cardId);}getBoardElement(){return this.boardEl;}cleanup(){this.columnElements.clear();this.columnBodyElements.clear();this.cardElements.clear();if(this.boardEl){this.boardEl.remove();this.boardEl=null;}}destroy(){this.cleanup();}};const VERSION="0.1.0";var KanbanBoard=class{constructor(options){this.destroyed=false;this.labels=[];this.renderTimer=null;this.options={draggable:true,columnDraggable:true,crossColumnDrag:true,searchable:true,showAddCard:true,showAddColumn:true,showColumnActions:true,showCardCount:true,classPrefix:"kanban",...options};const el=resolveElement(options.container);if(!el)throw new Error("[KanbanBoard]Container element not found");this.container=el;this.events=new EventEmitter();this.columnManager=new ColumnManager(this.events);this.dragController=new DragController(this.events,this.options.classPrefix||"kanban");this.filterEngine=new FilterEngine(this.events);this.renderer=new Renderer(this.container,this.events,this.dragController,this.options);this.labels=options.labels||[];this.init();}init(){if(this.options.columns?.length)this.columnManager.init(this.options.columns);this.bindInternalEvents();this.render();this.events.emit("ready",{instance:this});this.options.onReady?.(this);if(this.options.debug)console.log("[KanbanBoard]Initialized",{columns:this.columnManager.getColumns().length,cards:this.columnManager.getCardCount()});}bindInternalEvents(){this.events.on("card:drop",(payload)=>{this.columnManager.moveCard(payload.cardId,payload.toColumnId,payload.toIndex);this.options.onCardMove?.(this.columnManager.getCard(payload.cardId),payload.fromColumnId,payload.toColumnId,payload.toIndex);this.scheduleRender();});this.events.on("column:drop",(payload)=>{const fromIndex=this.columnManager.getColumns().findIndex((c)=>c.id===payload.fromColumnId);this.columnManager.moveColumn(payload.fromColumnId,payload.toIndex);const col=this.columnManager.getColumn(payload.fromColumnId);if(col)this.options.onColumnMove?.(col,fromIndex,payload.toIndex);this.scheduleRender();});this.events.on("ui:toggle-collapse",(payload)=>{this.columnManager.toggleCollapse(payload.columnId);const col=this.columnManager.getColumn(payload.columnId);if(col)this.options.onColumnCollapse?.(col,col.collapsed??false);this.render();});this.events.on("card:add",(p)=>this.options.onCardAdd?.(p.card,p.columnId));this.events.on("card:remove",(p)=>this.options.onCardRemove?.(p.card,p.columnId));this.events.on("card:click",(p)=>this.options.onCardClick?.(p.card));this.events.on("card:update",(p)=>this.options.onCardUpdate?.(p.card));this.events.on("column:add",(p)=>this.options.onColumnAdd?.(p.column));this.events.on("column:remove",(p)=>this.options.onColumnRemove?.(p.column));this.events.on("error",(p)=>this.options.onError?.(p.error));}addColumn(partial){const column=this.columnManager.addColumn(partial);this.render();return column;}removeColumn(id){const column=this.columnManager.removeColumn(id);if(column)this.render();return column;}updateColumn(id,changes){const column=this.columnManager.updateColumn(id,changes);if(column)this.render();return column;}moveColumn(id,toIndex){this.columnManager.moveColumn(id,toIndex);this.render();}toggleColumnCollapse(id){this.columnManager.toggleCollapse(id);this.render();}getColumns(){return this.columnManager.getColumns();}getColumn(id){return this.columnManager.getColumn(id);}addCard(columnId,partial){const card=this.columnManager.addCard(columnId,partial);if(card)this.render();return card;}removeCard(cardId){const card=this.columnManager.removeCard(cardId);if(card)this.render();return card;}updateCard(cardId,changes){const card=this.columnManager.updateCard(cardId,changes);if(card)this.render();return card;}moveCard(cardId,toColumnId,toIndex){this.columnManager.moveCard(cardId,toColumnId,toIndex);this.render();}getCard(cardId){return this.columnManager.getCard(cardId);}getAllCards(){return this.columnManager.getAllCards();}getCardCount(){return this.columnManager.getCardCount();}filter(criteria){this.filterEngine.filter(criteria,this.columnManager.getColumns());this.render();}search(keyword){const result=this.filterEngine.search(keyword,this.columnManager.getColumns());this.render();return result;}clearFilter(){this.filterEngine.clearFilter();this.render();}exportData(){const data={version:VERSION,title:this.options.title||"Kanban Board",columns:this.columnManager.getColumns(),labels:this.labels,exportedAt:(new Date()).toISOString()};this.events.emit("data:export",{board:data});return data;}importData(data){this.columnManager.setColumns(data.columns);this.labels=data.labels||[];this.filterEngine.clearFilter();this.events.emit("data:import",{board:data});this.render();}on(event,handler){return this.events.on(event,handler);}once(event,handler){return this.events.once(event,handler);}off(event,handler){this.events.off(event,handler);}getLabels(){return[...this.labels];}addLabel(label){this.labels.push(label);}removeLabel(id){this.labels=this.labels.filter((l)=>l.id!==id);}scheduleRender(){if(this.renderTimer!==null)return;this.renderTimer=setTimeout(()=>{this.renderTimer=null;this.render();},0);}render(){if(this.destroyed)return;this.dragController.destroy();this.dragController=new DragController(this.events,this.options.classPrefix||"kanban");this.renderer=new Renderer(this.container,this.events,this.dragController,this.options);this.renderer.render(this.columnManager.getColumns(),this.filterEngine.getHiddenCardIds());}getBoardElement(){return this.renderer.getBoardElement();}getOptions(){return{...this.options};}destroy(){if(this.destroyed)return;this.destroyed=true;if(this.renderTimer!==null){clearTimeout(this.renderTimer);this.renderTimer=null;}this.dragController.destroy();this.filterEngine.destroy();this.renderer.destroy();this.columnManager.clear();this.events.emit("destroy",void 0);this.options.onDestroy?.();this.events.removeAll();if(this.options.debug)console.log("[KanbanBoard]Destroyed");}isDestroyed(){return this.destroyed;}};function createKanbanBoard(options){return new KanbanBoard(options);}function getElement(target){if(typeof target==="string")return document.querySelector(target);return target;}exports.ColumnManager=ColumnManager;exports.DragController=DragController;exports.EventEmitter=EventEmitter;exports.FilterEngine=FilterEngine;exports.KanbanBoard=KanbanBoard;exports.Renderer=Renderer;exports.VERSION=VERSION;exports.clearElement=clearElement;exports.createElement=createElement;exports.createKanbanBoard=createKanbanBoard;exports.generateId=generateId;exports.getElement=getElement;exports.insertAt=insertAt;exports.removeElement=removeElement;exports.resetIdCounter=resetIdCounter;exports.resolveElement=resolveElement;});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { Column, Card } from '../types';
|
|
2
|
+
import { EventEmitter } from './EventEmitter';
|
|
3
|
+
/**
|
|
4
|
+
* 列管理器
|
|
5
|
+
*/
|
|
6
|
+
export declare class ColumnManager {
|
|
7
|
+
private columns;
|
|
8
|
+
private events;
|
|
9
|
+
constructor(events: EventEmitter);
|
|
10
|
+
/**
|
|
11
|
+
* 初始化列
|
|
12
|
+
*/
|
|
13
|
+
init(columns: Partial<Column>[]): void;
|
|
14
|
+
/**
|
|
15
|
+
* 标准化列数据
|
|
16
|
+
*/
|
|
17
|
+
private normalizeColumn;
|
|
18
|
+
/**
|
|
19
|
+
* 标准化卡片数据
|
|
20
|
+
*/
|
|
21
|
+
private normalizeCard;
|
|
22
|
+
/**
|
|
23
|
+
* 获取所有列
|
|
24
|
+
*/
|
|
25
|
+
getColumns(): Column[];
|
|
26
|
+
/**
|
|
27
|
+
* 获取单列
|
|
28
|
+
*/
|
|
29
|
+
getColumn(id: string): Column | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* 添加列
|
|
32
|
+
*/
|
|
33
|
+
addColumn(partial: Partial<Column>): Column;
|
|
34
|
+
/**
|
|
35
|
+
* 移除列
|
|
36
|
+
*/
|
|
37
|
+
removeColumn(id: string): Column | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* 更新列
|
|
40
|
+
*/
|
|
41
|
+
updateColumn(id: string, changes: Partial<Column>): Column | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* 移动列
|
|
44
|
+
*/
|
|
45
|
+
moveColumn(id: string, toIndex: number): void;
|
|
46
|
+
/**
|
|
47
|
+
* 折叠/展开列
|
|
48
|
+
*/
|
|
49
|
+
toggleCollapse(id: string): void;
|
|
50
|
+
/**
|
|
51
|
+
* 获取列中的卡片
|
|
52
|
+
*/
|
|
53
|
+
getCards(columnId: string): Card[];
|
|
54
|
+
/**
|
|
55
|
+
* 添加卡片到列
|
|
56
|
+
*/
|
|
57
|
+
addCard(columnId: string, partial: Partial<Card>): Card | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* 移除卡片
|
|
60
|
+
*/
|
|
61
|
+
removeCard(cardId: string): Card | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* 更新卡片
|
|
64
|
+
*/
|
|
65
|
+
updateCard(cardId: string, changes: Partial<Card>): Card | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* 获取单张卡片
|
|
68
|
+
*/
|
|
69
|
+
getCard(cardId: string): Card | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* 移动卡片
|
|
72
|
+
*/
|
|
73
|
+
moveCard(cardId: string, toColumnId: string, toIndex: number): void;
|
|
74
|
+
/**
|
|
75
|
+
* 获取所有卡片
|
|
76
|
+
*/
|
|
77
|
+
getAllCards(): Card[];
|
|
78
|
+
/**
|
|
79
|
+
* 获取卡片总数
|
|
80
|
+
*/
|
|
81
|
+
getCardCount(): number;
|
|
82
|
+
/**
|
|
83
|
+
* 重新排序列
|
|
84
|
+
*/
|
|
85
|
+
private reorderColumns;
|
|
86
|
+
/**
|
|
87
|
+
* 重新排序列中的卡片
|
|
88
|
+
*/
|
|
89
|
+
private reorderCards;
|
|
90
|
+
/**
|
|
91
|
+
* 设置列数据(用于导入)
|
|
92
|
+
*/
|
|
93
|
+
setColumns(columns: Column[]): void;
|
|
94
|
+
/**
|
|
95
|
+
* 清空所有数据
|
|
96
|
+
*/
|
|
97
|
+
clear(): void;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=ColumnManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ColumnManager.d.ts","sourceRoot":"","sources":["../../src/core/ColumnManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAE7C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,MAAM,CAAe;gBAEjB,MAAM,EAAE,YAAY;IAIhC;;OAEG;IACH,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,GAAG,IAAI;IAItC;;OAEG;IACH,OAAO,CAAC,eAAe;IAcvB;;OAEG;IACH,OAAO,CAAC,aAAa;IAkBrB;;OAEG;IACH,UAAU,IAAI,MAAM,EAAE;IAItB;;OAEG;IACH,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIzC;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM;IAS3C;;OAEG;IACH,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAS5C;;OAEG;IACH,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,SAAS;IAQtE;;OAEG;IACH,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAS7C;;OAEG;IACH,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAOhC;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE;IAKlC;;OAEG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,SAAS;IAsBnE;;OAEG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;IAa5C;;OAEG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,SAAS;IAapE;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;IAQzC;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IA6CnE;;OAEG;IACH,WAAW,IAAI,IAAI,EAAE;IAQrB;;OAEG;IACH,YAAY,IAAI,MAAM;IAItB;;OAEG;IACH,OAAO,CAAC,cAAc;IAMtB;;OAEG;IACH,OAAO,CAAC,YAAY;IAMpB;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI;IAInC;;OAEG;IACH,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -0,0 +1,285 @@
|
|
|
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
|
+
import { generateId } from "../utils/id.js";
|
|
11
|
+
//#region src/core/ColumnManager.ts
|
|
12
|
+
var ColumnManager = class {
|
|
13
|
+
constructor(events) {
|
|
14
|
+
this.columns = [];
|
|
15
|
+
this.events = events;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 初始化列
|
|
19
|
+
*/
|
|
20
|
+
init(columns) {
|
|
21
|
+
this.columns = columns.map((col, index) => this.normalizeColumn(col, index));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 标准化列数据
|
|
25
|
+
*/
|
|
26
|
+
normalizeColumn(partial, index) {
|
|
27
|
+
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)),
|
|
31
|
+
wipLimit: partial.wipLimit,
|
|
32
|
+
collapsed: partial.collapsed ?? false,
|
|
33
|
+
color: partial.color,
|
|
34
|
+
order: partial.order ?? index
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 标准化卡片数据
|
|
39
|
+
*/
|
|
40
|
+
normalizeCard(partial, columnId, index) {
|
|
41
|
+
return {
|
|
42
|
+
id: partial.id || generateId("card"),
|
|
43
|
+
title: partial.title || "",
|
|
44
|
+
description: partial.description,
|
|
45
|
+
labels: partial.labels || [],
|
|
46
|
+
assignees: partial.assignees || [],
|
|
47
|
+
dueDate: partial.dueDate,
|
|
48
|
+
priority: partial.priority,
|
|
49
|
+
order: partial.order ?? index,
|
|
50
|
+
columnId: partial.columnId || columnId,
|
|
51
|
+
data: partial.data,
|
|
52
|
+
createdAt: partial.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
53
|
+
updatedAt: partial.updatedAt
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 获取所有列
|
|
58
|
+
*/
|
|
59
|
+
getColumns() {
|
|
60
|
+
return [...this.columns].sort((a, b) => a.order - b.order);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 获取单列
|
|
64
|
+
*/
|
|
65
|
+
getColumn(id) {
|
|
66
|
+
return this.columns.find((c) => c.id === id);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 添加列
|
|
70
|
+
*/
|
|
71
|
+
addColumn(partial) {
|
|
72
|
+
const column = this.normalizeColumn(partial, this.columns.length);
|
|
73
|
+
column.cards = column.cards.map((card) => ({
|
|
74
|
+
...card,
|
|
75
|
+
columnId: column.id
|
|
76
|
+
}));
|
|
77
|
+
this.columns.push(column);
|
|
78
|
+
this.events.emit("column:add", { column });
|
|
79
|
+
return column;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 移除列
|
|
83
|
+
*/
|
|
84
|
+
removeColumn(id) {
|
|
85
|
+
const index = this.columns.findIndex((c) => c.id === id);
|
|
86
|
+
if (index === -1) return void 0;
|
|
87
|
+
const [column] = this.columns.splice(index, 1);
|
|
88
|
+
this.reorderColumns();
|
|
89
|
+
this.events.emit("column:remove", { column });
|
|
90
|
+
return column;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 更新列
|
|
94
|
+
*/
|
|
95
|
+
updateColumn(id, changes) {
|
|
96
|
+
const column = this.columns.find((c) => c.id === id);
|
|
97
|
+
if (!column) return void 0;
|
|
98
|
+
Object.assign(column, changes, { id });
|
|
99
|
+
this.events.emit("column:update", {
|
|
100
|
+
column,
|
|
101
|
+
changes
|
|
102
|
+
});
|
|
103
|
+
return column;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 移动列
|
|
107
|
+
*/
|
|
108
|
+
moveColumn(id, toIndex) {
|
|
109
|
+
const fromIndex = this.columns.findIndex((c) => c.id === id);
|
|
110
|
+
if (fromIndex === -1) return;
|
|
111
|
+
const [column] = this.columns.splice(fromIndex, 1);
|
|
112
|
+
this.columns.splice(toIndex, 0, column);
|
|
113
|
+
this.reorderColumns();
|
|
114
|
+
this.events.emit("column:move", {
|
|
115
|
+
column,
|
|
116
|
+
fromIndex,
|
|
117
|
+
toIndex
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* 折叠/展开列
|
|
122
|
+
*/
|
|
123
|
+
toggleCollapse(id) {
|
|
124
|
+
const column = this.columns.find((c) => c.id === id);
|
|
125
|
+
if (!column) return;
|
|
126
|
+
column.collapsed = !column.collapsed;
|
|
127
|
+
this.events.emit("column:collapse", {
|
|
128
|
+
column,
|
|
129
|
+
collapsed: column.collapsed
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* 获取列中的卡片
|
|
134
|
+
*/
|
|
135
|
+
getCards(columnId) {
|
|
136
|
+
const column = this.columns.find((c) => c.id === columnId);
|
|
137
|
+
return column ? [...column.cards].sort((a, b) => a.order - b.order) : [];
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* 添加卡片到列
|
|
141
|
+
*/
|
|
142
|
+
addCard(columnId, partial) {
|
|
143
|
+
const column = this.columns.find((c) => c.id === columnId);
|
|
144
|
+
if (!column) return void 0;
|
|
145
|
+
if (column.wipLimit && column.cards.length >= column.wipLimit) {
|
|
146
|
+
this.events.emit("error", { error: /* @__PURE__ */ new Error(`Column "${column.title}" reached WIP limit (${column.wipLimit})`) });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const card = this.normalizeCard({
|
|
150
|
+
...partial,
|
|
151
|
+
columnId
|
|
152
|
+
}, columnId, column.cards.length);
|
|
153
|
+
column.cards.push(card);
|
|
154
|
+
this.events.emit("card:add", {
|
|
155
|
+
card,
|
|
156
|
+
columnId
|
|
157
|
+
});
|
|
158
|
+
return card;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* 移除卡片
|
|
162
|
+
*/
|
|
163
|
+
removeCard(cardId) {
|
|
164
|
+
for (const column of this.columns) {
|
|
165
|
+
const index = column.cards.findIndex((c) => c.id === cardId);
|
|
166
|
+
if (index !== -1) {
|
|
167
|
+
const [card] = column.cards.splice(index, 1);
|
|
168
|
+
this.reorderCards(column);
|
|
169
|
+
this.events.emit("card:remove", {
|
|
170
|
+
card,
|
|
171
|
+
columnId: column.id
|
|
172
|
+
});
|
|
173
|
+
return card;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* 更新卡片
|
|
179
|
+
*/
|
|
180
|
+
updateCard(cardId, changes) {
|
|
181
|
+
for (const column of this.columns) {
|
|
182
|
+
const card = column.cards.find((c) => c.id === cardId);
|
|
183
|
+
if (card) {
|
|
184
|
+
Object.assign(card, changes, {
|
|
185
|
+
id: cardId,
|
|
186
|
+
columnId: column.id
|
|
187
|
+
});
|
|
188
|
+
card.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
189
|
+
this.events.emit("card:update", {
|
|
190
|
+
card,
|
|
191
|
+
changes
|
|
192
|
+
});
|
|
193
|
+
return card;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* 获取单张卡片
|
|
199
|
+
*/
|
|
200
|
+
getCard(cardId) {
|
|
201
|
+
for (const column of this.columns) {
|
|
202
|
+
const card = column.cards.find((c) => c.id === cardId);
|
|
203
|
+
if (card) return card;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* 移动卡片
|
|
208
|
+
*/
|
|
209
|
+
moveCard(cardId, toColumnId, toIndex) {
|
|
210
|
+
let fromColumn;
|
|
211
|
+
let cardIndex = -1;
|
|
212
|
+
for (const column of this.columns) {
|
|
213
|
+
const idx = column.cards.findIndex((c) => c.id === cardId);
|
|
214
|
+
if (idx !== -1) {
|
|
215
|
+
fromColumn = column;
|
|
216
|
+
cardIndex = idx;
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (!fromColumn || cardIndex === -1) return;
|
|
221
|
+
const toColumn = this.columns.find((c) => c.id === toColumnId);
|
|
222
|
+
if (!toColumn) return;
|
|
223
|
+
if (fromColumn.id !== toColumnId && toColumn.wipLimit && toColumn.cards.length >= toColumn.wipLimit) {
|
|
224
|
+
this.events.emit("error", { error: /* @__PURE__ */ new Error(`Column "${toColumn.title}" reached WIP limit (${toColumn.wipLimit})`) });
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const [card] = fromColumn.cards.splice(cardIndex, 1);
|
|
228
|
+
card.columnId = toColumnId;
|
|
229
|
+
toColumn.cards.splice(toIndex, 0, card);
|
|
230
|
+
this.reorderCards(fromColumn);
|
|
231
|
+
this.reorderCards(toColumn);
|
|
232
|
+
this.events.emit("card:move", {
|
|
233
|
+
card,
|
|
234
|
+
fromColumnId: fromColumn.id,
|
|
235
|
+
toColumnId,
|
|
236
|
+
toIndex
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* 获取所有卡片
|
|
241
|
+
*/
|
|
242
|
+
getAllCards() {
|
|
243
|
+
const cards = [];
|
|
244
|
+
for (const column of this.columns) cards.push(...column.cards);
|
|
245
|
+
return cards;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* 获取卡片总数
|
|
249
|
+
*/
|
|
250
|
+
getCardCount() {
|
|
251
|
+
return this.columns.reduce((sum, col) => sum + col.cards.length, 0);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* 重新排序列
|
|
255
|
+
*/
|
|
256
|
+
reorderColumns() {
|
|
257
|
+
this.columns.forEach((col, index) => {
|
|
258
|
+
col.order = index;
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* 重新排序列中的卡片
|
|
263
|
+
*/
|
|
264
|
+
reorderCards(column) {
|
|
265
|
+
column.cards.forEach((card, index) => {
|
|
266
|
+
card.order = index;
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* 设置列数据(用于导入)
|
|
271
|
+
*/
|
|
272
|
+
setColumns(columns) {
|
|
273
|
+
this.columns = columns;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* 清空所有数据
|
|
277
|
+
*/
|
|
278
|
+
clear() {
|
|
279
|
+
this.columns = [];
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
//#endregion
|
|
283
|
+
export { ColumnManager };
|
|
284
|
+
/*! End of @composy/kanban-core | Powered by @composy/builder */
|
|
285
|
+
//# sourceMappingURL=ColumnManager.js.map
|