Incorrect results for EditorView.domAtPos() for block replace decorations #1722

Closed
opened 2026-06-30 20:56:55 +02:00 by bjones1 · 6 comments

Thanks for building and maintaining an amazing, open-source editor. I really appreciate it and depend on your library in the application I've written. While using CodeMirror, I believe I've identified the following bug:

The value returned by EditorView.domAtPos() for positions that contain a block replace decoration return incorrect values in several cases, as shown in the attached example code. The screenshot below shows that lines 0, 2, and 4 are block replace decorations; lines 1 and 3 are text.

image

However, calling EditorView.domAtPos() only identifies the div these block replace decorations contain at position 0, but not at position 2 and 4. The output:

bug.mts:105 pos: 0  side: default  dom: <div class="CodeChat-doc" contenteditable="false"><p>0-1</p></div>
bug.mts:105 pos: 0  side: -1  dom: <div class="CodeChat-doc" contenteditable="false"><p>0-1</p></div>
bug.mts:105 pos: 0  side: 1  dom: <div class="CodeChat-doc" contenteditable="false"><p>0-1</p></div>
bug.mts:105 pos: 1  side: default  dom: 1
bug.mts:105 pos: 1  side: -1  dom: <div class="cm-line">1</div>
bug.mts:105 pos: 1  side: 1  dom: 1
bug.mts:105 pos: 2  side: default  dom: <div class="cm-line">3</div>
bug.mts:105 pos: 2  side: -1  dom: undefined
bug.mts:105 pos: 2  side: 1  dom: <div class="cm-line">3</div>
bug.mts:105 pos: 3  side: default  dom: 3
bug.mts:105 pos: 3  side: -1  dom: <div class="cm-line">3</div>
bug.mts:105 pos: 3  side: 1  dom: 3
bug.mts:105 pos: 4  side: default  dom: <div class="cm-line"><br></div>
bug.mts:105 pos: 4  side: -1  dom: undefined
bug.mts:105 pos: 4  side: 1  dom: <div class="cm-line"><br></div>

To build the attached code: esbuild src/bug.html src/bug.mts --bundle --outdir=./static/bundled --sourcemap --format=esm --loader:.html=copy.

Thanks for your time in looking at this.

Thanks for building and maintaining an amazing, open-source editor. I really appreciate it and depend on your library in the application I've written. While using CodeMirror, I believe I've identified the following bug: The value returned by `EditorView.domAtPos()` for positions that contain a block replace decoration return incorrect values in several cases, as shown in the attached example code. The screenshot below shows that lines 0, 2, and 4 are block replace decorations; lines 1 and 3 are text. ![image](/attachments/6bed23ab-fd34-4325-8dd1-d5bb83a76a11) However, calling `EditorView.domAtPos()` only identifies the `div` these block replace decorations contain at position 0, but not at position 2 and 4. The output: ``` bug.mts:105 pos: 0 side: default dom: <div class="CodeChat-doc" contenteditable="false"><p>0-1</p></div> bug.mts:105 pos: 0 side: -1 dom: <div class="CodeChat-doc" contenteditable="false"><p>0-1</p></div> bug.mts:105 pos: 0 side: 1 dom: <div class="CodeChat-doc" contenteditable="false"><p>0-1</p></div> bug.mts:105 pos: 1 side: default dom: 1 bug.mts:105 pos: 1 side: -1 dom: <div class="cm-line">1</div> bug.mts:105 pos: 1 side: 1 dom: 1 bug.mts:105 pos: 2 side: default dom: <div class="cm-line">3</div> bug.mts:105 pos: 2 side: -1 dom: undefined bug.mts:105 pos: 2 side: 1 dom: <div class="cm-line">3</div> bug.mts:105 pos: 3 side: default dom: 3 bug.mts:105 pos: 3 side: -1 dom: <div class="cm-line">3</div> bug.mts:105 pos: 3 side: 1 dom: 3 bug.mts:105 pos: 4 side: default dom: <div class="cm-line"><br></div> bug.mts:105 pos: 4 side: -1 dom: undefined bug.mts:105 pos: 4 side: 1 dom: <div class="cm-line"><br></div> ``` To build the attached code: `esbuild src/bug.html src/bug.mts --bundle --outdir=./static/bundled --sourcemap --format=esm --loader:.html=copy`. Thanks for your time in looking at this.
6.3 KiB
2.4 KiB
Owner

