Absolute position for widget

Hey there,

I have a widget that I need to locate in an absolute position using coordsAtPos but an error is been thrown saying Error: Reading the editor layout isn't allowed during an update.

Is there a proper way to position a widget with an absolute coordinates?

Thank you :pray:

This is my code:

import { Extension } from "@codemirror/state";
import { ViewPlugin, ViewUpdate } from "@codemirror/view";
import { Decoration, DecorationSet, WidgetType } from "@codemirror/view";
import { EditorView } from "@codemirror/view";
import { completionStatus, startCompletion } from "@codemirror/autocomplete";
import { getRange, replaceRange, setCursor } from "@/componentsLibrary/CodeEditor/v6/editorUtils";

export const openContext = (view: EditorView) => {
    replaceRange(view, "{{ $. }}", view.state.selection.main.head);
    setCursor(view, view.state.selection.main.head + 5);
    startCompletion(view);
};

class ContextButton extends WidgetType {
    private readonly _view: EditorView
    constructor(readonly view: EditorView) {
        super();
        this._view = view;
    }

    toDOM(view: EditorView) {
        const wrap = document.createElement("div");
        wrap.onclick = (ev) => {
            openContext(view);
            ev.preventDefault();
        };

        const coords = view.coordsAtPos(view.state.selection.main.head); // the error thrown here
        console.log({ coords });
        wrap.className = "context-btn-action";

        const iconEl = document.getElementById("context-button");
        const clonedIcon = iconEl?.cloneNode(true) as HTMLElement;
        clonedIcon.className = "";
        clonedIcon.id = "";
        wrap.appendChild(clonedIcon);
        return wrap;
    }

    ignoreEvent() { return false; }
}

export function contextButton(): Extension {
    return ViewPlugin.fromClass(class {
        contextButton: DecorationSet

        constructor(readonly view: EditorView) {
            const cursorPos = view.state.selection.main.head;
            this.contextButton = Decoration.set([Decoration.widget({ widget: new ContextButton(view), side: 1 }).range(cursorPos)]);
        }

        update(update: ViewUpdate) {
            const range = getRange(update.view, update.view.state.selection.main.head - 1, update.view.state.selection.main.head + 1).trim();

            if (completionStatus(update.state) !== null || !!range) {
                this.contextButton = Decoration.none;
                return;
            }
            if (!range) {
                const cursorPos = update.view.state.selection.main.head;
                this.contextButton = Decoration.set([Decoration.widget({ widget: new ContextButton(update.view), side: 1 }).range(cursorPos)]);
            }
        }
        get decorations() { return !this.view.hasFocus ? Decoration.none : this.contextButton; }
    }, { decorations: v => v.decorations });
}

Youโ€™ll want to go through EditorView.requestMeasure for this so that the DOM reading (and then writing) can be synchronized (and plugins donโ€™t cause layout trashing).

1 Like