prosemirror-view 1.27.0 → 1.28.0
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 +28 -0
- package/dist/index.cjs +146 -81
- package/dist/index.d.ts +7 -2
- package/dist/index.js +129 -62
- package/package.json +1 -1
- package/src/clipboard.ts +2 -0
- package/src/decoration.ts +17 -12
- package/src/domchange.ts +8 -9
- package/src/domcoords.ts +1 -1
- package/src/domobserver.ts +37 -18
- package/src/index.ts +39 -11
- package/src/input.ts +22 -8
- package/src/selection.ts +1 -1
- 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/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()
|
|
@@ -283,6 +292,21 @@ export class EditorView {
|
|
|
283
292
|
|
|
284
293
|
/// Query whether the view has focus.
|
|
285
294
|
hasFocus() {
|
|
295
|
+
// Work around IE not handling focus correctly if resize handles are shown.
|
|
296
|
+
// If the cursor is inside an element with resize handles, activeElement
|
|
297
|
+
// will be that element instead of this.dom.
|
|
298
|
+
if (browser.ie) {
|
|
299
|
+
// If activeElement is within this.dom, and there are no other elements
|
|
300
|
+
// setting `contenteditable` to false in between, treat it as focused.
|
|
301
|
+
let node = this.root.activeElement
|
|
302
|
+
if (node == this.dom) return true
|
|
303
|
+
if (!node || !this.dom.contains(node)) return false
|
|
304
|
+
while (node && this.dom != node && this.dom.contains(node)) {
|
|
305
|
+
if ((node as HTMLElement).contentEditable == 'false') return false
|
|
306
|
+
node = node.parentElement
|
|
307
|
+
}
|
|
308
|
+
return true
|
|
309
|
+
}
|
|
286
310
|
return this.root.activeElement == this.dom
|
|
287
311
|
}
|
|
288
312
|
|
|
@@ -623,10 +647,14 @@ export interface EditorProps<P = any> {
|
|
|
623
647
|
/// The `plain` flag will be true when the text is pasted as plain text.
|
|
624
648
|
clipboardTextParser?: (this: P, text: string, $context: ResolvedPos, plain: boolean) => Slice
|
|
625
649
|
|
|
626
|
-
/// Can be used to transform pasted
|
|
627
|
-
/// the document.
|
|
650
|
+
/// Can be used to transform pasted or dragged-and-dropped content
|
|
651
|
+
/// before it is applied to the document.
|
|
628
652
|
transformPasted?: (this: P, slice: Slice) => Slice
|
|
629
653
|
|
|
654
|
+
/// Can be used to transform copied or cut content before it is
|
|
655
|
+
/// serialized to the clipboard.
|
|
656
|
+
transformCopied?: (this: P, slice: Slice) => Slice
|
|
657
|
+
|
|
630
658
|
/// Allows you to pass custom rendering and behavior logic for
|
|
631
659
|
/// nodes. Should map node names to constructor functions that
|
|
632
660
|
/// produce a [`NodeView`](#view.NodeView) object implementing the
|
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
|
|
@@ -369,6 +372,7 @@ class MouseDown {
|
|
|
369
372
|
let pos: {pos: number, inside: number} | null = this.pos
|
|
370
373
|
if (this.view.state.doc != this.startDoc) pos = this.view.posAtCoords(eventCoords(event))
|
|
371
374
|
|
|
375
|
+
this.updateAllowDefault(event)
|
|
372
376
|
if (this.allowDefault || !pos) {
|
|
373
377
|
setSelectionOrigin(this.view, "pointer")
|
|
374
378
|
} else if (handleSingleClick(this.view, pos.pos, pos.inside, event, this.selectNode)) {
|
|
@@ -384,7 +388,7 @@ class MouseDown {
|
|
|
384
388
|
// (hidden) cursor is doesn't change the selection, and
|
|
385
389
|
// thus doesn't get a reaction from ProseMirror. This
|
|
386
390
|
// works around that.
|
|
387
|
-
(browser.chrome && !
|
|
391
|
+
(browser.chrome && !this.view.state.selection.visible &&
|
|
388
392
|
Math.min(Math.abs(pos.pos - this.view.state.selection.from),
|
|
389
393
|
Math.abs(pos.pos - this.view.state.selection.to)) <= 2))) {
|
|
390
394
|
updateSelection(this.view, Selection.near(this.view.state.doc.resolve(pos.pos)), "pointer")
|
|
@@ -395,19 +399,29 @@ class MouseDown {
|
|
|
395
399
|
}
|
|
396
400
|
|
|
397
401
|
move(event: MouseEvent) {
|
|
398
|
-
|
|
399
|
-
Math.abs(this.event.y - event.clientY) > 4))
|
|
400
|
-
this.allowDefault = true
|
|
402
|
+
this.updateAllowDefault(event)
|
|
401
403
|
setSelectionOrigin(this.view, "pointer")
|
|
402
404
|
if (event.buttons == 0) this.done()
|
|
403
405
|
}
|
|
406
|
+
|
|
407
|
+
updateAllowDefault(event: MouseEvent) {
|
|
408
|
+
if (!this.allowDefault && (Math.abs(this.event.x - event.clientX) > 4 ||
|
|
409
|
+
Math.abs(this.event.y - event.clientY) > 4))
|
|
410
|
+
this.allowDefault = true
|
|
411
|
+
}
|
|
404
412
|
}
|
|
405
413
|
|
|
406
|
-
handlers.
|
|
414
|
+
handlers.touchstart = view => {
|
|
415
|
+
view.input.lastTouch = Date.now()
|
|
407
416
|
forceDOMFlush(view)
|
|
408
417
|
setSelectionOrigin(view, "pointer")
|
|
409
418
|
}
|
|
410
419
|
|
|
420
|
+
handlers.touchmove = view => {
|
|
421
|
+
view.input.lastTouch = Date.now()
|
|
422
|
+
setSelectionOrigin(view, "pointer")
|
|
423
|
+
}
|
|
424
|
+
|
|
411
425
|
handlers.contextmenu = view => forceDOMFlush(view)
|
|
412
426
|
|
|
413
427
|
function inOrNearComposition(view: EditorView, event: Event) {
|
|
@@ -650,7 +664,6 @@ editHandlers.drop = (view, _event) => {
|
|
|
650
664
|
let eventPos = view.posAtCoords(eventCoords(event))
|
|
651
665
|
if (!eventPos) return
|
|
652
666
|
let $mouse = view.state.doc.resolve(eventPos.pos)
|
|
653
|
-
if (!$mouse) return
|
|
654
667
|
let slice = dragging && dragging.slice
|
|
655
668
|
if (slice) {
|
|
656
669
|
view.someProp("transformPasted", f => { slice = f(slice!) })
|
|
@@ -695,6 +708,7 @@ editHandlers.drop = (view, _event) => {
|
|
|
695
708
|
}
|
|
696
709
|
|
|
697
710
|
handlers.focus = view => {
|
|
711
|
+
view.input.lastFocus = Date.now()
|
|
698
712
|
if (!view.focused) {
|
|
699
713
|
view.domObserver.stop()
|
|
700
714
|
view.dom.classList.add("ProseMirror-focused")
|
package/src/selection.ts
CHANGED
|
@@ -181,7 +181,7 @@ export function selectionBetween(view: EditorView, $anchor: ResolvedPos, $head:
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
export function hasFocusAndSelection(view: EditorView) {
|
|
184
|
-
if (view.editable && view.
|
|
184
|
+
if (view.editable && !view.hasFocus()) return false
|
|
185
185
|
return hasSelection(view)
|
|
186
186
|
}
|
|
187
187
|
|
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)
|