Skip to content

Commit 076450d

Browse files
committed
Override cross-line replacements when the line after has a widget
FIX: Avoid an issue where on Chrome and Safari, typing over a cross-line selection can replace widgets on the line after the selection with their plain text content. Issue codemirror/dev#1630
1 parent 3b0c3b9 commit 076450d

File tree

3 files changed

+28
-1
lines changed

3 files changed

+28
-1
lines changed

src/docview.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {ChangeSet, RangeSet, findClusterBreak, SelectionRange} from "@codemirror/state"
22
import {ContentView, ChildCursor, ViewFlag, DOMPos, replaceRange} from "./contentview"
33
import {BlockView, LineView, BlockWidgetView, BlockGapWidget} from "./blockview"
4-
import {TextView, MarkView} from "./inlineview"
4+
import {TextView, MarkView, WidgetView} from "./inlineview"
55
import {ContentBuilder} from "./buildview"
66
import browser from "./browser"
77
import {Decoration, DecorationSet, addRange, MarkDecoration} from "./decoration"
@@ -571,6 +571,13 @@ export class DocView extends ContentView {
571571
this.view.textDirection == Direction.LTR)
572572
}
573573

574+
lineHasWidget(pos: number) {
575+
let {i} = this.childCursor().findPos(pos)
576+
if (i == this.children.length) return false
577+
let scan = (child: ContentView) => child instanceof WidgetView || child.children.some(scan)
578+
return scan(this.children[i])
579+
}
580+
574581
// Will never be called but needs to be present
575582
declare split: () => ContentView
576583
}

src/domchange.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,17 @@ export function applyDOMChange(view: EditorView, domChange: DOMChange): boolean
109109
from: sel.from, to: sel.to,
110110
insert: view.state.doc.slice(sel.from, change.from).append(change.insert).append(view.state.doc.slice(change.to, sel.to))
111111
}
112+
} else if (view.state.doc.lineAt(sel.from).to < sel.to && view.docView.lineHasWidget(sel.to) &&
113+
view.inputState.insertingTextAt > Date.now() - 50) {
114+
// For a cross-line insertion, Chrome and Safari will crudely take
115+
// the text of the line after the selection, flattening any
116+
// widgets, and move it into the joined line. This tries to detect
117+
// such a situation, and replaces the change with a selection
118+
// replace of the text provided by the beforeinput event.
119+
change = {
120+
from: sel.from, to: sel.to,
121+
insert: view.state.toText(view.inputState.insertingText)
122+
}
112123
} else if (browser.chrome && change && change.from == change.to && change.from == sel.head &&
113124
change.insert.toString() == "\n " && view.lineWrapping) {
114125
// In Chrome, if you insert a space at the start of a wrapped

src/input.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ export class InputState {
6060
// the mutation events fire shortly after the compositionend event
6161
compositionPendingChange = false
6262

63+
// Set by beforeinput, used in DOM change reader
64+
insertingText = ""
65+
insertingTextAt = 0
66+
6367
mouseSelection: MouseSelection | null = null
6468
// When a drag from the editor is active, this points at the range
6569
// being dragged.
@@ -857,6 +861,11 @@ observers.contextmenu = view => {
857861
}
858862

859863
handlers.beforeinput = (view, event: InputEvent) => {
864+
if (event.inputType == "insertText" || event.inputType == "insertCompositionText") {
865+
view.inputState.insertingText = event.data!
866+
view.inputState.insertingTextAt = Date.now()
867+
}
868+
860869
// In EditContext mode, we must handle insertReplacementText events
861870
// directly, to make spell checking corrections work
862871
if (event.inputType == "insertReplacementText" && view.observer.editContext) {

0 commit comments

Comments
 (0)