Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

contentEditable: clarification of selection and typing behavior #156

Open
rakuco opened this issue Nov 28, 2016 · 9 comments
Open

contentEditable: clarification of selection and typing behavior #156

rakuco opened this issue Nov 28, 2016 · 9 comments

Comments

@rakuco
Copy link
Member

rakuco commented Nov 28, 2016

This discussion started in Blink bug 663638.

The context

Consider the following snippet:

<div id="sample" contenteditable="true" style="border-style: solid">
  abc <a href='bob'>selectMeAndTypeOver</a> def
</div>

It is not clear what the behavior should be when a user selects "selectMeAndTypeOver" (i.e. the entirety of the text node within the <a> node) and types something else.

What I could gather from Blink (M57) and Gecko (Firefox 50) so far is this:

  • Gecko

    • Double-clicking selectMeAndTypeOver:
      • document.getSelection() returns a Selection object with the <div> as both anchor and focus nodes (anchorOffset==1, focusOffset==2).
      • document.execCommand("insertText", false, "foo") replaces the entire <a> node with a new text node. That is, document.getElementById("sample").childNodes returns 3 text nodes.
      • Just typing something manually also removes the <a> node and replaces it with a text node whose contents are whatever is being typed.
    • Selecting selectMeAndTypeOver with the mouse:
      • document.getSelection() returns a Selection object with the selectMeAndTypeOver text node as the anchor (anchorOffset==0) and the def text node as the focus (focusOffset==0).
      • document.execCommand("insertText", false, "foo") replaces the contents of text node within the <a> node with "foo". That is, document.getElementById("sample").childNodes returns 3 nodes, the second being <a>.
      • Just typing something manually also causes the contents of the text node within <a> to be replaced, while <a> itself remains in the tree.
    • When moving the caret with the left and right keyboard keys and then typing, the new text may be either a child of the <a> node or a sibling text node:
      • When the caret is anywhere in electMeAndTypeOve and is moved to either |selectMeAndTypeOver or selectMeAndTypeOver|, the new text is added to the existing text node inside . In other words, the contents of the link changes, but the link remains the same.
      • When the caret is moved from "abc " towards |selectMeAndTypeOver, the new text is appended to the "abc " text node (it does not become part of the link node).
      • When the caret is moved from " def" towards selectMeAndTypeOver|, the new text is added to the " def" text node (it does not become part of the link node).
  • Blink

    • Double-clicking selectMeAndTypeOver:
      • document.getSelection() returns a Selection object with the selectMeAndTypeOver text node as both anchor and focus (anchorOffset==0, focusOffset==19).
      • document.execCommand("insertText", false, "foo") replaces the contents of the text node within the <a> node with "foo". That is, the link remains but the text is changed.
      • Just typing something manually causes the first character to become the only contents of the text node within the <a> node, while any further input is added to a new text node that is inserted right after the <a> node. That is, the first character of the input becomes the link's text and all the rest becomes a new text node adjacent to <a> (document.getElementById("sample").childNodes returns 1 text node, <a> and 2 other text nodes).
    • The behavior when selecting selectMeAndTypeOver with the mouse is identical to what was described above.
    • Typing something either before or after selectMeAndTypeOver always creates text nodes (i.e. the text node within the <a> node is never touched):
      • When the caret is at |selectMeAndTypeOver, the new contents are appended to the "abc " text node.
      • When the caret is at selectMeAndTypeOver|, the new contents are added to a new text node that is inserted between the <a> node and the " def" text node.

I wasn't able to test Edge and Safari, but GNOME Web 3.22.1 with WebKitGTK+ 2.14.11 behaved identically to Blink.

The questions

  • Once selectMeAndTypeOver is selected, what should the Selection object returned by document.getSelection() contain as focus and anchor (and respective offsets)?
  • What's the expected behavior when selectMeAndTypeOver and the user then starts typing? Should the content replace the <a> node or the contents of the text node inside the <a>? Or should Blink/WebKit's behavior of just adding the first character to the text node within <a> be followed?
  • What happens when the caret is positioned at either edge of selectMeAndTypeOver and the user types something? Should the new text be part of the <a> node or adjacent to it? If it should be adjacent to it, should it be merged into the existing nodes or should new text nodes be created?
@rakuco
Copy link
Member Author

rakuco commented Nov 30, 2016

+@johanneswilm
Should I send an email to the list instead?

@johanneswilm
Copy link
Contributor

@rakuco You can try that. There is not always a great audience for such questions though. If you write to the list, link to this issue so we can have the discussion here.

@johanneswilm
Copy link
Contributor

Personally I am still a bit torn whether or not this is important. The replacement of the existing DOM content will in most cases be something the JS editor will want to control. JS editors also tend to not like it when selections change "automagically" because the browser thinks this is a good idea.

On the other hand, the selections should probably be the same. And if doing roughly the same thing in different browsers leads to entirely different selections, the JS editor will need to spend more time trying to standardize these selections before action on the insertion of new content.

@Reinmar
Copy link

Reinmar commented Nov 30, 2016

I believe, that there are two perspectives when talking about RTEs.

For those which don't implement a custom data model, it'd be great if it was consistent across browser and if the behaviour wasn't magical. For example, Chrome has that "funny" behaviour that deleting the content of <strong> and then typing leads to inserting <b>... And <a> is replaced with <u> (cause link was underlined, so Chrome tries to maintain that styling ;|) (see: https://bugs.chromium.org/p/chromium/issues/detail?id=226941). This is magic which breaks people content and cannot be controlled in a reliable way by RTE developers.

