@lexical/history 0.8.0 → 0.9.0

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,163 +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
- const prevSelection = prevEditorState._selection;
138
175
  const hasDirtyNodes = dirtyLeaves.size > 0 || dirtyElements.size > 0;
176
+
139
177
  if (!hasDirtyNodes) {
140
- if (prevSelection === null && selection !== null) {
178
+ if (selection !== null) {
141
179
  return HISTORY_MERGE;
142
180
  }
181
+
143
182
  return DISCARD_HISTORY_CANDIDATE;
144
183
  }
184
+
145
185
  if (shouldPushHistory === false && changeType !== OTHER && changeType === prevChangeType && changeTime < prevChangeTime + delay && isSameEditor) {
146
186
  return HISTORY_MERGE;
147
- }
148
-
149
- // 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
150
188
  // due to some node transform reverting the change.
189
+
190
+
151
191
  if (dirtyLeaves.size === 1) {
152
192
  const dirtyLeafKey = Array.from(dirtyLeaves)[0];
193
+
153
194
  if (isTextNodeUnchanged(dirtyLeafKey, prevEditorState, nextEditorState)) {
154
195
  return HISTORY_MERGE;
155
196
  }
156
197
  }
198
+
157
199
  return HISTORY_PUSH;
158
200
  })();
201
+
159
202
  prevChangeTime = changeTime;
160
203
  prevChangeType = changeType;
161
204
  return mergeAction;
162
205
  };
163
206
  }
