From 4dc21ab21ff9d4711bf90d981eeeb1bb3a0a5204 Mon Sep 17 00:00:00 2001 From: willliu Date: Thu, 19 Oct 2023 10:57:35 +0800 Subject: [PATCH 1/5] Add `onSelectorChange` and `onValueChange` in Slate React component --- packages/slate-react/src/components/slate.tsx | 45 ++++++++++++++----- packages/slate-react/src/plugin/with-react.ts | 2 +- packages/slate-react/src/utils/weak-maps.ts | 4 +- packages/slate-react/test/index.spec.tsx | 45 +++++++++++++++++++ 4 files changed, 81 insertions(+), 15 deletions(-) diff --git a/packages/slate-react/src/components/slate.tsx b/packages/slate-react/src/components/slate.tsx index 2e1d69d730..7a5dbac0fa 100644 --- a/packages/slate-react/src/components/slate.tsx +++ b/packages/slate-react/src/components/slate.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useState } from 'react' -import { Descendant, Editor, Node, Scrubber } from 'slate' +import { Descendant, Editor, Node, Operation, Scrubber } 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 + onSelectorChange?: (value: Descendant[]) => void + onValueChange?: (value: Descendant[]) => void }) => { - const { editor, children, onChange, initialValue, ...rest } = props + const { + editor, + children, + onChange, + onSelectorChange, + onValueChange, + initialValue, + ...rest + } = props const [context, setContext] = React.useState(() => { 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': + onSelectorChange?.(editor.children) + break + default: + onValueChange?.(editor.children) + } + + setContext(prevContext => ({ + v: prevContext.v + 1, + editor, + })) + handleSelectorChange(editor) + }, + [editor, handleSelectorChange, onChange, onSelectorChange, onValueChange] + ) useEffect(() => { EDITOR_TO_ON_CHANGE.set(editor, onContextChange) diff --git a/packages/slate-react/src/plugin/with-react.ts b/packages/slate-react/src/plugin/with-react.ts index 21d0ff10b7..395f208ddb 100644 --- a/packages/slate-react/src/plugin/with-react.ts +++ b/packages/slate-react/src/plugin/with-react.ts @@ -363,7 +363,7 @@ export const withReact = ( const onContextChange = EDITOR_TO_ON_CHANGE.get(e) if (onContextChange) { - onContextChange() + onContextChange(options) } onChange(options) diff --git a/packages/slate-react/src/utils/weak-maps.ts b/packages/slate-react/src/utils/weak-maps.ts index acad54176f..d179031aaf 100644 --- a/packages/slate-react/src/utils/weak-maps.ts +++ b/packages/slate-react/src/utils/weak-maps.ts @@ -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,7 @@ 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 void>() +export const EDITOR_TO_ON_CHANGE = new WeakMap void>() /** * Weak maps for saving pending state on composition stage. diff --git a/packages/slate-react/test/index.spec.tsx b/packages/slate-react/test/index.spec.tsx index 3839788360..75993e5d48 100644 --- a/packages/slate-react/test/index.spec.tsx +++ b/packages/slate-react/test/index.spec.tsx @@ -95,4 +95,49 @@ describe('slate-react', () => { }) }) }) + + test('calls onSelectorChange when editor select change', async () => { + const editor = withReact(createEditor()) + const initialValue = [ + { type: 'block', children: [{ text: 'te' }] }, + { type: 'block', children: [{ text: 'st' }] }, + ] + const onSelectorChange = jest.fn() + act(() => { + create( + + + , + { createNodeMock } + ) + }) + await act(async () => + Transforms.select(editor, { path: [0, 0], offset: 2 }) + ) + expect(onSelectorChange).toHaveBeenCalled() + }) + + test('calls onValueChange when editor children change', async () => { + const editor = withReact(createEditor()) + const initialValue = [{ type: 'block', children: [{ text: 'test' }] }] + const onValueChange = jest.fn() + act(() => { + create( + + + , + { createNodeMock } + ) + }) + await act(async () => Transforms.insertText(editor, 'Hello word!')) + expect(onValueChange).toHaveBeenCalled() + }) }) From 8f460be73fc6950f72fdb77ba118c5c401f890f4 Mon Sep 17 00:00:00 2001 From: willliu Date: Thu, 19 Oct 2023 11:12:40 +0800 Subject: [PATCH 2/5] docs: add changeset --- .changeset/curly-ligers-lay.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/curly-ligers-lay.md diff --git a/.changeset/curly-ligers-lay.md b/.changeset/curly-ligers-lay.md new file mode 100644 index 0000000000..a358295b3e --- /dev/null +++ b/.changeset/curly-ligers-lay.md @@ -0,0 +1,5 @@ +--- +'slate-react': minor +--- + +Add `onSelectorChange` and `onValueChange` in Slate React component From 2b415be1aa06126a83a4986ae251c12c2e77de12 Mon Sep 17 00:00:00 2001 From: willliu Date: Thu, 19 Oct 2023 11:17:29 +0800 Subject: [PATCH 3/5] fix: fixed lint error --- packages/slate-react/src/utils/weak-maps.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/slate-react/src/utils/weak-maps.ts b/packages/slate-react/src/utils/weak-maps.ts index d179031aaf..92ff09c544 100644 --- a/packages/slate-react/src/utils/weak-maps.ts +++ b/packages/slate-react/src/utils/weak-maps.ts @@ -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 void>() +export const EDITOR_TO_ON_CHANGE = new WeakMap< + Editor, + (options?: { operation?: Operation }) => void +>() /** * Weak maps for saving pending state on composition stage. From 7a9d33b938960ba3dc11be847c3569519d33bee9 Mon Sep 17 00:00:00 2001 From: willliu Date: Fri, 20 Oct 2023 10:51:30 +0800 Subject: [PATCH 4/5] Rename Slate React component `onSelectorChange` to `onSelectionChange`. Add more unit tests. --- packages/slate-react/src/components/slate.tsx | 10 +-- packages/slate-react/test/index.spec.tsx | 68 +++++++++++++++++-- 2 files changed, 68 insertions(+), 10 deletions(-) diff --git a/packages/slate-react/src/components/slate.tsx b/packages/slate-react/src/components/slate.tsx index 7a5dbac0fa..e7d176385b 100644 --- a/packages/slate-react/src/components/slate.tsx +++ b/packages/slate-react/src/components/slate.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useState } from 'react' -import { Descendant, Editor, Node, Operation, 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,14 +22,14 @@ export const Slate = (props: { initialValue: Descendant[] children: React.ReactNode onChange?: (value: Descendant[]) => void - onSelectorChange?: (value: Descendant[]) => void + onSelectionChange?: (selection: Selection) => void onValueChange?: (value: Descendant[]) => void }) => { const { editor, children, onChange, - onSelectorChange, + onSelectionChange, onValueChange, initialValue, ...rest @@ -66,7 +66,7 @@ export const Slate = (props: { switch (options?.operation?.type) { case 'set_selection': - onSelectorChange?.(editor.children) + onSelectionChange?.(editor.selection) break default: onValueChange?.(editor.children) @@ -78,7 +78,7 @@ export const Slate = (props: { })) handleSelectorChange(editor) }, - [editor, handleSelectorChange, onChange, onSelectorChange, onValueChange] + [editor, handleSelectorChange, onChange, onSelectionChange, onValueChange] ) useEffect(() => { diff --git a/packages/slate-react/test/index.spec.tsx b/packages/slate-react/test/index.spec.tsx index 75993e5d48..47de4135b0 100644 --- a/packages/slate-react/test/index.spec.tsx +++ b/packages/slate-react/test/index.spec.tsx @@ -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' @@ -96,48 +96,106 @@ describe('slate-react', () => { }) }) - test('calls onSelectorChange when editor select change', async () => { + 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 onSelectorChange = jest.fn() + const onChange = jest.fn() + const onValueChange = jest.fn() + const onSelectionChange = jest.fn() + act(() => { create( , { createNodeMock } ) }) + await act(async () => Transforms.select(editor, { path: [0, 0], offset: 2 }) ) - expect(onSelectorChange).toHaveBeenCalled() + + 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( , { 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( + + + , + { 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() }) }) From a73f7af140986e83fe4f8ec15534e5a9e5f10052 Mon Sep 17 00:00:00 2001 From: willliu Date: Fri, 20 Oct 2023 11:01:01 +0800 Subject: [PATCH 5/5] docs: update changeset --- .changeset/curly-ligers-lay.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/curly-ligers-lay.md b/.changeset/curly-ligers-lay.md index a358295b3e..9311e653b9 100644 --- a/.changeset/curly-ligers-lay.md +++ b/.changeset/curly-ligers-lay.md @@ -2,4 +2,4 @@ 'slate-react': minor --- -Add `onSelectorChange` and `onValueChange` in Slate React component +Add `onSelectionChange` and `onValueChange` in Slate React component