1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-01-18 05:59:13 +01:00

Fix(editable component): reselect the range created by triple click (#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
This commit is contained in:
Trang Le 2021-08-28 19:02:30 +07:00 committed by GitHub
parent 8eb1abac87
commit 2d1aaafa0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 100 additions and 0 deletions

View File

@ -0,0 +1,62 @@
describe('selection', () => {
// Currently, testing color property always yields rgb() value, even when stored
// as hex.
// Hence, we'll overwrite Cypress `should` command, in which we use
// `getComputedStyle` on both the subject element and creates a temp element
// to get the computed color and compares.
// Code by Nicholas Boll at https://github.com/cypress-io/cypress/issues/2186
// 2021/08/27
type CssStyleObject = Partial<CSSStyleDeclaration> &
Record<string, string | null>
const compareColor = (color: string, property: string) => (
targetElement: NodeListOf<Element>
) => {
const tempElement = document.createElement('div')
tempElement.style.color = color
tempElement.style.display = 'none' // make sure it doesn't actually render
document.body.appendChild(tempElement) // append so that `getComputedStyle` actually works
const tempColor = getComputedStyle(tempElement).color
// Calling window.getComputedStyle(element) returns `CSSStyleDeclaration`
// object which has numeric index signature with string keys.
// We need to declare a new object which retains the typings of
// CSSStyleDeclaration and yet is relaxed enough to accept an
// arbitrary property name.
const targetStyle = getComputedStyle(targetElement[0]) as CssStyleObject
const targetColor = targetStyle[property]
document.body.removeChild(tempElement) // remove it because we're done with it
expect(tempColor).to.equal(targetColor)
}
Cypress.Commands.overwrite(
'should',
(originalFn, subject, expectation, ...args) => {
const customMatchers: { [key: string]: any } = {
'have.backgroundColor': compareColor(args[0], 'backgroundColor'),
'have.color': compareColor(args[0], 'color'),
}
// See if the expectation is a string and if it is a member of Jest's expect
if (typeof expectation === 'string' && customMatchers[expectation]) {
return originalFn(subject, customMatchers[expectation])
}
return originalFn(subject, expectation, ...args)
}
)
const slateEditor = '[data-slate-node="element"]'
beforeEach(() => cy.visit('examples/richtext'))
it('select the correct block when triple clicking', () => {
// triple clicking the second block (paragraph) shouldn't highlight the
// quote button
for (let i = 0; i < 3; i++) {
cy.get(slateEditor)
.eq(1)
.click()
}
cy.contains('.material-icons', /format_quote/)
.parent()
.should('have.color', '#ccc')
})
})

View File

@ -560,6 +560,44 @@ export const ReactEditor = {
anchorOffset = domRange.anchorOffset
focusNode = domRange.focusNode
focusOffset = domRange.focusOffset
// When triple clicking a block, Chrome will return a selection object whose
// focus node is the next element sibling and focusOffset is 0.
// This will highlight the corresponding toolbar button for the sibling
// block even though users just want to target the previous block.
// (2021/08/24)
// Within the context of Slate and Chrome, if anchor and focus nodes don't have
// the same nodeValue and focusOffset is 0, then it's definitely a triple click
// behaviour.
if (
IS_CHROME &&
anchorNode?.nodeValue !== focusNode?.nodeValue &&
domRange.focusOffset === 0
) {
// If an anchorNode is an element node when triple clicked, then the focusNode
// should also be the same as anchorNode when triple clicked.
if (anchorNode!.nodeType === 1) {
focusNode = anchorNode
} else {
// Otherwise, anchorNode is a text node and we need to
// - climb up the DOM tree to get the farthest element node that receives
// triple click. It should have atribute 'data-slate-node' = "element"
// - get the last child of that element node
// - climb down the DOM tree to get the text node of the last child
// - this is also the end of the selection aka the focusNode
const anchorElement = anchorNode!.parentNode as HTMLElement
const tripleClickedBlock = anchorElement.closest(
'[data-slate-node="element"]'
)
const focusElement = tripleClickedBlock!.lastElementChild
// Get the element node that holds the focus text node
const innermostFocusElement = focusElement!.querySelector(
'[data-slate-string]'
)
const lastTextNode = innermostFocusElement!.childNodes[0]
focusNode = lastTextNode
}
}
// COMPAT: There's a bug in chrome that always returns `true` for
// `isCollapsed` for a Selection that comes from a ShadowRoot.
// (2020/08/08)