mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-18 05:01:17 +02:00
Change <Slate>
to a controlled component (#3216)
* change <Slate> to be a controlled component * add comment about unstable React API
This commit is contained in:
@@ -18,7 +18,6 @@
|
||||
"@types/debug": "^4.1.5",
|
||||
"@types/is-hotkey": "^0.1.1",
|
||||
"@types/lodash": "^4.14.149",
|
||||
"@types/react": "^16.9.13",
|
||||
"debounce": "^1.2.0",
|
||||
"direction": "^1.0.3",
|
||||
"is-hotkey": "^0.1.6",
|
||||
|
41
packages/slate-react/src/components/slate.tsx
Normal file
41
packages/slate-react/src/components/slate.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { Editor, Node, Range } from 'slate'
|
||||
|
||||
import { ReactEditor } from '../plugin/react-editor'
|
||||
import { FocusedContext } from '../hooks/use-focused'
|
||||
import { EditorContext } from '../hooks/use-editor'
|
||||
import { SlateContext } from '../hooks/use-slate'
|
||||
import { EDITOR_TO_ON_CHANGE } from '../utils/weak-maps'
|
||||
|
||||
/**
|
||||
* 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: Editor
|
||||
value: Node[]
|
||||
selection: Range | null
|
||||
children: React.ReactNode
|
||||
onChange: (children: Node[], selection: Range | null) => void
|
||||
[key: string]: any
|
||||
}) => {
|
||||
const { editor, children, onChange, value, selection, ...rest } = props
|
||||
const context: [Editor] = useMemo(() => {
|
||||
editor.children = value
|
||||
editor.selection = selection
|
||||
return [editor]
|
||||
}, [value, selection, ...Object.values(rest)])
|
||||
|
||||
EDITOR_TO_ON_CHANGE.set(editor, onChange)
|
||||
|
||||
return (
|
||||
<SlateContext.Provider value={context}>
|
||||
<EditorContext.Provider value={editor}>
|
||||
<FocusedContext.Provider value={ReactEditor.isFocused(editor)}>
|
||||
{children}
|
||||
</FocusedContext.Provider>
|
||||
</EditorContext.Provider>
|
||||
</SlateContext.Provider>
|
||||
)
|
||||
}
|
@@ -1,18 +1,5 @@
|
||||
import React, { useState, useMemo } from 'react'
|
||||
import { Editor, Node, Operation } from 'slate'
|
||||
import { Editor } from 'slate'
|
||||
import { createContext, useContext } from 'react'
|
||||
import { ReactEditor } from '../react-editor'
|
||||
import { FocusedContext } from './use-focused'
|
||||
import { EditorContext } from './use-editor'
|
||||
|
||||
/**
|
||||
* Associate the context change listener with the editor.
|
||||
*/
|
||||
|
||||
export const EDITOR_TO_CONTEXT_LISTENER = new WeakMap<
|
||||
Editor,
|
||||
(children: Node[], operations: Operation[]) => void
|
||||
>()
|
||||
|
||||
/**
|
||||
* A React context for sharing the `Editor` class, in a way that re-renders the
|
||||
@@ -21,42 +8,6 @@ export const EDITOR_TO_CONTEXT_LISTENER = new WeakMap<
|
||||
|
||||
export const SlateContext = createContext<[Editor] | null>(null)
|
||||
|
||||
/**
|
||||
* 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: Editor
|
||||
children: React.ReactNode
|
||||
defaultValue?: Node[]
|
||||
onChange?: (children: Node[], operations: Operation[]) => void
|
||||
}) => {
|
||||
const { editor, children, defaultValue = [], onChange = () => {} } = props
|
||||
const [context, setContext] = useState([editor])
|
||||
const value: [Editor] = useMemo(() => [editor], [context, editor])
|
||||
const listener = useMemo(() => {
|
||||
editor.children = defaultValue
|
||||
|
||||
return (children: Node[], operations: Operation[]) => {
|
||||
onChange(children, operations)
|
||||
setContext([editor])
|
||||
}
|
||||
}, [editor])
|
||||
|
||||
EDITOR_TO_CONTEXT_LISTENER.set(editor, listener)
|
||||
|
||||
return (
|
||||
<SlateContext.Provider value={value}>
|
||||
<EditorContext.Provider value={editor}>
|
||||
<FocusedContext.Provider value={ReactEditor.isFocused(editor)}>
|
||||
{children}
|
||||
</FocusedContext.Provider>
|
||||
</EditorContext.Provider>
|
||||
</SlateContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current `Editor` class that the component lives under.
|
||||
*/
|
||||
|
@@ -1,11 +1,21 @@
|
||||
export * from './components/editable'
|
||||
// Components
|
||||
export {
|
||||
RenderElementProps,
|
||||
RenderLeafProps,
|
||||
Editable,
|
||||
} from './components/editable'
|
||||
export { DefaultElement } from './components/element'
|
||||
export { DefaultLeaf } from './components/leaf'
|
||||
export * from './hooks/use-editor'
|
||||
export * from './hooks/use-focused'
|
||||
export * from './hooks/use-read-only'
|
||||
export * from './hooks/use-selected'
|
||||
export * from './hooks/use-slate'
|
||||
export * from './react-command'
|
||||
export * from './react-editor'
|
||||
export * from './with-react'
|
||||
export { Slate } from './components/slate'
|
||||
|
||||
// Hooks
|
||||
export { useEditor } from './hooks/use-editor'
|
||||
export { useFocused } from './hooks/use-focused'
|
||||
export { useReadOnly } from './hooks/use-read-only'
|
||||
export { useSelected } from './hooks/use-selected'
|
||||
export { useSlate } from './hooks/use-slate'
|
||||
|
||||
// Plugin
|
||||
export { InsertDataCommand, ReactCommand } from './plugin/react-command'
|
||||
export { ReactEditor } from './plugin/react-editor'
|
||||
export { withReact } from './plugin/with-react'
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Editor, Element, Node, Path, Point, Range } from 'slate'
|
||||
|
||||
import { Key } from './utils/key'
|
||||
import { Key } from '../utils/key'
|
||||
import {
|
||||
EDITOR_TO_ELEMENT,
|
||||
ELEMENT_TO_NODE,
|
||||
@@ -10,8 +10,7 @@ import {
|
||||
NODE_TO_INDEX,
|
||||
NODE_TO_KEY,
|
||||
NODE_TO_PARENT,
|
||||
PLACEHOLDER_SYMBOL,
|
||||
} from './utils/weak-maps'
|
||||
} from '../utils/weak-maps'
|
||||
import {
|
||||
DOMElement,
|
||||
DOMNode,
|
||||
@@ -21,7 +20,7 @@ import {
|
||||
DOMStaticRange,
|
||||
isDOMElement,
|
||||
normalizeDOMPoint,
|
||||
} from './utils/dom'
|
||||
} from '../utils/dom'
|
||||
|
||||
export interface ReactEditor extends Editor {}
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import { unstable_batchedUpdates } from 'react-dom'
|
||||
import { Editor, Node, Path, Operation, Command } from 'slate'
|
||||
|
||||
import { ReactEditor, ReactCommand } from '.'
|
||||
import { Key } from './utils/key'
|
||||
import { NODE_TO_KEY } from './utils/weak-maps'
|
||||
import { EDITOR_TO_CONTEXT_LISTENER } from './hooks/use-slate'
|
||||
import { ReactEditor } from './react-editor'
|
||||
import { ReactCommand } from './react-command'
|
||||
import { Key } from '../utils/key'
|
||||
import { EDITOR_TO_ON_CHANGE, NODE_TO_KEY } from '../utils/weak-maps'
|
||||
|
||||
/**
|
||||
* `withReact` adds React and DOM specific behaviors to the editor.
|
||||
@@ -87,14 +88,21 @@ export const withReact = (editor: Editor): Editor => {
|
||||
}
|
||||
}
|
||||
|
||||
editor.onChange = (children: Node[], operations: Operation[]) => {
|
||||
const contextOnChange = EDITOR_TO_CONTEXT_LISTENER.get(editor)
|
||||
editor.onChange = () => {
|
||||
// COMPAT: React doesn't batch `setState` hook calls, which means that the
|
||||
// children and selection can get out of sync for one render pass. So we
|
||||
// have to use this unstable API to ensure it batches them. (2019/12/03)
|
||||
// https://github.com/facebook/react/issues/14259#issuecomment-439702367
|
||||
unstable_batchedUpdates(() => {
|
||||
const contextOnChange = EDITOR_TO_ON_CHANGE.get(editor)
|
||||
|
||||
if (contextOnChange) {
|
||||
contextOnChange(children, operations)
|
||||
}
|
||||
if (contextOnChange) {
|
||||
const { children, selection } = editor
|
||||
contextOnChange(children, selection)
|
||||
}
|
||||
|
||||
onChange(children, operations)
|
||||
onChange()
|
||||
})
|
||||
}
|
||||
|
||||
return editor
|
@@ -1,4 +1,4 @@
|
||||
import { Node, Ancestor, Editor, Text } from 'slate'
|
||||
import { Node, Ancestor, Editor, Range } from 'slate'
|
||||
|
||||
import { Key } from './key'
|
||||
|
||||
@@ -31,4 +31,17 @@ export const IS_FOCUSED: WeakMap<Editor, boolean> = new WeakMap()
|
||||
export const IS_DRAGGING: WeakMap<Editor, boolean> = new WeakMap()
|
||||
export const IS_CLICKING: WeakMap<Editor, boolean> = new WeakMap()
|
||||
|
||||
/**
|
||||
* Weak map for associating the context `onChange` prop with the plugin.
|
||||
*/
|
||||
|
||||
export const EDITOR_TO_ON_CHANGE = new WeakMap<
|
||||
Editor,
|
||||
(children: Node[], selection: Range | null) => void
|
||||
>()
|
||||
|
||||
/**
|
||||
* Symbols.
|
||||
*/
|
||||
|
||||
export const PLACEHOLDER_SYMBOL = (Symbol('placeholder') as unknown) as string
|
||||
|
Reference in New Issue
Block a user