Skip to content

Commit bf8d961

Browse files
authored
fix: optimisation for large tables (#79)
1 parent f1b9e4a commit bf8d961

File tree

1 file changed

+59
-17
lines changed
  • src/extensions/behavior/Placeholder

1 file changed

+59
-17
lines changed

src/extensions/behavior/Placeholder/index.ts

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 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';
34
import {Decoration, DecorationSet} from 'prosemirror-view';
45
import {findChildren, findParentNodeClosestToPos} from 'prosemirror-utils';
56
import {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

102101
type 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

188230
declare module 'prosemirror-model' {

0 commit comments

Comments
 (0)