import { sound } from '../engine/audio.js'; export function createWordBuilder({ onTileRemoved, onTilesReordered, onSubmitWord, onClearWord, onBackspaceWord }) { const wrapper = document.createElement('div'); wrapper.className = 'word-builder-wrapper'; let draggedIndex = null; let touchStartX = 0; let touchMoved = false; function render(state) { const tileCount = state.activeTileIds.length; const hasTiles = tileCount > 0; const tileObjects = state.activeTileIds.map(id => state.boardTiles.find(t => t.id === id)).filter(Boolean); const pts = tileCount === 4 ? 8 : tileCount === 3 ? 4 : tileCount === 2 ? 2 : 1; wrapper.innerHTML = `
${ hasTiles ? tileObjects .map( (tile, idx) => `
${tile.text}
` ) .join('') : `Tap tiles below to build a word` }
`; // Event listeners for active chips const chips = wrapper.querySelectorAll('.active-tile-chip'); chips.forEach(chip => { const idx = parseInt(chip.getAttribute('data-idx'), 10); // Tap chip to remove (only if not dragged) chip.addEventListener('click', e => { if (!touchMoved) { sound.playTileRemove(); onTileRemoved(idx); } }); // Desktop HTML5 drag and drop chip.addEventListener('dragstart', e => { draggedIndex = idx; chip.classList.add('dragging'); e.dataTransfer.effectAllowed = 'move'; }); chip.addEventListener('dragend', () => { chip.classList.remove('dragging'); chips.forEach(c => c.classList.remove('drag-over')); }); chip.addEventListener('dragover', e => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; chip.classList.add('drag-over'); }); chip.addEventListener('dragleave', () => { chip.classList.remove('drag-over'); }); chip.addEventListener('drop', e => { e.preventDefault(); chip.classList.remove('drag-over'); if (draggedIndex !== null && draggedIndex !== idx) { onTilesReordered(draggedIndex, idx); } draggedIndex = null; }); // Touch events for mobile reordering chip.addEventListener( 'touchstart', e => { touchStartX = e.touches[0].clientX; touchMoved = false; draggedIndex = idx; }, { passive: true } ); chip.addEventListener( 'touchmove', e => { const currentX = e.touches[0].clientX; if (Math.abs(currentX - touchStartX) > 10) { touchMoved = true; chip.classList.add('dragging'); } }, { passive: true } ); chip.addEventListener('touchend', e => { chip.classList.remove('dragging'); if (touchMoved) { const endX = e.changedTouches[0].clientX; const endY = e.changedTouches[0].clientY; // Find which chip the touch ended on const targetEl = document.elementFromPoint(endX, endY); const targetChip = targetEl ? targetEl.closest('.active-tile-chip') : null; if (targetChip) { const targetIdx = parseInt(targetChip.getAttribute('data-idx'), 10); if (!isNaN(targetIdx) && targetIdx !== idx) { onTilesReordered(idx, targetIdx); } } setTimeout(() => { touchMoved = false; }, 50); } }); }); wrapper.querySelector('#btn-builder-clear')?.addEventListener('click', () => { sound.playTileRemove(); onClearWord(); }); wrapper.querySelector('#btn-builder-backspace')?.addEventListener('click', () => { sound.playTileRemove(); onBackspaceWord(); }); wrapper.querySelector('#btn-builder-submit')?.addEventListener('click', onSubmitWord); } function triggerShake() { const builder = wrapper.querySelector('#active-word-builder'); if (builder) { builder.classList.remove('invalid-shake'); void builder.offsetWidth; // Trigger reflow builder.classList.add('invalid-shake'); } } return { element: wrapper, render, triggerShake }; }