Skip to content
This repository has been archived by the owner on Oct 7, 2021. It is now read-only.

Alternative contenteditable implementation #11

Open
dbalcomb opened this issue Mar 17, 2021 · 5 comments
Open

Alternative contenteditable implementation #11

dbalcomb opened this issue Mar 17, 2021 · 5 comments

Comments

@dbalcomb
Copy link

I came across this project after discovering the limitations of the Selection API in Safari but was concerned about the impact of altering the DOM when modifying a large contenteditable document.

Instead I came up with a potential solution that only works for contenteditable areas but is a lot simpler for anyone who is not interested in selections outside of a contenteditable area. I imagine this is the case for most if not all of the editor libraries that have this problem. I haven't tested it extensively but at first glance it appears to work as expected in the latest version of Safari (macOS and iOS) as well as the technology preview.

You can see from the link above that the general idea is to listen to selectionchange events, trigger an execCommand which interacts with the selection, capture and cancel the resulting the beforeinput event, use the getTargetRanges method to get a list of static ranges, convert those to actual ranges and then build a fake selection object with the same API.

The only real concern is if beforeinput is changed to not be cancellable or the deprecated execCommand feature is removed. The former would be against the specification and the latter may break a lot of things (perhaps even Safari's touchbar contenteditable controls) so I don't see it being removed before an actual solution has been implemented.

I don't really have the time to fully flesh out the code outside of my use case but I thought it might be of some use here or perhaps someone will be able to tell me why this is such a terrible idea.

@samthor
Copy link
Contributor

samthor commented Mar 17, 2021

This is really interesting, thank you for contributing. We are a bit concerned that the approach in this polyfill is broken in the TP and that Safari has no real interest in "unfixing" the bug we used to make the polyfill work.

To be clear, the only reason you addRange to Selection is to emulate the experience a developer might expect, right? It seems like I could just as easily announce the selection from beforeinput another way.

@dbalcomb
Copy link
Author

I suppose that there is nothing stopping you from announcing the selection another way with whatever API you deem appropriate. Without any fallback such as your existing polyfill I would expect that a custom API would have to be provided to prevent unexpected results when selecting something that is not contenteditable.

The idea in my code was simply to polyfill the ShadowRoot.prototype.getSelection when dealing with contenteditable without altering the DOM or firing any other events. From a developers perspective my polyfill is completely transparent as the beforeinput event is captured and cancelled and not propagated. No need for hundreds of events and a custom event type.

The only possible issue would be if a user checked the selection using instanceof but that could presumably be remedied with Symbol.hasInstance. And now after further thought it may have been better to trigger the execCommand when calling getSelection, caching the result and invalidating it after selectionchange so as to not call it after every single change including every keypress.

I had considered if it was possible to briefly set the entire document as editable to capture the selection but it was more than I needed as I was only interested in getting a content editor working inside the Shadow DOM.

The selection refers to a custom shared ShadowSelection instance that I had hoped to make work identical to the standard Selection API as it is not possible to construct another Selection instance. I had initially tried adding the ranges to the document selection to see if that worked but it wouldn't update the ranges. I could still however update the visible selection.

@samthor
Copy link
Contributor

samthor commented Mar 23, 2021

Thanks for clarifying this.

I don't have time to fix or work on this polyfill right now, so I've made a note in the README and linked here.

@mscuthbert
Copy link
Contributor

Thank you @dbalcomb for implementing this and for @samthor for adding the link. I can confirm that this solved the problem for Safari 14 and Technology Preview for my project that uses a content-editable box within a custom-element w/ ShadowDOM.

For filling out of the rest of the Selection interface (I needed at least focusNode and isCollapsed) this older IE8 Selection polyfill will probably be useful:
https://github.com/luwes/selection-polyfill

Only the polyfill for Selection and not Range needs to be filled out:
https://github.com/luwes/selection-polyfill/blob/d536b16fed87d1da546b8fd5bd5348d05677c645/selection-polyfill.js#L116

@paladox
Copy link

paladox commented May 23, 2021

Is there a way to do this without contenteditable? or do you know how we can hide the boxes (styling)?

See https://gerrit-review.googlesource.com/c/gerrit/+/306913

The below is what it looks like. We want to hide the small boxes at the top and also remove the small bit at the bottom (below the line).

Screenshot 2021-05-23 at 15 31 40

This is what it looks like before:

Screenshot 2021-05-23 at 15 32 48

qtprojectorg pushed a commit to qtqa/gerrit that referenced this issue May 25, 2021
Since the selection shadow DOM is not implemented in Safari, a fix was
introduced in January 2021[1] to use the shadow-selection-polyfill
library to provide that functionality and allow selecting text to
comment.

As of March 2021 however, an fix to Safari[2] makes the
shadow-selection-polyfill not working from Safari v14.1 onwards.

It is not clear whether the library will ever work again in Safari, so
much so that a warning, explaining this, as been added to the
shadow-selection-polyfill README.md.

An alternative approach however has been suggested. The main idea is:
* Listen to 'selectionchange' events of 'contenteditable' areas.
* Trigger a 'beforeinput' event by running and immediately terminating
  an `execCommand`.
* use the getTargetRanges() method to get a list of static ranges

This change is the porting of that idea (as explained here[4]).

[1] https://gerrit-review.googlesource.com/c/gerrit/+/293242
[2] https://trac.webkit.org/changeset?reponame=webkit&new=269662
[3] https://github.com/GoogleChromeLabs/shadow-selection-polyfill
[4] GoogleChromeLabs/shadow-selection-polyfill#11

Bug: Issue 13955
Change-Id: I4daebbfd571dc3f11c2897a2282d6b6b3619a2c0
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants