@codemirror/view 6.39.0-beta.1 → 6.39.0-beta.3
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 +124 -244
- package/dist/index.d.cts +22 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +124 -244
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1750,8 +1750,8 @@ class Tile {
|
|
|
1750
1750
|
get posAtEnd() {
|
|
1751
1751
|
return this.posAtStart + this.length;
|
|
1752
1752
|
}
|
|
1753
|
-
posBefore(tile) {
|
|
1754
|
-
let pos =
|
|
1753
|
+
posBefore(tile, start = this.posAtStart) {
|
|
1754
|
+
let pos = start;
|
|
1755
1755
|
for (let child of this.children) {
|
|
1756
1756
|
if (child == tile)
|
|
1757
1757
|
return pos;
|
|
@@ -3540,199 +3540,6 @@ function groupAt(state$1, pos, bias = 1) {
|
|
|
3540
3540
|
}
|
|
3541
3541
|
return state.EditorSelection.range(from + line.from, to + line.from);
|
|
3542
3542
|
}
|
|
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
3543
|
function posAtCoordsImprecise(view, contentRect, block, x, y) {
|
|
3737
3544
|
let into = Math.round((x - contentRect.left) * view.defaultCharacterWidth);
|
|
3738
3545
|
if (view.lineWrapping && block.height > view.defaultLineHeight * 1.5) {
|
|
@@ -3743,49 +3550,6 @@ function posAtCoordsImprecise(view, contentRect, block, x, y) {
|
|
|
3743
3550
|
let content = view.state.sliceDoc(block.from, block.to);
|
|
3744
3551
|
return block.from + state.findColumn(content, into, view.state.tabSize);
|
|
3745
3552
|
}
|
|
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
3553
|
function blockAt(view, pos, side) {
|
|
3790
3554
|
let line = view.lineBlockAt(pos);
|
|
3791
3555
|
if (Array.isArray(line.type)) {
|
|
@@ -3875,11 +3639,7 @@ function moveVertically(view, start, forward, distance) {
|
|
|
3875
3639
|
for (let extra = 0;; extra += 10) {
|
|
3876
3640
|
let curY = startY + (dist + extra) * dir;
|
|
3877
3641
|
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
|
-
}
|
|
3642
|
+
return state.EditorSelection.cursor(pos.pos, pos.assoc, undefined, goal);
|
|
3883
3643
|
}
|
|
3884
3644
|
}
|
|
3885
3645
|
function skipAtomicRanges(atoms, pos, bias) {
|
|
@@ -3925,6 +3685,121 @@ function skipAtoms(view, oldPos, pos) {
|
|
|
3925
3685
|
let newPos = skipAtomicRanges(view.state.facet(atomicRanges).map(f => f(view)), pos.from, oldPos.head > pos.from ? -1 : 1);
|
|
3926
3686
|
return newPos == pos.from ? pos : state.EditorSelection.cursor(newPos, newPos < pos.from ? 1 : -1);
|
|
3927
3687
|
}
|
|
3688
|
+
class PosAssoc {
|
|
3689
|
+
constructor(pos, assoc) {
|
|
3690
|
+
this.pos = pos;
|
|
3691
|
+
this.assoc = assoc;
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
function posAtCoords(view, coords, precise, scanY) {
|
|
3695
|
+
let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
|
|
3696
|
+
let { x, y } = coords, yOffset = y - docTop, block;
|
|
3697
|
+
// First find the block at the given Y position, if any. If scanY is
|
|
3698
|
+
// given (used for vertical cursor motion), try to skip widgets.
|
|
3699
|
+
for (;;) {
|
|
3700
|
+
if (yOffset < 0)
|
|
3701
|
+
return new PosAssoc(0, 1);
|
|
3702
|
+
if (yOffset > view.viewState.docHeight)
|
|
3703
|
+
return new PosAssoc(view.state.doc.length, -1);
|
|
3704
|
+
block = view.elementAtHeight(yOffset);
|
|
3705
|
+
if (scanY == null || block.type == exports.BlockType.Text)
|
|
3706
|
+
break;
|
|
3707
|
+
let halfLine = view.viewState.heightOracle.textHeight / 2;
|
|
3708
|
+
yOffset = scanY > 0 ? block.bottom + halfLine : block.top - halfLine;
|
|
3709
|
+
}
|
|
3710
|
+
// If outside the viewport, return null if precise==true, an
|
|
3711
|
+
// estimate otherwise.
|
|
3712
|
+
if (view.viewport.from >= block.to || view.viewport.to <= block.from) {
|
|
3713
|
+
if (precise)
|
|
3714
|
+
return null;
|
|
3715
|
+
if (block.type == exports.BlockType.Text) {
|
|
3716
|
+
let pos = posAtCoordsImprecise(view, content, block, x, y);
|
|
3717
|
+
return new PosAssoc(pos, pos == block.from ? 1 : -1);
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
if (block.type != exports.BlockType.Text)
|
|
3721
|
+
return yOffset < (block.top + block.bottom) / 2 ? new PosAssoc(block.from, 1) : new PosAssoc(block.to, -1);
|
|
3722
|
+
// Here we know we're in a line, so run the logic for inline layout
|
|
3723
|
+
return posAtCoordsInline(view, view.docView.lineAt(block.from), block.from, x, y);
|
|
3724
|
+
}
|
|
3725
|
+
// Scan through the rectangles for the content of a tile, finding the
|
|
3726
|
+
// one closest to the given coordinates, prefering closeness in Y over
|
|
3727
|
+
// closeness in X.
|
|
3728
|
+
//
|
|
3729
|
+
// If this is a text tile, go character-by-character. For line or mark
|
|
3730
|
+
// tiles, check each non-point-widget child, and descend text or mark
|
|
3731
|
+
// tiles with a recursive call.
|
|
3732
|
+
//
|
|
3733
|
+
// For non-wrapped, purely left-to-right text, this could use a binary
|
|
3734
|
+
// search. But because this seems to be fast enough, for how often it
|
|
3735
|
+
// is called, there's not currently a specialized implementation for
|
|
3736
|
+
// that.
|
|
3737
|
+
function posAtCoordsInline(view, tile, offset, x, y) {
|
|
3738
|
+
let closest = -1, closestRect = null;
|
|
3739
|
+
let dxClosest = 1e9, dyClosest = 1e9;
|
|
3740
|
+
let rowTop = y, rowBot = y;
|
|
3741
|
+
let checkRects = (rects, index) => {
|
|
3742
|
+
for (let i = 0; i < rects.length; i++) {
|
|
3743
|
+
let rect = rects[i];
|
|
3744
|
+
if (rect.top == rect.bottom)
|
|
3745
|
+
continue;
|
|
3746
|
+
let dx = rect.left > x ? rect.left - x : rect.right < x ? x - rect.right : 0;
|
|
3747
|
+
let dy = rect.top > y ? rect.top - y : rect.bottom < y ? y - rect.bottom : 0;
|
|
3748
|
+
if (rect.top <= rowBot && rect.bottom >= rowTop) {
|
|
3749
|
+
// Rectangle is in the current row
|
|
3750
|
+
rowTop = Math.min(rect.top, rowTop);
|
|
3751
|
+
rowBot = Math.max(rect.bottom, rowBot);
|
|
3752
|
+
dy = 0;
|
|
3753
|
+
}
|
|
3754
|
+
if (closest < 0 || (dy - dyClosest || dx - dxClosest) < 0) {
|
|
3755
|
+
if (closest >= 0 && dyClosest && dxClosest < dx &&
|
|
3756
|
+
closestRect.top <= rowBot - 2 && closestRect.bottom >= rowTop + 2) {
|
|
3757
|
+
// Retroactively set dy to 0 if the current match is in this row.
|
|
3758
|
+
dyClosest = 0;
|
|
3759
|
+
}
|
|
3760
|
+
else {
|
|
3761
|
+
closest = index;
|
|
3762
|
+
dxClosest = dx;
|
|
3763
|
+
dyClosest = dy;
|
|
3764
|
+
closestRect = rect;
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3767
|
+
}
|
|
3768
|
+
};
|
|
3769
|
+
if (tile.isText()) {
|
|
3770
|
+
for (let i = 0; i < tile.length;) {
|
|
3771
|
+
let next = state.findClusterBreak(tile.text, i);
|
|
3772
|
+
checkRects(textRange(tile.dom, i, next).getClientRects(), i);
|
|
3773
|
+
if (!dxClosest && !dyClosest)
|
|
3774
|
+
break;
|
|
3775
|
+
i = next;
|
|
3776
|
+
}
|
|
3777
|
+
let after = (x > (closestRect.left + closestRect.right) / 2) == (dirAt(view, closest + offset) == exports.Direction.LTR);
|
|
3778
|
+
return after ? new PosAssoc(offset + state.findClusterBreak(tile.text, closest), -1) : new PosAssoc(offset + closest, 1);
|
|
3779
|
+
}
|
|
3780
|
+
else {
|
|
3781
|
+
if (!tile.length)
|
|
3782
|
+
return new PosAssoc(offset, 1);
|
|
3783
|
+
for (let i = 0; i < tile.children.length; i++) {
|
|
3784
|
+
let child = tile.children[i];
|
|
3785
|
+
if (child.flags & 48 /* TileFlag.PointWidget */)
|
|
3786
|
+
continue;
|
|
3787
|
+
let rects = (child.dom.nodeType == 1 ? child.dom : textRange(child.dom, 0, child.length)).getClientRects();
|
|
3788
|
+
checkRects(rects, i);
|
|
3789
|
+
if (!dxClosest && !dyClosest)
|
|
3790
|
+
break;
|
|
3791
|
+
}
|
|
3792
|
+
let inner = tile.children[closest], innerOff = tile.posBefore(inner, offset);
|
|
3793
|
+
if (inner.isComposite() || inner.isText())
|
|
3794
|
+
return posAtCoordsInline(view, inner, innerOff, Math.max(closestRect.left, Math.min(closestRect.right, x)), y);
|
|
3795
|
+
let after = (x > (closestRect.left + closestRect.right) / 2) == (dirAt(view, closest + offset) == exports.Direction.LTR);
|
|
3796
|
+
return after ? new PosAssoc(innerOff + inner.length, -1) : new PosAssoc(innerOff, 1);
|
|
3797
|
+
}
|
|
3798
|
+
}
|
|
3799
|
+
function dirAt(view, pos) {
|
|
3800
|
+
let line = view.state.doc.lineAt(pos), spans = view.bidiSpans(line);
|
|
3801
|
+
return spans[BidiSpan.find(view.bidiSpans(line), pos - line.from, -1, 1)].dir;
|
|
3802
|
+
}
|
|
3928
3803
|
|
|
3929
3804
|
const LineBreakPlaceholder = "\uffff";
|
|
3930
3805
|
class DOMReader {
|
|
@@ -3949,7 +3824,7 @@ class DOMReader {
|
|
|
3949
3824
|
this.readNode(cur);
|
|
3950
3825
|
let tile = Tile.get(cur), next = cur.nextSibling;
|
|
3951
3826
|
if (next == end) {
|
|
3952
|
-
if (tile === null || tile === void 0 ? void 0 : tile.breakAfter)
|
|
3827
|
+
if ((tile === null || tile === void 0 ? void 0 : tile.breakAfter) && !next)
|
|
3953
3828
|
this.lineBreak();
|
|
3954
3829
|
break;
|
|
3955
3830
|
}
|
|
@@ -8354,6 +8229,11 @@ class EditorView {
|
|
|
8354
8229
|
return this.docView.posFromDOM(node, offset);
|
|
8355
8230
|
}
|
|
8356
8231
|
posAtCoords(coords, precise = true) {
|
|
8232
|
+
this.readMeasured();
|
|
8233
|
+
let found = posAtCoords(this, coords, precise);
|
|
8234
|
+
return found && found.pos;
|
|
8235
|
+
}
|
|
8236
|
+
posAndSideAtCoords(coords, precise = true) {
|
|
8357
8237
|
this.readMeasured();
|
|
8358
8238
|
return posAtCoords(this, coords, precise);
|
|
8359
8239
|
}
|
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
|
@@ -1746,8 +1746,8 @@ class Tile {
|
|
|
1746
1746
|
get posAtEnd() {
|
|
1747
1747
|
return this.posAtStart + this.length;
|
|
1748
1748
|
}
|
|
1749
|
-
posBefore(tile) {
|
|
1750
|
-
let pos =
|
|
1749
|
+
posBefore(tile, start = this.posAtStart) {
|
|
1750
|
+
let pos = start;
|
|
1751
1751
|
for (let child of this.children) {
|
|
1752
1752
|
if (child == tile)
|
|
1753
1753
|
return pos;
|
|
@@ -3536,199 +3536,6 @@ function groupAt(state, pos, bias = 1) {
|
|
|
3536
3536
|
}
|
|
3537
3537
|
return EditorSelection.range(from + line.from, to + line.from);
|
|
3538
3538
|
}
|
|
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
3539
|
function posAtCoordsImprecise(view, contentRect, block, x, y) {
|
|
3733
3540
|
let into = Math.round((x - contentRect.left) * view.defaultCharacterWidth);
|
|
3734
3541
|
if (view.lineWrapping && block.height > view.defaultLineHeight * 1.5) {
|
|
@@ -3739,49 +3546,6 @@ function posAtCoordsImprecise(view, contentRect, block, x, y) {
|
|
|
3739
3546
|
let content = view.state.sliceDoc(block.from, block.to);
|
|
3740
3547
|
return block.from + findColumn(content, into, view.state.tabSize);
|
|
3741
3548
|
}
|
|
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
3549
|
function blockAt(view, pos, side) {
|
|
3786
3550
|
let line = view.lineBlockAt(pos);
|
|
3787
3551
|
if (Array.isArray(line.type)) {
|
|
@@ -3871,11 +3635,7 @@ function moveVertically(view, start, forward, distance) {
|
|
|
3871
3635
|
for (let extra = 0;; extra += 10) {
|
|
3872
3636
|
let curY = startY + (dist + extra) * dir;
|
|
3873
3637
|
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
|
-
}
|
|
3638
|
+
return EditorSelection.cursor(pos.pos, pos.assoc, undefined, goal);
|
|
3879
3639
|
}
|
|
3880
3640
|
}
|
|
3881
3641
|
function skipAtomicRanges(atoms, pos, bias) {
|
|
@@ -3921,6 +3681,121 @@ function skipAtoms(view, oldPos, pos) {
|
|
|
3921
3681
|
let newPos = skipAtomicRanges(view.state.facet(atomicRanges).map(f => f(view)), pos.from, oldPos.head > pos.from ? -1 : 1);
|
|
3922
3682
|
return newPos == pos.from ? pos : EditorSelection.cursor(newPos, newPos < pos.from ? 1 : -1);
|
|
3923
3683
|
}
|
|
3684
|
+
class PosAssoc {
|
|
3685
|
+
constructor(pos, assoc) {
|
|
3686
|
+
this.pos = pos;
|
|
3687
|
+
this.assoc = assoc;
|
|
3688
|
+
}
|
|
3689
|
+
}
|
|
3690
|
+
function posAtCoords(view, coords, precise, scanY) {
|
|
3691
|
+
let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
|
|
3692
|
+
let { x, y } = coords, yOffset = y - docTop, block;
|
|
3693
|
+
// First find the block at the given Y position, if any. If scanY is
|
|
3694
|
+
// given (used for vertical cursor motion), try to skip widgets.
|
|
3695
|
+
for (;;) {
|
|
3696
|
+
if (yOffset < 0)
|
|
3697
|
+
return new PosAssoc(0, 1);
|
|
3698
|
+
if (yOffset > view.viewState.docHeight)
|
|
3699
|
+
return new PosAssoc(view.state.doc.length, -1);
|
|
3700
|
+
block = view.elementAtHeight(yOffset);
|
|
3701
|
+
if (scanY == null || block.type == BlockType.Text)
|
|
3702
|
+
break;
|
|
3703
|
+
let halfLine = view.viewState.heightOracle.textHeight / 2;
|
|
3704
|
+
yOffset = scanY > 0 ? block.bottom + halfLine : block.top - halfLine;
|
|
3705
|
+
}
|
|
3706
|
+
// If outside the viewport, return null if precise==true, an
|
|
3707
|
+
// estimate otherwise.
|
|
3708
|
+
if (view.viewport.from >= block.to || view.viewport.to <= block.from) {
|
|
3709
|
+
if (precise)
|
|
3710
|
+
return null;
|
|
3711
|
+
if (block.type == BlockType.Text) {
|
|
3712
|
+
let pos = posAtCoordsImprecise(view, content, block, x, y);
|
|
3713
|
+
return new PosAssoc(pos, pos == block.from ? 1 : -1);
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
3716
|
+
if (block.type != BlockType.Text)
|
|
3717
|
+
return yOffset < (block.top + block.bottom) / 2 ? new PosAssoc(block.from, 1) : new PosAssoc(block.to, -1);
|
|
3718
|
+
// Here we know we're in a line, so run the logic for inline layout
|
|
3719
|
+
return posAtCoordsInline(view, view.docView.lineAt(block.from), block.from, x, y);
|
|
3720
|
+
}
|
|
3721
|
+
// Scan through the rectangles for the content of a tile, finding the
|
|
3722
|
+
// one closest to the given coordinates, prefering closeness in Y over
|
|
3723
|
+
// closeness in X.
|
|
3724
|
+
//
|
|
3725
|
+
// If this is a text tile, go character-by-character. For line or mark
|
|
3726
|
+
// tiles, check each non-point-widget child, and descend text or mark
|
|
3727
|
+
// tiles with a recursive call.
|
|
3728
|
+
//
|
|
3729
|
+
// For non-wrapped, purely left-to-right text, this could use a binary
|
|
3730
|
+
// search. But because this seems to be fast enough, for how often it
|
|
3731
|
+
// is called, there's not currently a specialized implementation for
|
|
3732
|
+
// that.
|
|
3733
|
+
function posAtCoordsInline(view, tile, offset, x, y) {
|
|
3734
|
+
let closest = -1, closestRect = null;
|
|
3735
|
+
let dxClosest = 1e9, dyClosest = 1e9;
|
|
3736
|
+
let rowTop = y, rowBot = y;
|
|
3737
|
+
let checkRects = (rects, index) => {
|
|
3738
|
+
for (let i = 0; i < rects.length; i++) {
|
|
3739
|
+
let rect = rects[i];
|
|
3740
|
+
if (rect.top == rect.bottom)
|
|
3741
|
+
continue;
|
|
3742
|
+
let dx = rect.left > x ? rect.left - x : rect.right < x ? x - rect.right : 0;
|
|
3743
|
+
let dy = rect.top > y ? rect.top - y : rect.bottom < y ? y - rect.bottom : 0;
|
|
3744
|
+
if (rect.top <= rowBot && rect.bottom >= rowTop) {
|
|
3745
|
+
// Rectangle is in the current row
|
|
3746
|
+
rowTop = Math.min(rect.top, rowTop);
|
|
3747
|
+
rowBot = Math.max(rect.bottom, rowBot);
|
|
3748
|
+
dy = 0;
|
|
3749
|
+
}
|
|
3750
|
+
if (closest < 0 || (dy - dyClosest || dx - dxClosest) < 0) {
|
|
3751
|
+
if (closest >= 0 && dyClosest && dxClosest < dx &&
|
|
3752
|
+
closestRect.top <= rowBot - 2 && closestRect.bottom >= rowTop + 2) {
|
|
3753
|
+
// Retroactively set dy to 0 if the current match is in this row.
|
|
3754
|
+
dyClosest = 0;
|
|
3755
|
+
}
|
|
3756
|
+
else {
|
|
3757
|
+
closest = index;
|
|
3758
|
+
dxClosest = dx;
|
|
3759
|
+
dyClosest = dy;
|
|
3760
|
+
closestRect = rect;
|
|
3761
|
+
}
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
};
|
|
3765
|
+
if (tile.isText()) {
|
|
3766
|
+
for (let i = 0; i < tile.length;) {
|
|
3767
|
+
let next = findClusterBreak(tile.text, i);
|
|
3768
|
+
checkRects(textRange(tile.dom, i, next).getClientRects(), i);
|
|
3769
|
+
if (!dxClosest && !dyClosest)
|
|
3770
|
+
break;
|
|
3771
|
+
i = next;
|
|
3772
|
+
}
|
|
3773
|
+
let after = (x > (closestRect.left + closestRect.right) / 2) == (dirAt(view, closest + offset) == Direction.LTR);
|
|
3774
|
+
return after ? new PosAssoc(offset + findClusterBreak(tile.text, closest), -1) : new PosAssoc(offset + closest, 1);
|
|
3775
|
+
}
|
|
3776
|
+
else {
|
|
3777
|
+
if (!tile.length)
|
|
3778
|
+
return new PosAssoc(offset, 1);
|
|
3779
|
+
for (let i = 0; i < tile.children.length; i++) {
|
|
3780
|
+
let child = tile.children[i];
|
|
3781
|
+
if (child.flags & 48 /* TileFlag.PointWidget */)
|
|
3782
|
+
continue;
|
|
3783
|
+
let rects = (child.dom.nodeType == 1 ? child.dom : textRange(child.dom, 0, child.length)).getClientRects();
|
|
3784
|
+
checkRects(rects, i);
|
|
3785
|
+
if (!dxClosest && !dyClosest)
|
|
3786
|
+
break;
|
|
3787
|
+
}
|
|
3788
|
+
let inner = tile.children[closest], innerOff = tile.posBefore(inner, offset);
|
|
3789
|
+
if (inner.isComposite() || inner.isText())
|
|
3790
|
+
return posAtCoordsInline(view, inner, innerOff, Math.max(closestRect.left, Math.min(closestRect.right, x)), y);
|
|
3791
|
+
let after = (x > (closestRect.left + closestRect.right) / 2) == (dirAt(view, closest + offset) == Direction.LTR);
|
|
3792
|
+
return after ? new PosAssoc(innerOff + inner.length, -1) : new PosAssoc(innerOff, 1);
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
function dirAt(view, pos) {
|
|
3796
|
+
let line = view.state.doc.lineAt(pos), spans = view.bidiSpans(line);
|
|
3797
|
+
return spans[BidiSpan.find(view.bidiSpans(line), pos - line.from, -1, 1)].dir;
|
|
3798
|
+
}
|
|
3924
3799
|
|
|
3925
3800
|
const LineBreakPlaceholder = "\uffff";
|
|
3926
3801
|
class DOMReader {
|
|
@@ -3945,7 +3820,7 @@ class DOMReader {
|
|
|
3945
3820
|
this.readNode(cur);
|
|
3946
3821
|
let tile = Tile.get(cur), next = cur.nextSibling;
|
|
3947
3822
|
if (next == end) {
|
|
3948
|
-
if (tile === null || tile === void 0 ? void 0 : tile.breakAfter)
|
|
3823
|
+
if ((tile === null || tile === void 0 ? void 0 : tile.breakAfter) && !next)
|
|
3949
3824
|
this.lineBreak();
|
|
3950
3825
|
break;
|
|
3951
3826
|
}
|
|
@@ -8349,6 +8224,11 @@ class EditorView {
|
|
|
8349
8224
|
return this.docView.posFromDOM(node, offset);
|
|
8350
8225
|
}
|
|
8351
8226
|
posAtCoords(coords, precise = true) {
|
|
8227
|
+
this.readMeasured();
|
|
8228
|
+
let found = posAtCoords(this, coords, precise);
|
|
8229
|
+
return found && found.pos;
|
|
8230
|
+
}
|
|
8231
|
+
posAndSideAtCoords(coords, precise = true) {
|
|
8352
8232
|
this.readMeasured();
|
|
8353
8233
|
return posAtCoords(this, coords, precise);
|
|
8354
8234
|
}
|