The situation may be different in case of RTEs with a custom data model. I don't know how it works in e.g. ProseMirror or Draft, but in CKEditor 5 we built an abstraction over the data and selection and we handle content modifications manually in the model and re-render the DOM if needed.

Due to the fact that beforeInput isn't yet available, we needed to implement some hacks in order to intercept user input. With beforeInput we won't need that, so we'll be fully independent on browsers' input handling. The only thing which beforeInput doesn't change is where we see selection in the DOM, so that's still a thing which could be improved, but it's not very important because the custom data model represents text attributes (bold, italic, links, etc.) differently.

See https://twitter.com/pomekPL/status/740149101334016000 for how the text attributes are represented. They are just a properties of characters. Hence these two positions: x^<b>x... and x<b>^x are in some way identical for us. They are both positions between x^x and the difference is in the selection attributes (whether the selection has bold or not – yes, the selection also has attributes). However, this only applies to collapsed selections, so in the selected link case there's just one selection for us.

Anyway, you can test how we decided to implement the case with typing over the link on https://ckeditor5.github.io – the link is always fully removed and the typed text isn't linked. Typing over a non-collapsed selection always triggers first a generic delete content action and only then inserts the new text. So it's the same behaviour which backspace + typing have. It was natural for us that after you select entire linked/bolded/etc fragment of text and deleted it you don't want to have that style anymore.

@Reinmar
Copy link

Reinmar commented Nov 30, 2016

PS. I forgot to link to #149 where I wrote more about how CKEditor 5 now handles deleting and typing and how it's going to change with beforeInput.

@Reinmar
Copy link

Reinmar commented Nov 30, 2016

PPS. I've just realised that I forgot to mention that replacing a text of a link and text of a bolded word are a different cases in terms of UX. For usability reasons a text of an inserted link needs to be replaceable without losing the link style. TBH, we're still thinking on how to best solve this. If we'll decide to modify the behaviour of typing over a link it will be that deleting the link will not reset selection's attributes. It will be consistently handled in all situations where the generic "delete content" algorithm is used.

@johanneswilm
Copy link
Contributor

For those which don't implement a custom data model, it'd be great if it was consistent across browser and if the behaviour wasn't magical.

As far as I have seen, both editors with and without a data model largely try to control what change happens in the DOM. But you are right, sometimes this means reverting changes that have occurred already due to the lack of the beforeinput event. Another reason for why some let native handling go through and then revert afterward seems to be that otherwise the native browser spell checking doesn't understand what word should be underlined. And I one editor also let undo/redo events go through first when the undo undos auto-correct or spellchecking (and then it cleans the DOM afterward), because this way the browser ";learns" that a particular auto-correct or spell checking is undesired.

So browser native handling of some input still occurs sometimes with some editors, but at least I have not seen where this is happening in an "uncontrolled" manner, where the editor just lets stuff happen and then doesn't try to clean it up/standardize it afterward. Or which editors are you thinking of that let the browser handle uncontrolled DOM-changes (without time traveling backward)?

What is definitely out of style is using execCommand except when there is no other way (clipboard-based events). So if part of the bug here is that execCommand does something inconsistent, I think all the JS editor projects would prefer if you could instead work on something that they use a lot more than fixing execCommand. Or what do you think @Reinmar?

@Reinmar
Copy link

Reinmar commented Nov 30, 2016

Or what do you think @Reinmar?

Yes, fixing execCommand() would be a waste of time. No sane RTE uses it.

Or which editors are you thinking of that let the browser handle uncontrolled DOM-changes (without time traveling backward)?

Hard for me to tell, because I don't know anything specific about any other editor than CKEditor, but I'd presume that all RTEs without a custom data model (and perhaps, to some extent, even some with custom data models) let the browser insert and delete text without much control. In other words – these RTEs behaviour would change if browsers would change their "type over" behaviour.

The group of editors with custom data models is pretty small (it's a great minority in fact), because developing such an RTE is extremely hard and long process. The one I know of are CKEditor 5, Quill, ProseMirror, Draft.js and Trix.

@johanneswilm
Copy link
Contributor

Hard for me to tell, because I don't know anything specific about any other editor than CKEditor, but I'd presume that all RTEs without a custom data model (and perhaps, to some extent, even some with custom data models) let the browser insert and delete text without much control.

You may be right that those editors exist. I just want to make sure that we don't spend a lot of time working features that effectively no-one is using. For the undo behavior, for example, I contacted all the editors I could find, and none of them let just the browser built-in undo work by itself, and everyone had their own undo manager[1], even those that I don't think have started working on an outright data-model. Now that is of course slightly different than whether one lets the browser handle text input, but if one does manage one's own undo-stack, but at least it means that the JS will have to record what changed on every user input.

What happens when the caret is positioned at either edge of selectMeAndTypeOver and the user types something? Should the new text be part of the node or adjacent to it? If it should be adjacent to it, should it be merged into the existing nodes or should new text nodes be created?

I think this depends a lot on the editor and the contents. For example, in links, I usually want to be able to escape them when writing at either end. For bold text the same behavior would be ultra-annoying. Or so I think right now. I am not sure that hard-coding either behavior into browsers is the right path. It's more something that editor projects like CKEditor and similar should experiment with. They'all also be the only ones who can really effectively make the right decision when it comes to "custom" text types such as "piece of text that is visually 'linked' to a comment that is floating to the right of it". But in order for them to be able to do that easily, it may be slightly easier if the selections were always the same (so one doesn't have to have code that checks whether all contents of a node have been selected and then select the node itself in some browsers and not in others). But then again, CKEditor seems to have this figured out, and the others will probably manage to do that as well eventually.

[1] #150 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants