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

Fix(editable component): reselect the range created by triple click #4455

Merged
merged 4 commits into from
Aug 28, 2021

Conversation

bytrangle
Copy link
Contributor

Description
When triple clicking inside the editable component of Slate, it doesn't select the correct block. This poses two problems when Slate is to be used as a rich text editor:

  • Give false visual feedback, e.g. the wrong toolbar button will be highlighted which leads to confusion
  • Unable to apply new style to the selected block only.

This PR tries to select the correct block that users triple click on.

Issue
Fixes #4329

Example

Before the fix:

triple-click-before

After the fix:

triple-click-after

Context

Given a node at path [1].

const editor = {
  children: [
    { // first node
    }
    {
      type: 'paragraph', // the node that receives triple click
      children: [
        { text: "Since it's rich text, you can do things like turn a selection of text " }, // path: [1, 0]
        { text: "bold", bold: true }, // path: [1, 1]
        { text: ", or add a semantically rendered block quote in the middle of the page, like this:" // path: [1, 2]
      ]
    }
  ]
}

Suppose we triple click just before the letter "S" of the paragraph.

  • Inside the onClick event check if there is triple click with event.detail === 3.
  • By design, the node that users click on will be the text node at path [1, 0]. Call Editor.above() to retrieve the closest element ancestor of that text node.
  • If the element exists, get all the children of the element. Create an array of their respective path. It should be
    [[1, 0], [1, 1], [1, 2]]
  • Create a range whose anchor is the first child and focus is the third child. Its anchor offset is 0 (because the selection starts from the very start of the block) and focus offset is the end point of the third child (82) in this case.
  • Calling Transforms.select(range) to select that range.

Checks

  • The new code matches the existing patterns and styles.
  • The tests pass with yarn test.
  • The linter passes with yarn lint. (Fix errors with yarn fix.)
  • The relevant examples still work. (Run examples with yarn start.)
  • You've added a changeset if changing functionality. (Add one with yarn changeset add.)

@changeset-bot
Copy link

changeset-bot bot commented Aug 16, 2021

⚠️ No Changeset found

Latest commit: 23405b8

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Collaborator

@dylans dylans left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this change seems reasonable to me. I've made a few minor suggestions.

// Check if user clicks thrice
if (event.detail === 3) {
// Check the closest element block above the selection
const elem = Editor.above(editor, {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be clearer if it was const block rather than const elem, since it's not returning a match if it's not a block?

match: n => Editor.isBlock(editor, n),
})
if (elem) {
// Get all the children of the element above
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Get all the children of the element above
// Get all the children of the block element above the selection

// Get all the children of the element above
const children = Node.children(editor, elem[1])
const childrenPath = []
// Loop through the generator of element children and create a list of their respective paths.
Copy link
Collaborator

@dylans dylans Aug 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please run the linter on this file, this comment and a few below likely exceed the line length rules. (Note it's not currently complaining, but I might still suggest getting them to be 80 characters wide.

@bytrangle
Copy link
Contributor Author

bytrangle commented Aug 16, 2021

Thanks, this change seems reasonable to me. I've made a few minor suggestions.

Thank you, I will fix them tomorrow.

But I've noticed that when I triple click on the second paragraph of the Rich Text example, the Quote Block button is highlighted for a split second and then it's not highlighted.

Do you experience the same?

@bytrangle
Copy link
Contributor Author

bytrangle commented Aug 17, 2021

Hello @dylans ,
I've discovered that when I triple click on a block, the Editor.nodes() method is supposed to grab a generator of nodes at the current selection, but somehow it always includes the node below the clicked node.

next-sibling-node-selected-when-triple-click

I've traced the problem down to Slate's transform operator.

This is what happens on the third click:

third-click-transform

Between createDraft and finishDraft, the selection object has been reassigned:

transform(editor, op): {
  // ...
  let selection = editor.selection && createDraft(editor.selection)
  try {
    selection = applyToDraft(editor, selection, op)
  } finally {
      // ...
      if (selection) {
        editor.selection = isDraft(selection) // this evaluates to true
          ? (finishDraft(selection) as Range)
          : selection
    }
  }
}

I think the culprit is probably the helper function applyToDraft(), so please allow me a few more days to figure it out.

@dylans
Copy link
Collaborator

dylans commented Aug 17, 2021

@bytrangle thanks, I appreciate you digging into this further.

@xenostar
Copy link

Just wanted to chime in and say I had some time to test this fix today and it is looking really good. This problem has been plaguing Slate for along time and I'm really looking forward to this fix!

const start = Editor.start(editor, path)
const end = Editor.end(editor, path)
// Check if user clicks thrice
if (event.detail === 3) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you thought about setting this to event.detail >= 3 instead? I noticed when trying a similar approach to this fix locally that if you spam click many times, event.detail continues to increment so this wouldn't be fired once you go past 3 clicks.

… click"

Reason: attaching a handler for `onClick` event is no longer needed.
Reason: Triple clicking an element in Chrome will falsely set the focus node as the next sibling node with focusOffset 0
@dylans
Copy link
Collaborator

dylans commented Aug 25, 2021

@bytrangle to clarify, is this really only an issue in Chrome/Chromium-based browsers? I ask as it is a very different PR from your original. Thanks!

@bytrangle
Copy link
Contributor Author

bytrangle commented Aug 26, 2021

@dylans
AFAIK, Firefox doesn't have this problem. You can checkout this JsFiddle in both Chrome and Firefox. When you open the console and run the script, then triple clicking the first paragraph, you can see that Chrome will identify the focusNode as the next block (a quoteblock), while Firefox is able to get the focusNode as the paragraph.

The triple-click "bug" is mentioned as a Chromium issue here. I think it's not so much an issue with Chrome, but an issue with the triple-click behaviour not specified in the spec.

@bytrangle
Copy link
Contributor Author

Hello @xenostar . Thanks for chiming in. I've used a new approach that doesn't require checking for event.detail. The reason is Slate calls for window.selection() way before the click event is fired. So it will get the incorrect focusNode and send highlight the wrong toolbar button before I can do anything in the click event handler.

Hope that makes sense. Tell me what you think.

Copy link
Collaborator

@dylans dylans left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @bytrangle this looks great and has my approval. I'll leave it open for a couple more days in case there's further feedback or anything I might have missed.

@bytrangle
Copy link
Contributor Author

@dylans Thank you, and actually that's a good call because I've written an e2e test for this triple-click behavior.

It passed, but it opened up a torrent of Typescript warnings. I've tried to fix it as I can, but since I've never used Typescript and just learn as I go, please take a look and tell me what you think.

@dylans
Copy link
Collaborator

dylans commented Aug 28, 2021

@dylans Thank you, and actually that's a good call because I've written an e2e test for this triple-click behavior.

It passed, but it opened up a torrent of Typescript warnings. I've tried to fix it as I can, but since I've never used Typescript and just learn as I go, please take a look and tell me what you think.

Your TS seems reasonable. Maybe there are ways to make it less messy, but I think it's fine. Landing this now.

@dylans dylans merged commit 2d1aaaf into ianstormtaylor:main Aug 28, 2021
@dylans
Copy link
Collaborator

dylans commented Sep 6, 2021

@bytrangle This appears to have caused a regression in Chrome... when you triple check on a link with marks (e.g. bold or italic) and press the backspace/delete key, it only deletes back to the point of the most recent mark.

The example in examples/richtext , triple click on the first line and press backspace to reproduce.

@TheSpyder
Copy link
Collaborator

Looking at the stack trace generated by #4490 the use of ! on values that might be null is to blame. I haven't been paying enough attention to PRs, sorry, I would've flagged that as a concern.

@bytrangle
Copy link
Contributor Author

@dylans You mean this?

2021-09-08--16-22-05__triple-click-on-marks.mp4

bytrangle added a commit to bytrangle/slate that referenced this pull request Sep 9, 2021
@bytrangle bytrangle mentioned this pull request Sep 9, 2021
5 tasks
dylans pushed a commit that referenced this pull request Sep 9, 2021
* Remove changeset for the fix for regression caused by triple click fix

* fix: incorrect Slate range when triple clicking a block (#4455)
dylans pushed a commit to dylans/slate that referenced this pull request Sep 9, 2021
* Remove changeset for the fix for regression caused by triple click fix

* fix: incorrect Slate range when triple clicking a block (ianstormtaylor#4455)
dylans pushed a commit to dylans/slate that referenced this pull request Sep 13, 2021
…anstormtaylor#4455)

* fix(editable component): reselect the range created by triple click

* Revert "fix(editable component): reselect the range created by triple click"

Reason: attaching a handler for `onClick` event is no longer needed.

* fix(react-editor): reselect DOMSelection when triple clicked

Reason: Triple clicking an element in Chrome will falsely set the focus node as the next sibling node with focusOffset 0

* test: add e2e test for triple click
dylans pushed a commit to dylans/slate that referenced this pull request Sep 13, 2021
* Remove changeset for the fix for regression caused by triple click fix

* fix: incorrect Slate range when triple clicking a block (ianstormtaylor#4455)
jaked added a commit to jaked/slate that referenced this pull request Oct 10, 2021
dylans pushed a commit that referenced this pull request Oct 11, 2021
…4588)

* revert #4455 / #4512; fix triple-click by unhanging range with void

* added changeset
@github-actions github-actions bot mentioned this pull request Oct 11, 2021
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

Successfully merging this pull request may close these issues.

Triple-click selection and Selection API issue
4 participants