mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-23 15:32:59 +02:00
Fix firefox disconnected selection api usage (#5486)
* Fix firefox disconnected selection api usage * Add changeset * Fix typo + add link to explanation * Fix integration tests
This commit is contained in:
5
.changeset/two-tomatoes-deliver.md
Normal file
5
.changeset/two-tomatoes-deliver.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'slate-react': minor
|
||||
---
|
||||
|
||||
Fix invalid usage of the selection API in firefox
|
@@ -305,12 +305,33 @@ export const Editable = (props: EditableProps) => {
|
||||
return
|
||||
}
|
||||
|
||||
// Get anchorNode and focusNode
|
||||
const focusNode = domSelection.focusNode
|
||||
let anchorNode
|
||||
|
||||
// COMPAT: In firefox the normal seletion way does not work
|
||||
// (https://github.com/ianstormtaylor/slate/pull/5486#issue-1820720223)
|
||||
if (IS_FIREFOX && domSelection.rangeCount > 1) {
|
||||
const firstRange = domSelection.getRangeAt(0)
|
||||
const lastRange = domSelection.getRangeAt(domSelection.rangeCount - 1)
|
||||
|
||||
// Right to left
|
||||
if (firstRange.startContainer === focusNode) {
|
||||
anchorNode = lastRange.endContainer
|
||||
} else {
|
||||
// Left to right
|
||||
anchorNode = firstRange.startContainer
|
||||
}
|
||||
} else {
|
||||
anchorNode = domSelection.anchorNode
|
||||
}
|
||||
|
||||
// verify that the dom selection is in the editor
|
||||
const editorElement = EDITOR_TO_ELEMENT.get(editor)!
|
||||
let hasDomSelectionInEditor = false
|
||||
if (
|
||||
editorElement.contains(domSelection.anchorNode) &&
|
||||
editorElement.contains(domSelection.focusNode)
|
||||
editorElement.contains(anchorNode) &&
|
||||
editorElement.contains(focusNode)
|
||||
) {
|
||||
hasDomSelectionInEditor = true
|
||||
}
|
||||
@@ -336,7 +357,6 @@ export const Editable = (props: EditableProps) => {
|
||||
}
|
||||
|
||||
// Ensure selection is inside the mark placeholder
|
||||
const { anchorNode } = domSelection
|
||||
if (
|
||||
anchorNode?.parentElement?.hasAttribute(
|
||||
'data-slate-mark-placeholder'
|
||||
@@ -391,19 +411,16 @@ export const Editable = (props: EditableProps) => {
|
||||
return newDomRange
|
||||
}
|
||||
|
||||
const newDomRange = setDomSelection()
|
||||
// In firefox if there is more then 1 range and we call setDomSelection we remove the ability to select more cells in a table
|
||||
if (domSelection.rangeCount <= 1) {
|
||||
setDomSelection()
|
||||
}
|
||||
|
||||
const ensureSelection =
|
||||
androidInputManagerRef.current?.isFlushing() === 'action'
|
||||
|
||||
if (!IS_ANDROID || !ensureSelection) {
|
||||
setTimeout(() => {
|
||||
// COMPAT: In Firefox, it's not enough to create a range, you also need
|
||||
// to focus the contenteditable element too. (2016/11/16)
|
||||
if (newDomRange && IS_FIREFOX) {
|
||||
const el = ReactEditor.toDOMNode(editor, editor)
|
||||
el.focus()
|
||||
}
|
||||
|
||||
state.isUpdatingSelection = false
|
||||
})
|
||||
return
|
||||
|
@@ -829,15 +829,37 @@ export const ReactEditor: ReactEditorInterface = {
|
||||
|
||||
if (el) {
|
||||
if (isDOMSelection(domRange)) {
|
||||
anchorNode = domRange.anchorNode
|
||||
anchorOffset = domRange.anchorOffset
|
||||
focusNode = domRange.focusNode
|
||||
focusOffset = domRange.focusOffset
|
||||
// COMPAT: In firefox the normal seletion way does not work
|
||||
// (https://github.com/ianstormtaylor/slate/pull/5486#issue-1820720223)
|
||||
if (IS_FIREFOX && domRange.rangeCount > 1) {
|
||||
focusNode = domRange.focusNode // Focus node works fine
|
||||
const firstRange = domRange.getRangeAt(0)
|
||||
const lastRange = domRange.getRangeAt(domRange.rangeCount - 1)
|
||||
|
||||
// Right to left
|
||||
if (firstRange.startContainer === focusNode) {
|
||||
anchorNode = lastRange.endContainer
|
||||
anchorOffset = lastRange.endOffset
|
||||
focusOffset = firstRange.startOffset
|
||||
} else {
|
||||
// Left to right
|
||||
anchorNode = firstRange.startContainer
|
||||
anchorOffset = firstRange.endOffset
|
||||
focusOffset = lastRange.startOffset
|
||||
}
|
||||
} else {
|
||||
anchorNode = domRange.anchorNode
|
||||
anchorOffset = domRange.anchorOffset
|
||||
focusNode = domRange.focusNode
|
||||
focusOffset = domRange.focusOffset
|
||||
}
|
||||
|
||||
// COMPAT: There's a bug in chrome that always returns `true` for
|
||||
// `isCollapsed` for a Selection that comes from a ShadowRoot.
|
||||
// (2020/08/08)
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=447523
|
||||
if (IS_CHROME && hasShadowRoot(anchorNode)) {
|
||||
// IsCollapsed might not work in firefox, but this will
|
||||
if ((IS_CHROME && hasShadowRoot(anchorNode)) || IS_FIREFOX) {
|
||||
isCollapsed =
|
||||
domRange.anchorNode === domRange.focusNode &&
|
||||
domRange.anchorOffset === domRange.focusOffset
|
||||
@@ -876,15 +898,19 @@ export const ReactEditor: ReactEditorInterface = {
|
||||
focusOffset = anchorNode.textContent?.length || 0
|
||||
}
|
||||
|
||||
let anchor = ReactEditor.toSlatePoint(editor, [anchorNode, anchorOffset], {
|
||||
exactMatch,
|
||||
suppressThrow,
|
||||
})
|
||||
const anchor = ReactEditor.toSlatePoint(
|
||||
editor,
|
||||
[anchorNode, anchorOffset],
|
||||
{
|
||||
exactMatch,
|
||||
suppressThrow,
|
||||
}
|
||||
)
|
||||
if (!anchor) {
|
||||
return null as T extends true ? Range | null : Range
|
||||
}
|
||||
|
||||
let focus = isCollapsed
|
||||
const focus = isCollapsed
|
||||
? anchor
|
||||
: ReactEditor.toSlatePoint(editor, [focusNode, focusOffset], {
|
||||
exactMatch,
|
||||
@@ -894,46 +920,6 @@ export const ReactEditor: ReactEditorInterface = {
|
||||
return null as T extends true ? Range | null : Range
|
||||
}
|
||||
|
||||
/**
|
||||
* suppose we have this document:
|
||||
*
|
||||
* { type: 'paragraph',
|
||||
* children: [
|
||||
* { text: 'foo ' },
|
||||
* { text: 'bar' },
|
||||
* { text: ' baz' }
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* a double click on "bar" on chrome will create this range:
|
||||
*
|
||||
* anchor -> [0,1] offset 0
|
||||
* focus -> [0,1] offset 3
|
||||
*
|
||||
* while on firefox will create this range:
|
||||
*
|
||||
* anchor -> [0,0] offset 4
|
||||
* focus -> [0,2] offset 0
|
||||
*
|
||||
* let's try to fix it...
|
||||
*/
|
||||
|
||||
if (IS_FIREFOX && !isCollapsed && anchorNode !== focusNode) {
|
||||
const isEnd = Editor.isEnd(editor, anchor!, anchor.path)
|
||||
const isStart = Editor.isStart(editor, focus!, focus.path)
|
||||
|
||||
if (isEnd) {
|
||||
const after = Editor.after(editor, anchor as Point)
|
||||
// Editor.after() might return undefined
|
||||
anchor = (after || anchor!) as T extends true ? Point | null : Point
|
||||
}
|
||||
|
||||
if (isStart) {
|
||||
const before = Editor.before(editor, focus as Point)
|
||||
focus = (before || focus!) as T extends true ? Point | null : Point
|
||||
}
|
||||
}
|
||||
|
||||
let range: Range = { anchor: anchor as Point, focus: focus as Point }
|
||||
// if the selection is a hanging range that ends in a void
|
||||
// and the DOM focus is an Element
|
||||
|
Reference in New Issue
Block a user