ianstormtaylor / slate

A completely customizable framework for building rich text editors. (Currently in beta.)
http://slatejs.org
MIT License
29.72k stars 3.24k forks source link

Writing inside inline elements #4074

Open jarosik10 opened 3 years ago

jarosik10 commented 3 years ago

Do you want to request a feature or report a bug?

A bug.

What's the current behavior?

Cannot write inside inline elements. This bug only occurs in Chrome. Firefox: writing_inline_el_firefox Chrome: writing_inline_el_chrome

sandbox: https://5dejg.csb.app/

Similar issue: https://github.com/ianstormtaylor/slate/issues/2019

What's the expected behavior?

It could be possible to write inside inline element with at least one char.

BrentFarese commented 3 years ago

I don't believe this is a bug. It has to do with how editor.insertText is implemented in core for inlines, I think. If you are typing in an inline and your cursor is at the last offset of the inline, editor.insertText will move your cursor outside the inline to mirror common rich-text editor behavior. You can solve this by overriding editor.insertText yourself in a plugin if you want different behavior.

Can you confirm this is your issue? If yes, I would close this as not being a bug as it's intended behavior.

BrentFarese commented 3 years ago

@jarosik10 see comment above. Any reply? I am not sure this is a bug and can be "solved" by you if you want to type inside an inline by overriding editor.insertText.

jarosik10 commented 3 years ago

@BrentFarese Hi, I'm really sorry for the late reply. I think that the buggy part of these inlines is the keyboard navigation through them. Whenever you try to enter the inline element (→) instead of going into position 0 of the inline it skips it and goes right into position 1.

jameshfisher commented 3 years ago

I think this is a bug in Chrome. This page (demo) demonstrates the issue:

<!doctype html>
<html>
  <head>
    <style>
      body { font-size: 32px; }
      p { padding: 1em; }
      code { background: lightgrey; padding: 5px; }
    </style>
  </head>
  <body>
    <p id="editable" contenteditable="true">abc<code>def</code>ghi</p>
  </body>
  <script>
    const editableEl = document.querySelector("#editable");
    setInterval(() => {
      const selection = window.getSelection();
      const range = document.createRange();
      range.setStart(editableEl.childNodes[1], 0);
      range.setEnd(editableEl.childNodes[1], 0);
      selection.removeAllRanges();
      selection.addRange(range);
      console.log(window.getSelection());
    }, 1000);
  </script>
</html>

This sets up a contenteditable paragraph with three child nodes: <p>abc<code>def</code>ghi</p>. Each second, the selection is reset to the start of the <code> node, so the end result that I expect is <p>abc<code><CURSOR/>def</code>ghi</p>. Then if hitting "x" on the keyboard, I expect the end result <p>abc<code>x<CURSOR/>def</code>ghi</p>.

Firefox behaves as I expect. But Chrome does not. When logging window.getSelection(), Chrome claims to have worked correctly, but its behavior shows otherwise. Instead, it sets the cursor to <p>abc<CURSOR/><code>def</code>ghi</p>. Not at the beginning of the <code> node, but immediately before it. Then if hitting "x" on the keyboard, Chrome inserts it before the <code> node: <p>abcx<CURSOR/><code>def</code>ghi</p> (this is expected, since the cursor position was already wrong).

In short, Chrome seems literally unable to represent the cursor position <p>abc<code><CURSOR/>def</code>ghi</p>. Given this, I don't think we can work around this with some custom behavior in Slate.

jameshfisher commented 3 years ago

I've filed this as a Chromium bug: https://bugs.chromium.org/p/chromium/issues/detail?id=1249405

jameshfisher commented 3 years ago

After a LOT of experimentation, I've been able to work around all of these bugs! Here is a demo. In short, these are the fixes:

  1. To work around the Chromium bug, insert "padding" elements at the start and end of your inline element. The padding elements must have contentEditable: false, and must have at least one character in them. I don't really know why this works around the Chromium bug, but it seems to work perfectly.
  2. To work around the Slate bug, follow @BrentFarese's advice, and modify insertText to remove its special behavior. Note I call this a bug - I really don't think Slate should have this behavior by default, and I'll open a separate issue to discuss this.