/** * @web/kanban * 原生 ESM 看板引擎 */ export class Kanban extends HTMLElement { #data = []; constructor() { super(); this.attachShadow({ mode: 'open' }); } connectedCallback() { this.#render(); } get data() { return this.#data; } set data(value) { this.#data = value; this.#render(); } #render() { const style = ` :host { display: flex; gap: 16px; padding: 16px; overflow-x: auto; font-family: system-ui, -apple-system, sans-serif; } .column { background: #f1f2f4; border-radius: 8px; width: 280px; min-width: 280px; display: flex; flex-direction: column; max-height: 100%; } .column-header { padding: 12px; font-weight: bold; display: flex; justify-content: space-between; align-items: center; } .items-container { flex: 1; padding: 8px; min-height: 50px; } .item { background: white; border-radius: 4px; padding: 12px; margin-bottom: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); cursor: grab; } .item:active { cursor: grabbing; } .item.dragging { opacity: 0.5; } .items-container.drag-over { background: rgba(0,0,0,0.05); } `; this.shadowRoot.innerHTML = ` ${this.#data.map(col => `
${col.title}
${(col.items || []).map(item => `
${item.content}
`).join('')}
`).join('')} `; this.#initDragAndDrop(); } #initDragAndDrop() { const items = this.shadowRoot.querySelectorAll('.item'); const containers = this.shadowRoot.querySelectorAll('.items-container'); items.forEach(item => { item.addEventListener('dragstart', (e) => { item.classList.add('dragging'); e.dataTransfer.setData('text/plain', JSON.stringify({ itemId: item.dataset.id, fromColumnId: item.dataset.columnId })); }); item.addEventListener('dragend', () => { item.classList.remove('dragging'); }); }); containers.forEach(container => { container.addEventListener('dragover', (e) => { e.preventDefault(); container.classList.add('drag-over'); }); container.addEventListener('dragleave', () => { container.classList.remove('drag-over'); }); container.addEventListener('drop', (e) => { e.preventDefault(); container.classList.remove('drag-over'); const dragData = JSON.parse(e.dataTransfer.getData('text/plain')); this.#moveItem(dragData.itemId, dragData.fromColumnId, container.dataset.columnId); }); }); } #moveItem(itemId, fromColumnId, toColumnId) { if (fromColumnId === toColumnId) return; const fromCol = this.#data.find(c => c.id === fromColumnId); const toCol = this.#data.find(c => c.id === toColumnId); const itemIndex = fromCol.items.findIndex(i => i.id === itemId); const [item] = fromCol.items.splice(itemIndex, 1); toCol.items.push(item); this.#render(); this.dispatchEvent(new CustomEvent('@update', { detail: this.#data, bubbles: true, composed: true })); } } customElements.define('web-kanban', Kanban);