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

Widgets and selection move #3893

Closed
szymonkups opened this issue Nov 24, 2016 · 2 comments
Closed

Widgets and selection move #3893

szymonkups opened this issue Nov 24, 2016 · 2 comments
Assignees
Labels
package:engine type:feature This issue reports a feature request (an idea for a new functionality or a missing option).
Milestone

Comments

@szymonkups
Copy link
Contributor

We need to handle keyboard events in a special way when dealing with widgets. First of all, I would like to gather some examples presenting how model's selection should look before and after certain key event. Feel free to add more examples if needed as this list is not a complete one.

Moving

[<widget></widget>]<p>foo</p>
--- right arrow pressed ---
<widget></widget><p>[]foo</p>  
<p>foo</p>[<widget></widget>]	
--- left arrow pressed ---
<p>foo[]</p><widget></widget>
<p>foo[]</p><widget></widget>	
--- right arrow pressed ---
<p>foo</p>[<widget></widget>]
<widget></widget><p>[]foo</p>
--- left arrow pressed ---
[<widget></widget>]<p>foo</p>
[<widget></widget>]<widget></widget>
--- right arrow pressed ---
<widget></widget>[<widget></widget>]
<widget></widget>[<widget></widget>]
--- left arrow pressed ---
[<widget></widget>]<widget></widget>

Deleting

<widget></widget><p>[]foo</p>
--- backspace pressed ---
[<widget></widget>]<p>foo</p>
--- backspace pressed ---
<p>[]foo</p>
<p>foo[]</p><widget></widget>
--- delete pressed ---
<p>foo</p>[<widget></widget>]
--- delete pressed ---
<p>foo[]</p>
[<widget>1</widget>]<widget>2</widget>
--- delete pressed ---
[<widget>2</widget>]
<widget>1<widget>[<widget>2</widget>]
--- backspace pressed ---
[<widget>1</widget>]
@szymonkups szymonkups self-assigned this Nov 24, 2016
@szymonkups
Copy link
Contributor Author

szymonkups commented Nov 25, 2016

Some idea on how this should actually work:

  1. Listen on keydown events on view document.
  2. Whenever event is fired, check (in the view) if one of the situations described above occured. We have no information in the model about widgets, so we'll need to do the check on view elements.
  3. Prevent default keydown handling.
  4. Modify selection in the model.

@Reinmar
Copy link
Member

Reinmar commented Nov 30, 2016

Overview

There are two different types of actions which we need to handle:

  1. forward delete and delete (delete action)
  2. arrow keys (move selection)

While nature of these actions is similar, we won't be able to handle them in the same way.

Delete

The delete/backspace keys are handled as follows:

  1. View#delete event is fired.
  2. DeleteCommand is executed.
  3. DataController#extendSelection() is used (note: this method is now called modifySelection() but should rather be renamed).
  4. DataController#deleteContent() is used.

And that's it. We always handle the process from start to the end.

The DataController's methods are extensible, allowing features to tune up their behaviour in very specific cases. The command and view event also make for extension points.

Move selection

Unfortunately we can't handle arrow keys in the same way. We have no access to necessary selection primitives like x-index and would have a lot of troubles calculating where to put selection in case of up/down.

Therefore, we don't want to expose DataController#moveSelection() method, neither a command, etc. In this case, the right way (at least at this stage) will be listening to the view event and implement the algorithm using the primitives that we have.

The primitive that we need to use in this case is extendSelection(). Moving selection can be implemented as:

  1. collapse selection to left/right,
  2. extend it left/right,
  3. collapse it once more

Extending selection is extensible, so implementing moving selection in this way will make it extensible as well.

In the case of widgets, we won't execute the step 3 from the above algorithm, but a bit different logic which will simply select the widget which was added to the selection in step 2.

Extensions for DataController#deleteContent

General rule of thumb – output selection should always be collapsed.

Handling selection after deleting content

If one or more objects were selected initially (or other odd situation occured), then, after steps 1+2 of the deleteContent algorithm we may have a selection in a place where text is not allowed.

For example:

  • <p>x</p>[<obj></obj>]<p>x</p> -> <p>x</p>^<p>x</p>
  • <p>x</p>[<p>x</p>]<p>x</p> -> <p>x</p>^<p>x</p>
  • [<p>x</p><p>x</p>] -> ^

We may also have a situation where the selection ends up in a correct place:

  • <p>x[<obj></obj>]x</p> -> <p>x^x</p>

If the selection ended up in a wrong place - just auto paragraph.

Note: This is the behaviour that we have currently implemented. We've been considering the behaviour from CKEditor 4 where the selection is moved to a closest block, but it's tricky to implement, because it's super custom and full of edge cases (e.g. root boundaries or selecting another block).

Extension for DataController#extendSelection

General rule of thumb – selection should not be moved to the inside of an object. E.g.:

<obj></obj><p>^x</p> + backspace => the selection must not be moved to <obj>^</obj>

This means that extending the selection to the left in the case mentioned above should not put the focus inside the object. The focus must be placed before the object.

  • <obj></obj><p>^x</p> + extend left -> [<obj></obj><p>]x</p>,
  • <obj></obj>[<obj></obj>] + extend left -> [<obj></obj><obj></obj>],

So in other words, the selection outputed from the extendSelection should not cross object boundaries.

Custom widget handler for delete

Use the view events, cause we shouldn't change the DataController#deleteContent semantics to "select".

Deleting before/after object

E.g.:

  • <obj></obj><p>^x</p> + backspace
  • <obj></obj><p>^</p> + backspace
  1. Select the object.
  2. If the initial selection container is empty, remove it.

Custom widget handler for arrows

Use the view events (keydown or selectionChange).

Object selected

  1. Collapse selection in the right direction (or even base on the view#selectionChange which currently implements the same behaviour).
  2. Use Document#getNearestSelectionPosition with the right direction param to find the next possible position.
  3. If not position found... rollback?

Selection next to an object

  1. If selection is not collapsed – do nothing.
  2. Two options:
  • Use DataController#extendSelection() on a selection clone to probe what's next. If selection focus is before/after an object, it means that this object should be selected.
  • Alternatively – use the walker to find a closest object (in the right direction, ofc). Check if position after/before that object is touching the current selection.
  1. Select the found object.

Note: The above algorithm (the option 2a) will automatically work with the case when e.g. we selected an object at the beginning of the root and pressed left arrow. The focus will not be moved any further, so on its right side there'll still be that object and we'll reselect it. 🎉

@mlewand mlewand transferred this issue from ckeditor/ckeditor5-engine Oct 9, 2019
@mlewand mlewand added this to the iteration 6 milestone Oct 9, 2019
@mlewand mlewand added status:confirmed type:feature This issue reports a feature request (an idea for a new functionality or a missing option). package:engine labels Oct 9, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
package:engine type:feature This issue reports a feature request (an idea for a new functionality or a missing option).
Projects
None yet
Development

No branches or pull requests

3 participants