mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-01-17 13:38:37 +01:00
Fix edge-cases when text leaf nodes are deleted (#5325)
This commit is contained in:
parent
4205e0f002
commit
af3f828b12
5
.changeset/android-removed-leaf.md
Normal file
5
.changeset/android-removed-leaf.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'slate-react': patch
|
||||
---
|
||||
|
||||
Fix edge-cases in the Android input manager when text leaf nodes are deleted, such as when deleting text leaf nodes adjacent to inline void nodes.
|
@ -2,6 +2,7 @@ import { DebouncedFunc } from 'lodash'
|
||||
import { Editor, Node, Path, Point, Range, Text, Transforms } from 'slate'
|
||||
import { ReactEditor } from '../../plugin/react-editor'
|
||||
import {
|
||||
applyStringDiff,
|
||||
mergeStringDiffs,
|
||||
normalizePoint,
|
||||
normalizeRange,
|
||||
@ -153,7 +154,7 @@ export function createAndroidInputManager({
|
||||
EDITOR_TO_PENDING_DIFFS.get(editor)
|
||||
)
|
||||
|
||||
let scheduleSelectionChange = !!EDITOR_TO_PENDING_DIFFS.get(editor)?.length
|
||||
let scheduleSelectionChange = hasPendingDiffs()
|
||||
|
||||
let diff: TextDiff | undefined
|
||||
while ((diff = EDITOR_TO_PENDING_DIFFS.get(editor)?.[0])) {
|
||||
@ -377,38 +378,75 @@ export function createAndroidInputManager({
|
||||
return
|
||||
}
|
||||
|
||||
if (Range.isExpanded(targetRange) && type.startsWith('delete')) {
|
||||
const [start, end] = Range.edges(targetRange)
|
||||
const leaf = Node.leaf(editor, start.path)
|
||||
// By default, the input manager tries to store text diffs so that we can
|
||||
// defer flushing them at a later point in time. We don't want to flush
|
||||
// for every input event as this can be expensive. However, there are some
|
||||
// scenarios where we cannot safely store the text diff and must instead
|
||||
// schedule an action to let Slate normalize the editor state.
|
||||
let canStoreDiff = true
|
||||
|
||||
if (leaf.text.length === start.offset && end.offset === 0) {
|
||||
const next = Editor.next(editor, { at: start.path, match: Text.isText })
|
||||
if (next && Path.equals(next[1], end.path)) {
|
||||
targetRange = { anchor: end, focus: end }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Range.isExpanded(targetRange) && type.startsWith('delete')) {
|
||||
if (Path.equals(targetRange.anchor.path, targetRange.focus.path)) {
|
||||
if (type.startsWith('delete')) {
|
||||
if (Range.isExpanded(targetRange)) {
|
||||
const [start, end] = Range.edges(targetRange)
|
||||
const leaf = Node.leaf(editor, start.path)
|
||||
|
||||
const point = { path: targetRange.anchor.path, offset: start.offset }
|
||||
const range = Editor.range(editor, point, point)
|
||||
handleUserSelect(range)
|
||||
|
||||
return storeDiff(targetRange.anchor.path, {
|
||||
text: '',
|
||||
end: end.offset,
|
||||
start: start.offset,
|
||||
})
|
||||
if (leaf.text.length === start.offset && end.offset === 0) {
|
||||
const next = Editor.next(editor, {
|
||||
at: start.path,
|
||||
match: Text.isText,
|
||||
})
|
||||
if (next && Path.equals(next[1], end.path)) {
|
||||
targetRange = { anchor: end, focus: end }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const direction = type.endsWith('Backward') ? 'backward' : 'forward'
|
||||
return scheduleAction(
|
||||
() => Editor.deleteFragment(editor, { direction }),
|
||||
{ at: targetRange }
|
||||
const [start, end] = Range.edges(targetRange)
|
||||
const [leaf, path] = Editor.leaf(editor, start.path)
|
||||
|
||||
const diff = {
|
||||
text: '',
|
||||
start: start.offset,
|
||||
end: end.offset,
|
||||
}
|
||||
const pendingDiffs = EDITOR_TO_PENDING_DIFFS.get(editor)
|
||||
const relevantPendingDiffs = pendingDiffs?.find(change =>
|
||||
Path.equals(change.path, path)
|
||||
)
|
||||
const diffs = relevantPendingDiffs
|
||||
? [relevantPendingDiffs.diff, diff]
|
||||
: [diff]
|
||||
const text = applyStringDiff(leaf.text, ...diffs)
|
||||
|
||||
if (text.length === 0) {
|
||||
// Text leaf will be removed, so we need to schedule an
|
||||
// action to remove it so that Slate can normalize instead
|
||||
// of storing as a diff
|
||||
canStoreDiff = false
|
||||
}
|
||||
|
||||
if (Range.isExpanded(targetRange)) {
|
||||
if (
|
||||
canStoreDiff &&
|
||||
Path.equals(targetRange.anchor.path, targetRange.focus.path)
|
||||
) {
|
||||
const point = { path: targetRange.anchor.path, offset: start.offset }
|
||||
const range = Editor.range(editor, point, point)
|
||||
handleUserSelect(range)
|
||||
|
||||
return storeDiff(targetRange.anchor.path, {
|
||||
text: '',
|
||||
end: end.offset,
|
||||
start: start.offset,
|
||||
})
|
||||
}
|
||||
|
||||
return scheduleAction(
|
||||
() => Editor.deleteFragment(editor, { direction }),
|
||||
{ at: targetRange }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
@ -423,7 +461,7 @@ export function createAndroidInputManager({
|
||||
case 'deleteContent':
|
||||
case 'deleteContentForward': {
|
||||
const { anchor } = targetRange
|
||||
if (Range.isCollapsed(targetRange)) {
|
||||
if (canStoreDiff && Range.isCollapsed(targetRange)) {
|
||||
const targetNode = Node.leaf(editor, anchor.path)
|
||||
|
||||
if (anchor.offset < targetNode.text.length) {
|
||||
@ -451,6 +489,7 @@ export function createAndroidInputManager({
|
||||
: !!nativeTargetRange?.collapsed
|
||||
|
||||
if (
|
||||
canStoreDiff &&
|
||||
nativeCollapsed &&
|
||||
Range.isCollapsed(targetRange) &&
|
||||
anchor.offset > 0
|
||||
@ -610,8 +649,10 @@ export function createAndroidInputManager({
|
||||
insertPositionHint = false
|
||||
}
|
||||
|
||||
storeDiff(start.path, diff)
|
||||
return
|
||||
if (canStoreDiff) {
|
||||
storeDiff(start.path, diff)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return scheduleAction(() => Editor.insertText(editor, text), {
|
||||
|
@ -52,7 +52,7 @@ export function verifyDiffState(editor: Editor, textDiff: TextDiff): boolean {
|
||||
return Text.isText(nextNode) && nextNode.text.startsWith(diff.text)
|
||||
}
|
||||
|
||||
function applyStringDiff(text: string, ...diffs: StringDiff[]) {
|
||||
export function applyStringDiff(text: string, ...diffs: StringDiff[]) {
|
||||
return diffs.reduce(
|
||||
(text, diff) =>
|
||||
text.slice(0, diff.start) + diff.text + text.slice(diff.end),
|
||||
|
Loading…
x
Reference in New Issue
Block a user