Running this code I do not get an error from the view.coordsAtPos(2) call. Here's the sandbox link.

Running this code I do not get an error from the `view.coordsAtPos(2)` call. Here's the [sandbox link](https://codemirror.net/try/#c=aW1wb3J0IHsgRWRpdG9yVmlldywgRGVjb3JhdGlvbiwgV2lkZ2V0VHlwZSwgfSBmcm9tICJAY29kZW1pcnJvci92aWV3IjsKaW1wb3J0IHsgRWRpdG9yU3RhdGUsIEVkaXRvclNlbGVjdGlvbiwgU3RhdGVGaWVsZCwgU3RhdGVFZmZlY3QsIH0gZnJvbSAiQGNvZGVtaXJyb3Ivc3RhdGUiOwoKY29uc3QgZGVjb3JhdGlvbk9wdGlvbnMgPSB7CiAgYmxvY2s6IHRydWUsCiAgaW5jbHVzaXZlRW5kOiBmYWxzZSwKfTsKCmNvbnN0IGRvY0Jsb2NrRmllbGQgPSBTdGF0ZUZpZWxkLmRlZmluZSh7CiAgY3JlYXRlKF9zdGF0ZSkgewogICAgcmV0dXJuIERlY29yYXRpb24ubm9uZTsKICB9LAogIHVwZGF0ZShkb2NfYmxvY2tzLCB0cikgewogICAgLy8gTWFwIGV4aXN0aW5nIGRvYyBibG9ja3MgdGhyb3VnaCB0aGUgdHJhbnNhY3Rpb24ncyBjaGFuZ2VzLgogICAgZG9jX2Jsb2NrcyA9IGRvY19ibG9ja3MubWFwKHRyLmNoYW5nZXMpOwogICAgZm9yIChjb25zdCBlZmZlY3Qgb2YgdHIuZWZmZWN0cykgewogICAgICBpZiAoZWZmZWN0LmlzKHVwZGF0ZURvY0Jsb2NrKSkgewogICAgICAgIC8vIEZpbmQgdGhlIGV4aXN0aW5nIGRvYyBibG9jayBhdCBgZnJvbWAuCiAgICAgICAgbGV0IHByZXY7CiAgICAgICAgbGV0IHRvOwogICAgICAgIGRvY19ibG9ja3MuYmV0d2VlbihlZmZlY3QudmFsdWUuZnJvbSwgZWZmZWN0LnZhbHVlLmZyb20sIChmcm9tLCB0b19mb3VuZCwgdmFsdWUpID0+IHsKICAgICAgICAgIGlmIChlZmZlY3QudmFsdWUuZnJvbSA9PT0gZnJvbSkgewogICAgICAgICAgICBwcmV2ID0gdmFsdWU7CiAgICAgICAgICAgIHRvID0gdG9fZm91bmQ7CiAgICAgICAgICB9CiAgICAgICAgfSk7CiAgICAgICAgaWYgKHByZXYgPT09IHVuZGVmaW5lZCB8fCB0byA9PT0gdW5kZWZpbmVkKSB7CiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoIk5vIGRvYyBibG9jayBmb3VuZC4iKTsKICAgICAgICB9CiAgICAgICAgLy8gUmVtb3ZlIHRoZSBvbGQgZG9jIGJsb2NrIGFuZCBhZGQgYW4gdXBkYXRlZCBvbmUuIFRoaXMgaXMgdGhlCiAgICAgICAgLy8gZWRpdCB0aGF0LCBmb3IgYWRqYWNlbnQgZG9jIGJsb2NrcywgY29ycnVwdHMgdGhlIHRpbGUKICAgICAgICAvLyBnZW9tZXRyeS4KICAgICAgICBkb2NfYmxvY2tzID0gZG9jX2Jsb2Nrcy51cGRhdGUoewogICAgICAgICAgZmlsdGVyOiAoZnJvbSwgX3RvLCBfdmFsdWUpID0+IGZyb20gIT09IGVmZmVjdC52YWx1ZS5mcm9tLAogICAgICAgICAgZmlsdGVyRnJvbTogZWZmZWN0LnZhbHVlLmZyb20sCiAgICAgICAgICBmaWx0ZXJUbzogZWZmZWN0LnZhbHVlLmZyb20sCiAgICAgICAgICBhZGQ6IFsKICAgICAgICAgICAgRGVjb3JhdGlvbi5yZXBsYWNlKHsKICAgICAgICAgICAgICB3aWRnZXQ6IG5ldyBEb2NCbG9ja1dpZGdldChlZmZlY3QudmFsdWUuY29udGVudHMpLAogICAgICAgICAgICAgIC4uLmRlY29yYXRpb25PcHRpb25zLAogICAgICAgICAgICB9KS5yYW5nZShlZmZlY3QudmFsdWUuZnJvbSwgdG8pLAogICAgICAgICAgXSwKICAgICAgICB9KTsKICAgICAgfQogICAgfQogICAgcmV0dXJuIGRvY19ibG9ja3M7CiAgfSwKICAvLyBUZWxsIENvZGVNaXJyb3IgdG8gcmVuZGVyIHRoZXNlIGRlY29yYXRpb25zLgogIHByb3ZpZGU6IChmaWVsZCkgPT4gRWRpdG9yVmlldy5kZWNvcmF0aW9ucy5mcm9tKGZpZWxkKSwKICAvLyBMb2FkIGRvYyBibG9ja3MgZnJvbSBKU09OIHdoZW4gYnVpbGRpbmcgdGhlIGVkaXRvciBzdGF0ZS4KICBmcm9tSlNPTjogKGpzb24sIF9zdGF0ZSkgPT4gRGVjb3JhdGlvbi5zZXQoanNvbi5tYXAoKFtmcm9tLCB0bywgLCAsIGNvbnRlbnRzXSkgPT4gRGVjb3JhdGlvbi5yZXBsYWNlKHsKICAgIHdpZGdldDogbmV3IERvY0Jsb2NrV2lkZ2V0KGNvbnRlbnRzKSwKICAgIC4uLmRlY29yYXRpb25PcHRpb25zLAogIH0pLnJhbmdlKGZyb20sIHRvKSkpLAp9KTsKCmV4cG9ydCBjb25zdCBDb2RlTWlycm9yX0pTT05fZmllbGRzID0geyBkb2NfYmxvY2tzOiBkb2NCbG9ja0ZpZWxkIH07CgpleHBvcnQgY29uc3QgdXBkYXRlRG9jQmxvY2sgPSBTdGF0ZUVmZmVjdC5kZWZpbmUoewogIG1hcDogKHsgZnJvbSwgY29udGVudHMgfSwgY2hhbmdlKSA9PiAoewogICAgZnJvbTogY2hhbmdlLm1hcFBvcyhmcm9tKSwKICAgIGNvbnRlbnRzLAogIH0pLAp9KTsKCmNsYXNzIERvY0Jsb2NrV2lkZ2V0IGV4dGVuZHMgV2lkZ2V0VHlwZSB7CiAgY29uc3RydWN0b3IoY29udGVudHMpIHsKICAgIHN1cGVyKCk7CiAgICB0aGlzLmNvbnRlbnRzID0gY29udGVudHM7CiAgfQogIGVxKG90aGVyKSB7CiAgICByZXR1cm4gb3RoZXIuY29udGVudHMgPT09IHRoaXMuY29udGVudHM7CiAgfQogIHRvRE9NKCkgewogICAgY29uc3Qgd3JhcCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImRpdiIpOwogICAgd3JhcC5jbGFzc05hbWUgPSAiQ29kZUNoYXQtZG9jIjsKICAgIHdyYXAuaW5uZXJIVE1MID0gYDxkaXYgY2xhc3M9IkNvZGVDaGF0LWRvYy1jb250ZW50cyI+JHt0aGlzLmNvbnRlbnRzfTwvZGl2PmA7CiAgICByZXR1cm4gd3JhcDsKICB9CiAgdXBkYXRlRE9NKGRvbSwgX3ZpZXcpIHsKICAgIGRvbS5jaGlsZE5vZGVzWzBdLmlubmVySFRNTCA9IHRoaXMuY29udGVudHM7CiAgICByZXR1cm4gdHJ1ZTsKICB9Cn0KCnZhciBfYTsKY29uc3QgcmVwcm9fZGl2ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiZGl2Iik7CigoX2EgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiQ29kZUNoYXQtYm9keSIpKSAhPT0gbnVsbCAmJiBfYSAhPT0gdm9pZCAwID8gX2EgOiBkb2N1bWVudC5ib2R5KS5hcHBlbmRDaGlsZChyZXByb19kaXYpOwpjb25zdCBzdGF0ZSA9IEVkaXRvclN0YXRlLmZyb21KU09OKHsKICBkb2M6ICJcblxuIiwKICBzZWxlY3Rpb246IEVkaXRvclNlbGVjdGlvbi5zaW5nbGUoMCkudG9KU09OKCksCiAgZG9jX2Jsb2NrczogWwogICAgWzAsIDEsICIiLCAiIyIsICI8cD4xPC9wPiJdLAogICAgWzEsIDIsICIiLCAiIyIsICI8cD4yPC9wPiJdLAogIF0sCn0sIC8vIDAgMSAyCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeyBleHRlbnNpb25zOiBbZG9jQmxvY2tGaWVsZF0gfSwgQ29kZU1pcnJvcl9KU09OX2ZpZWxkcyk7CmNvbnN0IHZpZXcgPSBuZXcgRWRpdG9yVmlldyh7IHBhcmVudDogcmVwcm9fZGl2LCBzdGF0ZSB9KTsKCnZpZXcuZGlzcGF0Y2goewogIGVmZmVjdHM6IHVwZGF0ZURvY0Jsb2NrLm9mKHsgZnJvbTogMCwgY29udGVudHM6ICI8cD5hMTwvcD4iIH0pLAp9KTsKY29uc29sZS5sb2codmlldy5jb29yZHNBdFBvcygyKSk7Cg==).
Author

