feat: selectie met toetsen toegevoegd
This commit is contained in:
@@ -0,0 +1,202 @@
|
|||||||
|
# UI Advanced Selection v1
|
||||||
|
|
||||||
|
## 1. Doel
|
||||||
|
Advanced Selection v1 maakt selectiegedrag in de dual-pane UI natuurlijker voor dagelijks file-managergebruik, zonder de bestaande interacties te breken. De uitbreiding richt zich op twee bekende patronen:
|
||||||
|
|
||||||
|
- range-selectie via `Shift + ArrowUp/ArrowDown`
|
||||||
|
- niet-aangrenzende selectie via `Cmd + klik` op macOS en `Ctrl + klik` op niet-Mac
|
||||||
|
|
||||||
|
Het doel is niet om een volledige desktop file manager exact te emuleren, maar om de meest bruikbare selectiepatronen toe te voegen met laag regressierisico.
|
||||||
|
|
||||||
|
## 2. Nieuwe Interacties In Scope
|
||||||
|
In scope voor v1:
|
||||||
|
|
||||||
|
- `Shift + ArrowDown`
|
||||||
|
- `Shift + ArrowUp`
|
||||||
|
- `Cmd + klik` op Mac
|
||||||
|
- `Ctrl + klik` op niet-Mac
|
||||||
|
|
||||||
|
Niet in scope in deze stap:
|
||||||
|
|
||||||
|
- `Shift + klik` range-selectie
|
||||||
|
- `Ctrl/Cmd + A`
|
||||||
|
- `Alt`-gebaseerde selectie
|
||||||
|
- OS-specifieke volledige parity met Finder/Explorer
|
||||||
|
- backendwijzigingen
|
||||||
|
|
||||||
|
## 3. Gewenst Gedrag
|
||||||
|
### Shift + ArrowDown / ArrowUp
|
||||||
|
`Shift + ArrowDown` en `Shift + ArrowUp` breiden de selectie uit of verkleinen die vanaf een vast selectie-anker binnen het actieve paneel.
|
||||||
|
|
||||||
|
Voorstel:
|
||||||
|
|
||||||
|
- elk paneel krijgt naast `currentRowIndex` en `selectedItems` ook `selectionAnchorIndex`
|
||||||
|
- als nog niets geselecteerd is:
|
||||||
|
- eerste `Shift + ArrowDown/ArrowUp` selecteert de current row en de eerstvolgende rij in de gekozen richting
|
||||||
|
- `selectionAnchorIndex` wordt gezet op de oorspronkelijke current row
|
||||||
|
- als al een range actief is:
|
||||||
|
- verdere `Shift + ArrowDown/ArrowUp` verplaatst de actieve rand van de selectie
|
||||||
|
- selectie groeit of krimpt tussen `selectionAnchorIndex` en de nieuwe `currentRowIndex`
|
||||||
|
- current row blijft het bewegende uiteinde van de range
|
||||||
|
|
||||||
|
### Ankerpunt
|
||||||
|
Het range-anker begint op het moment dat range-selectie start. Dat anker blijft staan totdat een andere actie de selectiemodus logisch reset, bijvoorbeeld:
|
||||||
|
|
||||||
|
- gewone rij-click zonder modifier
|
||||||
|
- klik op filenaam
|
||||||
|
- klik op directorynaam die navigeert
|
||||||
|
- `Escape`
|
||||||
|
- paneelnavigatie naar andere directory
|
||||||
|
|
||||||
|
### Current row versus selected items
|
||||||
|
- `currentRowIndex` blijft altijd exact 1 rij aanwijzen in het actieve paneel
|
||||||
|
- `selectedItems` kan 0, 1 of meerdere items bevatten
|
||||||
|
- bij range-selectie hoeft current row niet de enige geselecteerde rij te zijn; current row is alleen de focusrij binnen de geselecteerde set
|
||||||
|
|
||||||
|
### Als nog niets geselecteerd is
|
||||||
|
Voor veilige voorspelbaarheid:
|
||||||
|
|
||||||
|
- `Shift + ArrowDown/ArrowUp` gebruikt de huidige current row als startpunt
|
||||||
|
- current row moet dus al bestaan; in een leeg paneel doet de shortcut niets
|
||||||
|
|
||||||
|
### Cmd/Ctrl + klik
|
||||||
|
Modifier-click werkt als toggle op een individueel item zonder andere selectie te wissen.
|
||||||
|
|
||||||
|
Voorstel:
|
||||||
|
|
||||||
|
- `Cmd + klik` op Mac: toggle selectie van dat item
|
||||||
|
- `Ctrl + klik` op niet-Mac: toggle selectie van dat item
|
||||||
|
- current row verhuist naar het aangeklikte item
|
||||||
|
- `selectionAnchorIndex` wordt gezet op dat item, zodat een daaropvolgende range-selectie logisch verdergaat vanaf de laatst gemodificeerde rij
|
||||||
|
|
||||||
|
Dit geeft willekeurige toevoeging/verwijdering van items zonder checkbox verplicht te maken.
|
||||||
|
|
||||||
|
## 4. Interactie Met Bestaande Regels
|
||||||
|
### Klik op checkbox
|
||||||
|
Blijft een expliciete toggle van dat ene item.
|
||||||
|
|
||||||
|
Aanvulling:
|
||||||
|
|
||||||
|
- checkbox-toggle mag ook `selectionAnchorIndex` zetten op het betreffende item
|
||||||
|
- daarmee sluit checkboxgedrag aan op latere range-selectie
|
||||||
|
|
||||||
|
### Klik op rij
|
||||||
|
Blijft single-selectie op dat item zonder modifier.
|
||||||
|
|
||||||
|
Gevolg:
|
||||||
|
|
||||||
|
- eerdere multi-selectie wordt vervangen door exact dit item
|
||||||
|
- `currentRowIndex` en `selectionAnchorIndex` worden beide op deze rij gezet
|
||||||
|
|
||||||
|
### Klik op directorynaam
|
||||||
|
Blijft directory openen.
|
||||||
|
|
||||||
|
Gevolg:
|
||||||
|
|
||||||
|
- selectie van dat paneel wordt gewist
|
||||||
|
- current row reset logisch op de eerste zichtbare rij in de nieuwe map
|
||||||
|
- `selectionAnchorIndex` wordt gewist
|
||||||
|
|
||||||
|
### Klik op filenaam
|
||||||
|
Blijft single-selectie van dat item.
|
||||||
|
|
||||||
|
Gevolg:
|
||||||
|
|
||||||
|
- eerdere multi-selectie vervalt
|
||||||
|
- current row en anker worden op die rij gezet
|
||||||
|
|
||||||
|
### Space toggle
|
||||||
|
Blijft toggle op current row.
|
||||||
|
|
||||||
|
Aanvulling:
|
||||||
|
|
||||||
|
- `Space` zet ook `selectionAnchorIndex` op current row
|
||||||
|
- zo blijft keyboardselectie consistent met modifier-click en checkbox-toggle
|
||||||
|
|
||||||
|
### Escape clear
|
||||||
|
Blijft selectie van actief paneel wissen.
|
||||||
|
|
||||||
|
Aanvulling:
|
||||||
|
|
||||||
|
- wist ook `selectionAnchorIndex`
|
||||||
|
- current row blijft behouden
|
||||||
|
|
||||||
|
### currentRowIndex
|
||||||
|
Blijft de focusrij voor keyboardnavigatie en acties zoals `Space`, `Enter` en toekomstige range-selectie.
|
||||||
|
|
||||||
|
### activePane
|
||||||
|
Alle advanced selection-interacties gelden alleen voor het actieve paneel. Het inactieve paneel behoudt zijn selectie ongewijzigd.
|
||||||
|
|
||||||
|
## 5. Scopebeperking
|
||||||
|
Niet meenemen in v1, tenzij later expliciet gevraagd:
|
||||||
|
|
||||||
|
- `Shift + klik` range-selectie
|
||||||
|
- `Cmd/Ctrl + A`
|
||||||
|
- drag selection
|
||||||
|
- desktop-specifieke contextmenu-semantiek
|
||||||
|
- backendwijzigingen
|
||||||
|
|
||||||
|
Aanbeveling: `Shift + klik` nu niet toevoegen. Dat is bruikbaar, maar verhoogt regressierisico in een web-UI met bestaande naam-click, rij-click en checkbox-click verschillen.
|
||||||
|
|
||||||
|
## 6. UX-regels
|
||||||
|
- current row moet visueel zichtbaar blijven, ook binnen een multi-selectie
|
||||||
|
- range-selectie moet eruitzien als normale multi-selectie; current row blijft herkenbaar als focusrij
|
||||||
|
- selectiegedrag moet voorspelbaar blijven:
|
||||||
|
- gewone klik reset selectie
|
||||||
|
- modifier-click togglet één item
|
||||||
|
- shift-arrow werkt vanaf een vast anker
|
||||||
|
- current row moet bij keyboard range-selectie in beeld blijven via bestaande scrolllogica
|
||||||
|
- in een leeg paneel doen shortcuts niets
|
||||||
|
|
||||||
|
## 7. Impactanalyse
|
||||||
|
Waarschijnlijk te wijzigen frontendbestanden:
|
||||||
|
|
||||||
|
- `webui/html/app.js`
|
||||||
|
- mogelijk beperkt `webui/html/style.css` voor subtielere current-row/selected-row combinatie
|
||||||
|
- `webui/backend/tests/golden/test_ui_smoke_golden.py` waarschijnlijk niet of nauwelijks
|
||||||
|
|
||||||
|
Geen backendimpact verwacht.
|
||||||
|
|
||||||
|
Regressierisico:
|
||||||
|
|
||||||
|
- medium in `app.js`, omdat selectiegedrag al meerdere paden heeft: checkbox, rij-click, filenaam, directorynaam, keyboard en wildcardselectie
|
||||||
|
- laag in CSS, zolang alleen bestaande states duidelijker gecombineerd worden
|
||||||
|
|
||||||
|
Belangrijkste regressierisico's:
|
||||||
|
|
||||||
|
- onbedoeld resetten van multi-selectie bij modifier-click
|
||||||
|
- current row en selectie die uit sync raken
|
||||||
|
- range-selectie die een verkeerd anker gebruikt na navigatie of escape
|
||||||
|
|
||||||
|
## 8. Teststrategie
|
||||||
|
### Smoke/regressietests
|
||||||
|
Kleine frontend regressiechecks zijn zinvol voor:
|
||||||
|
|
||||||
|
- aanwezigheid van bestaande dual-pane UI na wijziging
|
||||||
|
- geen breuk in bestaande rooktesten voor panelen/modals/assets
|
||||||
|
|
||||||
|
Headless UI smoke-tests dekken keyboarddetail beperkt. De kernvalidatie zal daarom vooral handmatig zijn.
|
||||||
|
|
||||||
|
### Handmatige validatie
|
||||||
|
Minimaal handmatig verifiëren:
|
||||||
|
|
||||||
|
1. gewone rij-click blijft single-selectie
|
||||||
|
2. checkbox blijft toggle zonder neveneffecten
|
||||||
|
3. `Cmd/Ctrl + klik` voegt items toe en verwijdert ze weer
|
||||||
|
4. `Shift + ArrowDown` start een range vanaf current row
|
||||||
|
5. `Shift + ArrowUp` verkleint of verplaatst de range correct
|
||||||
|
6. `Escape` wist selectie maar laat current row staan
|
||||||
|
7. directory openen wist selectie van alleen dat paneel
|
||||||
|
8. current row blijft zichtbaar bij range-selectie met keyboard
|
||||||
|
9. bestaande copy/move/delete/rename werken nog op de resulterende selectie
|
||||||
|
|
||||||
|
## 9. Aanbeveling
|
||||||
|
Aanbevolen v1-richting met laag regressierisico:
|
||||||
|
|
||||||
|
- voeg alleen `Shift + ArrowUp/ArrowDown` toe voor keyboard range-selectie
|
||||||
|
- voeg alleen `Cmd + klik` / `Ctrl + klik` toe voor niet-aangrenzende toggle-selectie
|
||||||
|
- gebruik een expliciete `selectionAnchorIndex` per paneel
|
||||||
|
- laat gewone click-semantiek ongewijzigd
|
||||||
|
- voeg nog geen `Shift + klik` toe in deze fase
|
||||||
|
|
||||||
|
Deze aanpak is klein genoeg om veilig te implementeren, sluit aan op standaard file manager gedrag en versterkt de bestaande dual-pane workflow zonder de huidige interacties semantisch te herontwerpen.
|
||||||
+104
-3
@@ -7,6 +7,7 @@ let state = {
|
|||||||
selectedItems: [],
|
selectedItems: [],
|
||||||
visibleItems: [],
|
visibleItems: [],
|
||||||
currentRowIndex: -1,
|
currentRowIndex: -1,
|
||||||
|
selectionAnchorIndex: null,
|
||||||
},
|
},
|
||||||
right: {
|
right: {
|
||||||
currentPath: "/Volumes",
|
currentPath: "/Volumes",
|
||||||
@@ -15,6 +16,7 @@ let state = {
|
|||||||
selectedItems: [],
|
selectedItems: [],
|
||||||
visibleItems: [],
|
visibleItems: [],
|
||||||
currentRowIndex: -1,
|
currentRowIndex: -1,
|
||||||
|
selectionAnchorIndex: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
activePane: "left",
|
activePane: "left",
|
||||||
@@ -198,6 +200,14 @@ function setSelectedItem(pane, item) {
|
|||||||
updateActionButtons();
|
updateActionButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearSelectionAnchor(pane) {
|
||||||
|
paneState(pane).selectionAnchorIndex = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSelectionAnchor(pane, index) {
|
||||||
|
paneState(pane).selectionAnchorIndex = Number.isInteger(index) ? index : null;
|
||||||
|
}
|
||||||
|
|
||||||
function selectedPaths(pane) {
|
function selectedPaths(pane) {
|
||||||
return paneState(pane).selectedItems.map((item) => item.path);
|
return paneState(pane).selectedItems.map((item) => item.path);
|
||||||
}
|
}
|
||||||
@@ -206,6 +216,11 @@ function setSingleSelection(pane, item) {
|
|||||||
setSelectedItem(pane, item);
|
setSelectedItem(pane, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setSingleSelectionAtIndex(pane, item, index) {
|
||||||
|
setSingleSelection(pane, item);
|
||||||
|
setSelectionAnchor(pane, index);
|
||||||
|
}
|
||||||
|
|
||||||
function toggleSelection(pane, item) {
|
function toggleSelection(pane, item) {
|
||||||
const model = paneState(pane);
|
const model = paneState(pane);
|
||||||
const index = model.selectedItems.findIndex((selected) => selected.path === item.path);
|
const index = model.selectedItems.findIndex((selected) => selected.path === item.path);
|
||||||
@@ -222,6 +237,43 @@ function toggleSelection(pane, item) {
|
|||||||
updateActionButtons();
|
updateActionButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleSelectionAtIndex(pane, item, index) {
|
||||||
|
toggleSelection(pane, item);
|
||||||
|
setSelectionAnchor(pane, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectedEntryFromItem(entry) {
|
||||||
|
return { path: entry.path, name: entry.name, kind: entry.kind };
|
||||||
|
}
|
||||||
|
|
||||||
|
function setRangeSelection(pane, anchorIndex, currentIndex) {
|
||||||
|
const model = paneState(pane);
|
||||||
|
if (!Array.isArray(model.visibleItems) || model.visibleItems.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const start = Math.max(0, Math.min(anchorIndex, currentIndex));
|
||||||
|
const end = Math.min(model.visibleItems.length - 1, Math.max(anchorIndex, currentIndex));
|
||||||
|
model.selectedItems = model.visibleItems
|
||||||
|
.slice(start, end + 1)
|
||||||
|
.filter((entry) => !entry.isParent)
|
||||||
|
.map((entry) => selectedEntryFromItem(entry));
|
||||||
|
const current = model.visibleItems[currentIndex];
|
||||||
|
model.selectedItem = current && !current.isParent ? selectedEntryFromItem(current) : (model.selectedItems[model.selectedItems.length - 1] || null);
|
||||||
|
updateActionButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMacLike() {
|
||||||
|
const platform = navigator.platform || navigator.userAgent || "";
|
||||||
|
return /Mac|iPhone|iPad|iPod/i.test(platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isToggleModifierClick(event) {
|
||||||
|
if (isMacLike()) {
|
||||||
|
return !!event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey;
|
||||||
|
}
|
||||||
|
return !!event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey;
|
||||||
|
}
|
||||||
|
|
||||||
function currentRowItem(pane) {
|
function currentRowItem(pane) {
|
||||||
const model = paneState(pane);
|
const model = paneState(pane);
|
||||||
if (!Array.isArray(model.visibleItems) || model.visibleItems.length === 0) {
|
if (!Array.isArray(model.visibleItems) || model.visibleItems.length === 0) {
|
||||||
@@ -490,6 +542,7 @@ function renderPaneItems(pane) {
|
|||||||
up.onclick = () => {
|
up.onclick = () => {
|
||||||
setActivePane(pane);
|
setActivePane(pane);
|
||||||
model.currentRowIndex = index;
|
model.currentRowIndex = index;
|
||||||
|
clearSelectionAnchor(pane);
|
||||||
renderPaneItems(pane);
|
renderPaneItems(pane);
|
||||||
};
|
};
|
||||||
up.append(document.createElement("span"));
|
up.append(document.createElement("span"));
|
||||||
@@ -526,7 +579,7 @@ function renderPaneItems(pane) {
|
|||||||
row.onclick = () => {
|
row.onclick = () => {
|
||||||
setActivePane(pane);
|
setActivePane(pane);
|
||||||
model.currentRowIndex = index;
|
model.currentRowIndex = index;
|
||||||
setSingleSelection(pane, { path: entry.path, name: entry.name, kind: entry.kind });
|
setSingleSelectionAtIndex(pane, { path: entry.path, name: entry.name, kind: entry.kind }, index);
|
||||||
renderPaneItems(pane);
|
renderPaneItems(pane);
|
||||||
};
|
};
|
||||||
const checkbox = row.querySelector(".select-marker");
|
const checkbox = row.querySelector(".select-marker");
|
||||||
@@ -535,7 +588,7 @@ function renderPaneItems(pane) {
|
|||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
setActivePane(pane);
|
setActivePane(pane);
|
||||||
model.currentRowIndex = index;
|
model.currentRowIndex = index;
|
||||||
toggleSelection(pane, { path: entry.path, name: entry.name, kind: entry.kind });
|
toggleSelectionAtIndex(pane, { path: entry.path, name: entry.name, kind: entry.kind }, index);
|
||||||
renderPaneItems(pane);
|
renderPaneItems(pane);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -553,10 +606,24 @@ function renderPaneItems(pane) {
|
|||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
setActivePane(pane);
|
setActivePane(pane);
|
||||||
model.currentRowIndex = index;
|
model.currentRowIndex = index;
|
||||||
setSingleSelection(pane, { path: entry.path, name: entry.name, kind: entry.kind });
|
if (isToggleModifierClick(ev)) {
|
||||||
|
toggleSelectionAtIndex(pane, { path: entry.path, name: entry.name, kind: entry.kind }, index);
|
||||||
|
} else {
|
||||||
|
setSingleSelectionAtIndex(pane, { path: entry.path, name: entry.name, kind: entry.kind }, index);
|
||||||
|
}
|
||||||
renderPaneItems(pane);
|
renderPaneItems(pane);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
row.onclick = (ev) => {
|
||||||
|
setActivePane(pane);
|
||||||
|
model.currentRowIndex = index;
|
||||||
|
if (isToggleModifierClick(ev)) {
|
||||||
|
toggleSelectionAtIndex(pane, { path: entry.path, name: entry.name, kind: entry.kind }, index);
|
||||||
|
} else {
|
||||||
|
setSingleSelectionAtIndex(pane, { path: entry.path, name: entry.name, kind: entry.kind }, index);
|
||||||
|
}
|
||||||
|
renderPaneItems(pane);
|
||||||
|
};
|
||||||
items.append(row);
|
items.append(row);
|
||||||
});
|
});
|
||||||
updateActionButtons();
|
updateActionButtons();
|
||||||
@@ -606,6 +673,7 @@ function navigateTo(pane, path) {
|
|||||||
const model = paneState(pane);
|
const model = paneState(pane);
|
||||||
model.currentPath = path;
|
model.currentPath = path;
|
||||||
model.currentRowIndex = 0;
|
model.currentRowIndex = 0;
|
||||||
|
clearSelectionAnchor(pane);
|
||||||
setSelectedItem(pane, null);
|
setSelectedItem(pane, null);
|
||||||
loadBrowsePane(pane);
|
loadBrowsePane(pane);
|
||||||
}
|
}
|
||||||
@@ -1290,6 +1358,27 @@ function moveCurrentRow(delta) {
|
|||||||
scrollCurrentRowIntoView(pane);
|
scrollCurrentRowIntoView(pane);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extendSelectionByRow(delta) {
|
||||||
|
const pane = state.activePane;
|
||||||
|
const model = paneState(pane);
|
||||||
|
if (!Array.isArray(model.visibleItems) || model.visibleItems.length === 0) {
|
||||||
|
model.currentRowIndex = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const originalIndex = model.currentRowIndex < 0 ? 0 : model.currentRowIndex;
|
||||||
|
if (model.currentRowIndex < 0) {
|
||||||
|
model.currentRowIndex = 0;
|
||||||
|
}
|
||||||
|
if (!Number.isInteger(model.selectionAnchorIndex)) {
|
||||||
|
model.selectionAnchorIndex = originalIndex;
|
||||||
|
}
|
||||||
|
const maxIndex = model.visibleItems.length - 1;
|
||||||
|
model.currentRowIndex = Math.max(0, Math.min(maxIndex, model.currentRowIndex + delta));
|
||||||
|
setRangeSelection(pane, model.selectionAnchorIndex, model.currentRowIndex);
|
||||||
|
renderPaneItems(pane);
|
||||||
|
scrollCurrentRowIntoView(pane);
|
||||||
|
}
|
||||||
|
|
||||||
function jumpCurrentRow(edge) {
|
function jumpCurrentRow(edge) {
|
||||||
const pane = state.activePane;
|
const pane = state.activePane;
|
||||||
const model = paneState(pane);
|
const model = paneState(pane);
|
||||||
@@ -1317,12 +1406,14 @@ function toggleCurrentSelection() {
|
|||||||
if (!item || item.isParent) {
|
if (!item || item.isParent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setSelectionAnchor(pane, paneState(pane).currentRowIndex);
|
||||||
toggleSelection(pane, { path: item.path, name: item.name, kind: item.kind });
|
toggleSelection(pane, { path: item.path, name: item.name, kind: item.kind });
|
||||||
renderPaneItems(pane);
|
renderPaneItems(pane);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearSelectionForActivePane() {
|
function clearSelectionForActivePane() {
|
||||||
const pane = state.activePane;
|
const pane = state.activePane;
|
||||||
|
clearSelectionAnchor(pane);
|
||||||
setSelectedItem(pane, null);
|
setSelectedItem(pane, null);
|
||||||
renderPaneItems(pane);
|
renderPaneItems(pane);
|
||||||
}
|
}
|
||||||
@@ -1415,11 +1506,21 @@ function handleKeyboardShortcuts(event) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.key === "ArrowUp") {
|
if (event.key === "ArrowUp") {
|
||||||
|
if (event.shiftKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
extendSelectionByRow(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
moveCurrentRow(-1);
|
moveCurrentRow(-1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.key === "ArrowDown") {
|
if (event.key === "ArrowDown") {
|
||||||
|
if (event.shiftKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
extendSelectionByRow(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
moveCurrentRow(1);
|
moveCurrentRow(1);
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user