@lexical/history 0.12.2 → 0.12.3

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,213 +18,162 @@ 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
-
22
21
  function getDirtyNodes(editorState, dirtyLeaves, dirtyElements) {
23
22
  const nodeMap = editorState._nodeMap;
24
23
  const nodes = [];
25
-
26
24
  for (const dirtyLeafKey of dirtyLeaves) {
27
25
  const dirtyLeaf = nodeMap.get(dirtyLeafKey);
28
-
29
26
  if (dirtyLeaf !== undefined) {
30
27
  nodes.push(dirtyLeaf);
31
28
  }
32
29
  }
33
-
34
30
  for (const [dirtyElementKey, intentionallyMarkedAsDirty] of dirtyElements) {
35
31
  if (!intentionallyMarkedAsDirty) {
36
32
  continue;
37
33
  }
38
-
39
34
  const dirtyElement = nodeMap.get(dirtyElementKey);
40
-
41
35
  if (dirtyElement !== undefined && !lexical.$isRootNode(dirtyElement)) {
42
36
  nodes.push(dirtyElement);
43
37
  }
44
38
  }
45
-
46
39
  return nodes;
47
40
  }
48
-
49
41
  function getChangeType(prevEditorState, nextEditorState, dirtyLeavesSet, dirtyElementsSet, isComposing) {
50
42
  if (prevEditorState === null || dirtyLeavesSet.size === 0 && dirtyElementsSet.size === 0 && !isComposing) {
51
43
  return OTHER;
52
44
  }
53
-
54
45
  const nextSelection = nextEditorState._selection;
55
46
  const prevSelection = prevEditorState._selection;
56
-
57
47
  if (isComposing) {
58
48
  return COMPOSING_CHARACTER;
59
49
  }
60
-
61
50
  if (!lexical.$isRangeSelection(nextSelection) || !lexical.$isRangeSelection(prevSelection) || !prevSelection.isCollapsed() || !nextSelection.isCollapsed()) {
62
51
  return OTHER;
63
52
  }
64
-
65
53
  const dirtyNodes = getDirtyNodes(nextEditorState, dirtyLeavesSet, dirtyElementsSet);
66
-
67
54
  if (dirtyNodes.length === 0) {
68
55
  return OTHER;
69
- } // Catching the case when inserting new text node into an element (e.g. first char in paragraph/list),
70
- // or after existing node.
71
-
56
+ }
72
57
 
58
+ // Catching the case when inserting new text node into an element (e.g. first char in paragraph/list),
59
+ // or after existing node.
73
60
  if (dirtyNodes.length > 1) {
74
61
  const nextNodeMap = nextEditorState._nodeMap;
75
62
  const nextAnchorNode = nextNodeMap.get(nextSelection.anchor.key);
76
63
  const prevAnchorNode = nextNodeMap.get(prevSelection.anchor.key);
77
-
78
64
  if (nextAnchorNode && prevAnchorNode && !prevEditorState._nodeMap.has(nextAnchorNode.__key) && lexical.$isTextNode(nextAnchorNode) && nextAnchorNode.__text.length === 1 && nextSelection.anchor.offset === 1) {
79
65
  return INSERT_CHARACTER_AFTER_SELECTION;
80
66
  }
81
-
82
67
  return OTHER;
83
68
  }
84
-
85
69
  const nextDirtyNode = dirtyNodes[0];
86
-
87
70
  const prevDirtyNode = prevEditorState._nodeMap.get(nextDirtyNode.__key);
88
-
89
71
  if (!lexical.$isTextNode(prevDirtyNode) || !lexical.$isTextNode(nextDirtyNode) || prevDirtyNode.__mode !== nextDirtyNode.__mode) {
90
72
  return OTHER;
91
73
  }
92
-
93
74
  const prevText = prevDirtyNode.__text;
94
75
  const nextText = nextDirtyNode.__text;
95
-
96
76
  if (prevText === nextText) {
97
77
  return OTHER;
98
78
  }
99
-
100
79
  const nextAnchor = nextSelection.anchor;
101
80
  const prevAnchor = prevSelection.anchor;
102
-
103
81
  if (nextAnchor.key !== prevAnchor.key || nextAnchor.type !== 'text') {
104
82
  return OTHER;
105
83
  }
106
-
107
84
  const nextAnchorOffset = nextAnchor.offset;
108
85
  const prevAnchorOffset = prevAnchor.offset;
109
86
  const textDiff = nextText.length - prevText.length;
110
-
111
87
  if (textDiff === 1 && prevAnchorOffset === nextAnchorOffset - 1) {
112
88
  return INSERT_CHARACTER_AFTER_SELECTION;
113
89
  }
114
-
115
90
  if (textDiff === -1 && prevAnchorOffset === nextAnchorOffset + 1) {
116
91
  return DELETE_CHARACTER_BEFORE_SELECTION;
117
92
  }
