11import type { Node , Schema } from 'prosemirror-model' ;
2- import { EditorState , Plugin , PluginKey } from 'prosemirror-state' ;
2+ import { EditorState , Plugin , PluginKey , Transaction } from 'prosemirror-state' ;
3+ import { isEqual } from 'lodash' ;
34import { Decoration , DecorationSet } from 'prosemirror-view' ;
45import { findChildren , findParentNodeClosestToPos } from 'prosemirror-utils' ;
56import { cn } from '../../../classname' ;
@@ -82,6 +83,7 @@ const addDecoration = (
8283 widgetsMap [ decorationPosition ] = {
8384 pos : decorationPosition ,
8485 toDOM : placeholderDOM ,
86+ spec : { focus} ,
8587 } ;
8688} ;
8789
@@ -94,10 +96,7 @@ type WidgetSpec = {
9496 spec ?: DecoWidgetParameters [ 2 ] ;
9597} ;
9698
97- type PlaceholderPluginState = {
98- widgets : WidgetSpec [ ] ;
99- hasFocus : boolean ;
100- } ;
99+ type PlaceholderPluginState = { decorationSet : DecorationSet ; hasFocus : boolean } ;
101100
102101type WidgetsMap = Record < number , WidgetSpec | PluginKey > ;
103102
@@ -118,24 +117,18 @@ export const Placeholder: ExtensionAuto = (builder) => {
118117 return attrs ;
119118 } ,
120119 decorations ( state ) {
121- const { widgets} = pluginKey . getState ( state ) ! ;
122- return DecorationSet . create (
123- state . doc ,
124- widgets . map ( ( widget ) =>
125- Decoration . widget ( widget . pos , widget . toDOM , widget . spec ) ,
126- ) ,
127- ) ;
120+ return pluginKey . getState ( state ) ?. decorationSet ;
128121 } ,
129122 } ,
130123 state : {
131- init : ( _config , state ) => applyState ( state ) ,
132- apply : ( _tr , _value , _oldState , state ) => applyState ( state ) ,
124+ init : ( _config , state ) => initState ( state ) ,
125+ apply : applyState ,
133126 } ,
134127 } ) ,
135128 ) ;
136129} ;
137130
138- function applyState ( state : EditorState ) : PlaceholderPluginState {
131+ function getPlaceholderWidgetSpecs ( state : EditorState ) {
139132 const globalState : ApplyGlobalState = { hasFocus : false } ;
140133 const widgetsMap : WidgetsMap = { } ;
141134 const { selection} = state ;
@@ -178,11 +171,60 @@ function applyState(state: EditorState): PlaceholderPluginState {
178171 addDecoration ( widgetsMap , node , pos , parent , cursorPos , globalState ) ;
179172 }
180173
181- const widgets = Object . values ( widgetsMap ) . filter (
174+ const widgetSpecs = Object . values ( widgetsMap ) . filter (
182175 ( decoration ) => ! ( decoration instanceof PluginKey ) ,
183176 ) as WidgetSpec [ ] ;
184177
185- return { widgets, hasFocus : globalState . hasFocus } ;
178+ return { widgetSpecs, hasFocus : globalState . hasFocus } ;
179+ }
180+
181+ function initState ( state : EditorState ) : PlaceholderPluginState {
182+ const { widgetSpecs, hasFocus} = getPlaceholderWidgetSpecs ( state ) ;
183+ const decorationSet = DecorationSet . create (
184+ state . doc ,
185+ widgetSpecs . map ( ( widget ) => Decoration . widget ( widget . pos , widget . toDOM , widget . spec ) ) ,
186+ ) ;
187+
188+ return { decorationSet, hasFocus} ;
189+ }
190+
191+ function applyState (
192+ tr : Transaction ,
193+ oldPluginState : PlaceholderPluginState ,
194+ _oldState : EditorState ,
195+ newState : EditorState ,
196+ ) : PlaceholderPluginState {
197+ const { widgetSpecs, hasFocus} = getPlaceholderWidgetSpecs ( newState ) ;
198+ const { decorationSet} = oldPluginState ;
199+ const oldMappedSet = decorationSet . map ( tr . mapping , tr . doc ) ;
200+
201+ // Find all decorations that are present in old and new set
202+ const decorationsThatDidNotChange = widgetSpecs . reduce ( ( a : Decoration [ ] , { pos, spec} ) => {
203+ const deco = oldMappedSet . find ( pos , pos ) ;
204+ if ( deco . length && isEqual ( deco [ 0 ] . spec , spec ) ) a . push ( ...deco ) ;
205+ return a ;
206+ } , [ ] ) ;
207+
208+ // Those are decorations that are presenr only in new set
209+ const newAddedDecorations = widgetSpecs . filter (
210+ ( { pos} ) => ! decorationsThatDidNotChange . map ( ( { from} ) => from ) . includes ( pos ) ,
211+ ) ;
212+
213+ // That is a set with decorations that are present in old set and absent in new set
214+ const notRelevantDecorations = oldMappedSet . remove ( decorationsThatDidNotChange ) ;
215+ let newSet = oldMappedSet ;
216+ // Remove decorations that are not present in new set
217+ if ( notRelevantDecorations . find ( ) . length ) newSet = newSet . remove ( notRelevantDecorations . find ( ) ) ;
218+ // Add new decorations
219+ if ( newAddedDecorations . length )
220+ newSet = newSet . add (
221+ tr . doc ,
222+ newAddedDecorations . map ( ( widget ) =>
223+ Decoration . widget ( widget . pos , widget . toDOM , widget . spec ) ,
224+ ) ,
225+ ) ;
226+
227+ return { decorationSet : newSet , hasFocus} ;
186228}
187229
188230declare module 'prosemirror-model' {
0 commit comments