forked from ianstormtaylor/slate
-
Notifications
You must be signed in to change notification settings - Fork 0
/
slate.tsx
109 lines (97 loc) · 3.49 KB
/
slate.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import React, { useState, useCallback, useEffect, useRef } from 'react'
import { Editor, Node, Descendant, Scrubber } from 'slate'
import { ReactEditor } from '../plugin/react-editor'
import { FocusedContext } from '../hooks/use-focused'
import { EditorContext } from '../hooks/use-slate-static'
import { SlateContext } from '../hooks/use-slate'
import {
getSelectorContext,
SlateSelectorContext,
} from '../hooks/use-slate-selector'
import { EDITOR_TO_ON_CHANGE } from '../utils/weak-maps'
import { IS_REACT_VERSION_17_OR_ABOVE } from '../utils/environment'
import { useIsomorphicLayoutEffect } from '../hooks/use-isomorphic-layout-effect'
/**
* A wrapper around the provider to handle `onChange` events, because the editor
* is a mutable singleton so it won't ever register as "changed" otherwise.
*/
export const Slate = (props: {
editor: ReactEditor
value: Descendant[]
children: React.ReactNode
onChange?: (value: Descendant[]) => void
}) => {
const { editor, children, onChange, value, ...rest } = props
const unmountRef = useRef(false)
const [context, setContext] = React.useState<[ReactEditor]>(() => {
if (!Node.isNodeList(value)) {
throw new Error(
`[Slate] value is invalid! Expected a list of elements` +
`but got: ${Scrubber.stringify(value)}`
)
}
if (!Editor.isEditor(editor)) {
throw new Error(
`[Slate] editor is invalid! you passed:` +
`${Scrubber.stringify(editor)}`
)
}
editor.children = value
Object.assign(editor, rest)
return [editor]
})
const {
selectorContext,
onChange: handleSelectorChange,
} = getSelectorContext(editor)
const onContextChange = useCallback(() => {
if (onChange) {
onChange(editor.children)
}
setContext([editor])
handleSelectorChange(editor)
}, [onChange])
EDITOR_TO_ON_CHANGE.set(editor, onContextChange)
useEffect(() => {
return () => {
EDITOR_TO_ON_CHANGE.set(editor, () => {})
unmountRef.current = true
}
}, [])
const [isFocused, setIsFocused] = useState(ReactEditor.isFocused(editor))
useEffect(() => {
setIsFocused(ReactEditor.isFocused(editor))
})
useIsomorphicLayoutEffect(() => {
const fn = () => setIsFocused(ReactEditor.isFocused(editor))
if (IS_REACT_VERSION_17_OR_ABOVE) {
// In React >= 17 onFocus and onBlur listen to the focusin and focusout events during the bubbling phase.
// Therefore in order for <Editable />'s handlers to run first, which is necessary for ReactEditor.isFocused(editor)
// to return the correct value, we have to listen to the focusin and focusout events without useCapture here.
document.addEventListener('focusin', fn)
document.addEventListener('focusout', fn)
return () => {
document.removeEventListener('focusin', fn)
document.removeEventListener('focusout', fn)
}
} else {
document.addEventListener('focus', fn, true)
document.addEventListener('blur', fn, true)
return () => {
document.removeEventListener('focus', fn, true)
document.removeEventListener('blur', fn, true)
}
}
}, [])
return (
<SlateSelectorContext.Provider value={selectorContext}>
<SlateContext.Provider value={context}>
<EditorContext.Provider value={editor}>
<FocusedContext.Provider value={isFocused}>
{children}
</FocusedContext.Provider>
</EditorContext.Provider>
</SlateContext.Provider>
</SlateSelectorContext.Provider>
)
}