Skip to content

Commit e663595

Browse files
committed
fix: issue with expanding selection down using Shift+ArrowDown
1 parent 20d9e7f commit e663595

File tree

3 files changed

+121
-0
lines changed

3 files changed

+121
-0
lines changed

.changeset/bumpy-spoons-check.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@portabletext/editor': patch
3+
---
4+
5+
fix: issue with expanding selection down using Shift+ArrowDown

packages/editor/gherkin-spec/selection.feature

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@ Feature: Selection
33
Background:
44
Given one editor
55

6+
Scenario Outline: Expanding selection down
7+
Given the text <text>
8+
When the caret is put <position>
9+
And "{Shift>}{ArrowDown}{/Shift}" is pressed
10+
And "{Shift>}{ArrowDown}{/Shift}" is pressed
11+
Then <selection> is selected
12+
13+
Examples:
14+
| text | position | selection |
15+
| "foo\|bar\|baz" | before "foo" | "foo\|bar\|" |
16+
| "foo\|>#:bar\|baz" | before "foo" | "foo\|>#:bar\|" |
17+
| "foo\|>#:bar\|{image}" | before "foo" | "foo\|>#:bar\|{image}" |
18+
619
Scenario: Expanding collapsed selection backwards from empty line
720
Given the text "foo|"
821
And the editor is focused

packages/editor/src/behaviors/behavior.abstract.keyboard.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ import {isTextBlock} from '@portabletext/schema'
33
import {defaultKeyboardShortcuts} from '../keyboard-shortcuts/default-keyboard-shortcuts'
44
import {getFocusBlock} from '../selectors/selector.get-focus-block'
55
import {getFocusInlineObject} from '../selectors/selector.get-focus-inline-object'
6+
import {getFocusTextBlock} from '../selectors/selector.get-focus-text-block'
7+
import {getNextBlock} from '../selectors/selector.get-next-block'
68
import {getPreviousBlock} from '../selectors/selector.get-previous-block'
79
import {isSelectionCollapsed} from '../selectors/selector.is-selection-collapsed'
810
import {isSelectionExpanded} from '../selectors/selector.is-selection-expanded'
911
import {getBlockEndPoint} from '../utils/util.get-block-end-point'
12+
import {getBlockStartPoint} from '../utils/util.get-block-start-point'
1013
import {isEmptyTextBlock} from '../utils/util.is-empty-text-block'
1114
import {raise} from './behavior.types.action'
1215
import {defineBehavior} from './behavior.types.behavior'
@@ -22,6 +25,17 @@ const shiftLeft = createKeyboardShortcut({
2225
},
2326
],
2427
})
28+
const shiftDown = createKeyboardShortcut({
29+
default: [
30+
{
31+
key: 'ArrowDown',
32+
shift: true,
33+
meta: false,
34+
ctrl: false,
35+
alt: false,
36+
},
37+
],
38+
})
2539

2640
export const abstractKeyboardBehaviors = [
2741
/**
@@ -171,4 +185,93 @@ export const abstractKeyboardBehaviors = [
171185
],
172186
],
173187
}),
188+
189+
defineBehavior({
190+
on: 'keyboard.keydown',
191+
guard: ({snapshot, event, dom}) => {
192+
if (!snapshot.context.selection || !shiftDown.guard(event.originEvent)) {
193+
return false
194+
}
195+
196+
const focusTextBlock = getFocusTextBlock(snapshot)
197+
198+
if (!focusTextBlock) {
199+
return false
200+
}
201+
202+
const nextBlock = getNextBlock(snapshot)
203+
204+
if (!nextBlock) {
205+
return false
206+
}
207+
208+
if (!isTextBlock(snapshot.context, nextBlock.node)) {
209+
return false
210+
}
211+
212+
const focusBlockEndPoint = getBlockEndPoint({
213+
context: snapshot.context,
214+
block: focusTextBlock,
215+
})
216+
217+
// Find the DOM position of the current focus point
218+
const focusRect = dom.getSelectionRect({
219+
...snapshot,
220+
context: {
221+
...snapshot.context,
222+
selection: {
223+
anchor: snapshot.context.selection.focus,
224+
focus: snapshot.context.selection.focus,
225+
},
226+
},
227+
})
228+
// Find the DOM position of the focus block end point
229+
const endPointRect = dom.getSelectionRect({
230+
...snapshot,
231+
context: {
232+
...snapshot.context,
233+
selection: {
234+
anchor: focusBlockEndPoint,
235+
focus: focusBlockEndPoint,
236+
},
237+
},
238+
})
239+
240+
if (!focusRect || !endPointRect) {
241+
return false
242+
}
243+
244+
if (endPointRect.top > focusRect.top) {
245+
// If the end point is positioned further from the top than the current
246+
// focus point, then we can deduce that the end point is on the next
247+
// line. In this case, we don't want to interfere since the browser
248+
// does right thing and expands the selection to the end of the current
249+
// line.
250+
return false
251+
}
252+
253+
// If the end point is positioned at the same level as the current focus
254+
// point, then we can deduce that the end point is on the same line. In
255+
// this case, we want to expand the selection to the end of the start
256+
// block. This mitigates a Chromium bug where Shift+ArrowDown can expand
257+
// further into the next block.
258+
const nextBlockStartPoint = getBlockStartPoint({
259+
context: snapshot.context,
260+
block: nextBlock,
261+
})
262+
263+
return {nextBlockStartPoint, selection: snapshot.context.selection}
264+
},
265+
actions: [
266+
(_, {nextBlockStartPoint, selection}) => [
267+
raise({
268+
type: 'select',
269+
at: {
270+
anchor: selection.anchor,
271+
focus: nextBlockStartPoint,
272+
},
273+
}),
274+
],
275+
],
276+
}),
174277
]

0 commit comments

Comments
 (0)