- From: Piotr Koszuliński <p.koszulinski@cksource.com>
- Date: Wed, 28 May 2014 09:22:44 +0200
- To: Robin Berjon <robin@w3.org>
- Cc: Jonas Sicking <jonas@sicking.cc>, Ben Peters <Ben.Peters@microsoft.com>, "public-webapps@w3.org" <public-webapps@w3.org>
- Message-ID: <CAFk9nex83tcMPNNKMoNmuQhV=FkzxvUnQw3vB2rA=B6tKi6bDw@mail.gmail.com>
On Tue, May 27, 2014 at 11:01 AM, Robin Berjon <robin@w3.org> wrote: > On 25/05/2014 20:40 , Piotr Koszuliński wrote: > >> Making some things unselectable might also be useful. IE has >> unselectable, there's also -moz-user-select and friends. But this is >> small fries for later I'd reckon. >> >> There are also nested non-editable islands. We built very important >> feature based on them - http://ckeditor.com/demo#widgets. Currently we >> block their selection by preventing mousedown and we handle left/right >> arrows. But cancelling selectionchange would allow us to control more >> cases in a cleaner way. >> > > I'd be curious to know what your take is on the best way to expose this. > IE has an unselectable attribute, whereas Gecko and WebKit have a CSS > property. In this thread we've been talking about using cancellable events > for this (or if not cancellable, ones in which the selection can be > modified on the fly). > > On instinct I would tend to think that this not a great usage of CSS, it's > much more tied to behaviour at a lower level. But it sort of is one of > those borderline things (as many of the properties that initially came from > the CSS UI module). > > The scriptable option that we're consider is good in that it enables > arbitrary cases, but it could be interesting to support a number of cases > out of the box with a simpler (for developers) approach. > > Let's imagine the following DOM: > > <div contenteditable=minimal> > <p>blah blah blah</p> > <div class=widget unselectable>...</div> > <p>blah blah blah</p> > </div> > > If the cursor is at the beginning of the first p, you hold Shift, and > click at the end of the second p, we could imagine that you'd get a > Selection with two Ranges (one for each p) and not containing the > unselectable widget. I *think* that's the most desirable default behaviour, > and it's also one that can be pretty painful to control through script. In > that sense an unselectable attribute would make sense. (I reckon that > setting the widget to be cE=false would have the same effect, but it is > nevertheless an orthogonal property.) > > WDYT? > > My previous email might be confusing because I mentioned only that CKEditor handles mousedown event. Actually, this is a tip of an iceberg :). I think that it may be an interesting case, because we were forced to overwrite most of browser behaviours when implementing the widget system. Also, this case is so complex that it's easier to start from what we experienced rather than saying that if X and Y will be possible, then our needs will be satisfied. I hope you forgive me this laziness :) The main requirements for the widget system were: 1. User must not be able to modify a non-editable part of widget. 2. Widget must be selectable as a whole (like an image). 3. Widget may contain editable fragments (nested editables). 4. There are block and inline widgets (surprisingly important requirement). 5. Widget may contain other widgets in its nested editables. So widgets may be nested multiple times. 6. Widget must be selectable by click, arrow keys, etc. It also has to be deletable by backspace/delete and copyable by CTRL+C. This is a structure that we used: <div contenteditable=true> <!-- editor's main editable --> <p>foo</p> <div contenteditable=false> <!-- widget wrapper --> <p contenteditable=true>nested editable</p> <p>non-editable content</p> <div contenteditable=true> <p>another nested editable</p> </div> </div> <p>foo2 <span contenteditable=false>inline widget</span></p> </div> Why did we choose contenteditable=false? At the beginning it was simply intuitive. We don't fully control the interaction methods (mouse, keyboard, selection), so let browser do that. Then we overwrote most of the interaction methods so currently the attribute is not so crucial. However, there's one aspect of interaction which we cannot control and I'm not sure if we'll ever be able - dynamically changing selection (its visual and logical aspect) around block widgets, their nested editables and a special case that complicates all this - blocks may be floated. Let's try to imagine how we could handle selection without using contenteditable=false. <p>foo</p> <div class="widget">widget</div> <p>bar</p> TC1: User clicks main editable's margin, on the side of the block widget. If he clicks closer to the top of the widget, we would like to place caret at the end of preceding editable block (foo^), and when closer to bottom, then in the following block (^bar). Without checking dimensions, that would not be doable, because browser would suggest a selection inside <div>widget</div>. On the other hand, we could consider always selecting entire widget if selection was proposed inside it. This seems to be a good option, especially taking into account that we may have multiple block widgets one after another, and then you cannot place selection between them anyway. TC2: Another aspect is selection normalisation. When we make a decision that entire block widget should be selected we want to be able to anchor the selection in widget's parent element: <p>foo</p>[<div>widget</div>]<p>foo</p>. Since browser does not know the semantics of our widget, will it preserve the selection exactly were we put it? Because if it makes a decision to move it closer to text nodes, then we hit a wall. TC3: And how will browser render the selection? For our case it should select the widget like an image, not like a text (whole area should be marked, not only lines of text). Why? For usability reasons - this will indicate that this block is non-editable. And additionally the block may be empty and have size set by CSS and then its selection will mark it if it marks only text. TC4: <p>f<span>widget</span>bar</p> Now user wants to select "f" by mouse. He starts at "[f" and drags to the right. When he reaches "[f<span>]widget" we need to make a decision - select entire widget or right trim selection? If we start selecting entire widget too quickly we'll make it very hard to select only "f" by mouse or simply impossible if browser will normalise selection (move its right boundary to the <span>). When we delay selecting entire widget, then we have a problem on the right end: "[f<span>widget]</span>bar". TC5: <p><span>widget</span></p> If the normalisation happens at left or right end, then clicking a margin of editable will never make a collapsed selection outside widget. TC6: <p>foo<span>wid get</span>bar</p> User doubleclicks "foo", browser proposes [foo<span>wid]. Should we select "foo" or the widget? We would need to know the click target. And what if it was "f<b>o</b>o"? TC7: widget with nested editable (see the simple box example on http://ckeditor.com/demo#widgets) When user clicks border of the box, he intends to select the widget. But in such case browser would propose a selection in the nested editable. Again, we would need to check the click target. As you can imagine, there will be more cases. Floated content, nested editables, complex DOM structures. Most of the TCs might be handled in better or worse way, but I think it's clear that it will be better if browsers handle that. Imagine that all this would be done by JavaScript on every mousemove (when making selection by dragging) :D. Now, should the behaviour come from CSS or attributes? First of all, I think that there are two separate behaviours involved. Being non-editable and being selectable (and how that selection looks). The first part, being non-editable, is more about semantics, so for me it should be handled like today - using the contenteditable attribute. Second part, selection, includes 3 options: normal, none, like an image. The problem with unselectable style/property is that in our case we want the content to be selectable. If user selects all content of editor and copy, widgets should be copied too. So currently existing unselectable style/attribute is not an option for us. I think that non-editable content can't be selected as we would like it to be by default, so there'd need to exist a new option. For now, I was trying to add a style to selected widget, but this is hard, because a layer needs to be rendered over the widget and it visually conflicts with native selection, so this is not a good solution either. Besides handling selection changes, I want to emphasise one more problem - where the selection engine allows us to place the selection. Inline widgets works pretty well - we are able to place caret before, between and after non-editable inline elements. Only selecting whole elements was a problem. The bigger problem, though, is with block widgets, because it was neither possible to select them, nor place selection between them, nor at the beginning of editable if its contents start with widget. The ultimate edge case is editable which contains only a non-editable block :D. We worked around this by moving the real selection to a hidden container inside editable. We call this a fake selection, because what you actually see when you click a widget is a CSS-based outline and selection is kept somewhere outside of the viewport. Thanks to that we are able to select every element we want, because we remember what's selected. Then we overwrote most methods for changing selection, so we control how the widget may be unselected. This works pretty well and with more controllable selectionchange event could be even nicer. However, it only works for a single element selections. It's not possible to select two widgets this way. Therefore selection engines could be improved to handle selection around non-editable elements. Two aspects could be fixed: 1. Selection containing non-editable element(s) should be preserved when made from API (and should be doable manually): <p>foo</p>[<div contenteditable="false">foo</div>]<p>foo</p> 2. To avoid problems with selection beside non-editable blocks (because it's tricky), when user clicks a non-editable block or next to it, it could be selected. Actually, we went further and we select widgets when navigating with arrow keys, deleting, etc., because this indicates better where is the selection. That's especially important when there's a floated content, because collapsed selection poorly indicates what's going on. -- Piotrek Koszuliński CKEditor JavaScript Lead Developer
Received on Wednesday, 28 May 2014 07:23:14 UTC