Inability to use ::selector with an <input /> widget when using the `drawSelection` plugin

I’m trying to add an inline input (as a widget) to my editor, but struggling to accomplish various things. One of the main things I haven’t managed to sort out yet is getting the selection styling back so that you can actually see text you’ve selected. The cursor caret itself was easy to get back with style.caretColor, but something extra apparently is needed for pseudo-selectors. It is hard to demonstrate in the playground (as it is hard to add a pseudo-selector like this), but if you just add a

input::selection {
  background-color: rgba(0,255,0,0.4);
}

to your devtools you should see what I mean. Here is the playground code to at least get a caret back:

import {minimalSetup, EditorView} from "codemirror"
import {ViewPlugin, WidgetType, Decoration } from "@codemirror/view"

class InputWidget extends WidgetType {
  constructor() {
    super();
  }

  eq() {
    return false;
  }

  toDOM() {
    const wrapper = document.createElement('div');
    wrapper.style.display = 'inline-block';
    const input = document.createElement('input');
    input.style.caretColor = "green";
    wrapper.appendChild(input);
    return wrapper;
  }

  ignoreEvent() {
    return true;
  }
}

const renderInput = () => Decoration.set(
  Decoration.widget({ widget: new InputWidget(), side: 1 }).range(0)
);

const inputWidgetPlugin = ViewPlugin.fromClass(
  class {
    constructor(view) {
      this.decorations = renderInput();
    }
    update(update) {
      this.decorations = renderInput();
    }
  },
  { decorations: (view) => view.decorations }
);


new EditorView({
  doc: "...",
  extensions: [minimalSetup, inputWidgetPlugin],
  parent: document.body
})

Does it help if you use ::selection rather than ::selector?

Whoops typo in the OP, ::selection is indeed what I’ve been trying.

After a little digging, I have found that it is the presence of the drawSelection plugin which is breaking ::selection (which makes sense). Here is an updated repro to demonstrate:

import {EditorView, ViewPlugin, WidgetType, Decoration, drawSelection } from "@codemirror/view"

class InputWidget extends WidgetType {
  constructor() {
    super();
  }

  eq() {
    return false;
  }

  toDOM() {
    const wrapper = document.createElement('div');
    wrapper.style.display = 'inline-block';
    const input = document.createElement('input');
    input.style.caretColor = "green";
    wrapper.appendChild(input);
    return wrapper;
  }

  ignoreEvent() {
    return true;
  }
}

const renderInput = () => Decoration.set(
  Decoration.widget({ widget: new InputWidget(), side: 1 }).range(0)
);

const inputWidgetPlugin = ViewPlugin.fromClass(
  class {
    constructor(view) {
      this.decorations = renderInput();
    }
    update(update) {
      this.decorations = renderInput();
    }
  },
  { decorations: (view) => view.decorations }
);


new EditorView({
  doc: "...",
  extensions: [
    // drawSelection(), // comment out to fix ::selection
    inputWidgetPlugin
  ],
  parent: document.body
})

And here is the simple repro on CodeSandbox

1 Like

Also another issue I’m having is how to make the <input /> autofocus when it is rendered. I can technically make it work by adding a setTimeout or requestAnimationFrame right before I return the DOM element in toDOM, but obviously I don’t want to actually do that.

The hideNativeSelection bit of drawSelection is the culprit. It targets the ::selection with !important not only for the .cm-line but for all (not even direct) children as well, which is the issue. Would it be possible to make it a little less aggressive, @marijn? Not sure what the broader implications are.

const themeSpec: {[selector: string]: StyleSpec} = {
  ".cm-line": {
    "& ::selection": {backgroundColor: "transparent !important"},
    "&::selection": {backgroundColor: "transparent !important"}
  }
}
if (CanHidePrimary) {
  themeSpec[".cm-line"].caretColor = "transparent !important"
  themeSpec[".cm-content"] = {caretColor: "transparent !important"}
}
const hideNativeSelection = Prec.highest(EditorView.theme(themeSpec))

This patch disables those styles for nested focused elements, which should work here. Unfortunately, Chrome (at least on Linux) doesn’t seem to make it possible to revert to the default selection color (initial and revert styles don’t seem to do anything for ::selection).

This works, thanks for the quick fix.