1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-20 06:01:24 +02:00

Fix handling of editor.isSelectable in Editor.positions (#5929)

* Fix handling of `editor.isSelectable` in `Editor.positions`

* Clean-up
This commit is contained in:
Joe Anderson
2025-08-13 22:28:16 +01:00
committed by GitHub
parent cf10119ad8
commit fdaa9c8088
8 changed files with 140 additions and 6 deletions

View File

@@ -0,0 +1,7 @@
---
'slate': patch
---
- Fix error when a non-selectable node has no next or previous node
- Do not return points from `Editor.positions` that are inside non-selectable nodes
- 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`.

View File

@@ -51,6 +51,7 @@ export function* positions(
let distance = 0 // Distance for leafText to catch up to blockText. let distance = 0 // Distance for leafText to catch up to blockText.
let leafTextRemaining = 0 let leafTextRemaining = 0
let leafTextOffset = 0 let leafTextOffset = 0
const skippedPaths: Path[] = []
// Iterate through all nodes in range, grabbing entire textual content // Iterate through all nodes in range, grabbing entire textual content
// of block nodes in blockText, and text nodes in leafText. // of block nodes in blockText, and text nodes in leafText.
@@ -63,19 +64,35 @@ export function* positions(
reverse, reverse,
voids, voids,
})) { })) {
// If the node is inside a skipped ancestor, do not return any points, but
// still process its content so that the iteration state remains correct.
const hasSkippedAncestor = skippedPaths.some(p => Path.isAncestor(p, path))
function* maybeYield(point: Point) {
if (!hasSkippedAncestor) {
yield point
}
}
/* /*
* ELEMENT NODE - Yield position(s) for voids, collect blockText for blocks * ELEMENT NODE - Yield position(s) for voids, collect blockText for blocks
*/ */
if (Element.isElement(node)) { if (Element.isElement(node)) {
if (!editor.isSelectable(node)) { if (!editor.isSelectable(node)) {
/** /**
* If the node is not selectable, skip it * If the node is not selectable, skip it and its descendants
*/ */
skippedPaths.push(path)
if (reverse) { if (reverse) {
yield Editor.end(editor, Path.previous(path)) if (Path.hasPrevious(path)) {
yield* maybeYield(Editor.end(editor, Path.previous(path)))
}
continue continue
} else { } else {
yield Editor.start(editor, Path.next(path)) const nextPath = Path.next(path)
if (Editor.hasPath(editor, nextPath)) {
yield* maybeYield(Editor.start(editor, nextPath))
}
continue continue
} }
} }
@@ -84,7 +101,7 @@ export function* positions(
// yield their first point. If the `voids` option is set to true, // yield their first point. If the `voids` option is set to true,
// then we will iterate over their content. // then we will iterate over their content.
if (!voids && (editor.isVoid(node) || editor.isElementReadOnly(node))) { if (!voids && (editor.isVoid(node) || editor.isElementReadOnly(node))) {
yield Editor.start(editor, path) yield* maybeYield(Editor.start(editor, path))
continue continue
} }
@@ -143,7 +160,7 @@ export function* positions(
// Yield position at the start of node (potentially). // Yield position at the start of node (potentially).
if (isFirst || isNewBlock || unit === 'offset') { if (isFirst || isNewBlock || unit === 'offset') {
yield { path, offset: leafTextOffset } yield* maybeYield({ path, offset: leafTextOffset })
isNewBlock = false isNewBlock = false
} }
@@ -178,7 +195,7 @@ export function* positions(
// to catch up with `blockText`, so we can reset `distance` // to catch up with `blockText`, so we can reset `distance`
// and yield this position in this node. // and yield this position in this node.
distance = 0 distance = 0
yield { path, offset: leafTextOffset } yield* maybeYield({ path, offset: leafTextOffset })
} }
} }
} }

View File

@@ -0,0 +1,17 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const input = (
<editor>
<block>one</block>
<block nonSelectable>two</block>
</editor>
)
export const test = editor => {
return Editor.after(editor, { path: [0, 0], offset: 3 })
}
export const output = undefined

View File

@@ -0,0 +1,18 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const input = (
<editor>
<block>one</block>
<block nonSelectable>two</block>
<block>three</block>
</editor>
)
export const test = editor => {
return Editor.after(editor, { path: [0, 0], offset: 3 })
}
export const output = { path: [2, 0], offset: 0 }

View File

@@ -0,0 +1,20 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
// This is invalid due to the lack of a text node after the inline, but this
// case can arise prior to normalization so it needs to be handled anyway.
export const input = (
<editor>
<block>
one<inline nonSelectable>two</inline>
</block>
</editor>
)
export const test = editor => {
return Editor.after(editor, { path: [0, 0], offset: 3 })
}
export const output = undefined

View File

@@ -0,0 +1,17 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const input = (
<editor>
<block nonSelectable>two</block>
<block>three</block>
</editor>
)
export const test = editor => {
return Editor.before(editor, { path: [1, 0], offset: 0 })
}
export const output = undefined

View File

@@ -0,0 +1,18 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const input = (
<editor>
<block>one</block>
<block nonSelectable>two</block>
<block>three</block>
</editor>
)
export const test = editor => {
return Editor.before(editor, { path: [2, 0], offset: 0 })
}
export const output = { path: [0, 0], offset: 3 }

View File

@@ -0,0 +1,20 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
// This is invalid due to the lack of a text node before the inline, but this
// case can arise prior to normalization so it needs to be handled anyway.
export const input = (
<editor>
<block>
<inline nonSelectable>two</inline>three
</block>
</editor>
)
export const test = editor => {
return Editor.before(editor, { path: [0, 1], offset: 0 })
}
export const output = undefined