@lexical/history 0.8.1 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,162 +18,213 @@ const COMPOSING_CHARACTER = 1;
18
18
  const INSERT_CHARACTER_AFTER_SELECTION = 2;
19
19
  const DELETE_CHARACTER_BEFORE_SELECTION = 3;
20
20
  const DELETE_CHARACTER_AFTER_SELECTION = 4;
21
+
21
22
  function getDirtyNodes(editorState, dirtyLeaves, dirtyElements) {
22
23
  const nodeMap = editorState._nodeMap;
23
24
  const nodes = [];
25
+
24
26
  for (const dirtyLeafKey of dirtyLeaves) {
25
27
  const dirtyLeaf = nodeMap.get(dirtyLeafKey);
28
+
26
29
  if (dirtyLeaf !== undefined) {
27
30
  nodes.push(dirtyLeaf);
28
31
  }
29
32
  }
33
+
30
34
  for (const [dirtyElementKey, intentionallyMarkedAsDirty] of dirtyElements) {
31
35
  if (!intentionallyMarkedAsDirty) {
32
36
  continue;
33
37
  }
38
+
34
39
  const dirtyElement = nodeMap.get(dirtyElementKey);
40
+
35
41
  if (dirtyElement !== undefined && !lexical.$isRootNode(dirtyElement)) {
36
42
  nodes.push(dirtyElement);
37
43
  }
38
44
  }
45
+
39
46
  return nodes;
40
47
  }
48
+
41
49
  function getChangeType(prevEditorState, nextEditorState, dirtyLeavesSet, dirtyElementsSet, isComposing) {
42
50
  if (prevEditorState === null || dirtyLeavesSet.size === 0 && dirtyElementsSet.size === 0 && !isComposing) {
43
51
  return OTHER;
44
52
  }
53
+
45
54
  const nextSelection = nextEditorState._selection;
46
55
  const prevSelection = prevEditorState._selection;
56
+
47
57
  if (isComposing) {
48
58
  return COMPOSING_CHARACTER;
49
59
  }
60
+
50
61
  if (!lexical.$isRangeSelection(nextSelection) || !lexical.$isRangeSelection(prevSelection) || !prevSelection.isCollapsed() || !nextSelection.isCollapsed()) {
51
62
  return OTHER;
52
63
  }
64
+
53
65
  const dirtyNodes = getDirtyNodes(nextEditorState, dirtyLeavesSet, dirtyElementsSet);
66
+
54
67
  if (dirtyNodes.length === 0) {
55
68
  return OTHER;
56
- }
57
-
58
- // Catching the case when inserting new text node into an element (e.g. first char in paragraph/list),
69
+ } // Catching the case when inserting new text node into an element (e.g. first char in paragraph/list),
59
70
  // or after existing node.
71
+
72
+
60
73
  if (dirtyNodes.length > 1) {
61
74
  const nextNodeMap = nextEditorState._nodeMap;
62
75
  const nextAnchorNode = nextNodeMap.get(nextSelection.anchor.key);
63
76
  const prevAnchorNode = nextNodeMap.get(prevSelection.anchor.key);
77
+
64
78
  if (nextAnchorNode && prevAnchorNode && !prevEditorState._nodeMap.has(nextAnchorNode.__key) && lexical.$isTextNode(nextAnchorNode) && nextAnchorNode.__text.length === 1 && nextSelection.anchor.offset === 1) {
65
79
  return INSERT_CHARACTER_AFTER_SELECTION;
66
80
  }
81
+
67
82
  return OTHER;
68
83
  }
84
+
69
85
  const nextDirtyNode = dirtyNodes[0];
86
+
70
87
  const prevDirtyNode = prevEditorState._nodeMap.get(nextDirtyNode.__key);
88
+
71
89
  if (!lexical.$isTextNode(prevDirtyNode) || !lexical.$isTextNode(nextDirtyNode) || prevDirtyNode.__mode !== nextDirtyNode.__mode) {
72
90
  return OTHER;
73
91
  }
