mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-01-17 21:49:20 +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:
parent
8eb1abac87
commit
2d1aaafa0b
62
cypress/integration/examples/select.ts
Normal file
62
cypress/integration/examples/select.ts
Normal 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')
|
||||
})
|
||||
})
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user