1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-01-17 13:38:37 +01: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:
LW 2023-10-20 20:00:48 +08:00 committed by GitHub
parent 0bdff51a08
commit 623f44521e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 148 additions and 16 deletions

View File

@ -0,0 +1,5 @@
---
'slate-react': minor
---
Add `onSelectionChange` and `onValueChange` in Slate React component

View File

@ -1,5 +1,5 @@
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 { useIsomorphicLayoutEffect } from '../hooks/use-isomorphic-layout-effect'
import { SlateContext, SlateContextValue } from '../hooks/use-slate'
@ -22,8 +22,18 @@ export const Slate = (props: {
initialValue: Descendant[]
children: React.ReactNode
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>(() => {
if (!Node.isNodeList(initialValue)) {
@ -48,17 +58,28 @@ export const Slate = (props: {
onChange: handleSelectorChange,
} = useSelectorContext(editor)
const onContextChange = useCallback(() => {
if (onChange) {
onChange(editor.children)
}
const onContextChange = useCallback(
(options?: { operation?: Operation }) => {
if (onChange) {
onChange(editor.children)
}
setContext(prevContext => ({
v: prevContext.v + 1,
editor,
}))
handleSelectorChange(editor)
}, [editor, handleSelectorChange, onChange])
switch (options?.operation?.type) {
case 'set_selection':
onSelectionChange?.(editor.selection)
break
default:
onValueChange?.(editor.children)
}
setContext(prevContext => ({
v: prevContext.v + 1,
editor,
}))
handleSelectorChange(editor)
},
[editor, handleSelectorChange, onChange, onSelectionChange, onValueChange]
)
useEffect(() => {
EDITOR_TO_ON_CHANGE.set(editor, onContextChange)

View File

@ -363,7 +363,7 @@ export const withReact = <T extends BaseEditor>(
const onContextChange = EDITOR_TO_ON_CHANGE.get(e)
if (onContextChange) {
onContextChange()
onContextChange(options)
}
onChange(options)

View File

@ -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 { TextDiff } from './diff-text'
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.
*/
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.

View File

@ -1,5 +1,5 @@
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 { 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()
})
})