prosemirror-view 1.27.0 → 1.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/index.cjs +103 -60
- package/dist/index.d.ts +2 -2
- package/dist/index.js +93 -49
- package/package.json +1 -1
- package/src/decoration.ts +16 -12
- package/src/domcoords.ts +1 -1
- package/src/domobserver.ts +37 -18
- package/src/index.ts +20 -11
- package/src/input.ts +13 -4
- package/src/viewdesc.ts +4 -3
- package/dist/index.es.js +0 -5558
- package/dist/index.es.js.map +0 -1
- package/dist/index.js.map +0 -1
package/src/decoration.ts
CHANGED
|
@@ -550,20 +550,24 @@ function mapChildren(
|
|
|
550
550
|
|
|
551
551
|
// Mark the children that are directly touched by changes, and
|
|
552
552
|
// move those that are after the changes.
|
|
553
|
-
let
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
let
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
553
|
+
for (let i = 0; i < mapping.maps.length; i++) {
|
|
554
|
+
let moved = 0
|
|
555
|
+
mapping.maps[i].forEach((oldStart: number, oldEnd: number, newStart: number, newEnd: number) => {
|
|
556
|
+
let dSize = (newEnd - newStart) - (oldEnd - oldStart)
|
|
557
|
+
for (let i = 0; i < children.length; i += 3) {
|
|
558
|
+
let end = children[i + 1] as number
|
|
559
|
+
if (end < 0 || oldStart > end + oldOffset - moved) continue
|
|
560
|
+
let start = (children[i] as number) + oldOffset - moved
|
|
561
|
+
if (oldEnd >= start) {
|
|
562
|
+
children[i + 1] = oldStart <= start ? -2 : -1
|
|
563
|
+
} else if (newStart >= offset && dSize) {
|
|
564
|
+
;(children[i] as number) += dSize
|
|
565
|
+
;(children[i + 1] as number) += dSize
|
|
566
|
+
}
|
|
563
567
|
}
|
|
564
|
-
|
|
568
|
+
moved += dSize
|
|
569
|
+
})
|
|
565
570
|
}
|
|
566
|
-
for (let i = 0; i < mapping.maps.length; i++) mapping.maps[i].forEach(shift)
|
|
567
571
|
|
|
568
572
|
// Find the child nodes that still correspond to a single node,
|
|
569
573
|
// recursively call mapInner on them and update their positions.
|
package/src/domcoords.ts
CHANGED
|
@@ -341,7 +341,7 @@ export function coordsAtPos(view: EditorView, pos: number, side: number): Rect {
|
|
|
341
341
|
else if (side >= 0 && offset == node.nodeValue!.length) { from--; takeSide = 1 }
|
|
342
342
|
else if (side < 0) { from-- }
|
|
343
343
|
else { to ++ }
|
|
344
|
-
return flattenV(singleRect(textRange(node as Text, from, to),
|
|
344
|
+
return flattenV(singleRect(textRange(node as Text, from, to), 1), takeSide < 0)
|
|
345
345
|
}
|
|
346
346
|
}
|
|
347
347
|
|
package/src/domobserver.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import {Selection} from "prosemirror-state"
|
|
1
2
|
import * as browser from "./browser"
|
|
2
|
-
import {domIndex, isEquivalentPosition} from "./dom"
|
|
3
|
-
import {hasFocusAndSelection, selectionToDOM} from "./selection"
|
|
3
|
+
import {domIndex, isEquivalentPosition, selectionCollapsed, DOMSelection} from "./dom"
|
|
4
|
+
import {hasFocusAndSelection, selectionToDOM, selectionFromDOM} from "./selection"
|
|
4
5
|
import {EditorView} from "./index"
|
|
5
6
|
|
|
6
7
|
const observeOptions = {
|
|
@@ -20,7 +21,7 @@ class SelectionState {
|
|
|
20
21
|
focusNode: Node | null = null
|
|
21
22
|
focusOffset: number = 0
|
|
22
23
|
|
|
23
|
-
set(sel:
|
|
24
|
+
set(sel: DOMSelection) {
|
|
24
25
|
this.anchorNode = sel.anchorNode; this.anchorOffset = sel.anchorOffset
|
|
25
26
|
this.focusNode = sel.focusNode; this.focusOffset = sel.focusOffset
|
|
26
27
|
}
|
|
@@ -29,7 +30,7 @@ class SelectionState {
|
|
|
29
30
|
this.anchorNode = this.focusNode = null
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
eq(sel:
|
|
33
|
+
eq(sel: DOMSelection) {
|
|
33
34
|
return sel.anchorNode == this.anchorNode && sel.anchorOffset == this.anchorOffset &&
|
|
34
35
|
sel.focusNode == this.focusNode && sel.focusOffset == this.focusOffset
|
|
35
36
|
}
|
|
@@ -138,7 +139,7 @@ export class DOMObserver {
|
|
|
138
139
|
this.currentSelection.set(this.view.domSelection())
|
|
139
140
|
}
|
|
140
141
|
|
|
141
|
-
ignoreSelectionChange(sel:
|
|
142
|
+
ignoreSelectionChange(sel: DOMSelection) {
|
|
142
143
|
if (sel.rangeCount == 0) return true
|
|
143
144
|
let container = sel.getRangeAt(0).commonAncestorContainer
|
|
144
145
|
let desc = this.view.docView.nearestDesc(container)
|
|
@@ -152,18 +153,19 @@ export class DOMObserver {
|
|
|
152
153
|
}
|
|
153
154
|
|
|
154
155
|
flush() {
|
|
155
|
-
|
|
156
|
+
let {view} = this
|
|
157
|
+
if (!view.docView || this.flushingSoon > -1) return
|
|
156
158
|
let mutations = this.observer ? this.observer.takeRecords() : []
|
|
157
159
|
if (this.queue.length) {
|
|
158
160
|
mutations = this.queue.concat(mutations)
|
|
159
161
|
this.queue.length = 0
|
|
160
162
|
}
|
|
161
163
|
|
|
162
|
-
let sel =
|
|
163
|
-
let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(
|
|
164
|
+
let sel = view.domSelection()
|
|
165
|
+
let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel)
|
|
164
166
|
|
|
165
167
|
let from = -1, to = -1, typeOver = false, added: Node[] = []
|
|
166
|
-
if (
|
|
168
|
+
if (view.editable) {
|
|
167
169
|
for (let i = 0; i < mutations.length; i++) {
|
|
168
170
|
let result = this.registerMutation(mutations[i], added)
|
|
169
171
|
if (result) {
|
|
@@ -183,14 +185,26 @@ export class DOMObserver {
|
|
|
183
185
|
}
|
|
184
186
|
}
|
|
185
187
|
|
|
186
|
-
|
|
188
|
+
let readSel: Selection | null = null
|
|
189
|
+
// If it looks like the browser has reset the selection to the
|
|
190
|
+
// start of the document after focus, restore the selection from
|
|
191
|
+
// the state
|
|
192
|
+
if (from < 0 && newSel && view.input.lastFocus > Date.now() - 200 &&
|
|
193
|
+
view.input.lastTouch < Date.now() - 300 &&
|
|
194
|
+
selectionCollapsed(sel) && (readSel = selectionFromDOM(view)) &&
|
|
195
|
+
readSel.eq(Selection.near(view.state.doc.resolve(0), 1))) {
|
|
196
|
+
view.input.lastFocus = 0
|
|
197
|
+
selectionToDOM(view)
|
|
198
|
+
this.currentSelection.set(sel)
|
|
199
|
+
view.scrollToSelection()
|
|
200
|
+
} else if (from > -1 || newSel) {
|
|
187
201
|
if (from > -1) {
|
|
188
|
-
|
|
189
|
-
checkCSS(
|
|
202
|
+
view.docView.markDirty(from, to)
|
|
203
|
+
checkCSS(view)
|
|
190
204
|
}
|
|
191
205
|
this.handleDOMChange(from, to, typeOver, added)
|
|
192
|
-
if (
|
|
193
|
-
else if (!this.currentSelection.eq(sel)) selectionToDOM(
|
|
206
|
+
if (view.docView && view.docView.dirty) view.updateState(view.state)
|
|
207
|
+
else if (!this.currentSelection.eq(sel)) selectionToDOM(view)
|
|
194
208
|
this.currentSelection.set(sel)
|
|
195
209
|
}
|
|
196
210
|
}
|
|
@@ -243,11 +257,16 @@ export class DOMObserver {
|
|
|
243
257
|
}
|
|
244
258
|
}
|
|
245
259
|
|
|
246
|
-
let cssChecked =
|
|
260
|
+
let cssChecked: WeakMap<EditorView, null> = new WeakMap()
|
|
261
|
+
let cssCheckWarned: boolean = false
|
|
247
262
|
|
|
248
263
|
function checkCSS(view: EditorView) {
|
|
249
|
-
if (cssChecked) return
|
|
250
|
-
cssChecked
|
|
251
|
-
if (getComputedStyle(view.dom).whiteSpace
|
|
264
|
+
if (cssChecked.has(view)) return
|
|
265
|
+
cssChecked.set(view, null)
|
|
266
|
+
if (['normal', 'nowrap', 'pre-line'].indexOf(getComputedStyle(view.dom).whiteSpace) !== -1) {
|
|
267
|
+
view.requiresGeckoHackNode = browser.gecko
|
|
268
|
+
if (cssCheckWarned) return
|
|
252
269
|
console["warn"]("ProseMirror expects the CSS white-space property to be set, preferably to 'pre-wrap'. It is recommended to load style/prosemirror.css from the prosemirror-view package.")
|
|
270
|
+
cssCheckWarned = true
|
|
271
|
+
}
|
|
253
272
|
}
|
package/src/index.ts
CHANGED
|
@@ -53,6 +53,10 @@ export class EditorView {
|
|
|
53
53
|
private pluginViews: PluginView[] = []
|
|
54
54
|
/// @internal
|
|
55
55
|
domObserver!: DOMObserver
|
|
56
|
+
/// Holds `true` when a hack node is needed in Firefox to prevent the
|
|
57
|
+
/// [space is eaten issue](https://github.com/ProseMirror/prosemirror/issues/651)
|
|
58
|
+
/// @internal
|
|
59
|
+
requiresGeckoHackNode: boolean = false
|
|
56
60
|
|
|
57
61
|
/// The view's current [state](#state.EditorState).
|
|
58
62
|
public state: EditorState
|
|
@@ -214,20 +218,25 @@ export class EditorView {
|
|
|
214
218
|
if (scroll == "reset") {
|
|
215
219
|
this.dom.scrollTop = 0
|
|
216
220
|
} else if (scroll == "to selection") {
|
|
217
|
-
|
|
218
|
-
if (this.someProp("handleScrollToSelection", f => f(this))) {
|
|
219
|
-
// Handled
|
|
220
|
-
} else if (state.selection instanceof NodeSelection) {
|
|
221
|
-
let target = this.docView.domAfterPos(state.selection.from)
|
|
222
|
-
if (target.nodeType == 1) scrollRectIntoView(this, (target as HTMLElement).getBoundingClientRect(), startDOM)
|
|
223
|
-
} else {
|
|
224
|
-
scrollRectIntoView(this, this.coordsAtPos(state.selection.head, 1), startDOM)
|
|
225
|
-
}
|
|
221
|
+
this.scrollToSelection()
|
|
226
222
|
} else if (oldScrollPos) {
|
|
227
223
|
resetScrollPos(oldScrollPos)
|
|
228
224
|
}
|
|
229
225
|
}
|
|
230
226
|
|
|
227
|
+
/// @internal
|
|
228
|
+
scrollToSelection() {
|
|
229
|
+
let startDOM = this.domSelection().focusNode!
|
|
230
|
+
if (this.someProp("handleScrollToSelection", f => f(this))) {
|
|
231
|
+
// Handled
|
|
232
|
+
} else if (this.state.selection instanceof NodeSelection) {
|
|
233
|
+
let target = this.docView.domAfterPos(this.state.selection.from)
|
|
234
|
+
if (target.nodeType == 1) scrollRectIntoView(this, (target as HTMLElement).getBoundingClientRect(), startDOM)
|
|
235
|
+
} else {
|
|
236
|
+
scrollRectIntoView(this, this.coordsAtPos(this.state.selection.head, 1), startDOM)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
231
240
|
private destroyPluginViews() {
|
|
232
241
|
let view
|
|
233
242
|
while (view = this.pluginViews.pop()) if (view.destroy) view.destroy()
|
|
@@ -623,8 +632,8 @@ export interface EditorProps<P = any> {
|
|
|
623
632
|
/// The `plain` flag will be true when the text is pasted as plain text.
|
|
624
633
|
clipboardTextParser?: (this: P, text: string, $context: ResolvedPos, plain: boolean) => Slice
|
|
625
634
|
|
|
626
|
-
/// Can be used to transform pasted
|
|
627
|
-
/// the document.
|
|
635
|
+
/// Can be used to transform pasted or dragged-and-dropped content
|
|
636
|
+
/// before it is applied to the document.
|
|
628
637
|
transformPasted?: (this: P, slice: Slice) => Slice
|
|
629
638
|
|
|
630
639
|
/// Allows you to pass custom rendering and behavior logic for
|
package/src/input.ts
CHANGED
|
@@ -13,7 +13,8 @@ import {ViewDesc} from "./viewdesc"
|
|
|
13
13
|
// A collection of DOM events that occur within the editor, and callback functions
|
|
14
14
|
// to invoke when the event fires.
|
|
15
15
|
const handlers: {[event: string]: (view: EditorView, event: Event) => void} = {}
|
|
16
|
-
|
|
16
|
+
const editHandlers: {[event: string]: (view: EditorView, event: Event) => void} = {}
|
|
17
|
+
const passiveHandlers: Record<string, boolean> = {touchstart: true, touchmove: true}
|
|
17
18
|
|
|
18
19
|
export class InputState {
|
|
19
20
|
shiftKey = false
|
|
@@ -25,6 +26,8 @@ export class InputState {
|
|
|
25
26
|
lastSelectionTime = 0
|
|
26
27
|
lastIOSEnter = 0
|
|
27
28
|
lastIOSEnterFallbackTimeout = -1
|
|
29
|
+
lastFocus = 0
|
|
30
|
+
lastTouch = 0
|
|
28
31
|
lastAndroidDelete = 0
|
|
29
32
|
composing = false
|
|
30
33
|
composingTimeout = -1
|
|
@@ -42,7 +45,7 @@ export function initInput(view: EditorView) {
|
|
|
42
45
|
if (eventBelongsToView(view, event) && !runCustomHandler(view, event) &&
|
|
43
46
|
(view.editable || !(event.type in editHandlers)))
|
|
44
47
|
handler(view, event)
|
|
45
|
-
})
|
|
48
|
+
}, passiveHandlers[event] ? {passive: true} : undefined)
|
|
46
49
|
}
|
|
47
50
|
// On Safari, for reasons beyond my understanding, adding an input
|
|
48
51
|
// event handler makes an issue where the composition vanishes when
|
|
@@ -403,11 +406,17 @@ class MouseDown {
|
|
|
403
406
|
}
|
|
404
407
|
}
|
|
405
408
|
|
|
406
|
-
handlers.
|
|
409
|
+
handlers.touchstart = view => {
|
|
410
|
+
view.input.lastTouch = Date.now()
|
|
407
411
|
forceDOMFlush(view)
|
|
408
412
|
setSelectionOrigin(view, "pointer")
|
|
409
413
|
}
|
|
410
414
|
|
|
415
|
+
handlers.touchmove = view => {
|
|
416
|
+
view.input.lastTouch = Date.now()
|
|
417
|
+
setSelectionOrigin(view, "pointer")
|
|
418
|
+
}
|
|
419
|
+
|
|
411
420
|
handlers.contextmenu = view => forceDOMFlush(view)
|
|
412
421
|
|
|
413
422
|
function inOrNearComposition(view: EditorView, event: Event) {
|
|
@@ -650,7 +659,6 @@ editHandlers.drop = (view, _event) => {
|
|
|
650
659
|
let eventPos = view.posAtCoords(eventCoords(event))
|
|
651
660
|
if (!eventPos) return
|
|
652
661
|
let $mouse = view.state.doc.resolve(eventPos.pos)
|
|
653
|
-
if (!$mouse) return
|
|
654
662
|
let slice = dragging && dragging.slice
|
|
655
663
|
if (slice) {
|
|
656
664
|
view.someProp("transformPasted", f => { slice = f(slice!) })
|
|
@@ -695,6 +703,7 @@ editHandlers.drop = (view, _event) => {
|
|
|
695
703
|
}
|
|
696
704
|
|
|
697
705
|
handlers.focus = view => {
|
|
706
|
+
view.input.lastFocus = Date.now()
|
|
698
707
|
if (!view.focused) {
|
|
699
708
|
view.domObserver.stop()
|
|
700
709
|
view.dom.classList.add("ProseMirror-focused")
|
package/src/viewdesc.ts
CHANGED
|
@@ -709,7 +709,7 @@ export class NodeViewDesc extends ViewDesc {
|
|
|
709
709
|
let composition = view.composing ? this.localCompositionInfo(view, pos) : null
|
|
710
710
|
let localComposition = composition && composition.pos > -1 ? composition : null
|
|
711
711
|
let compositionInChild = composition && composition.pos < 0
|
|
712
|
-
let updater = new ViewTreeUpdater(this, localComposition && localComposition.node)
|
|
712
|
+
let updater = new ViewTreeUpdater(this, localComposition && localComposition.node, view)
|
|
713
713
|
iterDeco(this.node, this.innerDeco, (widget, i, insideNode) => {
|
|
714
714
|
if (widget.spec.marks)
|
|
715
715
|
updater.syncToMarks(widget.spec.marks, inline, view)
|
|
@@ -1109,7 +1109,7 @@ class ViewTreeUpdater {
|
|
|
1109
1109
|
preMatch: {index: number, matched: Map<ViewDesc, number>, matches: readonly ViewDesc[]}
|
|
1110
1110
|
top: ViewDesc
|
|
1111
1111
|
|
|
1112
|
-
constructor(top: NodeViewDesc, readonly lock: DOMNode | null) {
|
|
1112
|
+
constructor(top: NodeViewDesc, readonly lock: DOMNode | null, private readonly view: EditorView) {
|
|
1113
1113
|
this.top = top
|
|
1114
1114
|
this.preMatch = preMatch(top.node.content, top)
|
|
1115
1115
|
}
|
|
@@ -1273,7 +1273,8 @@ class ViewTreeUpdater {
|
|
|
1273
1273
|
|
|
1274
1274
|
if (!lastChild || // Empty textblock
|
|
1275
1275
|
!(lastChild instanceof TextViewDesc) ||
|
|
1276
|
-
/\n$/.test(lastChild.node.text!)
|
|
1276
|
+
/\n$/.test(lastChild.node.text!) ||
|
|
1277
|
+
(this.view.requiresGeckoHackNode && /\s$/.test(lastChild.node.text!))) {
|
|
1277
1278
|
// Avoid bugs in Safari's cursor drawing (#1165) and Chrome's mouse selection (#1152)
|
|
1278
1279
|
if ((browser.safari || browser.chrome) && lastChild && (lastChild.dom as HTMLElement).contentEditable == "false")
|
|
1279
1280
|
this.addHackNode("IMG", parent)
|