From 6b41c0dd03ec3e749b474700f50d291bd2695151 Mon Sep 17 00:00:00 2001 From: Raffael Wannenmacher Date: Wed, 6 Aug 2025 15:24:48 +0200 Subject: [PATCH 1/3] Implement useNodePath plugin --- .../core/src/lib/plugins/UseNodePathPlugin.ts | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 packages/core/src/lib/plugins/UseNodePathPlugin.ts diff --git a/packages/core/src/lib/plugins/UseNodePathPlugin.ts b/packages/core/src/lib/plugins/UseNodePathPlugin.ts new file mode 100644 index 0000000000..ba8fc0c749 --- /dev/null +++ b/packages/core/src/lib/plugins/UseNodePathPlugin.ts @@ -0,0 +1,111 @@ +import { useEffect, useState } from 'react'; + +import { + type Path, + type PathRef, + type PluginConfig, + type TNode, + type Value, + OperationApi, + PathApi, +} from 'platejs'; +import { + type TPlateEditor, + createTPlatePlugin, + useEditorRef, +} from 'platejs/react'; + +type Listener = { + id: string; + pathRef: PathRef; + prevPath: PathRef['current']; + fn: (path: PathRef['current']) => void; +}; + +const KEY = 'useNodePath'; + +type UseNodePathConfig = PluginConfig< + typeof KEY, + { listeners: Listener[] }, + Record< + typeof KEY, + { + addListener: (path: Path, fn: Listener['fn']) => () => void; + } + > +>; + +export const UseNodePathPlugin = createTPlatePlugin({ + key: KEY, + options: { + listeners: [], + }, +}) + .extendApi( + ({ editor, getOption, setOption }) => ({ + addListener: (path, fn) => { + const id = crypto.randomUUID(); + const pathRef = editor.api.pathRef(path, { affinity: 'backward' }); + + getOption('listeners').push({ + id, + fn, + pathRef, + prevPath: pathRef.current ? [...pathRef.current] : pathRef.current, + }); + + return () => { + setOption( + 'listeners', + getOption('listeners').filter((item) => item.id !== id) + ); + }; + }, + }) + ) + .overrideEditor(({ getOption, tf: { apply } }) => ({ + transforms: { + apply: (op) => { + apply(op); + + if (OperationApi.isNodeOperation(op)) { + getOption('listeners').forEach((item) => { + if ( + ((item.pathRef.current === null || item.prevPath === null) && + item.pathRef.current !== item.prevPath) || + (item.pathRef.current && + item.prevPath && + !PathApi.equals(item.pathRef.current, item.prevPath)) + ) { + item.fn(item.pathRef.current); + + // eslint-disable-next-line no-param-reassign + item.prevPath = + item.pathRef.current === null + ? null + : [...item.pathRef.current]; + } + }); + } + }, + }, + })); + +export const useNodePath = (node: TNode) => { + const editor = useEditorRef>(); + const [path, setPath] = useState( + () => editor.api.findPath(node) ?? null + ); + + useEffect(() => { + if (!(KEY in editor.plugins) || !path) { + return; + } + + return editor.api[KEY].addListener(path, (path) => { + setPath(path); + }); + }, []); + + return path; +}; From 52b22fa317b84199845493bb163336c85bbd4791 Mon Sep 17 00:00:00 2001 From: Raffael Wannenmacher Date: Wed, 6 Aug 2025 15:25:58 +0200 Subject: [PATCH 2/3] Add changeset --- .changeset/brave-hats-carry.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/brave-hats-carry.md diff --git a/.changeset/brave-hats-carry.md b/.changeset/brave-hats-carry.md new file mode 100644 index 0000000000..8ca4d0da24 --- /dev/null +++ b/.changeset/brave-hats-carry.md @@ -0,0 +1,5 @@ +--- +'@platejs/core': minor +--- + +Add useNodePath plugin which will re-render when path changes From e4291ad184ec6cc234f1f512979fd662d258719a Mon Sep 17 00:00:00 2001 From: Raffael Wannenmacher Date: Wed, 6 Aug 2025 15:30:21 +0200 Subject: [PATCH 3/3] Let's change --- packages/core/src/lib/plugins/UseNodePathPlugin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/lib/plugins/UseNodePathPlugin.ts b/packages/core/src/lib/plugins/UseNodePathPlugin.ts index ba8fc0c749..68f8b7395d 100644 --- a/packages/core/src/lib/plugins/UseNodePathPlugin.ts +++ b/packages/core/src/lib/plugins/UseNodePathPlugin.ts @@ -15,6 +15,8 @@ import { useEditorRef, } from 'platejs/react'; +const KEY = 'useNodePath'; + type Listener = { id: string; pathRef: PathRef; @@ -22,8 +24,6 @@ type Listener = { fn: (path: PathRef['current']) => void; }; -const KEY = 'useNodePath'; - type UseNodePathConfig = PluginConfig< typeof KEY, { listeners: Listener[] },