Thanks for taking a look! The code in the sandbox looks fairly different from the code in the attached .zip file. The function under test is view.domAtPos(), not view.coordsAtPos(). Would you like me to rework this code to run in the sandbox?

Here's the code I see:

// `bug.mts` -- minimal reproduction of an `EditorView.domAtPos()` bug
// ====================================================================
//
// This builds a CodeMirror editor whose document is interleaved with block
// `Decoration.replace` widgets, then walks every document position -- across
// the public `side` biases (default, -1, 1) and with a mix of `inclusiveEnd`
// settings -- printing what `domAtPos(pos, side)` returns. For positions that
// sit on a block widget, the values are incorrect: they point at bare
// `cm-line` elements instead of the `CodeChat-doc` widget DOM.
//
// To build: `node_modules\\.bin\\esbuild src/bug.html src/bug.mts --bundle --outdir=./static/bundled --sourcemap --format=esm --loader:.html=copy`.
//
// Imports
// -------
import {
    EditorView,
    Decoration,
    DecorationSet,
    WidgetType,
} from "@codemirror/view";
import { EditorState, EditorSelection, StateField } from "@codemirror/state";

// One serialized doc block: [from, to, contents].
type CodeMirrorDocBlockTuple = [number, number, string];

const docBlockField = StateField.define<DecorationSet>({
    create(_state: EditorState) {
        return Decoration.none;
    },

    update(doc_blocks: DecorationSet) {
        return doc_blocks;
    },

    provide: (field: StateField<DecorationSet>) =>
        EditorView.decorations.from(field),

    fromJSON: (json: CodeMirrorDocBlockTuple[], _state: EditorState) =>
        Decoration.set(
            json.map(([from, to, contents]: CodeMirrorDocBlockTuple) =>
                Decoration.replace({
                    widget: new DocBlockWidget(contents),
                    block: true,
                    inclusiveEnd: false,
                }).range(from, to),
            ),
        ),
});