92
+
74
93
  const prevText = prevDirtyNode.__text;
75
94
  const nextText = nextDirtyNode.__text;
95
+
76
96
  if (prevText === nextText) {
77
97
  return OTHER;
78
98
  }
99
+
79
100
  const nextAnchor = nextSelection.anchor;
80
101
  const prevAnchor = prevSelection.anchor;
102
+
81
103
  if (nextAnchor.key !== prevAnchor.key || nextAnchor.type !== 'text') {
82
104
  return OTHER;
83
105
  }
106
+
84
107
  const nextAnchorOffset = nextAnchor.offset;
85
108
  const prevAnchorOffset = prevAnchor.offset;
86
109
  const textDiff = nextText.length - prevText.length;
110
+
87
111
  if (textDiff === 1 && prevAnchorOffset === nextAnchorOffset - 1) {
88
112
  return INSERT_CHARACTER_AFTER_SELECTION;
89
113
  }
114
+
90
115
  if (textDiff === -1 && prevAnchorOffset === nextAnchorOffset + 1) {
91
116
  return DELETE_CHARACTER_BEFORE_SELECTION;
92
117
  }
118
+
93
119
  if (textDiff === -1 && prevAnchorOffset === nextAnchorOffset) {
94
120
  return DELETE_CHARACTER_AFTER_SELECTION;
95
121
  }
122
+
96
123
  return OTHER;
97
124
  }
125
+
98
126
  function isTextNodeUnchanged(key, prevEditorState, nextEditorState) {
99
127
  const prevNode = prevEditorState._nodeMap.get(key);
128
+
100
129
  const nextNode = nextEditorState._nodeMap.get(key);
130
+
101
131
  const prevSelection = prevEditorState._selection;
102
132
  const nextSelection = nextEditorState._selection;
103
133
  let isDeletingLine = false;
134
+
104
135
  if (lexical.$isRangeSelection(prevSelection) && lexical.$isRangeSelection(nextSelection)) {
105
136
  isDeletingLine = prevSelection.anchor.type === 'element' && prevSelection.focus.type === 'element' && nextSelection.anchor.type === 'text' && nextSelection.focus.type === 'text';
106
137
  }
138
+
107
139
  if (!isDeletingLine && lexical.$isTextNode(prevNode) && lexical.$isTextNode(nextNode)) {
108
140
  return prevNode.__type === nextNode.__type && prevNode.__text === nextNode.__text && prevNode.__mode === nextNode.__mode && prevNode.__detail === nextNode.__detail && prevNode.__style === nextNode.__style && prevNode.__format === nextNode.__format && prevNode.__parent === nextNode.__parent;
109
141
  }
142
+
110
143
  return false;
111
144
  }