118
-
119
93
  if (textDiff === -1 && prevAnchorOffset === nextAnchorOffset) {
120
94
  return DELETE_CHARACTER_AFTER_SELECTION;
121
95
  }
122
-
123
96
  return OTHER;
124
97
  }
125
-
126
98
  function isTextNodeUnchanged(key, prevEditorState, nextEditorState) {
127
99
  const prevNode = prevEditorState._nodeMap.get(key);
128
-
129
100
  const nextNode = nextEditorState._nodeMap.get(key);
130
-
131
101
  const prevSelection = prevEditorState._selection;
132
102
  const nextSelection = nextEditorState._selection;
133
103
  let isDeletingLine = false;
134
-
135
104
  if (lexical.$isRangeSelection(prevSelection) && lexical.$isRangeSelection(nextSelection)) {
136
105
  isDeletingLine = prevSelection.anchor.type === 'element' && prevSelection.focus.type === 'element' && nextSelection.anchor.type === 'text' && nextSelection.focus.type === 'text';
137
106
  }
138
-
139
107
  if (!isDeletingLine && lexical.$isTextNode(prevNode) && lexical.$isTextNode(nextNode)) {
140
108
  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;
141
109
  }
142
-
143
110
  return false;
144
111
  }
145
-
146
112
  function createMergeActionGetter(editor, delay) {
147
113
  let prevChangeTime = Date.now();
148
114
  let prevChangeType = OTHER;
149
115
  return (prevEditorState, nextEditorState, currentHistoryEntry, dirtyLeaves, dirtyElements, tags) => {
150
- const changeTime = Date.now(); // If applying changes from history stack there's no need
151
- // to run history logic again, as history entries already calculated
116
+ const changeTime = Date.now();
152
117
 
118
+ // If applying changes from history stack there's no need
119
+ // to run history logic again, as history entries already calculated
153
120
  if (tags.has('historic')) {
154
121
  prevChangeType = OTHER;
155
122
  prevChangeTime = changeTime;
156
123
  return DISCARD_HISTORY_CANDIDATE;
157
124
  }
158
-
159
125
  const changeType = getChangeType(prevEditorState, nextEditorState, dirtyLeaves, dirtyElements, editor.isComposing());
160
-
161
126
  const mergeAction = (() => {
162
127
  const isSameEditor = currentHistoryEntry === null || currentHistoryEntry.editor === editor;
163
128
  const shouldPushHistory = tags.has('history-push');
164
129
  const shouldMergeHistory = !shouldPushHistory && isSameEditor && tags.has('history-merge');
165
-
166
130
  if (shouldMergeHistory) {
167
131
  return HISTORY_MERGE;
168
132
  }
169
-
170
133
  if (prevEditorState === null) {
171
134
  return HISTORY_PUSH;
172
135
  }
173
-
174
136
  const selection = nextEditorState._selection;
175
137
  const hasDirtyNodes = dirtyLeaves.size > 0 || dirtyElements.size > 0;
176
-
177
138
  if (!hasDirtyNodes) {
178
139
  if (selection !== null) {
179
140
  return HISTORY_MERGE;
180
141
  }
181
-
182
142
  return DISCARD_HISTORY_CANDIDATE;
183
143
  }
184
-
185
144
  if (shouldPushHistory === false && changeType !== OTHER && changeType === prevChangeType && changeTime < prevChangeTime + delay && isSameEditor) {
186
145
  return HISTORY_MERGE;
187
- } // A single node might have been marked as dirty, but not have changed
188
- // due to some node transform reverting the change.
189
-
146
+ }
190
147
 
148
+ // A single node might have been marked as dirty, but not have changed
149
+ // due to some node transform reverting the change.
191
150
  if (dirtyLeaves.size === 1) {
192
151
  const dirtyLeafKey = Array.from(dirtyLeaves)[0];
193
-
194
152
  if (isTextNodeUnchanged(dirtyLeafKey, prevEditorState, nextEditorState)) {
195
153
  return HISTORY_MERGE;
196
154
  }
197
155
  }
198
-
199
156
  return HISTORY_PUSH;
200
157
  })();
201
-
202
158
  prevChangeTime = changeTime;
203
159
  prevChangeType = changeType;
204
160
  return mergeAction;
205
161
  };
206
162
  }
