mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-10 17:24:02 +02:00
Add onSelectionChange
and onValueChange
in Slate React component (#5526)
* Add `onSelectorChange` and `onValueChange` in Slate React component * docs: add changeset * fix: fixed lint error * Rename Slate React component `onSelectorChange` to `onSelectionChange`. Add more unit tests. * docs: update changeset --------- Co-authored-by: willliu <willliu@distinctclinic.com>
This commit is contained in:
5
.changeset/curly-ligers-lay.md
Normal file
5
.changeset/curly-ligers-lay.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'slate-react': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add `onSelectionChange` and `onValueChange` in Slate React component
|
@@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react'
|
import React, { useCallback, useEffect, useState } from 'react'
|
||||||
import { Descendant, Editor, Node, Scrubber } from 'slate'
|
import { Descendant, Editor, Node, Operation, Scrubber, Selection } from 'slate'
|
||||||
import { FocusedContext } from '../hooks/use-focused'
|
import { FocusedContext } from '../hooks/use-focused'
|
||||||
import { useIsomorphicLayoutEffect } from '../hooks/use-isomorphic-layout-effect'
|
import { useIsomorphicLayoutEffect } from '../hooks/use-isomorphic-layout-effect'
|
||||||
import { SlateContext, SlateContextValue } from '../hooks/use-slate'
|
import { SlateContext, SlateContextValue } from '../hooks/use-slate'
|
||||||
@@ -22,8 +22,18 @@ export const Slate = (props: {
|
|||||||
initialValue: Descendant[]
|
initialValue: Descendant[]
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
onChange?: (value: Descendant[]) => void
|
onChange?: (value: Descendant[]) => void
|
||||||
|
onSelectionChange?: (selection: Selection) => void
|
||||||
|
onValueChange?: (value: Descendant[]) => void
|
||||||
}) => {
|
}) => {
|
||||||
const { editor, children, onChange, initialValue, ...rest } = props
|
const {
|
||||||
|
editor,
|
||||||
|
children,
|
||||||
|
onChange,
|
||||||
|
onSelectionChange,
|
||||||
|
onValueChange,
|
||||||
|
initialValue,
|
||||||
|
...rest
|
||||||
|
} = props
|
||||||
|
|
||||||
const [context, setContext] = React.useState<SlateContextValue>(() => {
|
const [context, setContext] = React.useState<SlateContextValue>(() => {
|
||||||
if (!Node.isNodeList(initialValue)) {
|
if (!Node.isNodeList(initialValue)) {
|
||||||
@@ -48,17 +58,28 @@ export const Slate = (props: {
|
|||||||
onChange: handleSelectorChange,
|
onChange: handleSelectorChange,
|
||||||
} = useSelectorContext(editor)
|
} = useSelectorContext(editor)
|
||||||
|
|
||||||
const onContextChange = useCallback(() => {
|
const onContextChange = useCallback(
|
||||||
if (onChange) {
|
(options?: { operation?: Operation }) => {
|
||||||
onChange(editor.children)
|
if (onChange) {
|
||||||
}
|
onChange(editor.children)
|
||||||
|
}
|
||||||
|
|
||||||
setContext(prevContext => ({
|
switch (options?.operation?.type) {
|
||||||
v: prevContext.v + 1,
|
case 'set_selection':
|
||||||
editor,
|
onSelectionChange?.(editor.selection)
|
||||||
}))
|
break
|
||||||
handleSelectorChange(editor)
|
default:
|
||||||
}, [editor, handleSelectorChange, onChange])
|
onValueChange?.(editor.children)
|
||||||
|
}
|
||||||
|
|
||||||
|
setContext(prevContext => ({
|
||||||
|
v: prevContext.v + 1,
|
||||||
|
editor,
|
||||||
|
}))
|
||||||
|
handleSelectorChange(editor)
|
||||||
|
},
|
||||||
|
[editor, handleSelectorChange, onChange, onSelectionChange, onValueChange]
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
EDITOR_TO_ON_CHANGE.set(editor, onContextChange)
|
EDITOR_TO_ON_CHANGE.set(editor, onContextChange)
|
||||||
|
@@ -363,7 +363,7 @@ export const withReact = <T extends BaseEditor>(
|
|||||||
const onContextChange = EDITOR_TO_ON_CHANGE.get(e)
|
const onContextChange = EDITOR_TO_ON_CHANGE.get(e)
|
||||||
|
|
||||||
if (onContextChange) {
|
if (onContextChange) {
|
||||||
onContextChange()
|
onContextChange(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange(options)
|
onChange(options)
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Ancestor, Editor, Node, Range, RangeRef, Text } from 'slate'
|
import { Ancestor, Editor, Node, Operation, Range, RangeRef, Text } from 'slate'
|
||||||
import { Action } from '../hooks/android-input-manager/android-input-manager'
|
import { Action } from '../hooks/android-input-manager/android-input-manager'
|
||||||
import { TextDiff } from './diff-text'
|
import { TextDiff } from './diff-text'
|
||||||
import { Key } from './key'
|
import { Key } from './key'
|
||||||
@@ -47,7 +47,10 @@ export const EDITOR_TO_USER_SELECTION: WeakMap<
|
|||||||
* Weak map for associating the context `onChange` context with the plugin.
|
* Weak map for associating the context `onChange` context with the plugin.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const EDITOR_TO_ON_CHANGE = new WeakMap<Editor, () => void>()
|
export const EDITOR_TO_ON_CHANGE = new WeakMap<
|
||||||
|
Editor,
|
||||||
|
(options?: { operation?: Operation }) => void
|
||||||
|
>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Weak maps for saving pending state on composition stage.
|
* Weak maps for saving pending state on composition stage.
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import { createEditor, Element, Transforms } from 'slate'
|
import { createEditor, Text, Transforms } from 'slate'
|
||||||
import { create, act, ReactTestRenderer } from 'react-test-renderer'
|
import { create, act, ReactTestRenderer } from 'react-test-renderer'
|
||||||
import { Slate, withReact, Editable } from '../src'
|
import { Slate, withReact, Editable } from '../src'
|
||||||
|
|
||||||
@@ -95,4 +95,107 @@ describe('slate-react', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('calls onSelectionChange when editor select change', async () => {
|
||||||
|
const editor = withReact(createEditor())
|
||||||
|
const initialValue = [
|
||||||
|
{ type: 'block', children: [{ text: 'te' }] },
|
||||||
|
{ type: 'block', children: [{ text: 'st' }] },
|
||||||
|
]
|
||||||
|
const onChange = jest.fn()
|
||||||
|
const onValueChange = jest.fn()
|
||||||
|
const onSelectionChange = jest.fn()
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
create(
|
||||||
|
<Slate
|
||||||
|
editor={editor}
|
||||||
|
initialValue={initialValue}
|
||||||
|
onChange={onChange}
|
||||||
|
onValueChange={onValueChange}
|
||||||
|
onSelectionChange={onSelectionChange}
|
||||||
|
>
|
||||||
|
<Editable />
|
||||||
|
</Slate>,
|
||||||
|
{ createNodeMock }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await act(async () =>
|
||||||
|
Transforms.select(editor, { path: [0, 0], offset: 2 })
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(onSelectionChange).toHaveBeenCalled()
|
||||||
|
expect(onChange).toHaveBeenCalled()
|
||||||
|
expect(onValueChange).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('calls onValueChange when editor children change', async () => {
|
||||||
|
const editor = withReact(createEditor())
|
||||||
|
const initialValue = [{ type: 'block', children: [{ text: 'test' }] }]
|
||||||
|
const onChange = jest.fn()
|
||||||
|
const onValueChange = jest.fn()
|
||||||
|
const onSelectionChange = jest.fn()
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
create(
|
||||||
|
<Slate
|
||||||
|
editor={editor}
|
||||||
|
initialValue={initialValue}
|
||||||
|
onChange={onChange}
|
||||||
|
onValueChange={onValueChange}
|
||||||
|
onSelectionChange={onSelectionChange}
|
||||||
|
>
|
||||||
|
<Editable />
|
||||||
|
</Slate>,
|
||||||
|
{ createNodeMock }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await act(async () => Transforms.insertText(editor, 'Hello word!'))
|
||||||
|
|
||||||
|
expect(onValueChange).toHaveBeenCalled()
|
||||||
|
expect(onChange).toHaveBeenCalled()
|
||||||
|
expect(onSelectionChange).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('calls onValueChange when editor setNodes', async () => {
|
||||||
|
const editor = withReact(createEditor())
|
||||||
|
const initialValue = [{ type: 'block', children: [{ text: 'test' }] }]
|
||||||
|
const onChange = jest.fn()
|
||||||
|
const onValueChange = jest.fn()
|
||||||
|
const onSelectionChange = jest.fn()
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
create(
|
||||||
|
<Slate
|
||||||
|
editor={editor}
|
||||||
|
initialValue={initialValue}
|
||||||
|
onChange={onChange}
|
||||||
|
onValueChange={onValueChange}
|
||||||
|
onSelectionChange={onSelectionChange}
|
||||||
|
>
|
||||||
|
<Editable />
|
||||||
|
</Slate>,
|
||||||
|
{ createNodeMock }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await act(async () =>
|
||||||
|
Transforms.setNodes(
|
||||||
|
editor,
|
||||||
|
// @ts-ignore
|
||||||
|
{ bold: true },
|
||||||
|
{
|
||||||
|
at: { path: [0, 0], offset: 2 },
|
||||||
|
match: Text.isText,
|
||||||
|
split: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(onChange).toHaveBeenCalled()
|
||||||
|
expect(onValueChange).toHaveBeenCalled()
|
||||||
|
expect(onSelectionChange).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user