145
+
112
146
  function createMergeActionGetter(editor, delay) {
113
147
  let prevChangeTime = Date.now();
114
148
  let prevChangeType = OTHER;
115
149
  return (prevEditorState, nextEditorState, currentHistoryEntry, dirtyLeaves, dirtyElements, tags) => {
116
- const changeTime = Date.now();
117
-
118
- // If applying changes from history stack there's no need
150
+ const changeTime = Date.now(); // If applying changes from history stack there's no need
119
151
  // to run history logic again, as history entries already calculated
152
+
120
153
  if (tags.has('historic')) {
121
154
  prevChangeType = OTHER;
122
155
  prevChangeTime = changeTime;
123
156
  return DISCARD_HISTORY_CANDIDATE;
124
157
  }
158
+
125
159
  const changeType = getChangeType(prevEditorState, nextEditorState, dirtyLeaves, dirtyElements, editor.isComposing());
160
+
126
161
  const mergeAction = (() => {
127
162
  const isSameEditor = currentHistoryEntry === null || currentHistoryEntry.editor === editor;
128
163
  const shouldPushHistory = tags.has('history-push');
129
164
  const shouldMergeHistory = !shouldPushHistory && isSameEditor && tags.has('history-merge');
165
+
130
166
  if (shouldMergeHistory) {
131
167
  return HISTORY_MERGE;
132
168
  }
169
+
133
170
  if (prevEditorState === null) {
134
171
  return HISTORY_PUSH;
135
172
  }
173
+
136
174
  const selection = nextEditorState._selection;
137
175
  const hasDirtyNodes = dirtyLeaves.size > 0 || dirtyElements.size > 0;
176
+
138
177
  if (!hasDirtyNodes) {
139
178
  if (selection !== null) {
140
179
  return HISTORY_MERGE;
141
180
  }
181
+
142
182
  return DISCARD_HISTORY_CANDIDATE;
143
183
  }
184
+
144
185
  if (shouldPushHistory === false && changeType !== OTHER && changeType === prevChangeType && changeTime < prevChangeTime + delay && isSameEditor) {
145
186
  return HISTORY_MERGE;
146
- }
147
-
148
- // A single node might have been marked as dirty, but not have changed
187
+ } // A single node might have been marked as dirty, but not have changed
149
188
  // due to some node transform reverting the change.
189
+
190
+
150
191
  if (dirtyLeaves.size === 1) {
151
192
  const dirtyLeafKey = Array.from(dirtyLeaves)[0];
193
+
152
194
  if (isTextNodeUnchanged(dirtyLeafKey, prevEditorState, nextEditorState)) {
153
195
  return HISTORY_MERGE;
154
196
  }
155
197
  }
198
+
156
199
  return HISTORY_PUSH;
157
200
  })();
201
+
158
202
  prevChangeTime = changeTime;
159
203
  prevChangeType = changeType;
160
204
  return mergeAction;
161
205
  };
162
206
  }
