@@ -90,54 +90,27 @@ const editorState = EditorState.create({
9090});
9191```
9292
93- ## Configure drag handles
93+ ## Configure hover decorations
9494
95- Some editor components, like images, can be easily dragged and resized on their own. Others, like
96- paragraphs, benefit from a drag handle to provide an easy target to click and move. To add drag
97- handles pass a record of drag handles to the shuffle plugin. These should be React ProseMirror
98- widget components.
95+ In schemas that can have deeply nested nodes, it can be helpful to use borders or highlights to
96+ indicate to the user which nodes are being hovered over.
9997
100- The example below creates a ` ParagraphHandle ` widget that positions a handle to the top left of an
101- element, and adds it to all paragraph nodes .
98+ The ` hoverDecorations ` argument to the shuffle plugin creator will be called with each hovered node
99+ to determine whether to render a node decoration .
102100
103101``` tsx
104- import {
105- reactKeys ,
106- useEditorEffect ,
107- type WidgetViewComponentProps ,
108- } from " @handlewithcare/react-prosemirror" ;
102+ import { reactKeys } from " @handlewithcare/react-prosemirror" ;
109103import { shuffle } from " @pitter-patter/shuffle" ;
104+ import { Decoration } from " prosemirror-view" ;
105+ import { Node } from " prosemirror-model" ;
110106
111- function ParagraphHandle({ widget , ref , getPos , ... props } : WidgetViewComponentProps ) {
112- const [top, setTop] = useState ( 0 );
113- const [left, setLeft] = useState ( 0 ) ;
107+ function hoverDecorations( from : number , to : number , node : Node ) {
108+ // Return null to skip decorations for a given node
109+ if ( node . type . name === " image " ) return null ;
114110
115- useEditorEffect (
116- (view ) => {
117- const viewRect = view .dom .getBoundingClientRect ();
118- const coords = view .coordsAtPos (widget .spec .nodePos , 1 );
119- setTop (coords .top - viewRect .top );
120- setLeft (coords .left - viewRect .left + (widget .spec .nodeDepth - 1 ) * 24 );
121- },
122- [widget .spec .nodePos , widget .spec .nodeDepth ],
123- );
124-
125- return (
126- <div
127- ref = { ref }
128- { ... props }
129- contentEditable = { false }
130- style = { {
131- position: " absolute" ,
132- backgroundColor: " lightblue" ,
133- transform: " translateY(-1.5rem)" ,
134- top ,
135- left ,
136- }}
137- >
138- { name }
139- </div >
140- );
111+ return Decoration .node (from , to , {
112+ class: " shuffle-hover-block" ,
113+ });
141114}
142115
143116const editorState = EditorState .create ({
@@ -146,22 +119,20 @@ const editorState = EditorState.create({
146119 plugins: [
147120 reactKeys (),
148121 shuffle ({
149- dragHandles: {
150- paragraph: ParagraphHandle ,
151- },
122+ hoverDecorations ,
152123 }),
153124 ],
154125});
155126```
156127
157- ## Wrap your ProseMirror component with the ` ShuffleSkeleton ` and add ` ResizeHandles `
128+ ## Wrap your ProseMirror component with the ` ShuffleSkeleton ` and add ` ResizeHandles ` and ` DragHandles `
158129
159130Shuffle provides a ` ShuffleSkeleton ` component that wraps your ` ProseMirrorDoc ` . It renders
160131Shuffle's grid skeleton, and must be rendered for resize and reposition behaviors to work correctly.
161132The component should be a direct parent of the ` ProseMirrorDoc ` component.
162133
163134To add resize handles to your elements, include the ` ResizeHandles ` component as a child of your
164- ` ShuffleSkeleton ` .
135+ ` ShuffleSkeleton ` . Likewise, include the ` DragHandles ` component to render drag handles.
165136
166137``` tsx
167138function Editor() {
@@ -170,6 +141,7 @@ function Editor() {
170141 <ShuffleSkeleton >
171142 <ProseMirrorDoc />
172143 <ResizeHandles />
144+ <DragHandles />
173145 </ShuffleSkeleton >
174146 </ProseMirror >
175147 );
@@ -210,7 +182,9 @@ interface Props {
210182}
211183
212184function ResizeHandle({ styles , onPointerDown }) {
213- return <button type = " button" styles = { styles } onPointerDown = { onPointerDown } />;
185+ return (
186+ <button type = " button" className = " resize-handle" styles = { styles } onPointerDown = { onPointerDown } />
187+ );
214188}
215189
216190function Editor() {
@@ -224,3 +198,35 @@ function Editor() {
224198 );
225199}
226200```
201+
202+ Similarly, the ` DragHandles ` component optionally takes a ` handleComponent ` prop that will be used
203+ instead of the default light blue button:
204+
205+ ``` tsx
206+ import { DragHandles } from " @pitter-patter/shuffle" ;
207+ import { EventHandler , PointerDown } from " react" ;
208+
209+ interface Props {
210+ style: { top: number ; left: number };
211+ onPointerDown: EventHandler <PointerDown >;
212+ }
213+
214+ function DragHandle({ styles , onPointerDown , node }) {
215+ return (
216+ <button type = " button" className = " drag-handle" styles = { styles } onPointerDown = { onPointerDown } >
217+ { node .type .name [0 ].toUpperCase () + node .type .name .slice (1 )}
218+ </button >
219+ );
220+ }
221+
222+ function Editor() {
223+ return (
224+ <ProseMirror defaultState = { editorState } >
225+ <ShuffleSkeleton >
226+ <ProseMirrorDoc />
227+ <DragHandles handleComponent = { DragHandle } />
228+ </ShuffleSkeleton >
229+ </ProseMirror >
230+ );
231+ }
232+ ```
0 commit comments