Description
Slatejs editor only in Firefox fails with text selection and caret positioning (by mouse). It always selects position before first character (path[0] offset[0]). You can still use the keyboard to select text and position caret.
Recording
Expectation
It should select text and position caret as it does in other browsers.
Environment
- Slate Version: 0.63.0
- Operating System: Windows 10
- Browser: Firefox 90.0.1 (64-bit)
Editor implementation for reference
JavaScript
x
193
193
1
import React, { useCallback, useMemo, useState } from "react";
2
import {
3
BaseEditor,
4
createEditor,
5
Descendant,
6
Editor,
7
Element as SlateElement,
8
Node,
9
Text,
10
Transforms
11
} from "slate";
12
import { Slate, Editable, ReactEditor, withReact } from "slate-react";
13
import { HistoryEditor, withHistory } from "slate-history";
14
15
export type CustomEditor = BaseEditor & ReactEditor & HistoryEditor
16
17
export type ParagraphElement = {
18
type: 'paragraph'
19
children: CustomText[]
20
}
21
22
export type TitleElement = {
23
type: "title"
24
children: CustomText[]
25
}
26
27
type CustomElement = ParagraphElement | TitleElement;
28
type FormattedText = { text: string, bold?: true };
29
type CustomText = FormattedText;
30
31
32
declare module "slate" {
33
interface CustomTypes {
34
Editor: CustomEditor
35
Element: CustomElement
36
Text: FormattedText
37
}
38
}
39
40
////////////////////////////////////
41
// Custom helpers
42
////////////////////////////////////
43
const customEditor = {
44
isBoldMarkActive(editor: CustomEditor) {
45
const [match] = Editor.nodes(editor, {
46
match: (n: any) => n.bold === true,
47
universal: true,
48
})
49
return !!match
50
},
51
isTitleActive(editor: CustomEditor) {
52
const [match] = Editor.nodes(editor, {
53
match: (n: any) => n.type === "title",
54
})
55
return !!match
56
},
57
toggleBoldMark(editor: CustomEditor) {
58
const isActive = customEditor.isBoldMarkActive(editor)
59
Transforms.setNodes(
60
editor,
61
{ bold: isActive ? undefined : true },
62
{ match: n => Text.isText(n), split: true }
63
)
64
},
65
toggleTitle(editor: CustomEditor) {
66
const isActive = customEditor.isTitleActive(editor)
67
Transforms.setNodes(
68
editor,
69
{ type: isActive ? undefined : "title" },
70
{ match: n => Editor.isBlock(editor, n) }
71
)
72
},
73
}
74
////////////////////////////////////
75
// Forced layout setup - title + paragraph
76
////////////////////////////////////
77
const withLayout = (editor: CustomEditor) => {
78
const { normalizeNode } = editor
79
80
editor.normalizeNode = ([node, path]) => {
81
if (path.length === 0) {
82
if (editor.children.length < 1) {
83
const title: TitleElement = {
84
type: "title",
85
children: [{ text: 'Untitled' }],
86
}
87
Transforms.insertNodes(editor, title, { at: path.concat(0) })
88
}
89
90
if (editor.children.length < 2) {
91
const paragraph: ParagraphElement = {
92
type: 'paragraph',
93
children: [{ text: '' }],
94
}
95
Transforms.insertNodes(editor, paragraph, { at: path.concat(1) })
96
}
97
98
for (const [child, childPath] of Node.children(editor, path)) {
99
const type = childPath[0] === 0 ? "title" : 'paragraph'
100
101
if (SlateElement.isElement(child) && child.type !== type) {
102
const newProperties: Partial<SlateElement> = { type }
103
Transforms.setNodes(editor, newProperties, { at: childPath })
104
}
105
}
106
}
107
return normalizeNode([node, path]);
108
}
109
return editor;
110
}
111
////////////////////////////////////
112
113
const TextEditor = () => {
114
const initialValue: Descendant[] = [
115
{
116
type: 'title',
117
children: [{ text: 'Enter a title...' }],
118
},
119
{
120
type: 'paragraph',
121
children: [{ text: 'Enter your question'}]
122
}
123
];
124
125
const editor = useMemo(() => withLayout(withHistory(withReact(createEditor()))), []);
126
Transforms.deselect(editor);
127
const [value, setValue] = useState<Descendant[]>(initialValue);
128
129
const renderElement = useCallback((props) => <Element {props} />, [])
130
// Define a leaf rendering function that is memoized with `useCallback`.
131
const renderLeaf = useCallback((props) => {
132
return <Leaf {props} />
133
}, []);
134
135
return (
136
// Add the editable component inside the context.
137
<Slate
138
editor={editor}
139
value={value}
140
onChange={(value) => setValue(value)}
141
>
142
<div>
143
<button
144
onMouseDown={event => {
145
event.preventDefault()
146
customEditor.toggleBoldMark(editor)
147
}}
148
>
149
B
150
</button>
151
<button
152
onMouseDown={event => {
153
event.preventDefault()
154
customEditor.toggleTitle(editor)
155
}}
156
>
157
H2
158
</button>
159
</div>
160
<Editable
161
renderElement={renderElement}
162
autoFocus
163
renderLeaf={renderLeaf}
164
/>
165
</Slate>
166
)
167
}
168
169
export default TextEditor
170
171
// Define a React component to render leaves with bold text.
172
const Leaf = (props: any) => {
173
return (
174
<span
175
{props.attributes}
176
style={{ fontWeight: props.leaf.bold ? 'bold' : 'normal' }}
177
>
178
{props.children}
179
</span>
180
)
181
}
182
183
const Element = ({ attributes, children, element }: any) => {
184
switch (element.type) {
185
case 'title':
186
return <h2 {attributes}>{children}</h2>
187
case 'paragraph':
188
return <p {attributes}>{children}</p>
189
default:
190
return <p {attributes}>{children}</p>
191
}
192
}
193
Any ideas what might have caused it?
Advertisement
Answer
Dumb mistake. My CSS reset file contained: “user-select: none”, which was somehow ignored by Chrome.