@codemirror/view 6.39.0-beta.2 → 6.39.0-beta.4
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/dist/index.cjs +125 -243
- package/dist/index.d.cts +22 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +125 -243
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1598,6 +1598,8 @@ class ChangedRange {
|
|
|
1598
1598
|
let end = ranges[rI + 1];
|
|
1599
1599
|
rI += 2;
|
|
1600
1600
|
toB = Math.max(toB, end);
|
|
1601
|
+
for (let i = dI; i < diff.length && diff[i].fromB <= toB; i++)
|
|
1602
|
+
off = diff[i].toA - diff[i].toB;
|
|
1601
1603
|
toA = Math.max(toA, end + off);
|
|
1602
1604
|
}
|
|
1603
1605
|
else if (dI < diff.length && diff[dI].fromB <= toB) {
|
|
@@ -1750,8 +1752,8 @@ class Tile {
|
|
|
1750
1752
|
get posAtEnd() {
|
|
1751
1753
|
return this.posAtStart + this.length;
|
|
1752
1754
|
}
|
|
1753
|
-
posBefore(tile) {
|
|
1754
|
-
let pos =
|
|
1755
|
+
posBefore(tile, start = this.posAtStart) {
|
|
1756
|
+
let pos = start;
|
|
1755
1757
|
for (let child of this.children) {
|
|
1756
1758
|
if (child == tile)
|
|
1757
1759
|
return pos;
|
|
@@ -3540,199 +3542,6 @@ function groupAt(state$1, pos, bias = 1) {
|
|
|
3540
3542
|
}
|
|
3541
3543
|
return state.EditorSelection.range(from + line.from, to + line.from);
|
|
3542
3544
|
}
|
|
3543
|
-
// Search the DOM for the {node, offset} position closest to the given
|
|
3544
|
-
// coordinates. Very inefficient and crude, but can usually be avoided
|
|
3545
|
-
// by calling caret(Position|Range)FromPoint instead.
|
|
3546
|
-
function getdx(x, rect) {
|
|
3547
|
-
return rect.left > x ? rect.left - x : Math.max(0, x - rect.right);
|
|
3548
|
-
}
|
|
3549
|
-
function getdy(y, rect) {
|
|
3550
|
-
return rect.top > y ? rect.top - y : Math.max(0, y - rect.bottom);
|
|
3551
|
-
}
|
|
3552
|
-
function yOverlap(a, b) {
|
|
3553
|
-
return a.top < b.bottom - 1 && a.bottom > b.top + 1;
|
|
3554
|
-
}
|
|
3555
|
-
function upTop(rect, top) {
|
|
3556
|
-
return top < rect.top ? { top, left: rect.left, right: rect.right, bottom: rect.bottom } : rect;
|
|
3557
|
-
}
|
|
3558
|
-
function upBot(rect, bottom) {
|
|
3559
|
-
return bottom > rect.bottom ? { top: rect.top, left: rect.left, right: rect.right, bottom } : rect;
|
|
3560
|
-
}
|
|
3561
|
-
function domPosAtCoords(parent, x, y) {
|
|
3562
|
-
let closest, closestRect, closestX, closestY, closestOverlap = false;
|
|
3563
|
-
let above, below, aboveRect, belowRect;
|
|
3564
|
-
for (let child = parent.firstChild; child; child = child.nextSibling) {
|
|
3565
|
-
let rects = clientRectsFor(child);
|
|
3566
|
-
for (let i = 0; i < rects.length; i++) {
|
|
3567
|
-
let rect = rects[i];
|
|
3568
|
-
if (closestRect && yOverlap(closestRect, rect))
|
|
3569
|
-
rect = upTop(upBot(rect, closestRect.bottom), closestRect.top);
|
|
3570
|
-
let dx = getdx(x, rect), dy = getdy(y, rect);
|
|
3571
|
-
if (dx == 0 && dy == 0)
|
|
3572
|
-
return child.nodeType == 3 ? domPosInText(child, x, y) : domPosAtCoords(child, x, y);
|
|
3573
|
-
if (!closest || closestY > dy || closestY == dy && closestX > dx) {
|
|
3574
|
-
closest = child;
|
|
3575
|
-
closestRect = rect;
|
|
3576
|
-
closestX = dx;
|
|
3577
|
-
closestY = dy;
|
|
3578
|
-
closestOverlap = !dx ? true : x < rect.left ? i > 0 : i < rects.length - 1;
|
|
3579
|
-
}
|
|
3580
|
-
if (dx == 0) {
|
|
3581
|
-
if (y > rect.bottom && (!aboveRect || aboveRect.bottom < rect.bottom)) {
|
|
3582
|
-
above = child;
|
|
3583
|
-
aboveRect = rect;
|
|
3584
|
-
}
|
|
3585
|
-
else if (y < rect.top && (!belowRect || belowRect.top > rect.top)) {
|
|
3586
|
-
below = child;
|
|
3587
|
-
belowRect = rect;
|
|
3588
|
-
}
|
|
3589
|
-
}
|
|
3590
|
-
else if (aboveRect && yOverlap(aboveRect, rect)) {
|
|
3591
|
-
aboveRect = upBot(aboveRect, rect.bottom);
|
|
3592
|
-
}
|
|
3593
|
-
else if (belowRect && yOverlap(belowRect, rect)) {
|
|
3594
|
-
belowRect = upTop(belowRect, rect.top);
|
|
3595
|
-
}
|
|
3596
|
-
}
|
|
3597
|
-
}
|
|
3598
|
-
if (aboveRect && aboveRect.bottom >= y) {
|
|
3599
|
-
closest = above;
|
|
3600
|
-
closestRect = aboveRect;
|
|
3601
|
-
}
|
|
3602
|
-
else if (belowRect && belowRect.top <= y) {
|
|
3603
|
-
closest = below;
|
|
3604
|
-
closestRect = belowRect;
|
|
3605
|
-
}
|
|
3606
|
-
if (!closest)
|
|
3607
|
-
return { node: parent, offset: 0 };
|
|
3608
|
-
let clipX = Math.max(closestRect.left, Math.min(closestRect.right, x));
|
|
3609
|
-
if (closest.nodeType == 3)
|
|
3610
|
-
return domPosInText(closest, clipX, y);
|
|
3611
|
-
if (closestOverlap && closest.contentEditable != "false")
|
|
3612
|
-
return domPosAtCoords(closest, clipX, y);
|
|
3613
|
-
let offset = Array.prototype.indexOf.call(parent.childNodes, closest) +
|
|
3614
|
-
(x >= (closestRect.left + closestRect.right) / 2 ? 1 : 0);
|
|
3615
|
-
return { node: parent, offset };
|
|
3616
|
-
}
|
|
3617
|
-
function domPosInText(node, x, y) {
|
|
3618
|
-
let len = node.nodeValue.length;
|
|
3619
|
-
let closestOffset = -1, closestDY = 1e9, generalSide = 0;
|
|
3620
|
-
for (let i = 0; i < len; i++) {
|
|
3621
|
-
let rects = textRange(node, i, i + 1).getClientRects();
|
|
3622
|
-
for (let j = 0; j < rects.length; j++) {
|
|
3623
|
-
let rect = rects[j];
|
|
3624
|
-
if (rect.top == rect.bottom)
|
|
3625
|
-
continue;
|
|
3626
|
-
if (!generalSide)
|
|
3627
|
-
generalSide = x - rect.left;
|
|
3628
|
-
let dy = (rect.top > y ? rect.top - y : y - rect.bottom) - 1;
|
|
3629
|
-
if (rect.left - 1 <= x && rect.right + 1 >= x && dy < closestDY) {
|
|
3630
|
-
let right = x >= (rect.left + rect.right) / 2, after = right;
|
|
3631
|
-
if (browser.chrome || browser.gecko) {
|
|
3632
|
-
// Check for RTL on browsers that support getting client
|
|
3633
|
-
// rects for empty ranges.
|
|
3634
|
-
let rectBefore = textRange(node, i).getBoundingClientRect();
|
|
3635
|
-
if (Math.abs(rectBefore.left - rect.right) < 0.1)
|
|
3636
|
-
after = !right;
|
|
3637
|
-
}
|
|
3638
|
-
if (dy <= 0)
|
|
3639
|
-
return { node, offset: i + (after ? 1 : 0) };
|
|
3640
|
-
closestOffset = i + (after ? 1 : 0);
|
|
3641
|
-
closestDY = dy;
|
|
3642
|
-
}
|
|
3643
|
-
}
|
|
3644
|
-
}
|
|
3645
|
-
return { node, offset: closestOffset > -1 ? closestOffset : generalSide > 0 ? node.nodeValue.length : 0 };
|
|
3646
|
-
}
|
|
3647
|
-
function posAtCoords(view, coords, precise, bias = -1) {
|
|
3648
|
-
var _a;
|
|
3649
|
-
let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
|
|
3650
|
-
let block, { docHeight } = view.viewState;
|
|
3651
|
-
let { x, y } = coords, yOffset = y - docTop;
|
|
3652
|
-
if (yOffset < 0)
|
|
3653
|
-
return 0;
|
|
3654
|
-
if (yOffset > docHeight)
|
|
3655
|
-
return view.state.doc.length;
|
|
3656
|
-
// Scan for a text block near the queried y position
|
|
3657
|
-
for (let halfLine = view.viewState.heightOracle.textHeight / 2, bounced = false;;) {
|
|
3658
|
-
block = view.elementAtHeight(yOffset);
|
|
3659
|
-
if (block.type == exports.BlockType.Text)
|
|
3660
|
-
break;
|
|
3661
|
-
for (;;) {
|
|
3662
|
-
// Move the y position out of this block
|
|
3663
|
-
yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
|
|
3664
|
-
if (yOffset >= 0 && yOffset <= docHeight)
|
|
3665
|
-
break;
|
|
3666
|
-
// If the document consists entirely of replaced widgets, we
|
|
3667
|
-
// won't find a text block, so return 0
|
|
3668
|
-
if (bounced)
|
|
3669
|
-
return precise ? null : 0;
|
|
3670
|
-
bounced = true;
|
|
3671
|
-
bias = -bias;
|
|
3672
|
-
}
|
|
3673
|
-
}
|
|
3674
|
-
y = docTop + yOffset;
|
|
3675
|
-
let lineStart = block.from;
|
|
3676
|
-
// If this is outside of the rendered viewport, we can't determine a position
|
|
3677
|
-
if (lineStart < view.viewport.from)
|
|
3678
|
-
return view.viewport.from == 0 ? 0 : precise ? null : posAtCoordsImprecise(view, content, block, x, y);
|
|
3679
|
-
if (lineStart > view.viewport.to)
|
|
3680
|
-
return view.viewport.to == view.state.doc.length ? view.state.doc.length :
|
|
3681
|
-
precise ? null : posAtCoordsImprecise(view, content, block, x, y);
|
|
3682
|
-
// Prefer ShadowRootOrDocument.elementFromPoint if present, fall back to document if not
|
|
3683
|
-
let doc = view.dom.ownerDocument;
|
|
3684
|
-
let root = view.root.elementFromPoint ? view.root : doc;
|
|
3685
|
-
let element = root.elementFromPoint(x, y);
|
|
3686
|
-
if (element && !view.contentDOM.contains(element))
|
|
3687
|
-
element = null;
|
|
3688
|
-
// If the element is unexpected, clip x at the sides of the content area and try again
|
|
3689
|
-
if (!element) {
|
|
3690
|
-
x = Math.max(content.left + 1, Math.min(content.right - 1, x));
|
|
3691
|
-
element = root.elementFromPoint(x, y);
|
|
3692
|
-
if (element && !view.contentDOM.contains(element))
|
|
3693
|
-
element = null;
|
|
3694
|
-
}
|
|
3695
|
-
// There's visible editor content under the point, so we can try
|
|
3696
|
-
// using caret(Position|Range)FromPoint as a shortcut
|
|
3697
|
-
let node, offset = -1;
|
|
3698
|
-
if (element && !((_a = view.docView.tile.nearest(element)) === null || _a === void 0 ? void 0 : _a.isWidget())) {
|
|
3699
|
-
if (doc.caretPositionFromPoint) {
|
|
3700
|
-
let pos = doc.caretPositionFromPoint(x, y);
|
|
3701
|
-
if (pos)
|
|
3702
|
-
({ offsetNode: node, offset } = pos);
|
|
3703
|
-
}
|
|
3704
|
-
else if (doc.caretRangeFromPoint) {
|
|
3705
|
-
let range = doc.caretRangeFromPoint(x, y);
|
|
3706
|
-
if (range)
|
|
3707
|
-
({ startContainer: node, startOffset: offset } = range);
|
|
3708
|
-
}
|
|
3709
|
-
if (node && (!view.contentDOM.contains(node) ||
|
|
3710
|
-
browser.safari && isSuspiciousSafariCaretResult(node, offset, x) ||
|
|
3711
|
-
browser.chrome && isSuspiciousChromeCaretResult(node, offset, x)))
|
|
3712
|
-
node = undefined;
|
|
3713
|
-
// Chrome will return offsets into <input> elements without child
|
|
3714
|
-
// nodes, which will lead to a null deref below, so clip the
|
|
3715
|
-
// offset to the node size.
|
|
3716
|
-
if (node)
|
|
3717
|
-
offset = Math.min(maxOffset(node), offset);
|
|
3718
|
-
}
|
|
3719
|
-
// No luck, do our own (potentially expensive) search
|
|
3720
|
-
if (!node || !view.contentDOM.contains(node)) {
|
|
3721
|
-
let line = view.docView.lineAt(lineStart);
|
|
3722
|
-
if (!line)
|
|
3723
|
-
return yOffset > block.top + block.height / 2 ? block.to : block.from;
|
|
3724
|
-
({ node, offset } = domPosAtCoords(line.dom, x, y));
|
|
3725
|
-
}
|
|
3726
|
-
let nearest = view.docView.tile.nearest(node);
|
|
3727
|
-
if (!nearest)
|
|
3728
|
-
return null;
|
|
3729
|
-
if (nearest.isWidget()) {
|
|
3730
|
-
let rect = nearest.dom.getBoundingClientRect();
|
|
3731
|
-
return coords.y < rect.top || coords.y <= rect.bottom && coords.x <= (rect.left + rect.right) / 2
|
|
3732
|
-
? nearest.posAtStart : nearest.posAtEnd;
|
|
3733
|
-
}
|
|
3734
|
-
return view.docView.posFromDOM(node, offset);
|
|
3735
|
-
}
|
|
3736
3545
|
function posAtCoordsImprecise(view, contentRect, block, x, y) {
|
|
3737
3546
|
let into = Math.round((x - contentRect.left) * view.defaultCharacterWidth);
|
|
3738
3547
|
if (view.lineWrapping && block.height > view.defaultLineHeight * 1.5) {
|
|
@@ -3743,49 +3552,6 @@ function posAtCoordsImprecise(view, contentRect, block, x, y) {
|
|
|
3743
3552
|
let content = view.state.sliceDoc(block.from, block.to);
|
|
3744
3553
|
return block.from + state.findColumn(content, into, view.state.tabSize);
|
|
3745
3554
|
}
|
|
3746
|
-
function isEndOfLineBefore(node, offset, x) {
|
|
3747
|
-
let len, scan = node;
|
|
3748
|
-
if (node.nodeType != 3 || offset != (len = node.nodeValue.length))
|
|
3749
|
-
return false;
|
|
3750
|
-
for (;;) { // Check that there is no content after this node
|
|
3751
|
-
let next = scan.nextSibling;
|
|
3752
|
-
if (next) {
|
|
3753
|
-
if (next.nodeName == "BR")
|
|
3754
|
-
break;
|
|
3755
|
-
return false;
|
|
3756
|
-
}
|
|
3757
|
-
else {
|
|
3758
|
-
let parent = scan.parentNode;
|
|
3759
|
-
if (!parent || parent.nodeName == "DIV")
|
|
3760
|
-
break;
|
|
3761
|
-
scan = parent;
|
|
3762
|
-
}
|
|
3763
|
-
}
|
|
3764
|
-
return textRange(node, len - 1, len).getBoundingClientRect().right > x;
|
|
3765
|
-
}
|
|
3766
|
-
// In case of a high line height, Safari's caretRangeFromPoint treats
|
|
3767
|
-
// the space between lines as belonging to the last character of the
|
|
3768
|
-
// line before. This is used to detect such a result so that it can be
|
|
3769
|
-
// ignored (issue #401).
|
|
3770
|
-
function isSuspiciousSafariCaretResult(node, offset, x) {
|
|
3771
|
-
return isEndOfLineBefore(node, offset, x);
|
|
3772
|
-
}
|
|
3773
|
-
// Chrome will move positions between lines to the start of the next line
|
|
3774
|
-
function isSuspiciousChromeCaretResult(node, offset, x) {
|
|
3775
|
-
if (offset != 0)
|
|
3776
|
-
return isEndOfLineBefore(node, offset, x);
|
|
3777
|
-
for (let cur = node;;) {
|
|
3778
|
-
let parent = cur.parentNode;
|
|
3779
|
-
if (!parent || parent.nodeType != 1 || parent.firstChild != cur)
|
|
3780
|
-
return false;
|
|
3781
|
-
if (parent.classList.contains("cm-line"))
|
|
3782
|
-
break;
|
|
3783
|
-
cur = parent;
|
|
3784
|
-
}
|
|
3785
|
-
let rect = node.nodeType == 1 ? node.getBoundingClientRect()
|
|
3786
|
-
: textRange(node, 0, Math.max(node.nodeValue.length, 1)).getBoundingClientRect();
|
|
3787
|
-
return x - rect.left > 5;
|
|
3788
|
-
}
|
|
3789
3555
|
function blockAt(view, pos, side) {
|
|
3790
3556
|
let line = view.lineBlockAt(pos);
|
|
3791
3557
|
if (Array.isArray(line.type)) {
|
|
@@ -3875,11 +3641,7 @@ function moveVertically(view, start, forward, distance) {
|
|
|
3875
3641
|
for (let extra = 0;; extra += 10) {
|
|
3876
3642
|
let curY = startY + (dist + extra) * dir;
|
|
3877
3643
|
let pos = posAtCoords(view, { x: resolvedGoal, y: curY }, false, dir);
|
|
3878
|
-
|
|
3879
|
-
let charRect = view.docView.coordsForChar(pos);
|
|
3880
|
-
let assoc = !charRect || curY < charRect.top ? -1 : 1;
|
|
3881
|
-
return state.EditorSelection.cursor(pos, assoc, undefined, goal);
|
|
3882
|
-
}
|
|
3644
|
+
return state.EditorSelection.cursor(pos.pos, pos.assoc, undefined, goal);
|
|
3883
3645
|
}
|
|
3884
3646
|
}
|
|
3885
3647
|
function skipAtomicRanges(atoms, pos, bias) {
|
|
@@ -3925,6 +3687,121 @@ function skipAtoms(view, oldPos, pos) {
|
|
|
3925
3687
|
let newPos = skipAtomicRanges(view.state.facet(atomicRanges).map(f => f(view)), pos.from, oldPos.head > pos.from ? -1 : 1);
|
|
3926
3688
|
return newPos == pos.from ? pos : state.EditorSelection.cursor(newPos, newPos < pos.from ? 1 : -1);
|
|
3927
3689
|
}
|
|
3690
|
+
class PosAssoc {
|
|
3691
|
+
constructor(pos, assoc) {
|
|
3692
|
+
this.pos = pos;
|
|
3693
|
+
this.assoc = assoc;
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
function posAtCoords(view, coords, precise, scanY) {
|
|
3697
|
+
let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
|
|
3698
|
+
let { x, y } = coords, yOffset = y - docTop, block;
|
|
3699
|
+
// First find the block at the given Y position, if any. If scanY is
|
|
3700
|
+
// given (used for vertical cursor motion), try to skip widgets.
|
|
3701
|
+
for (;;) {
|
|
3702
|
+
if (yOffset < 0)
|
|
3703
|
+
return new PosAssoc(0, 1);
|
|
3704
|
+
if (yOffset > view.viewState.docHeight)
|
|
3705
|
+
return new PosAssoc(view.state.doc.length, -1);
|
|
3706
|
+
block = view.elementAtHeight(yOffset);
|
|
3707
|
+
if (scanY == null || block.type == exports.BlockType.Text)
|
|
3708
|
+
break;
|
|
3709
|
+
let halfLine = view.viewState.heightOracle.textHeight / 2;
|
|
3710
|
+
yOffset = scanY > 0 ? block.bottom + halfLine : block.top - halfLine;
|
|
3711
|
+
}
|
|
3712
|
+
// If outside the viewport, return null if precise==true, an
|
|
3713
|
+
// estimate otherwise.
|
|
3714
|
+
if (view.viewport.from >= block.to || view.viewport.to <= block.from) {
|
|
3715
|
+
if (precise)
|
|
3716
|
+
return null;
|
|
3717
|
+
if (block.type == exports.BlockType.Text) {
|
|
3718
|
+
let pos = posAtCoordsImprecise(view, content, block, x, y);
|
|
3719
|
+
return new PosAssoc(pos, pos == block.from ? 1 : -1);
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
if (block.type != exports.BlockType.Text)
|
|
3723
|
+
return yOffset < (block.top + block.bottom) / 2 ? new PosAssoc(block.from, 1) : new PosAssoc(block.to, -1);
|
|
3724
|
+
// Here we know we're in a line, so run the logic for inline layout
|
|
3725
|
+
return posAtCoordsInline(view, view.docView.lineAt(block.from), block.from, x, y);
|
|
3726
|
+
}
|
|
3727
|
+
// Scan through the rectangles for the content of a tile, finding the
|
|
3728
|
+
// one closest to the given coordinates, prefering closeness in Y over
|
|
3729
|
+
// closeness in X.
|
|
3730
|
+
//
|
|
3731
|
+
// If this is a text tile, go character-by-character. For line or mark
|
|
3732
|
+
// tiles, check each non-point-widget child, and descend text or mark
|
|
3733
|
+
// tiles with a recursive call.
|
|
3734
|
+
//
|
|
3735
|
+
// For non-wrapped, purely left-to-right text, this could use a binary
|
|
3736
|
+
// search. But because this seems to be fast enough, for how often it
|
|
3737
|
+
// is called, there's not currently a specialized implementation for
|
|
3738
|
+
// that.
|
|
3739
|
+
function posAtCoordsInline(view, tile, offset, x, y) {
|
|
3740
|
+
let closest = -1, closestRect = null;
|
|
3741
|
+
let dxClosest = 1e9, dyClosest = 1e9;
|
|
3742
|
+
let rowTop = y, rowBot = y;
|
|
3743
|
+
let checkRects = (rects, index) => {
|
|
3744
|
+
for (let i = 0; i < rects.length; i++) {
|
|
3745
|
+
let rect = rects[i];
|
|
3746
|
+
if (rect.top == rect.bottom)
|
|
3747
|
+
continue;
|
|
3748
|
+
let dx = rect.left > x ? rect.left - x : rect.right < x ? x - rect.right : 0;
|
|
3749
|
+
let dy = rect.top > y ? rect.top - y : rect.bottom < y ? y - rect.bottom : 0;
|
|
3750
|
+
if (rect.top <= rowBot && rect.bottom >= rowTop) {
|
|
3751
|
+
// Rectangle is in the current row
|
|
3752
|
+
rowTop = Math.min(rect.top, rowTop);
|
|
3753
|
+
rowBot = Math.max(rect.bottom, rowBot);
|
|
3754
|
+
dy = 0;
|
|
3755
|
+
}
|
|
3756
|
+
if (closest < 0 || (dy - dyClosest || dx - dxClosest) < 0) {
|
|
3757
|
+
if (closest >= 0 && dyClosest && dxClosest < dx &&
|
|
3758
|
+
closestRect.top <= rowBot - 2 && closestRect.bottom >= rowTop + 2) {
|
|
3759
|
+
// Retroactively set dy to 0 if the current match is in this row.
|
|
3760
|
+
dyClosest = 0;
|
|
3761
|
+
}
|
|
3762
|
+
else {
|
|
3763
|
+
closest = index;
|
|
3764
|
+
dxClosest = dx;
|
|
3765
|
+
dyClosest = dy;
|
|
3766
|
+
closestRect = rect;
|
|
3767
|
+
}
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
};
|
|
3771
|
+
if (tile.isText()) {
|
|
3772
|
+
for (let i = 0; i < tile.length;) {
|
|
3773
|
+
let next = state.findClusterBreak(tile.text, i);
|
|
3774
|
+
checkRects(textRange(tile.dom, i, next).getClientRects(), i);
|
|
3775
|
+
if (!dxClosest && !dyClosest)
|
|
3776
|
+
break;
|
|
3777
|
+
i = next;
|
|
3778
|
+
}
|
|
3779
|
+
let after = (x > (closestRect.left + closestRect.right) / 2) == (dirAt(view, closest + offset) == exports.Direction.LTR);
|
|
3780
|
+
return after ? new PosAssoc(offset + state.findClusterBreak(tile.text, closest), -1) : new PosAssoc(offset + closest, 1);
|
|
3781
|
+
}
|
|
3782
|
+
else {
|
|
3783
|
+
if (!tile.length)
|
|
3784
|
+
return new PosAssoc(offset, 1);
|
|
3785
|
+
for (let i = 0; i < tile.children.length; i++) {
|
|
3786
|
+
let child = tile.children[i];
|
|
3787
|
+
if (child.flags & 48 /* TileFlag.PointWidget */)
|
|
3788
|
+
continue;
|
|
3789
|
+
let rects = (child.dom.nodeType == 1 ? child.dom : textRange(child.dom, 0, child.length)).getClientRects();
|
|
3790
|
+
checkRects(rects, i);
|
|
3791
|
+
if (!dxClosest && !dyClosest)
|
|
3792
|
+
break;
|
|
3793
|
+
}
|
|
3794
|
+
let inner = tile.children[closest], innerOff = tile.posBefore(inner, offset);
|
|
3795
|
+
if (inner.isComposite() || inner.isText())
|
|
3796
|
+
return posAtCoordsInline(view, inner, innerOff, Math.max(closestRect.left, Math.min(closestRect.right, x)), y);
|
|
3797
|
+
let after = (x > (closestRect.left + closestRect.right) / 2) == (dirAt(view, closest + offset) == exports.Direction.LTR);
|
|
3798
|
+
return after ? new PosAssoc(innerOff + inner.length, -1) : new PosAssoc(innerOff, 1);
|
|
3799
|
+
}
|
|
3800
|
+
}
|
|
3801
|
+
function dirAt(view, pos) {
|
|
3802
|
+
let line = view.state.doc.lineAt(pos), spans = view.bidiSpans(line);
|
|
3803
|
+
return spans[BidiSpan.find(view.bidiSpans(line), pos - line.from, -1, 1)].dir;
|
|
3804
|
+
}
|
|
3928
3805
|
|
|
3929
3806
|
const LineBreakPlaceholder = "\uffff";
|
|
3930
3807
|
class DOMReader {
|
|
@@ -8354,6 +8231,11 @@ class EditorView {
|
|
|
8354
8231
|
return this.docView.posFromDOM(node, offset);
|
|
8355
8232
|
}
|
|
8356
8233
|
posAtCoords(coords, precise = true) {
|
|
8234
|
+
this.readMeasured();
|
|
8235
|
+
let found = posAtCoords(this, coords, precise);
|
|
8236
|
+
return found && found.pos;
|
|
8237
|
+
}
|
|
8238
|
+
posAndSideAtCoords(coords, precise = true) {
|
|
8357
8239
|
this.readMeasured();
|
|
8358
8240
|
return posAtCoords(this, coords, precise);
|
|
8359
8241
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -1017,6 +1017,28 @@ declare class EditorView {
|
|
|
1017
1017
|
y: number;
|
|
1018
1018
|
}): number | null;
|
|
1019
1019
|
/**
|
|
1020
|
+
Like [`posAtCoords`](https://codemirror.net/6/docs/ref/#view.EditorView.posAtCoords), but also
|
|
1021
|
+
returns which side of the position the coordinates are closest
|
|
1022
|
+
to. For example, for coordinates on the left side of a
|
|
1023
|
+
left-to-right character, the position before that letter is
|
|
1024
|
+
returned, with `assoc` 1, whereas on the right side, you'd get
|
|
1025
|
+
the position after the character, with `assoc` -1.
|
|
1026
|
+
*/
|
|
1027
|
+
posAndSideAtCoords(coords: {
|
|
1028
|
+
x: number;
|
|
1029
|
+
y: number;
|
|
1030
|
+
}, precise: false): {
|
|
1031
|
+
pos: number;
|
|
1032
|
+
assoc: -1 | 1;
|
|
1033
|
+
};
|
|
1034
|
+
posAndSideAtCoords(coords: {
|
|
1035
|
+
x: number;
|
|
1036
|
+
y: number;
|
|
1037
|
+
}): {
|
|
1038
|
+
pos: number;
|
|
1039
|
+
assoc: -1 | 1;
|
|
1040
|
+
} | null;
|
|
1041
|
+
/**
|
|
1020
1042
|
Get the screen coordinates at the given document position.
|
|
1021
1043
|
`side` determines whether the coordinates are based on the
|
|
1022
1044
|
element before (-1) or after (1) the position (if no element is
|
package/dist/index.d.ts
CHANGED
|
@@ -1017,6 +1017,28 @@ declare class EditorView {
|
|
|
1017
1017
|
y: number;
|
|
1018
1018
|
}): number | null;
|
|
1019
1019
|
/**
|
|
1020
|
+
Like [`posAtCoords`](https://codemirror.net/6/docs/ref/#view.EditorView.posAtCoords), but also
|
|
1021
|
+
returns which side of the position the coordinates are closest
|
|
1022
|
+
to. For example, for coordinates on the left side of a
|
|
1023
|
+
left-to-right character, the position before that letter is
|
|
1024
|
+
returned, with `assoc` 1, whereas on the right side, you'd get
|
|
1025
|
+
the position after the character, with `assoc` -1.
|
|
1026
|
+
*/
|
|
1027
|
+
posAndSideAtCoords(coords: {
|
|
1028
|
+
x: number;
|
|
1029
|
+
y: number;
|
|
1030
|
+
}, precise: false): {
|
|
1031
|
+
pos: number;
|
|
1032
|
+
assoc: -1 | 1;
|
|
1033
|
+
};
|
|
1034
|
+
posAndSideAtCoords(coords: {
|
|
1035
|
+
x: number;
|
|
1036
|
+
y: number;
|
|
1037
|
+
}): {
|
|
1038
|
+
pos: number;
|
|
1039
|
+
assoc: -1 | 1;
|
|
1040
|
+
} | null;
|
|
1041
|
+
/**
|
|
1020
1042
|
Get the screen coordinates at the given document position.
|
|
1021
1043
|
`side` determines whether the coordinates are based on the
|
|
1022
1044
|
element before (-1) or after (1) the position (if no element is
|
package/dist/index.js
CHANGED
|
@@ -1594,6 +1594,8 @@ class ChangedRange {
|
|
|
1594
1594
|
let end = ranges[rI + 1];
|
|
1595
1595
|
rI += 2;
|
|
1596
1596
|
toB = Math.max(toB, end);
|
|
1597
|
+
for (let i = dI; i < diff.length && diff[i].fromB <= toB; i++)
|
|
1598
|
+
off = diff[i].toA - diff[i].toB;
|
|
1597
1599
|
toA = Math.max(toA, end + off);
|
|
1598
1600
|
}
|
|
1599
1601
|
else if (dI < diff.length && diff[dI].fromB <= toB) {
|
|
@@ -1746,8 +1748,8 @@ class Tile {
|
|
|
1746
1748
|
get posAtEnd() {
|
|
1747
1749
|
return this.posAtStart + this.length;
|
|
1748
1750
|
}
|
|
1749
|
-
posBefore(tile) {
|
|
1750
|
-
let pos =
|
|
1751
|
+
posBefore(tile, start = this.posAtStart) {
|
|
1752
|
+
let pos = start;
|
|
1751
1753
|
for (let child of this.children) {
|
|
1752
1754
|
if (child == tile)
|
|
1753
1755
|
return pos;
|
|
@@ -3536,199 +3538,6 @@ function groupAt(state, pos, bias = 1) {
|
|
|
3536
3538
|
}
|
|
3537
3539
|
return EditorSelection.range(from + line.from, to + line.from);
|
|
3538
3540
|
}
|
|
3539
|
-
// Search the DOM for the {node, offset} position closest to the given
|
|
3540
|
-
// coordinates. Very inefficient and crude, but can usually be avoided
|
|
3541
|
-
// by calling caret(Position|Range)FromPoint instead.
|
|
3542
|
-
function getdx(x, rect) {
|
|
3543
|
-
return rect.left > x ? rect.left - x : Math.max(0, x - rect.right);
|
|
3544
|
-
}
|
|
3545
|
-
function getdy(y, rect) {
|
|
3546
|
-
return rect.top > y ? rect.top - y : Math.max(0, y - rect.bottom);
|
|
3547
|
-
}
|
|
3548
|
-
function yOverlap(a, b) {
|
|
3549
|
-
return a.top < b.bottom - 1 && a.bottom > b.top + 1;
|
|
3550
|
-
}
|
|
3551
|
-
function upTop(rect, top) {
|
|
3552
|
-
return top < rect.top ? { top, left: rect.left, right: rect.right, bottom: rect.bottom } : rect;
|
|
3553
|
-
}
|
|
3554
|
-
function upBot(rect, bottom) {
|
|
3555
|
-
return bottom > rect.bottom ? { top: rect.top, left: rect.left, right: rect.right, bottom } : rect;
|
|
3556
|
-
}
|
|
3557
|
-
function domPosAtCoords(parent, x, y) {
|
|
3558
|
-
let closest, closestRect, closestX, closestY, closestOverlap = false;
|
|
3559
|
-
let above, below, aboveRect, belowRect;
|
|
3560
|
-
for (let child = parent.firstChild; child; child = child.nextSibling) {
|
|
3561
|
-
let rects = clientRectsFor(child);
|
|
3562
|
-
for (let i = 0; i < rects.length; i++) {
|
|
3563
|
-
let rect = rects[i];
|
|
3564
|
-
if (closestRect && yOverlap(closestRect, rect))
|
|
3565
|
-
rect = upTop(upBot(rect, closestRect.bottom), closestRect.top);
|
|
3566
|
-
let dx = getdx(x, rect), dy = getdy(y, rect);
|
|
3567
|
-
if (dx == 0 && dy == 0)
|
|
3568
|
-
return child.nodeType == 3 ? domPosInText(child, x, y) : domPosAtCoords(child, x, y);
|
|
3569
|
-
if (!closest || closestY > dy || closestY == dy && closestX > dx) {
|
|
3570
|
-
closest = child;
|
|
3571
|
-
closestRect = rect;
|
|
3572
|
-
closestX = dx;
|
|
3573
|
-
closestY = dy;
|
|
3574
|
-
closestOverlap = !dx ? true : x < rect.left ? i > 0 : i < rects.length - 1;
|
|
3575
|
-
}
|
|
3576
|
-
if (dx == 0) {
|
|
3577
|
-
if (y > rect.bottom && (!aboveRect || aboveRect.bottom < rect.bottom)) {
|
|
3578
|
-
above = child;
|
|
3579
|
-
aboveRect = rect;
|
|
3580
|
-
}
|
|
3581
|
-
else if (y < rect.top && (!belowRect || belowRect.top > rect.top)) {
|
|
3582
|
-
below = child;
|
|
3583
|
-
belowRect = rect;
|
|
3584
|
-
}
|
|
3585
|
-
}
|
|
3586
|
-
else if (aboveRect && yOverlap(aboveRect, rect)) {
|
|
3587
|
-
aboveRect = upBot(aboveRect, rect.bottom);
|
|
3588
|
-
}
|
|
3589
|
-
else if (belowRect && yOverlap(belowRect, rect)) {
|
|
3590
|
-
belowRect = upTop(belowRect, rect.top);
|
|
3591
|
-
}
|
|
3592
|
-
}
|
|
3593
|
-
}
|
|
3594
|
-
if (aboveRect && aboveRect.bottom >= y) {
|
|
3595
|
-
closest = above;
|
|
3596
|
-
closestRect = aboveRect;
|
|
3597
|
-
}
|
|
3598
|
-
else if (belowRect && belowRect.top <= y) {
|
|
3599
|
-
closest = below;
|
|
3600
|
-
closestRect = belowRect;
|
|
3601
|
-
}
|
|
3602
|
-
if (!closest)
|
|
3603
|
-
return { node: parent, offset: 0 };
|
|
3604
|
-
let clipX = Math.max(closestRect.left, Math.min(closestRect.right, x));
|
|
3605
|
-
if (closest.nodeType == 3)
|
|
3606
|
-
return domPosInText(closest, clipX, y);
|
|
3607
|
-
if (closestOverlap && closest.contentEditable != "false")
|
|
3608
|
-
return domPosAtCoords(closest, clipX, y);
|
|
3609
|
-
let offset = Array.prototype.indexOf.call(parent.childNodes, closest) +
|
|
3610
|
-
(x >= (closestRect.left + closestRect.right) / 2 ? 1 : 0);
|
|
3611
|
-
return { node: parent, offset };
|
|
3612
|
-
}
|
|
3613
|
-
function domPosInText(node, x, y) {
|
|
3614
|
-
let len = node.nodeValue.length;
|
|
3615
|
-
let closestOffset = -1, closestDY = 1e9, generalSide = 0;
|
|
3616
|
-
for (let i = 0; i < len; i++) {
|
|
3617
|
-
let rects = textRange(node, i, i + 1).getClientRects();
|
|
3618
|
-
for (let j = 0; j < rects.length; j++) {
|
|
3619
|
-
let rect = rects[j];
|
|
3620
|
-
if (rect.top == rect.bottom)
|
|
3621
|
-
continue;
|
|
3622
|
-
if (!generalSide)
|
|
3623
|
-
generalSide = x - rect.left;
|
|
3624
|
-
let dy = (rect.top > y ? rect.top - y : y - rect.bottom) - 1;
|
|
3625
|
-
if (rect.left - 1 <= x && rect.right + 1 >= x && dy < closestDY) {
|
|
3626
|
-
let right = x >= (rect.left + rect.right) / 2, after = right;
|
|
3627
|
-
if (browser.chrome || browser.gecko) {
|
|
3628
|
-
// Check for RTL on browsers that support getting client
|
|
3629
|
-
// rects for empty ranges.
|
|
3630
|
-
let rectBefore = textRange(node, i).getBoundingClientRect();
|
|
3631
|
-
if (Math.abs(rectBefore.left - rect.right) < 0.1)
|
|
3632
|
-
after = !right;
|
|
3633
|
-
}
|
|
3634
|
-
if (dy <= 0)
|
|
3635
|
-
return { node, offset: i + (after ? 1 : 0) };
|
|
3636
|
-
closestOffset = i + (after ? 1 : 0);
|
|
3637
|
-
closestDY = dy;
|
|
3638
|
-
}
|
|
3639
|
-
}
|
|
3640
|
-
}
|
|
3641
|
-
return { node, offset: closestOffset > -1 ? closestOffset : generalSide > 0 ? node.nodeValue.length : 0 };
|
|
3642
|
-
}
|
|
3643
|
-
function posAtCoords(view, coords, precise, bias = -1) {
|
|
3644
|
-
var _a;
|
|
3645
|
-
let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
|
|
3646
|
-
let block, { docHeight } = view.viewState;
|
|
3647
|
-
let { x, y } = coords, yOffset = y - docTop;
|
|
3648
|
-
if (yOffset < 0)
|
|
3649
|
-
return 0;
|
|
3650
|
-
if (yOffset > docHeight)
|
|
3651
|
-
return view.state.doc.length;
|
|
3652
|
-
// Scan for a text block near the queried y position
|
|
3653
|
-
for (let halfLine = view.viewState.heightOracle.textHeight / 2, bounced = false;;) {
|
|
3654
|
-
block = view.elementAtHeight(yOffset);
|
|
3655
|
-
if (block.type == BlockType.Text)
|
|
3656
|
-
break;
|
|
3657
|
-
for (;;) {
|
|
3658
|
-
// Move the y position out of this block
|
|
3659
|
-
yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
|
|
3660
|
-
if (yOffset >= 0 && yOffset <= docHeight)
|
|
3661
|
-
break;
|
|
3662
|
-
// If the document consists entirely of replaced widgets, we
|
|
3663
|
-
// won't find a text block, so return 0
|
|
3664
|
-
if (bounced)
|
|
3665
|
-
return precise ? null : 0;
|
|
3666
|
-
bounced = true;
|
|
3667
|
-
bias = -bias;
|
|
3668
|
-
}
|
|
3669
|
-
}
|
|
3670
|
-
y = docTop + yOffset;
|
|
3671
|
-
let lineStart = block.from;
|
|
3672
|
-
// If this is outside of the rendered viewport, we can't determine a position
|
|
3673
|
-
if (lineStart < view.viewport.from)
|
|
3674
|
-
return view.viewport.from == 0 ? 0 : precise ? null : posAtCoordsImprecise(view, content, block, x, y);
|
|
3675
|
-
if (lineStart > view.viewport.to)
|
|
3676
|
-
return view.viewport.to == view.state.doc.length ? view.state.doc.length :
|
|
3677
|
-
precise ? null : posAtCoordsImprecise(view, content, block, x, y);
|
|
3678
|
-
// Prefer ShadowRootOrDocument.elementFromPoint if present, fall back to document if not
|
|
3679
|
-
let doc = view.dom.ownerDocument;
|
|
3680
|
-
let root = view.root.elementFromPoint ? view.root : doc;
|
|
3681
|
-
let element = root.elementFromPoint(x, y);
|
|
3682
|
-
if (element && !view.contentDOM.contains(element))
|
|
3683
|
-
element = null;
|
|
3684
|
-
// If the element is unexpected, clip x at the sides of the content area and try again
|
|
3685
|
-
if (!element) {
|
|
3686
|
-
x = Math.max(content.left + 1, Math.min(content.right - 1, x));
|
|
3687
|
-
element = root.elementFromPoint(x, y);
|
|
3688
|
-
if (element && !view.contentDOM.contains(element))
|
|
3689
|
-
element = null;
|
|
3690
|
-
}
|
|
3691
|
-
// There's visible editor content under the point, so we can try
|
|
3692
|
-
// using caret(Position|Range)FromPoint as a shortcut
|
|
3693
|
-
let node, offset = -1;
|
|
3694
|
-
if (element && !((_a = view.docView.tile.nearest(element)) === null || _a === void 0 ? void 0 : _a.isWidget())) {
|
|
3695
|
-
if (doc.caretPositionFromPoint) {
|
|
3696
|
-
let pos = doc.caretPositionFromPoint(x, y);
|
|
3697
|
-
if (pos)
|
|
3698
|
-
({ offsetNode: node, offset } = pos);
|
|
3699
|
-
}
|
|
3700
|
-
else if (doc.caretRangeFromPoint) {
|
|
3701
|
-
let range = doc.caretRangeFromPoint(x, y);
|
|
3702
|
-
if (range)
|
|
3703
|
-
({ startContainer: node, startOffset: offset } = range);
|
|
3704
|
-
}
|
|
3705
|
-
if (node && (!view.contentDOM.contains(node) ||
|
|
3706
|
-
browser.safari && isSuspiciousSafariCaretResult(node, offset, x) ||
|
|
3707
|
-
browser.chrome && isSuspiciousChromeCaretResult(node, offset, x)))
|
|
3708
|
-
node = undefined;
|
|
3709
|
-
// Chrome will return offsets into <input> elements without child
|
|
3710
|
-
// nodes, which will lead to a null deref below, so clip the
|
|
3711
|
-
// offset to the node size.
|
|
3712
|
-
if (node)
|
|
3713
|
-
offset = Math.min(maxOffset(node), offset);
|
|
3714
|
-
}
|
|
3715
|
-
// No luck, do our own (potentially expensive) search
|
|
3716
|
-
if (!node || !view.contentDOM.contains(node)) {
|
|
3717
|
-
let line = view.docView.lineAt(lineStart);
|
|
3718
|
-
if (!line)
|
|
3719
|
-
return yOffset > block.top + block.height / 2 ? block.to : block.from;
|
|
3720
|
-
({ node, offset } = domPosAtCoords(line.dom, x, y));
|
|
3721
|
-
}
|
|
3722
|
-
let nearest = view.docView.tile.nearest(node);
|
|
3723
|
-
if (!nearest)
|
|
3724
|
-
return null;
|
|
3725
|
-
if (nearest.isWidget()) {
|
|
3726
|
-
let rect = nearest.dom.getBoundingClientRect();
|
|
3727
|
-
return coords.y < rect.top || coords.y <= rect.bottom && coords.x <= (rect.left + rect.right) / 2
|
|
3728
|
-
? nearest.posAtStart : nearest.posAtEnd;
|
|
3729
|
-
}
|
|
3730
|
-
return view.docView.posFromDOM(node, offset);
|
|
3731
|
-
}
|
|
3732
3541
|
function posAtCoordsImprecise(view, contentRect, block, x, y) {
|
|
3733
3542
|
let into = Math.round((x - contentRect.left) * view.defaultCharacterWidth);
|
|
3734
3543
|
if (view.lineWrapping && block.height > view.defaultLineHeight * 1.5) {
|
|
@@ -3739,49 +3548,6 @@ function posAtCoordsImprecise(view, contentRect, block, x, y) {
|
|
|
3739
3548
|
let content = view.state.sliceDoc(block.from, block.to);
|
|
3740
3549
|
return block.from + findColumn(content, into, view.state.tabSize);
|
|
3741
3550
|
}
|
|
3742
|
-
function isEndOfLineBefore(node, offset, x) {
|
|
3743
|
-
let len, scan = node;
|
|
3744
|
-
if (node.nodeType != 3 || offset != (len = node.nodeValue.length))
|
|
3745
|
-
return false;
|
|
3746
|
-
for (;;) { // Check that there is no content after this node
|
|
3747
|
-
let next = scan.nextSibling;
|
|
3748
|
-
if (next) {
|
|
3749
|
-
if (next.nodeName == "BR")
|
|
3750
|
-
break;
|
|
3751
|
-
return false;
|
|
3752
|
-
}
|
|
3753
|
-
else {
|
|
3754
|
-
let parent = scan.parentNode;
|
|
3755
|
-
if (!parent || parent.nodeName == "DIV")
|
|
3756
|
-
break;
|
|
3757
|
-
scan = parent;
|
|
3758
|
-
}
|
|
3759
|
-
}
|
|
3760
|
-
return textRange(node, len - 1, len).getBoundingClientRect().right > x;
|
|
3761
|
-
}
|
|
3762
|
-
// In case of a high line height, Safari's caretRangeFromPoint treats
|
|
3763
|
-
// the space between lines as belonging to the last character of the
|
|
3764
|
-
// line before. This is used to detect such a result so that it can be
|
|
3765
|
-
// ignored (issue #401).
|
|
3766
|
-
function isSuspiciousSafariCaretResult(node, offset, x) {
|
|
3767
|
-
return isEndOfLineBefore(node, offset, x);
|
|
3768
|
-
}
|
|
3769
|
-
// Chrome will move positions between lines to the start of the next line
|
|
3770
|
-
function isSuspiciousChromeCaretResult(node, offset, x) {
|
|
3771
|
-
if (offset != 0)
|
|
3772
|
-
return isEndOfLineBefore(node, offset, x);
|
|
3773
|
-
for (let cur = node;;) {
|
|
3774
|
-
let parent = cur.parentNode;
|
|
3775
|
-
if (!parent || parent.nodeType != 1 || parent.firstChild != cur)
|
|
3776
|
-
return false;
|
|
3777
|
-
if (parent.classList.contains("cm-line"))
|
|
3778
|
-
break;
|
|
3779
|
-
cur = parent;
|
|
3780
|
-
}
|
|
3781
|
-
let rect = node.nodeType == 1 ? node.getBoundingClientRect()
|
|
3782
|
-
: textRange(node, 0, Math.max(node.nodeValue.length, 1)).getBoundingClientRect();
|
|
3783
|
-
return x - rect.left > 5;
|
|
3784
|
-
}
|
|
3785
3551
|
function blockAt(view, pos, side) {
|
|
3786
3552
|
let line = view.lineBlockAt(pos);
|
|
3787
3553
|
if (Array.isArray(line.type)) {
|
|
@@ -3871,11 +3637,7 @@ function moveVertically(view, start, forward, distance) {
|
|
|
3871
3637
|
for (let extra = 0;; extra += 10) {
|
|
3872
3638
|
let curY = startY + (dist + extra) * dir;
|
|
3873
3639
|
let pos = posAtCoords(view, { x: resolvedGoal, y: curY }, false, dir);
|
|
3874
|
-
|
|
3875
|
-
let charRect = view.docView.coordsForChar(pos);
|
|
3876
|
-
let assoc = !charRect || curY < charRect.top ? -1 : 1;
|
|
3877
|
-
return EditorSelection.cursor(pos, assoc, undefined, goal);
|
|
3878
|
-
}
|
|
3640
|
+
return EditorSelection.cursor(pos.pos, pos.assoc, undefined, goal);
|
|
3879
3641
|
}
|
|
3880
3642
|
}
|
|
3881
3643
|
function skipAtomicRanges(atoms, pos, bias) {
|
|
@@ -3921,6 +3683,121 @@ function skipAtoms(view, oldPos, pos) {
|
|
|
3921
3683
|
let newPos = skipAtomicRanges(view.state.facet(atomicRanges).map(f => f(view)), pos.from, oldPos.head > pos.from ? -1 : 1);
|
|
3922
3684
|
return newPos == pos.from ? pos : EditorSelection.cursor(newPos, newPos < pos.from ? 1 : -1);
|
|
3923
3685
|
}
|
|
3686
|
+
class PosAssoc {
|
|
3687
|
+
constructor(pos, assoc) {
|
|
3688
|
+
this.pos = pos;
|
|
3689
|
+
this.assoc = assoc;
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
function posAtCoords(view, coords, precise, scanY) {
|
|
3693
|
+
let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
|
|
3694
|
+
let { x, y } = coords, yOffset = y - docTop, block;
|
|
3695
|
+
// First find the block at the given Y position, if any. If scanY is
|
|
3696
|
+
// given (used for vertical cursor motion), try to skip widgets.
|
|
3697
|
+
for (;;) {
|
|
3698
|
+
if (yOffset < 0)
|
|
3699
|
+
return new PosAssoc(0, 1);
|
|
3700
|
+
if (yOffset > view.viewState.docHeight)
|
|
3701
|
+
return new PosAssoc(view.state.doc.length, -1);
|
|
3702
|
+
block = view.elementAtHeight(yOffset);
|
|
3703
|
+
if (scanY == null || block.type == BlockType.Text)
|
|
3704
|
+
break;
|
|
3705
|
+
let halfLine = view.viewState.heightOracle.textHeight / 2;
|
|
3706
|
+
yOffset = scanY > 0 ? block.bottom + halfLine : block.top - halfLine;
|
|
3707
|
+
}
|
|
3708
|
+
// If outside the viewport, return null if precise==true, an
|
|
3709
|
+
// estimate otherwise.
|
|
3710
|
+
if (view.viewport.from >= block.to || view.viewport.to <= block.from) {
|
|
3711
|
+
if (precise)
|
|
3712
|
+
return null;
|
|
3713
|
+
if (block.type == BlockType.Text) {
|
|
3714
|
+
let pos = posAtCoordsImprecise(view, content, block, x, y);
|
|
3715
|
+
return new PosAssoc(pos, pos == block.from ? 1 : -1);
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
if (block.type != BlockType.Text)
|
|
3719
|
+
return yOffset < (block.top + block.bottom) / 2 ? new PosAssoc(block.from, 1) : new PosAssoc(block.to, -1);
|
|
3720
|
+
// Here we know we're in a line, so run the logic for inline layout
|
|
3721
|
+
return posAtCoordsInline(view, view.docView.lineAt(block.from), block.from, x, y);
|
|
3722
|
+
}
|
|
3723
|
+
// Scan through the rectangles for the content of a tile, finding the
|
|
3724
|
+
// one closest to the given coordinates, prefering closeness in Y over
|
|
3725
|
+
// closeness in X.
|
|
3726
|
+
//
|
|
3727
|
+
// If this is a text tile, go character-by-character. For line or mark
|
|
3728
|
+
// tiles, check each non-point-widget child, and descend text or mark
|
|
3729
|
+
// tiles with a recursive call.
|
|
3730
|
+
//
|
|
3731
|
+
// For non-wrapped, purely left-to-right text, this could use a binary
|
|
3732
|
+
// search. But because this seems to be fast enough, for how often it
|
|
3733
|
+
// is called, there's not currently a specialized implementation for
|
|
3734
|
+
// that.
|
|
3735
|
+
function posAtCoordsInline(view, tile, offset, x, y) {
|
|
3736
|
+
let closest = -1, closestRect = null;
|
|
3737
|
+
let dxClosest = 1e9, dyClosest = 1e9;
|
|
3738
|
+
let rowTop = y, rowBot = y;
|
|
3739
|
+
let checkRects = (rects, index) => {
|
|
3740
|
+
for (let i = 0; i < rects.length; i++) {
|
|
3741
|
+
let rect = rects[i];
|
|
3742
|
+
if (rect.top == rect.bottom)
|
|
3743
|
+
continue;
|
|
3744
|
+
let dx = rect.left > x ? rect.left - x : rect.right < x ? x - rect.right : 0;
|
|
3745
|
+
let dy = rect.top > y ? rect.top - y : rect.bottom < y ? y - rect.bottom : 0;
|
|
3746
|
+
if (rect.top <= rowBot && rect.bottom >= rowTop) {
|
|
3747
|
+
// Rectangle is in the current row
|
|
3748
|
+
rowTop = Math.min(rect.top, rowTop);
|
|
3749
|
+
rowBot = Math.max(rect.bottom, rowBot);
|
|
3750
|
+
dy = 0;
|
|
3751
|
+
}
|
|
3752
|
+
if (closest < 0 || (dy - dyClosest || dx - dxClosest) < 0) {
|
|
3753
|
+
if (closest >= 0 && dyClosest && dxClosest < dx &&
|
|
3754
|
+
closestRect.top <= rowBot - 2 && closestRect.bottom >= rowTop + 2) {
|
|
3755
|
+
// Retroactively set dy to 0 if the current match is in this row.
|
|
3756
|
+
dyClosest = 0;
|
|
3757
|
+
}
|
|
3758
|
+
else {
|
|
3759
|
+
closest = index;
|
|
3760
|
+
dxClosest = dx;
|
|
3761
|
+
dyClosest = dy;
|
|
3762
|
+
closestRect = rect;
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
};
|
|
3767
|
+
if (tile.isText()) {
|
|
3768
|
+
for (let i = 0; i < tile.length;) {
|
|
3769
|
+
let next = findClusterBreak(tile.text, i);
|
|
3770
|
+
checkRects(textRange(tile.dom, i, next).getClientRects(), i);
|
|
3771
|
+
if (!dxClosest && !dyClosest)
|
|
3772
|
+
break;
|
|
3773
|
+
i = next;
|
|
3774
|
+
}
|
|
3775
|
+
let after = (x > (closestRect.left + closestRect.right) / 2) == (dirAt(view, closest + offset) == Direction.LTR);
|
|
3776
|
+
return after ? new PosAssoc(offset + findClusterBreak(tile.text, closest), -1) : new PosAssoc(offset + closest, 1);
|
|
3777
|
+
}
|
|
3778
|
+
else {
|
|
3779
|
+
if (!tile.length)
|
|
3780
|
+
return new PosAssoc(offset, 1);
|
|
3781
|
+
for (let i = 0; i < tile.children.length; i++) {
|
|
3782
|
+
let child = tile.children[i];
|
|
3783
|
+
if (child.flags & 48 /* TileFlag.PointWidget */)
|
|
3784
|
+
continue;
|
|
3785
|
+
let rects = (child.dom.nodeType == 1 ? child.dom : textRange(child.dom, 0, child.length)).getClientRects();
|
|
3786
|
+
checkRects(rects, i);
|
|
3787
|
+
if (!dxClosest && !dyClosest)
|
|
3788
|
+
break;
|
|
3789
|
+
}
|
|
3790
|
+
let inner = tile.children[closest], innerOff = tile.posBefore(inner, offset);
|
|
3791
|
+
if (inner.isComposite() || inner.isText())
|
|
3792
|
+
return posAtCoordsInline(view, inner, innerOff, Math.max(closestRect.left, Math.min(closestRect.right, x)), y);
|
|
3793
|
+
let after = (x > (closestRect.left + closestRect.right) / 2) == (dirAt(view, closest + offset) == Direction.LTR);
|
|
3794
|
+
return after ? new PosAssoc(innerOff + inner.length, -1) : new PosAssoc(innerOff, 1);
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
function dirAt(view, pos) {
|
|
3798
|
+
let line = view.state.doc.lineAt(pos), spans = view.bidiSpans(line);
|
|
3799
|
+
return spans[BidiSpan.find(view.bidiSpans(line), pos - line.from, -1, 1)].dir;
|
|
3800
|
+
}
|
|
3924
3801
|
|
|
3925
3802
|
const LineBreakPlaceholder = "\uffff";
|
|
3926
3803
|
class DOMReader {
|
|
@@ -8349,6 +8226,11 @@ class EditorView {
|
|
|
8349
8226
|
return this.docView.posFromDOM(node, offset);
|
|
8350
8227
|
}
|
|
8351
8228
|
posAtCoords(coords, precise = true) {
|
|
8229
|
+
this.readMeasured();
|
|
8230
|
+
let found = posAtCoords(this, coords, precise);
|
|
8231
|
+
return found && found.pos;
|
|
8232
|
+
}
|
|
8233
|
+
posAndSideAtCoords(coords, precise = true) {
|
|
8352
8234
|
this.readMeasured();
|
|
8353
8235
|
return posAtCoords(this, coords, precise);
|
|
8354
8236
|
}
|