207
+
163
208
  function redo(editor, historyState) {
164
209
  const redoStack = historyState.redoStack;
165
210
  const undoStack = historyState.undoStack;
211
+
166
212
  if (redoStack.length !== 0) {
167
213
  const current = historyState.current;
214
+
168
215
  if (current !== null) {
169
216
  undoStack.push(current);
170
217
  editor.dispatchCommand(lexical.CAN_UNDO_COMMAND, true);
171
218
  }
219
+
172
220
  const historyStateEntry = redoStack.pop();
221
+
173
222
  if (redoStack.length === 0) {
174
223
  editor.dispatchCommand(lexical.CAN_REDO_COMMAND, false);
175
224
  }
225
+
176
226
  historyState.current = historyStateEntry || null;
227
+
177
228
  if (historyStateEntry) {
178
229
  historyStateEntry.editor.setEditorState(historyStateEntry.editorState, {
179
230
  tag: 'historic'
@@ -181,21 +232,27 @@ function redo(editor, historyState) {
181
232
  }
182
233
  }
183
234
  }
235
+
184
236
  function undo(editor, historyState) {
185
237
  const redoStack = historyState.redoStack;
186
238
  const undoStack = historyState.undoStack;
187
239
  const undoStackLength = undoStack.length;
240
+
188
241
  if (undoStackLength !== 0) {
189
242
  const current = historyState.current;
190
243
  const historyStateEntry = undoStack.pop();
244
+
191
245
  if (current !== null) {
192
246
  redoStack.push(current);
193
247
  editor.dispatchCommand(lexical.CAN_REDO_COMMAND, true);
194
248
  }
249
+
195
250
  if (undoStack.length === 0) {
196
251
  editor.dispatchCommand(lexical.CAN_UNDO_COMMAND, false);
197
252
  }
253
+
198
254
  historyState.current = historyStateEntry || null;
255
+
199
256
  if (historyStateEntry) {
200
257
  historyStateEntry.editor.setEditorState(historyStateEntry.editorState.clone(historyStateEntry.undoSelection), {
201
258
  tag: 'historic'
@@ -203,13 +260,16 @@ function undo(editor, historyState) {
203
260
  }
204
261
  }
205
262
  }
263
+
206
264
  function clearHistory(historyState) {
207
265
  historyState.undoStack = [];
208
266
  historyState.redoStack = [];
209
267
  historyState.current = null;
210
268
  }
269
+
211
270
  function registerHistory(editor, historyState, delay) {
212
271
  const getMergeAction = createMergeActionGetter(editor, delay);
272
+
213
273
  const applyChange = ({
214
274
  editorState,
215
275
  prevEditorState,
@@ -221,31 +281,36 @@ function registerHistory(editor, historyState, delay) {
221
281
  const redoStack = historyState.redoStack;
222
282
  const undoStack = historyState.undoStack;
223
283
  const currentEditorState = current === null ? null : current.editorState;
284
+
224
285
  if (current !== null && editorState === currentEditorState) {
225
286
  return;
226
287
  }
288
+
227
289
  const mergeAction = getMergeAction(prevEditorState, editorState, current, dirtyLeaves, dirtyElements, tags);
290
+
228
291
  if (mergeAction === HISTORY_PUSH) {
229
292
  if (redoStack.length !== 0) {
230
293
  historyState.redoStack = [];
294
+ editor.dispatchCommand(lexical.CAN_REDO_COMMAND, false);
231
295
  }
296
+
232
297
  if (current !== null) {
233
- undoStack.push({
234
- ...current,
298
+ undoStack.push({ ...current,
235
299
  undoSelection: prevEditorState.read(lexical.$getSelection)
236
300
  });
237
301
  editor.dispatchCommand(lexical.CAN_UNDO_COMMAND, true);
238
302
  }
239
303
  } else if (mergeAction === DISCARD_HISTORY_CANDIDATE) {
240
304
  return;
241
- }
305
+ } // Else we merge
306
+
242
307
 
243
- // Else we merge
244
308
  historyState.current = {
245
309
  editor,
246
310
  editorState
247
311
  };
248
312
  };
313
+
249
314
  const unregisterCommandListener = utils.mergeRegister(editor.registerCommand(lexical.UNDO_COMMAND, () => {
250
315
  undo(editor, historyState);
251
316
  return true;
@@ -9,7 +9,7 @@ function y(b,a,m,k,p){if(null===b||0===m.size&&0===k.size&&!p)return 0;var f=a._
9
9
  1===a.__text.length&&1===f.anchor.offset?2:0;a=e[0];b=b._nodeMap.get(a.__key);if(!x.$isTextNode(b)||!x.$isTextNode(a)||b.__mode!==a.__mode)return 0;b=b.__text;a=a.__text;if(b===a)return 0;f=f.anchor;d=d.anchor;if(f.key!==d.key||"text"!==f.type)return 0;f=f.offset;d=d.offset;b=a.length-b.length;return 1===b&&d===f-1?2:-1===b&&d===f+1?3:-1===b&&d===f?4:0}
10
10
  function z(b,a){let m=Date.now(),k=0;return(p,f,d,e,g,n)=>{let r=Date.now();if(n.has("historic"))return k=0,m=r,2;let q=y(p,f,e,g,b.isComposing()),v=(()=>{var l=null===d||d.editor===b,h=n.has("history-push");if(!h&&l&&n.has("history-merge"))return 0;if(null===p)return 1;var t=f._selection;if(!(0<e.size||0<g.size))return null!==t?0:2;if(!1===h&&0!==q&&q===k&&r<m+a&&l)return 0;if(1===e.size){{h=Array.from(e)[0];l=p._nodeMap.get(h);h=f._nodeMap.get(h);t=p._selection;let u=f._selection,w=!1;x.$isRangeSelection(t)&&
11
11
  x.$isRangeSelection(u)&&(w="element"===t.anchor.type&&"element"===t.focus.type&&"text"===u.anchor.type&&"text"===u.focus.type);l=!w&&x.$isTextNode(l)&&x.$isTextNode(h)?l.__type===h.__type&&l.__text===h.__text&&l.__mode===h.__mode&&l.__detail===h.__detail&&l.__style===h.__style&&l.__format===h.__format&&l.__parent===h.__parent:!1}if(l)return 0}return 1})();m=r;k=q;return v}}exports.createEmptyHistoryState=function(){return{current:null,redoStack:[],undoStack:[]}};
12
- exports.registerHistory=function(b,a,m){let k=z(b,m);m=({editorState:d,prevEditorState:e,dirtyLeaves:g,dirtyElements:n,tags:r})=>{const q=a.current,v=a.redoStack,l=a.undoStack,h=null===q?null:q.editorState;if(null===q||d!==h){g=k(e,d,q,g,n,r);if(1===g)0!==v.length&&(a.redoStack=[]),null!==q&&(l.push({...q,undoSelection:e.read(x.$getSelection)}),b.dispatchCommand(x.CAN_UNDO_COMMAND,!0));else if(2===g)return;a.current={editor:b,editorState:d}}};let p=c.mergeRegister(b.registerCommand(x.UNDO_COMMAND,
12
+ exports.registerHistory=function(b,a,m){let k=z(b,m);m=({editorState:d,prevEditorState:e,dirtyLeaves:g,dirtyElements:n,tags:r})=>{const q=a.current,v=a.redoStack,l=a.undoStack,h=null===q?null:q.editorState;if(null===q||d!==h){g=k(e,d,q,g,n,r);if(1===g)0!==v.length&&(a.redoStack=[],b.dispatchCommand(x.CAN_REDO_COMMAND,!1)),null!==q&&(l.push({...q,undoSelection:e.read(x.$getSelection)}),b.dispatchCommand(x.CAN_UNDO_COMMAND,!0));else if(2===g)return;a.current={editor:b,editorState:d}}};let p=c.mergeRegister(b.registerCommand(x.UNDO_COMMAND,
13
13
  ()=>{let d=a.redoStack,e=a.undoStack;if(0!==e.length){let g=a.current,n=e.pop();null!==g&&(d.push(g),b.dispatchCommand(x.CAN_REDO_COMMAND,!0));0===e.length&&b.dispatchCommand(x.CAN_UNDO_COMMAND,!1);a.current=n||null;n&&n.editor.setEditorState(n.editorState.clone(n.undoSelection),{tag:"historic"})}return!0},x.COMMAND_PRIORITY_EDITOR),b.registerCommand(x.REDO_COMMAND,()=>{let d=a.redoStack;var e=a.undoStack;if(0!==d.length){let g=a.current;null!==g&&(e.push(g),b.dispatchCommand(x.CAN_UNDO_COMMAND,!0));
14
14
  e=d.pop();0===d.length&&b.dispatchCommand(x.CAN_REDO_COMMAND,!1);a.current=e||null;e&&e.editor.setEditorState(e.editorState,{tag:"historic"})}return!0},x.COMMAND_PRIORITY_EDITOR),b.registerCommand(x.CLEAR_EDITOR_COMMAND,()=>{a.undoStack=[];a.redoStack=[];a.current=null;return!1},x.COMMAND_PRIORITY_EDITOR),b.registerCommand(x.CLEAR_HISTORY_COMMAND,()=>{a.undoStack=[];a.redoStack=[];a.current=null;b.dispatchCommand(x.CAN_REDO_COMMAND,!1);b.dispatchCommand(x.CAN_UNDO_COMMAND,!1);return!0},x.COMMAND_PRIORITY_EDITOR),
15
15
  b.registerUpdateListener(m)),f=b.registerUpdateListener(m);return()=>{p();f()}}
package/package.json CHANGED
@@ -8,13 +8,13 @@
8
8
  "history"
9
9
  ],
10
10
  "license": "MIT",
11
- "version": "0.8.1",
11
+ "version": "0.9.1",
12
12
  "main": "LexicalHistory.js",
13
13
  "peerDependencies": {
14
- "lexical": "0.8.1"
14
+ "lexical": "0.9.1"
15
15
  },
16
16
  "dependencies": {
17
- "@lexical/utils": "0.8.1"
17
+ "@lexical/utils": "0.9.1"
18
18
  },
19
19
  "repository": {
20
20
  "type": "git",