Re: [editing] selection across editing host boundaries

On Tue, Jun 24, 2014 at 12:34 PM, Robin Berjon <robin@w3.org> wrote:

> On 23/06/2014 20:33 , Johannes Wilm wrote:
>
>> I filed bugs on this on both Firefox and Chrome in spring 2013. It was
>> briefly fixed in Chrome, but the fix was then retracted and we never
>> heard any more of it. It was also reported in Firefox by someone else in
>> 2011. [1]
>>
>> I also had some contact with Webkit people working in this area who
>> unfortunately didn't find the behavior to be problematic.
>>
>> This is probably the main reason why at least I am wondering if editing
>> maybe just is an area that won't be of enough interest to browser makers
>> to ever fix and that it may therefore just make more sense to just ask
>> them to remove it and do everything in Javascript. But now it seeems
>> this is about to change, which would be really really good.
>>
>
> From discussions I've had in the past on this topic, it's not so much that
> browser-folks don't want to fix this. The problem is more that 1) this is
> hard, so fixing can often only happen if someone really owns the problem
> rather than contributing the odd fix, and such a person isn't always
> available; 2) the current state of contentEditable is such a mess, notably
> with libraries doing per-UA workarounds, that fixes that make the behaviour
> globally saner actually break deployed code.
>
> This has conspired to make the situation rot. Hopefully we can fix this
> with the new tack taken here, notably by using contentEditable=cursor as a
> "sanity hook" which doesn't have to be backwards compatible with the
> existing madness.
>

I am happy to see more cases that force us to propose something new,
instead of patching rotting contentEditable=true :D.


>
> That's all nice and well, but what should the range be in the following
> cases:
>
>   A:
>   <editable>blah <non-editable>foo</non-editable>| blah</editable>
>
>   B:
>   <editable>blah [<non-editable>foo</non-editable>] blah</editable>
>
>   C:
>   <editable>bl[ah <non-editable>foo</non-editable> bl]ah</editable>
>
>   D:
>   <editable>blah <non-editable>f[oo</non-editable> bl]ah</editable>
>
>   E:
>   <editable>blah <non-editable>|foo</non-editable> blah</editable>
>
> Or in this:
>
>   F:
>   <ul contenteditable=cursor>
>     <li>blah</li>
>     <li>|blah</li>
>   </ul>
>
>   F2:
>   <ul contenteditable=cursor>
>     <li>blah</li>
>     <li>|</li>
>   </ul>
>
> or just for kicks, one could make the argument that this is different:
>
>   G:
>   <ul contenteditable=cursor>
>     <li contenteditable=cursor>blah</li>
>     <li contenteditable=cursor>|blah</li>
>   </ul>
>
> ?
>
> I think that we can start to solve this in the following manner:
>
>   • When a deletion event would produce a range crossing an odd number of
> editing boundaries (either because there's a selection doing so in the sum
> of its ranges or for instance given backspace in case A above) then its
> range is empty and corresponds to a collapsed range a the cursor position
> (this is defined even for selections).
>
>   • Conversely, when it would cross an even number of editing boundaries,
> then the range covers them.
>
>   • Deletion events capture information expressing the direction of the
> deletion ("previous", "next", or possibly "both" in the delete-line case).
> This makes it possible for script to know how to hand the empty-range case.
>
>   • Additionally, the deletion event can expose convenient information
> about the editing boundaries being crossed, their number, their hosts.
>
> This means that, assuming backspace, the cases above can be handled as
> follows:
>
> A: empty range, the script can decide whether to select or delete the
> non-editable content (direction "previous").
> B: a range containing non-editable. Presumably deleted.
> C: same as B with some extra content on both sides. Presumably deleted.
> D: empty range, the script can decide what makes most sense. (Stabbing the
> user in the face sounds good.)
> E: empty range, the script decides which is best.
>
> For F, F2, G, and an awful lot of other cases (dt/dd, td, etc.) I think we
> should take the minimalist approach: just produce a deletion event
> indicating its direction but with an empty range. Scripts can decide if
> they wish to merge elements, delete empty ones, outdent, etc.
>
>
> This is getting complicated enough (and I haven't mentioned other cases
> such as br, script, hr, td, img, video...) that I wonder if the Deletion
> event shouldn't have its own spec.
>