class DocBlockWidget extends WidgetType {
    constructor(readonly contents: string) {
        super();
    }

    toDOM() {
        const wrap = document.createElement("div");
        wrap.className = "CodeChat-doc";
        wrap.innerHTML = this.contents;
        return wrap;
    }
}

// Reproduction
// ------------
const reproduce_bug = () => {
    const state = EditorState.fromJSON(
        {
            /// Character index into `doc`:
            ///    01 23 4
            doc: "\n1\n3\n",
            selection: EditorSelection.single(0).toJSON(),
            doc_blocks: [
                // A mix of `inclusiveEnd` settings so the `covers(1)`
                // (inclusive-end) branch of `resolveBlock` is exercised too.
                //
                // from, to, contentsd
                [0, 1, "<p>0-1</p>"],
                [2, 3, "<p>2-3</p>"],
                [4, 5, "<p>4-5</p>"],
            ],
        },
        { extensions: [docBlockField] },
        { doc_blocks: docBlockField },
    );
    const view = new EditorView({
        parent: document.getElementById("CodeChat-body")!,
        state,
    });

    // Describe what `domAtPos()` returned at a given (pos, side). `side` stays
    // within the public signature, `1 | -1 | undefined`.
    const describe = (pos: number, side: 1 | -1 | undefined) => {
        const dom_at_pos = view.domAtPos(pos, side);
        const node = dom_at_pos.node;
        return node.nodeType === Node.TEXT_NODE
            ? (node as Text).data[dom_at_pos.offset]
            : (dom_at_pos.node.childNodes[dom_at_pos.offset] as HTMLElement)
                  .outerHTML;
    };

    const sides: Array<1 | -1 | undefined> = [undefined, -1, 1];
    for (let pos = 0; pos < view.state.doc.length; ++pos) {
        for (const side of sides) {
            console.log(
                `pos: ${pos}  side: ${side ?? "default"}  dom: ${describe(pos, side)}`,
            );
        }
    }
};

