|
|
|
@@ -1,4 +1,4 @@
|
|
|
|
|
let _rubberBand, _grid;
|
|
|
|
|
let _rubberBand, _grid, _gridContainer;
|
|
|
|
|
let _state, _cb;
|
|
|
|
|
|
|
|
|
|
let rbActive = false;
|
|
|
|
@@ -8,15 +8,24 @@ let _rafPending = false;
|
|
|
|
|
let _lastMoveEvent = null;
|
|
|
|
|
|
|
|
|
|
let lastSelectedIdx = null;
|
|
|
|
|
let keyboardTargetIdx = null;
|
|
|
|
|
let keyboardExtendBaseSelection = null;
|
|
|
|
|
let _rbStartSelection = new Set(); // 러버밴드 시작 시점의 선택 스냅샷
|
|
|
|
|
|
|
|
|
|
export function resetAnchor() {
|
|
|
|
|
lastSelectedIdx = null;
|
|
|
|
|
keyboardTargetIdx = null;
|
|
|
|
|
keyboardExtendBaseSelection = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function isKeyboardTarget(idx) {
|
|
|
|
|
return keyboardTargetIdx === idx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function init({ gridContainer, rubberBand, grid, state, callbacks }) {
|
|
|
|
|
_rubberBand = rubberBand;
|
|
|
|
|
_grid = grid;
|
|
|
|
|
_gridContainer = gridContainer;
|
|
|
|
|
_state = state;
|
|
|
|
|
_cb = callbacks;
|
|
|
|
|
|
|
|
|
@@ -51,10 +60,39 @@ export function onCardClick(e) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
keyboardTargetIdx = idx;
|
|
|
|
|
keyboardExtendBaseSelection = null;
|
|
|
|
|
_cb.refreshCardSelection();
|
|
|
|
|
_cb.updateSelCount();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function handleKeyboardNavigation(code, { extend = false } = {}) {
|
|
|
|
|
if (!_state?.files.length) return false;
|
|
|
|
|
|
|
|
|
|
const currentIdx = _getCurrentKeyboardIndex(code);
|
|
|
|
|
const nextIdx = _getNextKeyboardIndex(currentIdx, code);
|
|
|
|
|
if (nextIdx === null) return false;
|
|
|
|
|
|
|
|
|
|
if (extend) {
|
|
|
|
|
if (lastSelectedIdx === null) lastSelectedIdx = currentIdx ?? nextIdx;
|
|
|
|
|
if (!keyboardExtendBaseSelection) {
|
|
|
|
|
keyboardExtendBaseSelection = new Set(_state.selectedIdxs);
|
|
|
|
|
}
|
|
|
|
|
_selectKeyboardRange(lastSelectedIdx, nextIdx, keyboardExtendBaseSelection);
|
|
|
|
|
} else {
|
|
|
|
|
_state.selectedIdxs.clear();
|
|
|
|
|
_state.selectedIdxs.add(nextIdx);
|
|
|
|
|
lastSelectedIdx = nextIdx;
|
|
|
|
|
keyboardExtendBaseSelection = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
keyboardTargetIdx = nextIdx;
|
|
|
|
|
_cb.refreshCardSelection();
|
|
|
|
|
_cb.updateSelCount();
|
|
|
|
|
_scrollKeyboardTargetIntoView(code, nextIdx);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function _toggle(idx) {
|
|
|
|
|
if (_state.selectedIdxs.has(idx)) {
|
|
|
|
|
_state.selectedIdxs.delete(idx);
|
|
|
|
@@ -63,6 +101,7 @@ function _toggle(idx) {
|
|
|
|
|
_state.selectedIdxs.add(idx);
|
|
|
|
|
lastSelectedIdx = idx;
|
|
|
|
|
}
|
|
|
|
|
keyboardExtendBaseSelection = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function _selectRange(idx) {
|
|
|
|
@@ -70,6 +109,7 @@ function _selectRange(idx) {
|
|
|
|
|
_state.selectedIdxs.clear();
|
|
|
|
|
_state.selectedIdxs.add(idx);
|
|
|
|
|
lastSelectedIdx = idx;
|
|
|
|
|
keyboardExtendBaseSelection = null;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const start = Math.min(lastSelectedIdx, idx);
|
|
|
|
@@ -77,6 +117,74 @@ function _selectRange(idx) {
|
|
|
|
|
_state.selectedIdxs.clear();
|
|
|
|
|
for (let i = start; i <= end; i++) _state.selectedIdxs.add(i);
|
|
|
|
|
// anchor(lastSelectedIdx)는 Shift 클릭으로 변경하지 않음
|
|
|
|
|
keyboardExtendBaseSelection = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function _selectKeyboardRange(anchorIdx, targetIdx, baseSelection) {
|
|
|
|
|
const start = Math.min(anchorIdx, targetIdx);
|
|
|
|
|
const end = Math.max(anchorIdx, targetIdx);
|
|
|
|
|
_state.selectedIdxs.clear();
|
|
|
|
|
baseSelection.forEach(idx => {
|
|
|
|
|
if (_isValidIndex(idx)) _state.selectedIdxs.add(idx);
|
|
|
|
|
});
|
|
|
|
|
for (let i = start; i <= end; i++) _state.selectedIdxs.add(i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function _getCurrentKeyboardIndex(code) {
|
|
|
|
|
if (_isValidIndex(keyboardTargetIdx)) return keyboardTargetIdx;
|
|
|
|
|
if (_state.selectedIdxs.size) return Math.min(..._state.selectedIdxs);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function _getNextKeyboardIndex(currentIdx, code) {
|
|
|
|
|
const lastIdx = _state.files.length - 1;
|
|
|
|
|
|
|
|
|
|
if (currentIdx === null) {
|
|
|
|
|
return (code === 'ArrowLeft' || code === 'ArrowUp' || code === 'End')
|
|
|
|
|
? lastIdx
|
|
|
|
|
: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (code === 'Home') return 0;
|
|
|
|
|
if (code === 'End') return lastIdx;
|
|
|
|
|
if (code === 'ArrowLeft') return Math.max(0, currentIdx - 1);
|
|
|
|
|
if (code === 'ArrowRight') return Math.min(lastIdx, currentIdx + 1);
|
|
|
|
|
const columnCount = _getColumnCount();
|
|
|
|
|
if (code === 'ArrowUp') return Math.max(0, currentIdx - columnCount);
|
|
|
|
|
if (code === 'ArrowDown') return Math.min(lastIdx, currentIdx + columnCount);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function _getColumnCount() {
|
|
|
|
|
const cards = Array.from(_grid.children).filter(card => card.classList.contains('card'));
|
|
|
|
|
if (!cards.length) return 1;
|
|
|
|
|
|
|
|
|
|
const firstTop = cards[0].getBoundingClientRect().top;
|
|
|
|
|
const firstRowCount = cards.findIndex(card =>
|
|
|
|
|
Math.abs(card.getBoundingClientRect().top - firstTop) > 2
|
|
|
|
|
);
|
|
|
|
|
return firstRowCount === -1 ? cards.length : Math.max(1, firstRowCount);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function _scrollCardIntoView(idx) {
|
|
|
|
|
const card = _grid.querySelector(`.card[data-idx="${idx}"]`);
|
|
|
|
|
card?.scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function _scrollKeyboardTargetIntoView(code, idx) {
|
|
|
|
|
if (code === 'Home') {
|
|
|
|
|
_gridContainer.scrollTop = 0;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (code === 'End') {
|
|
|
|
|
_gridContainer.scrollTop = _gridContainer.scrollHeight - _gridContainer.clientHeight;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_scrollCardIntoView(idx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function _isValidIndex(idx) {
|
|
|
|
|
return Number.isInteger(idx) && idx >= 0 && idx < _state.files.length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function _onStart(e) {
|
|
|
|
@@ -102,6 +210,8 @@ function _onStart(e) {
|
|
|
|
|
_state.selectedIdxs.clear();
|
|
|
|
|
_rbStartSelection.clear();
|
|
|
|
|
lastSelectedIdx = null;
|
|
|
|
|
keyboardTargetIdx = null;
|
|
|
|
|
keyboardExtendBaseSelection = null;
|
|
|
|
|
_cb.refreshCardSelection();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|