export const createSelectionManager = (container, state) => { let activeBounds = null; // { minRow, maxRow, minCol, maxCol } let startCell = null; // { row, col } let multiSelections = []; const isCellSelected = (r, c) => { if (activeBounds && r >= activeBounds.minRow && r <= activeBounds.maxRow && c >= activeBounds.minCol && c <= activeBounds.maxCol) return true; return multiSelections.some(s => r >= s.minRow && r <= s.maxRow && c >= s.minCol && c <= s.maxCol); }; const applySelectionUI = () => { const rowNodes = container.querySelectorAll('.dt-body-row'); let boundMinRow = Infinity, boundMaxRow = -Infinity; if (activeBounds) { boundMinRow = Math.min(boundMinRow, activeBounds.minRow); boundMaxRow = Math.max(boundMaxRow, activeBounds.maxRow); } multiSelections.forEach(s => { boundMinRow = Math.min(boundMinRow, s.minRow); boundMaxRow = Math.max(boundMaxRow, s.maxRow); }); const hasSelection = boundMinRow !== Infinity; rowNodes.forEach((rowNode, r) => { const absoluteRow = r + state._listStartIndex; const cellNodes = rowNode.children; // Fast path: Row completely outside selection if (!hasSelection || absoluteRow < boundMinRow || absoluteRow > boundMaxRow) { for (let i = 0; i < cellNodes.length; i++) { if (cellNodes[i].classList.contains('dt-cell-selected')) { cellNodes[i].classList.remove('dt-cell-selected'); } } return; } for (let i = 0; i < cellNodes.length; i++) { const isSelected = isCellSelected(absoluteRow, i); const hasClass = cellNodes[i].classList.contains('dt-cell-selected'); if (isSelected && !hasClass) { cellNodes[i].classList.add('dt-cell-selected'); } else if (!isSelected && hasClass) { cellNodes[i].classList.remove('dt-cell-selected'); } } }); }; const updateStatus = () => { let count = 0; if (activeBounds) count += (activeBounds.maxRow - activeBounds.minRow + 1); multiSelections.forEach(s => count += (s.maxRow - s.minRow + 1)); state.selectedRowCount = count; }; const clearAllActive = (keepSelection = false) => { if (!keepSelection) { activeBounds = null; startCell = null; multiSelections = []; applySelectionUI(); updateStatus(); } }; const startSelect = (row, col, e) => { const alreadySelected = isCellSelected(row, col); if (e.shiftKey && startCell) { activeBounds = { minRow: Math.min(startCell.row, row), maxRow: Math.max(startCell.row, row), minCol: Math.min(startCell.col, col), maxCol: Math.max(startCell.col, col) }; } else { if (!alreadySelected) { if (!e.ctrlKey && !e.metaKey) { clearAllActive(); } else if (activeBounds) { multiSelections.push(activeBounds); } startCell = { row, col }; activeBounds = { minRow: row, maxRow: row, minCol: col, maxCol: col }; } state.isSelecting = true; } applySelectionUI(); updateStatus(); container.focus(); }; const updateSelect = (row, col) => { if (state.isSelecting && startCell) { activeBounds = { minRow: Math.min(startCell.row, row), maxRow: Math.max(startCell.row, row), minCol: Math.min(startCell.col, col), maxCol: Math.max(startCell.col, col) }; applySelectionUI(); updateStatus(); } }; const endSelect = () => { state.isSelecting = false; }; const getSelectionBounds = () => { if (!activeBounds) return null; let minRow = activeBounds.minRow, maxRow = activeBounds.maxRow; let minCol = activeBounds.minCol, maxCol = activeBounds.maxCol; multiSelections.forEach(s => { minRow = Math.min(minRow, s.minRow); maxRow = Math.max(maxRow, s.maxRow); minCol = Math.min(minCol, s.minCol); maxCol = Math.max(maxCol, s.maxCol); }); return { minRow, maxRow, minCol, maxCol }; }; const deleteSelected = () => { const bounds = getSelectionBounds(); if (!bounds) return; // This is a naive deletion that removes the bounding box rows entirely. // A complete implementation might filter row by row checking multi-selections. const rMin = bounds.minRow, rMax = bounds.maxRow; const newList = state.list.filter((_, i) => !(i >= rMin && i <= rMax)); state.list = newList; clearAllActive(); }; return { applySelectionUI, clearAllActive, startSelect, updateSelect, endSelect, deleteSelected, getSelectionBounds }; };