Skip to content

Commit fdaa9c8

Browse files
authored
Fix handling of editor.isSelectable in Editor.positions (#5929)
* Fix handling of `editor.isSelectable` in `Editor.positions` * Clean-up
1 parent cf10119 commit fdaa9c8

File tree

8 files changed

+140
-6
lines changed

8 files changed

+140
-6
lines changed

.changeset/eleven-clocks-begin.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'slate': patch
3+
---
4+
5+
- Fix error when a non-selectable node has no next or previous node
6+
- Do not return points from `Editor.positions` that are inside non-selectable nodes
7+
- Previously, `editor.isSelectable` was handled incorrectly inside `Editor.positions`. When encountering a non-selectable node, it would immediately return the point before or after it (depending on `reverse`), but it would not skip returning points inside the non-selectable node if more than one point was consumed from `Editor.positions`.

packages/slate/src/editor/positions.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export function* positions(
5151
let distance = 0 // Distance for leafText to catch up to blockText.
5252
let leafTextRemaining = 0
5353
let leafTextOffset = 0
54+
const skippedPaths: Path[] = []
5455

5556
// Iterate through all nodes in range, grabbing entire textual content
5657
// of block nodes in blockText, and text nodes in leafText.
@@ -63,19 +64,35 @@ export function* positions(
6364
reverse,
6465
voids,
6566
})) {
67+
// If the node is inside a skipped ancestor, do not return any points, but
68+
// still process its content so that the iteration state remains correct.
69+
const hasSkippedAncestor = skippedPaths.some(p => Path.isAncestor(p, path))
70+
71+
function* maybeYield(point: Point) {
72+
if (!hasSkippedAncestor) {
73+
yield point
74+
}
75+
}
76+
6677
/*
6778
* ELEMENT NODE - Yield position(s) for voids, collect blockText for blocks
6879
*/
6980
if (Element.isElement(node)) {
7081
if (!editor.isSelectable(node)) {
7182
/**
72-
* If the node is not selectable, skip it
83+
* If the node is not selectable, skip it and its descendants
7384
*/
85+
skippedPaths.push(path)
7486
if (reverse) {
75-
yield Editor.end(editor, Path.previous(path))
87+
if (Path.hasPrevious(path)) {
88+
yield* maybeYield(Editor.end(editor, Path.previous(path)))
89+
}
7690
continue
7791
} else {
78-
yield Editor.start(editor, Path.next(path))
92+
const nextPath = Path.next(path)
93+
if (Editor.hasPath(editor, nextPath)) {
94+
yield* maybeYield(Editor.start(editor, nextPath))
95+
}
7996
continue
8097
}
8198
}
@@ -84,7 +101,7 @@ export function* positions(
84101
// yield their first point. If the `voids` option is set to true,
85102
// then we will iterate over their content.
86103
if (!voids && (editor.isVoid(node) || editor.isElementReadOnly(node))) {
87-
yield Editor.start(editor, path)
104+
yield* maybeYield(Editor.start(editor, path))
88105
continue
89106
}
90107

@@ -143,7 +160,7 @@ export function* positions(
143160

144161
// Yield position at the start of node (potentially).
145162
if (isFirst || isNewBlock || unit === 'offset') {
146-
yield { path, offset: leafTextOffset }
163+
yield* maybeYield({ path, offset: leafTextOffset })
147164
isNewBlock = false
148165
}
149166

@@ -178,7 +195,7 @@ export function* positions(
178195
// to catch up with `blockText`, so we can reset `distance`
179196
// and yield this position in this node.
180197
distance = 0
181-
yield { path, offset: leafTextOffset }
198+
yield* maybeYield({ path, offset: leafTextOffset })
182199
}
183200
}
184201
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/** @jsx jsx */
2+
3+
import { Editor } from 'slate'
4+
import { jsx } from '../../..'
5+
6+
export const input = (
7+
<editor>
8+
<block>one</block>
9+
<block nonSelectable>two</block>
10+
</editor>
11+
)
12+
13+
export const test = editor => {
14+
return Editor.after(editor, { path: [0, 0], offset: 3 })
15+
}
16+
17+
export const output = undefined
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/** @jsx jsx */
2+
3+
import { Editor } from 'slate'
4+
import { jsx } from '../../..'
5+
6+
export const input = (
7+
<editor>
8+
<block>one</block>
9+
<block nonSelectable>two</block>
10+
<block>three</block>
11+
</editor>
12+
)
13+
14+
export const test = editor => {
15+
return Editor.after(editor, { path: [0, 0], offset: 3 })
16+
}
17+
18+
export const output = { path: [2, 0], offset: 0 }
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/** @jsx jsx */
2+
3+
import { Editor } from 'slate'
4+
import { jsx } from '../../..'
5+
6+
// This is invalid due to the lack of a text node after the inline, but this
7+
// case can arise prior to normalization so it needs to be handled anyway.
8+
export const input = (
9+
<editor>
10+
<block>
11+
one<inline nonSelectable>two</inline>
12+
</block>
13+
</editor>
14+
)
15+
16+
export const test = editor => {
17+
return Editor.after(editor, { path: [0, 0], offset: 3 })
18+
}
19+
20+
export const output = undefined
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/** @jsx jsx */
2+
3+
import { Editor } from 'slate'
4+
import { jsx } from '../../..'
5+
6+
export const input = (
7+
<editor>
8+
<block nonSelectable>two</block>
9+
<block>three</block>
10+
</editor>
11+
)
12+
13+
export const test = editor => {
14+
return Editor.before(editor, { path: [1, 0], offset: 0 })
15+
}
16+
17+
export const output = undefined
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/** @jsx jsx */
2+
3+
import { Editor } from 'slate'
4+
import { jsx } from '../../..'
5+
6+
export const input = (
7+
<editor>
8+
<block>one</block>
9+
<block nonSelectable>two</block>
10+
<block>three</block>
11+
</editor>
12+
)
13+
14+
export const test = editor => {
15+
return Editor.before(editor, { path: [2, 0], offset: 0 })
16+
}
17+
18+
export const output = { path: [0, 0], offset: 3 }
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/** @jsx jsx */
2+
3+
import { Editor } from 'slate'
4+
import { jsx } from '../../..'
5+
6+
// This is invalid due to the lack of a text node before the inline, but this
7+
// case can arise prior to normalization so it needs to be handled anyway.
8+
export const input = (
9+
<editor>
10+
<block>
11+
<inline nonSelectable>two</inline>three
12+
</block>
13+
</editor>
14+
)
15+
16+
export const test = editor => {
17+
return Editor.before(editor, { path: [0, 1], offset: 0 })
18+
}
19+
20+
export const output = undefined

0 commit comments

Comments
 (0)