207
+
164
208
  function redo(editor, historyState) {
165
209
  const redoStack = historyState.redoStack;
166
210
  const undoStack = historyState.undoStack;
211
+
167
212
  if (redoStack.length !== 0) {
168
213
  const current = historyState.current;
214
+
169
215
  if (current !== null) {
170
216
  undoStack.push(current);
171
217
  editor.dispatchCommand(lexical.CAN_UNDO_COMMAND, true);
172
218
  }
219
+
173
220
  const historyStateEntry = redoStack.pop();
221
+
174
222
  if (redoStack.length === 0) {
175
223
  editor.dispatchCommand(lexical.CAN_REDO_COMMAND, false);
176
224
  }
225
+
177
226
  historyState.current = historyStateEntry || null;
227
+
178
228
  if (historyStateEntry) {
179
229
  historyStateEntry.editor.setEditorState(historyStateEntry.editorState, {
180
230
  tag: 'historic'
@@ -182,21 +232,27 @@ function redo(editor, historyState) {
182
232
  }
183
233
  }
184
234
  }
235
+
185
236
  function undo(editor, historyState) {
186
237
  const redoStack = historyState.redoStack;
187
238
  const undoStack = historyState.undoStack;
188
239
  const undoStackLength = undoStack.length;
240
+
189
241
  if (undoStackLength !== 0) {
190
242
  const current = historyState.current;
191
243
  const historyStateEntry = undoStack.pop();
244
+
192
245
  if (current !== null) {
193
246
  redoStack.push(current);
194
247
  editor.dispatchCommand(lexical.CAN_REDO_COMMAND, true);
195
248
  }
249
+
196
250
  if (undoStack.length === 0) {
197
251
  editor.dispatchCommand(lexical.CAN_UNDO_COMMAND, false);
198
252
  }
253
+
199
254
  historyState.current = historyStateEntry || null;
255
+
200
256
  if (historyStateEntry) {
201
257
  historyStateEntry.editor.setEditorState(historyStateEntry.editorState.clone(historyStateEntry.undoSelection), {
202
258
  tag: 'historic'
@@ -204,13 +260,16 @@ function undo(editor, historyState) {
204
260
  }
205
261
  }
206
262
  }
263
+
207
264
  function clearHistory(historyState) {
208
265
  historyState.undoStack = [];
209
266
  historyState.redoStack = [];
210
267
  historyState.current = null;
211
268
  }
269
+
212
270
  function registerHistory(editor, historyState, delay) {
213
271
  const getMergeAction = createMergeActionGetter(editor, delay);
272
+
214
273
  const applyChange = ({
215
274
  editorState,
216
275
  prevEditorState,
@@ -222,31 +281,36 @@ function registerHistory(editor, historyState, delay) {
222
281
  const redoStack = historyState.redoStack;
223
282
  const undoStack = historyState.undoStack;
224
283
  const currentEditorState = current === null ? null : current.editorState;
284
+
225
285
  if (current !== null && editorState === currentEditorState) {
226
286
  return;
227
287
  }
288
+
228
289
  const mergeAction = getMergeAction(prevEditorState, editorState, current, dirtyLeaves, dirtyElements, tags);
290
+
229
291
  if (mergeAction === HISTORY_PUSH) {
230
292
  if (redoStack.length !== 0) {
231
293
  historyState.redoStack = [];
294
+ editor.dispatchCommand(lexical.CAN_REDO_COMMAND, false);
232
295
  }
296
+
233
297
  if (current !== null) {
234
- undoStack.push({
235
- ...current,
298
+ undoStack.push({ ...current,
236
299
  undoSelection: prevEditorState.read(lexical.$getSelection)
237
300
  });
238
301
  editor.dispatchCommand(lexical.CAN_UNDO_COMMAND, true);
239
302
  }
240
303
  } else if (mergeAction === DISCARD_HISTORY_CANDIDATE) {
241
304
  return;
242
- }
305
+ } // Else we merge
306
+
243
307
 
244
- // Else we merge
245
308
  historyState.current = {
246
309
  editor,
247
310
  editorState
248
311
  };
249
312
  };
313
+
250
314
  const unregisterCommandListener = utils.mergeRegister(editor.registerCommand(lexical.UNDO_COMMAND, () => {
251
315
  undo(editor, historyState);
252
316
  return true;
@@ -5,11 +5,11 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
  'use strict';var c=require("@lexical/utils"),x=require("lexical");
8
- function y(b,a,m,k,n){if(null===b||0===m.size&&0===k.size&&!n)return 0;var f=a._selection,d=b._selection;if(n)return 1;if(!(x.$isRangeSelection(f)&&x.$isRangeSelection(d)&&d.isCollapsed()&&f.isCollapsed()))return 0;n=a._nodeMap;let e=[];for(let g of m)m=n.get(g),void 0!==m&&e.push(m);for(let [g,p]of k)p&&(k=n.get(g),void 0===k||x.$isRootNode(k)||e.push(k));if(0===e.length)return 0;if(1<e.length)return k=a._nodeMap,a=k.get(f.anchor.key),d=k.get(d.anchor.key),a&&d&&!b._nodeMap.has(a.__key)&&x.$isTextNode(a)&&
8
+ function y(b,a,m,k,p){if(null===b||0===m.size&&0===k.size&&!p)return 0;var f=a._selection,d=b._selection;if(p)return 1;if(!(x.$isRangeSelection(f)&&x.$isRangeSelection(d)&&d.isCollapsed()&&f.isCollapsed()))return 0;p=a._nodeMap;let e=[];for(let g of m)m=p.get(g),void 0!==m&&e.push(m);for(let [g,n]of k)n&&(k=p.get(g),void 0===k||x.$isRootNode(k)||e.push(k));if(0===e.length)return 0;if(1<e.length)return k=a._nodeMap,a=k.get(f.anchor.key),d=k.get(d.anchor.key),a&&d&&!b._nodeMap.has(a.__key)&&x.$isTextNode(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
- function z(b,a){let m=Date.now(),k=0;return(n,f,d,e,g,p)=>{let r=Date.now();if(p.has("historic"))return k=0,m=r,2;let q=y(n,f,e,g,b.isComposing()),v=(()=>{var l=null===d||d.editor===b,h=p.has("history-push");if(!h&&l&&p.has("history-merge"))return 0;if(null===n)return 1;var t=f._selection,u=n._selection;if(!(0<e.size||0<g.size))return null===u&&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=n._nodeMap.get(h);h=f._nodeMap.get(h);t=n._selection;u=f._selection;
11
- let w=!1;x.$isRangeSelection(t)&&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:p,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,p,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 n=c.mergeRegister(b.registerCommand(x.UNDO_COMMAND,
13
- ()=>{let d=a.redoStack,e=a.undoStack;if(0!==e.length){let g=a.current,p=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=p||null;p&&p.editor.setEditorState(p.editorState.clone(p.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));
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
+ 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=[],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
+ ()=>{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
- b.registerUpdateListener(m)),f=b.registerUpdateListener(m);return()=>{n();f()}}
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.0",
11
+ "version": "0.9.0",
12
12
  "main": "LexicalHistory.js",
13
13
  "peerDependencies": {
14
- "lexical": "0.8.0"
14
+ "lexical": "0.9.0"
15
15
  },
16
16
  "dependencies": {
17
- "@lexical/utils": "0.8.0"
17
+ "@lexical/utils": "0.9.0"
18
18
  },
19
19
  "repository": {
20
20
  "type": "git",