// Page initialization
// -------------------
if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", reproduce_bug);
} else {
    reproduce_bug();
}

Thanks for taking a look! The code in the sandbox looks fairly different from the code in the attached .zip file. The function under test is `view.domAtPos()`, not `view.coordsAtPos()`. Would you like me to rework this code to run in the sandbox? Here's the code I see: ```TypeScript // `bug.mts` -- minimal reproduction of an `EditorView.domAtPos()` bug // ==================================================================== // // This builds a CodeMirror editor whose document is interleaved with block // `Decoration.replace` widgets, then walks every document position -- across // the public `side` biases (default, -1, 1) and with a mix of `inclusiveEnd` // settings -- printing what `domAtPos(pos, side)` returns. For positions that // sit on a block widget, the values are incorrect: they point at bare // `cm-line` elements instead of the `CodeChat-doc` widget DOM. // // To build: `node_modules\\.bin\\esbuild src/bug.html src/bug.mts --bundle --outdir=./static/bundled --sourcemap --format=esm --loader:.html=copy`. // // Imports // ------- import { EditorView, Decoration, DecorationSet, WidgetType, } from "@codemirror/view"; import { EditorState, EditorSelection, StateField } from "@codemirror/state"; // One serialized doc block: [from, to, contents]. type CodeMirrorDocBlockTuple = [number, number, string]; const docBlockField = StateField.define<DecorationSet>({ create(_state: EditorState) { return Decoration.none; }, update(doc_blocks: DecorationSet) { return doc_blocks; }, provide: (field: StateField<DecorationSet>) => EditorView.decorations.from(field), fromJSON: (json: CodeMirrorDocBlockTuple[], _state: EditorState) => Decoration.set( json.map(([from, to, contents]: CodeMirrorDocBlockTuple) => Decoration.replace({ widget: new DocBlockWidget(contents), block: true, inclusiveEnd: false, }).range(from, to), ), ), }); class DocBlockWidget extends WidgetType { constructor(readonly contents: string) { super(); } toDOM() { const wrap = document.createElement("div"); wrap.className = "CodeChat-doc"; wrap.innerHTML = this.contents; return wrap; } } // Reproduction // ------------ const reproduce_bug = () => { const state = EditorState.fromJSON( { /// Character index into `doc`: /// 01 23 4 doc: "\n1\n3\n", selection: EditorSelection.single(0).toJSON(), doc_blocks: [ // A mix of `inclusiveEnd` settings so the `covers(1)` // (inclusive-end) branch of `resolveBlock` is exercised too. // // from, to, contentsd [0, 1, "<p>0-1</p>"], [2, 3, "<p>2-3</p>"], [4, 5, "<p>4-5</p>"], ], }, { extensions: [docBlockField] }, { doc_blocks: docBlockField }, ); const view = new EditorView({ parent: document.getElementById("CodeChat-body")!, state, }); // Describe what `domAtPos()` returned at a given (pos, side). `side` stays // within the public signature, `1 | -1 | undefined`. const describe = (pos: number, side: 1 | -1 | undefined) => { const dom_at_pos = view.domAtPos(pos, side); const node = dom_at_pos.node; return node.nodeType === Node.TEXT_NODE ? (node as Text).data[dom_at_pos.offset] : (dom_at_pos.node.childNodes[dom_at_pos.offset] as HTMLElement) .outerHTML; }; const sides: Array<1 | -1 | undefined> = [undefined, -1, 1]; for (let pos = 0; pos < view.state.doc.length; ++pos) { for (const side of sides) { console.log( `pos: ${pos} side: ${side ?? "default"} dom: ${describe(pos, side)}`, ); } } }; // Page initialization // ------------------- if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", reproduce_bug); } else { reproduce_bug(); } ```
Owner

