dataTable/src/selection.js

145 lines
5.2 KiB
JavaScript
Raw Normal View History

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
};
};