207
-
208
163
  function redo(editor, historyState) {
209
164
  const redoStack = historyState.redoStack;
210
165
  const undoStack = historyState.undoStack;
211
-
212
166
  if (redoStack.length !== 0) {
213
167
  const current = historyState.current;
214
-
215
168
  if (current !== null) {
216
169
  undoStack.push(current);
217
170
  editor.dispatchCommand(lexical.CAN_UNDO_COMMAND, true);
218
171
  }
219
-
220
172
  const historyStateEntry = redoStack.pop();
221
-
222
173
  if (redoStack.length === 0) {
223
174
  editor.dispatchCommand(lexical.CAN_REDO_COMMAND, false);
224
175
  }
225
-
226
176
  historyState.current = historyStateEntry || null;
227
-
228
177
  if (historyStateEntry) {
229
178
  historyStateEntry.editor.setEditorState(historyStateEntry.editorState, {
230
179
  tag: 'historic'
@@ -232,27 +181,21 @@ function redo(editor, historyState) {
232
181
  }
233
182
  }
234
183
  }
235
-
236
184
  function undo(editor, historyState) {
237
185
  const redoStack = historyState.redoStack;
238
186
  const undoStack = historyState.undoStack;
239
187
  const undoStackLength = undoStack.length;
240
-
241
188
  if (undoStackLength !== 0) {
242
189
  const current = historyState.current;
243
190
  const historyStateEntry = undoStack.pop();
244
-
245
191
  if (current !== null) {
246
192
  redoStack.push(current);
247
193
  editor.dispatchCommand(lexical.CAN_REDO_COMMAND, true);
248
194
  }
249
-
250
195
  if (undoStack.length === 0) {
251
196
  editor.dispatchCommand(lexical.CAN_UNDO_COMMAND, false);
252
197
  }
253
-
254
198
  historyState.current = historyStateEntry || null;
255
-
256
199
  if (historyStateEntry) {
257
200
  historyStateEntry.editor.setEditorState(historyStateEntry.editorState, {
258
201
  tag: 'historic'
@@ -260,12 +203,12 @@ function undo(editor, historyState) {
260
203
  }
261
204
  }
262
205
  }
263
-
264
206
  function clearHistory(historyState) {
265
207
  historyState.undoStack = [];
266
208
  historyState.redoStack = [];
267
209
  historyState.current = null;
268
210
  }
211
+
269
212
  /**
270
213
  * Registers necessary listeners to manage undo/redo history stack and related editor commands.
271
214
  * It returns `unregister` callback that cleans up all listeners and should be called on editor unmount.
@@ -275,11 +218,8 @@ function clearHistory(historyState) {
275
218
  * instead of merging the current changes with the current stack.
276
219
  * @returns The listeners cleanup callback function.
277
220
  */
278
-
279
-
280
221
  function registerHistory(editor, historyState, delay) {
281
222
  const getMergeAction = createMergeActionGetter(editor, delay);
282
-
283
223
  const applyChange = ({
284
224
  editorState,
285
225
  prevEditorState,
@@ -291,35 +231,31 @@ function registerHistory(editor, historyState, delay) {
291
231
  const redoStack = historyState.redoStack;
292
232
  const undoStack = historyState.undoStack;
293
233
  const currentEditorState = current === null ? null : current.editorState;
294
-
295
234
  if (current !== null && editorState === currentEditorState) {
296
235
  return;
297
236
  }
298
-
299
237
  const mergeAction = getMergeAction(prevEditorState, editorState, current, dirtyLeaves, dirtyElements, tags);
300
-
301
238
  if (mergeAction === HISTORY_PUSH) {
302
239
  if (redoStack.length !== 0) {
303
240
  historyState.redoStack = [];
304
241
  editor.dispatchCommand(lexical.CAN_REDO_COMMAND, false);
305
242
  }
306
-
307
243
  if (current !== null) {
308
- undoStack.push({ ...current
244
+ undoStack.push({
245
+ ...current
309
246
  });
310
247
  editor.dispatchCommand(lexical.CAN_UNDO_COMMAND, true);
311
248
  }
312
249
  } else if (mergeAction === DISCARD_HISTORY_CANDIDATE) {
313
250
  return;
314
- } // Else we merge
315
-
251
+ }
316
252
 
253
+ // Else we merge
317
254
  historyState.current = {
318
255
  editor,
319
256
  editorState
320
257
  };
321
258
  };
322
-
323
259
  const unregisterCommandListener = utils.mergeRegister(editor.registerCommand(lexical.UNDO_COMMAND, () => {
324
260
  undo(editor, historyState);
325
261
  return true;
@@ -341,11 +277,11 @@ function registerHistory(editor, historyState, delay) {
341
277
  unregisterUpdateListener();
342
278
  };
343
279
  }
280
+
344
281
  /**
345
282
  * Creates an empty history state.
346
283
  * @returns - The empty history state, as an object.
347
284
  */
348
-
349
285
  function createEmptyHistoryState() {
350
286
  return {
351
287
  current: null,
package/package.json CHANGED
@@ -8,13 +8,13 @@
8
8
  "history"
9
9
  ],
10
10
  "license": "MIT",
11
- "version": "0.12.2",
11
+ "version": "0.12.3",
12
12
  "main": "LexicalHistory.js",
13
13
  "peerDependencies": {
14
- "lexical": "0.12.2"
14
+ "lexical": "0.12.3"
15
15
  },
16
16
  "dependencies": {
17
- "@lexical/utils": "0.12.2"
17
+ "@lexical/utils": "0.12.3"
18
18
  },
19
19
  "repository": {
20
20
  "type": "git",