I agree about A, B and C. Selection in D in my opinion should not be
possible (I'll comment on that later [1]). Selection in E also should not
be possible because empty selection can't be made in non-editable element.

As for the complexity, I feel that there's an indefinite number of cases
(e.g. we can start including styling - hidden elements, block/inline etc.).
Since the information about proposed result range is just an information -
can't we provide it only in some cases. For example, considering
CTRL+backspace in this scenarios:

1. <p>foo bar|</p>  => [bar], direction:previous
2. <p>foo</p><non-editable-block/><p>|bar</p> => direction:previous

In the second case editor may choose between multiple possible results
(select non-editable block, delete it, merge paragraphs, move caret and
don't merge, delete "foo" or not), so proposing anything only complicates
spec and may not be really valuable.

I think that browser could propose range if it does not:

* cross any block/hidden/table elements,
* cross any empty element (br, hr),
* cross any element which cannot contain visible text (video),
* cross any elements with contenteditable attribute.

(By "cross" I mean a theoretical case that range was expanded in a given
direction).

So information will be provided only when deleting text with possible
intermixed inline elements.


>
> Other question: when there's a selection and an input event takes place,
> should it be preceded by a deletion event? I think we need to because the
> alternative is to have the input event handler have to perform its own
> logic equivalent to deletion, which would be painful. But it comes with its
> own interesting challenges.
>

One event handler may may reuse logic of another event handler - I don't
think it's a problem. Additionally, input may have a different result than
deletion+input. I mean that "emptying" selection performed while inputting
text may need to work differently than deleting.


>
>
>
>  Use cases for this:
>>
>> 1. We use it for footnotes which we float of to the right of the text in
>> a <span class="footnote" contneteditable=false><span><span
>> contenteditable="true">[FOOTNOTE TEXT]</span></span></span>. If one has
>> access to CSS regions, one can even float them to be under the text on
>> each page. The other <span class="footnote"> is used to draw the
>> footnote number in the main text. If one hits backspace behind it, the
>> entire footnote should disappear. as it is now, instead the "back wall"
>> of the footnote is removed  which means that the rest of the paragraph
>> is being added to the footnote.
>>
>
> A question for you: how would you like selection to work in this case,
> notably for copy/pasting? As a user, I would tend to expect that if I
> select from before the <sup>1</sup> to after it and copy, I would get a
> buffer containing the <sup>1</sup> but *not* the footnote content
> (otherwise you get the famed "PDF effect" with lots of junk in your
> buffer). It also looks visually weird if you have the footnote to the side
> of the page being selected. But with the logical document order you use, it
> would get selected. Do you make use of selection-preventing tricks?
>
> These likely have their own amusing interactions with deletion: if you
> make the footnote non-selectable but wish to drop it when a selection
> encompassing it is deleted, you're facing a fun new challenge.
>
>
I'll second Johannes here. I would also expect that footnote's content is
copied/cut. This is a piece of sup's semantic and should not be left alone.
Problem starts when we keep meaningless (e.g. related to UI) content in a
selected part of DOM, but in such cases we have a copy/cut events to handle
them differently. For example CKEditor will do that, because it needs to
downcast (transform internal format of a widget [2] into its data format)
content so when pasted into another editor, that editor will be able to
treat the pasted content as a content coming from outer world (e.g. apply
content filters [3]).


[1] Now, back to selection crossing editing hosts boundaries. I can't think
of a case when it would be justified and convenient to work on a selection
starting and ending in different editing hosts. Every editing host, even a
nested one, can be understood as a separate editor. Obviously, we don't
want a selection starting in one editor and ending in a totally different
editor - which of them would handle interaction? What if these editors have
different rules regarding what's selectable (they handle selectionchange
event differently)? Entire selection must be rooted in one editor. In my
opinion the same applies to nested editing hosts. Each of them is a
separate, focusable element on which editing events are fired. We do use
nested editable elements exactly to inform browser that these are separate
editable elements - that different rules may apply to them and that content
outside them may not be editable.

I based this opinion on cases I know from CKEditor's widgets system,
spreadsheet example (<td>f[oo</td><td>ba]r</td> is not allowed AFAICS), and
couple of other use cases I saw. Unless someone can point out a scenario in
which it must be possible to make a selection starting and ending in a
different editing hosts, I don't think we should make it possible, because
it's hard to imagine how it should work (events, focus).

To clarify, I consider all following selections to have boundaries in two
different editing contexts (using term editing hosts may not be correct):
<editable>[foo<non-editable>bar]</non-editable></editable> (non-editable
element creates a new context)
<editable>[foo<non-editable><editable>bar]</editable></non-editable></editable>
<editable><non-editable><editable>[foo</editable></non-editable><non-editable><editable>bar]</editable></non-editable></editable>
All of them should not be possible to create.

PS. Some time ago I wrote
http://lists.w3.org/Archives/Public/public-webapps/2014AprJun/0655.html
where I analysed whether it could be possible to leave a decision what
should be selected for editor (which would use selectionchange event). I
think that it would be too much trouble for JS. Additionally, I mentioned
the problem with selection around non-editable block elements.

[2] http://ckeditor.com/demo#widgets
[3] docs.ckeditor.com/#!/guide/dev_advanced_content_filter


-- 
Piotrek Koszuliński
CKEditor JavaScript Lead Developer

Received on Tuesday, 24 June 2014 13:39:33 UTC