prosemirror-view
Advanced tools
Comparing version
@@ -0,1 +1,11 @@ | ||
## 1.16.0 (2020-10-01) | ||
### Bug fixes | ||
Fix an issue where a drag starting briefly after an aborted drag could confuse the view and break the second drag. Allow callers of coordsAtPos to specify a side | ||
### New features | ||
`EditorView.coordsAtPos` now takes a `side` argument that determines which side of the position to look, if ambiguous. | ||
## 1.15.7 (2020-09-11) | ||
@@ -2,0 +12,0 @@ |
{ | ||
"name": "prosemirror-view", | ||
"version": "1.15.7", | ||
"version": "1.16.0", | ||
"description": "ProseMirror's view component", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -15,4 +15,9 @@ import browser from "./browser" | ||
let reusedRange = null | ||
// Note that this will always return the same range, because DOM range | ||
// objects are every expensive, and keep slowing down subsequent DOM | ||
// updates, for some reason. | ||
export const textRange = function(node, from, to) { | ||
let range = document.createRange() | ||
let range = reusedRange || (reusedRange = document.createRange()) | ||
range.setEnd(node, to == null ? node.nodeValue.length : to) | ||
@@ -19,0 +24,0 @@ range.setStart(node, from || 0) |
@@ -1,2 +0,2 @@ | ||
import {nodeSize, textRange, parentNode} from "./dom" | ||
import {nodeSize, textRange, parentNode, domIndex} from "./dom" | ||
import browser from "./browser" | ||
@@ -288,11 +288,16 @@ | ||
// : (EditorView, number) → {left: number, top: number, right: number, bottom: number} | ||
const BIDI = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/ | ||
// : (EditorView, number, number) → {left: number, top: number, right: number, bottom: number} | ||
// Given a position in the document model, get a bounding box of the | ||
// character at that position, relative to the window. | ||
export function coordsAtPos(view, pos) { | ||
export function coordsAtPos(view, pos, side) { | ||
let {node, offset} = view.docView.domFromPos(pos) | ||
let $pos = view.state.doc.resolve(pos), inline = $pos.parent.inlineContent | ||
// These browsers support querying empty text ranges | ||
if (node.nodeType == 3 && (browser.webkit || browser.gecko)) { | ||
let rect = singleRect(textRange(node, offset, offset), 0) | ||
// These browsers support querying empty text ranges. Prefer that in | ||
// bidi context. | ||
let supportEmptyRange = browser.webkit || browser.gecko | ||
if (node.nodeType == 3 && supportEmptyRange && BIDI.test(node.nodeValue)) { | ||
let rect = singleRect(textRange(node, offset, offset), side) | ||
// Firefox returns bad results (the position before the space) | ||
@@ -312,43 +317,49 @@ // when querying a position directly after line-broken | ||
if (node.nodeType == 1 && !view.state.doc.resolve(pos).parent.inlineContent) { | ||
// Return a horizontal line in block context | ||
let top = true, rect | ||
if (offset < node.childNodes.length) { | ||
let after = node.childNodes[offset] | ||
if (after.nodeType == 1) rect = after.getBoundingClientRect() | ||
// Move up the DOM as far as possible when in inline context. | ||
if (inline) { | ||
let parent = $pos.depth ? view.docView.domAfterPos($pos.before()) : view.dom | ||
while (side < 0 && !offset && node != parent) { | ||
offset = domIndex(node) | ||
node = node.parentNode | ||
} | ||
if (!rect && offset) { | ||
let before = node.childNodes[offset - 1] | ||
if (before.nodeType == 1) { rect = before.getBoundingClientRect(); top = false } | ||
while (side >= 0 && offset == nodeSize(node) && node != parent) { | ||
offset = domIndex(node) + 1 | ||
node = node.parentNode | ||
} | ||
return flattenH(rect || node.getBoundingClientRect(), top) | ||
} | ||
// Not Firefox/Chrome, or not in a text node, so we have to use | ||
// actual element/character rectangles to get a solution (this part | ||
// is not very bidi-safe) | ||
// | ||
// Try the left side first, fall back to the right one if that | ||
// doesn't work. | ||
for (let dir = -1; dir < 2; dir += 2) { | ||
if (dir < 0 && offset) { | ||
let prev, target = node.nodeType == 3 ? textRange(node, offset - 1, offset) | ||
: (prev = node.childNodes[offset - 1]).nodeType == 3 ? textRange(prev) | ||
: prev.nodeType == 1 && prev.nodeName != "BR" ? prev : null // BR nodes tend to only return the rectangle before them | ||
if (target) { | ||
let rect = singleRect(target, 1) | ||
if (rect.top < rect.bottom) return flattenV(rect, false) | ||
} | ||
} else if (dir > 0 && offset < nodeSize(node)) { | ||
let next, target = node.nodeType == 3 ? textRange(node, offset, offset + 1) | ||
: (next = node.childNodes[offset]).nodeType == 3 ? textRange(next) | ||
: next.nodeType == 1 ? next : null | ||
if (target) { | ||
let rect = singleRect(target, -1) | ||
if (rect.top < rect.bottom) return flattenV(rect, true) | ||
} | ||
if (node.nodeType == 3) { | ||
if (side < 0) return flattenV(singleRect(textRange(node, offset - 1, offset), 1), false) | ||
return flattenV(singleRect(textRange(node, offset, offset + 1), -1), true) | ||
} | ||
// Return a horizontal line in block context | ||
if (!inline) { | ||
if (offset && (side < 0 || offset == nodeSize(node))) { | ||
let before = node.childNodes[offset - 1] | ||
if (before.nodeType == 1) return flattenH(before.getBoundingClientRect(), false) | ||
} | ||
if (offset < nodeSize(node)) { | ||
let after = node.childNodes[offset] | ||
if (after.nodeType == 1) return flattenH(after.getBoundingClientRect(), true) | ||
} | ||
return flattenH(node.getBoundingClientRect(), side >= 0) | ||
} | ||
// Inline, not in text node (this is not Bidi-safe) | ||
if (offset && (side < 0 || offset == nodeSize(node))) { | ||
let before = node.childNodes[offset - 1] | ||
let target = before.nodeType == 3 ? textRange(before, nodeSize(before) - (supportEmptyRange ? 0 : 1)) | ||
// BR nodes tend to only return the rectangle before them | ||
: before.nodeType == 1 && before.nodeName != "BR" ? before : null | ||
if (target) return flattenV(singleRect(target, 1), false) | ||
} | ||
if (offset < nodeSize(node)) { | ||
let after = node.childNodes[offset] | ||
let target = after.nodeType == 3 ? textRange(after, 0, (supportEmptyRange ? 0 : 1)) | ||
: after.nodeType == 1 ? after : null | ||
if (target) return flattenV(singleRect(target, -1), true) | ||
} | ||
// All else failed, just try to get a rectangle for the target node | ||
return flattenV(singleRect(node.nodeType == 3 ? textRange(node) : node, 0), false) | ||
return flattenV(singleRect(node.nodeType == 3 ? textRange(node) : node, -side), side >= 0) | ||
} | ||
@@ -394,3 +405,3 @@ | ||
} | ||
let coords = coordsAtPos(view, $pos.pos) | ||
let coords = coordsAtPos(view, $pos.pos, 1) | ||
for (let child = dom.firstChild; child; child = child.nextSibling) { | ||
@@ -397,0 +408,0 @@ let boxes |
@@ -192,3 +192,3 @@ import {NodeSelection} from "prosemirror-state" | ||
else | ||
scrollRectIntoView(this, this.coordsAtPos(state.selection.head), startDOM) | ||
scrollRectIntoView(this, this.coordsAtPos(state.selection.head, 1), startDOM) | ||
} else if (oldScrollPos) { | ||
@@ -279,8 +279,11 @@ resetScrollPos(oldScrollPos) | ||
// :: (number) → {left: number, right: number, top: number, bottom: number} | ||
// Returns the viewport rectangle at a given document position. `left` | ||
// and `right` will be the same number, as this returns a flat | ||
// cursor-ish rectangle. | ||
coordsAtPos(pos) { | ||
return coordsAtPos(this, pos) | ||
// :: (number, number) → {left: number, right: number, top: number, bottom: number} | ||
// Returns the viewport rectangle at a given document position. | ||
// `left` and `right` will be the same number, as this returns a | ||
// flat cursor-ish rectangle. If the position is between two things | ||
// that aren't directly adjacent, `side` determines which element is | ||
// used. When < 0, the element before the position is used, | ||
// otherwise the element after. | ||
coordsAtPos(pos, side = 1) { | ||
return coordsAtPos(this, pos, side) | ||
} | ||
@@ -287,0 +290,0 @@ |
@@ -585,3 +585,6 @@ import {Selection, NodeSelection, TextSelection} from "prosemirror-state" | ||
handlers.dragend = view => { | ||
window.setTimeout(() => view.dragging = null, 50) | ||
let dragging = view.dragging | ||
window.setTimeout(() => { | ||
if (view.dragging == dragging) view.dragging = null | ||
}, 50) | ||
} | ||
@@ -588,0 +591,0 @@ |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
1482581
0.48%13918
0.4%