The code in the sandbox looks fairly different from the code in the attached .zip file.

My bad. I still had the old bug.zip from your earlier issue in my downloads folder and didn't notice my browser added a (1) to the new download, so I ended up unzipping the old file again.

But yes, links to /try are much more convenient for me, since I know exactly how they work and don't have to be paranoid about people trying to slip bad stuff onto my machine.

Now that I'm actually running the code, I'm not seeing anything wrong with the output of posAtDOM. Yes, it can return the last position in a node, so no, you cannot assume that there will be a node at result.node.childNodes[result.offset]. It's returning a position in the DOM tree, not a pointer to a specific node.

> The code in the sandbox looks fairly different from the code in the attached .zip file. My bad. I still had the old bug.zip from your earlier issue in my downloads folder and didn't notice my browser added a `(1)` to the new download, so I ended up unzipping the old file again. But yes, links to /try are much more convenient for me, since I know exactly how they work and don't have to be paranoid about people trying to slip bad stuff onto my machine. Now that I'm actually running the code, I'm not seeing anything wrong with the output of `posAtDOM`. Yes, it can return the last position in a node, so no, you cannot assume that there will be a node at `result.node.childNodes[result.offset]`. It's returning a position in the DOM tree, not a pointer to a specific node.
Author

But yes, links to /try are much more convenient for me, since I know exactly how they work and don't have to be paranoid about people trying to slip bad stuff onto my machine.

Sounds good -- I'll create links to this in the future.

I'm not seeing anything wrong with the output of posAtDOM.

Perhaps I'm confused -- here's what I expected:

Call Expected output actual output
view.domAtPos(0) <div class="CodeChat-doc" contenteditable="false"><p>0-1</p></div> Same
view.domAtPos(2) <div class="CodeChat-doc" contenteditable="false"><p>2-3</p></div> <div class="cm-line">3</div>
view.domAtPos(4) <div class="CodeChat-doc" contenteditable="false"><p>4-5</p></div> <div class="cm-line"><br></div>

I'm hoping to use view.domAtPos() to recover the DOM element for block replace decorations. So, I would expect in this example that view.domAtPos(2) or view.domAtPos(4) would return a reference to the block decoration DOM, just as the call to view.domAtPos(0) does. Instead, these calls returns a reference to the DOM for following CodeMirror line. Is there some other way to retrieve the DOM for a block decoration given a position?

Again, thank you for your time.

> But yes, links to /try are much more convenient for me, since I know exactly how they work and don't have to be paranoid about people trying to slip bad stuff onto my machine. Sounds good -- I'll create links to this in the future. > I'm not seeing anything wrong with the output of `posAtDOM`. Perhaps I'm confused -- here's what I expected: Call | Expected output | actual output -----|-----|----- `view.domAtPos(0)` | `<div class="CodeChat-doc" contenteditable="false"><p>0-1</p></div>` | Same `view.domAtPos(2)` | `<div class="CodeChat-doc" contenteditable="false"><p>2-3</p></div>` | `<div class="cm-line">3</div>` `view.domAtPos(4)` | `<div class="CodeChat-doc" contenteditable="false"><p>4-5</p></div>` | `<div class="cm-line"><br></div>` I'm hoping to use `view.domAtPos()` to recover the DOM element for block replace decorations. So, I would expect in this example that `view.domAtPos(2)` or `view.domAtPos(4)` would return a reference to the block decoration DOM, just as the call to `view.domAtPos(0)` does. Instead, these calls returns a reference to the DOM for following CodeMirror line. Is there some other way to retrieve the DOM for a block decoration given a position? Again, thank you for your time.
marijn referenced this issue from a commit 2026-07-01 17:44:53 +02:00
Owner

Oh, now I get it. The framing of domAtPos as identifying a node confused me. Yes, this is actually a bug. Attached patch should help.

Oh, now I get it. The framing of `domAtPos` as identifying a node confused me. Yes, this is actually a bug. Attached patch should help.
Author

Fantastic. Thanks again for your patience in resolving this. The change works great. I appreciate it!

Fantastic. Thanks again for your patience in resolving this. The change works great. I appreciate it!
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
codemirror/dev#1722
No description provided.