If you have a RangeSet
of GutterMarkers
, is there a way to update the position of a specific GutterMarker
rather than removing the marker and creating a new one?
Use case: Adding indicators to specific lines in a gutter. The indicator can only be on one line and may move around between lines.
Problem: If there’s an img
in the marker, the remove-and-add-new process causes the browser to reload the image causing a visual flash.
The RangeSet
API doesn’t seem to have a way to change an existing cursor’s range unless I’m missing something.
It seems like the Marker’s toDOM
is called each time so a new element is made and so the browser completely re-renders it.
I’ve tried keeping a cache of the markers in the StateField
outside of the RangeSet
so the actual marker isn’t being created or destroyed, but that doesn’t seem to help.
Example code:
StateField.define<CollabRemoteGutterState>({
create: () => {
return {
markers: {},
rangeSet: RangeSet.empty
};
},
provide: (s) => [
gutter({
class: GUTTER_CLASS,
renderEmptyElements: true,
markers(view: EditorView) {
return view.state.field(s).rangeSet ?? RangeSet.empty;
}
})
],
update({ markers, rangeSet }, tr) {
if (tr.docChanged) rangeSet = rangeSet.map(tr.changes);
const add: Range<CollabGutterMarker>[] = [];
for (const e of tr.effects) {
if (e.is(updateMarkerPositionEffect)) {
const { id, start } = e.value;
if (!id) continue;
let marker = markers[id];
if (!marker) {
marker = new MyCustomMarker(id);
markers[id] = marker;
}
const startLine = tr.state.doc.lineAt(start);
add.push(marker.range(startLine.from, startLine.from));
}
}
if (add.length > 0) {
rangeSet = rangeSet.update({
add,
sort: true,
filter: (f, t, m) => !add.some(({ value }) => value.id === m.id)
});
}
return { markers, rangeSet };
}
});