@lexical/clipboard 0.7.8 → 0.7.9

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.
@@ -20,84 +20,75 @@ var lexical = require('lexical');
20
20
  */
21
21
  function $getHtmlContent(editor) {
22
22
  const selection = lexical.$getSelection();
23
-
24
23
  if (selection == null) {
25
24
  {
26
25
  throw Error(`Expected valid LexicalSelection`);
27
26
  }
28
- } // If we haven't selected anything
29
-
27
+ }
30
28
 
29
+ // If we haven't selected anything
31
30
  if (lexical.$isRangeSelection(selection) && selection.isCollapsed() || selection.getNodes().length === 0) {
32
31
  return '';
33
32
  }
34
-
35
33
  return html.$generateHtmlFromNodes(editor, selection);
36
- } // TODO 0.6.0 Return a blank string instead
37
- // TODO 0.6.0 Rename to $getJSON
34
+ }
38
35
 
36
+ // TODO 0.6.0 Return a blank string instead
37
+ // TODO 0.6.0 Rename to $getJSON
39
38
  function $getLexicalContent(editor) {
40
39
  const selection = lexical.$getSelection();
41
-
42
40
  if (selection == null) {
43
41
  {
44
42
  throw Error(`Expected valid LexicalSelection`);
45
43
  }
46
- } // If we haven't selected anything
47
-
44
+ }
48
45
 
46
+ // If we haven't selected anything
49
47
  if (lexical.$isRangeSelection(selection) && selection.isCollapsed() || selection.getNodes().length === 0) {
50
48
  return null;
51
49
  }
52
-
53
50
  return JSON.stringify($generateJSONFromSelectedNodes(editor, selection));
54
51
  }
55
52
  function $insertDataTransferForPlainText(dataTransfer, selection) {
56
53
  const text = dataTransfer.getData('text/plain');
57
-
58
54
  if (text != null) {
59
55
  selection.insertRawText(text);
60
56
  }
61
57
  }
62
58
  function $insertDataTransferForRichText(dataTransfer, selection, editor) {
63
59
  const lexicalString = dataTransfer.getData('application/x-lexical-editor');
64
-
65
60
  if (lexicalString) {
66
61
  try {
67
62
  const payload = JSON.parse(lexicalString);
68
-
69
63
  if (payload.namespace === editor._config.namespace && Array.isArray(payload.nodes)) {
70
64
  const nodes = $generateNodesFromSerializedNodes(payload.nodes);
71
65
  return $insertGeneratedNodes(editor, nodes, selection);
72
66
  }
73
- } catch {// Fail silently.
67
+ } catch {
68
+ // Fail silently.
74
69
  }
75
70
  }
76
-
77
71
  const htmlString = dataTransfer.getData('text/html');
78
-
79
72
  if (htmlString) {
80
73
  try {
81
74
  const parser = new DOMParser();
82
75
  const dom = parser.parseFromString(htmlString, 'text/html');
83
76
  const nodes = html.$generateNodesFromDOM(editor, dom);
84
77
  return $insertGeneratedNodes(editor, nodes, selection);
85
- } catch {// Fail silently.
78
+ } catch {
79
+ // Fail silently.
86
80
  }
87
- } // Multi-line plain text in rich text mode pasted as separate paragraphs
88
- // instead of single paragraph with linebreaks.
89
-
81
+ }
90
82
 
83
+ // Multi-line plain text in rich text mode pasted as separate paragraphs
84
+ // instead of single paragraph with linebreaks.
91
85
  const text = dataTransfer.getData('text/plain');
92
-
93
86
  if (text != null) {
94
87
  if (lexical.$isRangeSelection(selection)) {
95
88
  const lines = text.split(/\r?\n/);
96
89
  const linesLength = lines.length;
97
-
98
90
  for (let i = 0; i < linesLength; i++) {
99
91
  selection.insertText(lines[i]);
100
-
101
92
  if (i < linesLength - 1) {
102
93
  selection.insertParagraph();
103
94
  }
@@ -109,36 +100,30 @@ function $insertDataTransferForRichText(dataTransfer, selection, editor) {
109
100
  }
110
101
  function $insertGeneratedNodes(editor, nodes, selection) {
111
102
  const isSelectionInsideOfGrid = lexical.DEPRECATED_$isGridSelection(selection) || utils.$findMatchingParent(selection.anchor.getNode(), n => lexical.DEPRECATED_$isGridCellNode(n)) !== null && utils.$findMatchingParent(selection.focus.getNode(), n => lexical.DEPRECATED_$isGridCellNode(n)) !== null;
112
-
113
103
  if (isSelectionInsideOfGrid && nodes.length === 1 && lexical.DEPRECATED_$isGridNode(nodes[0])) {
114
104
  $mergeGridNodesStrategy(nodes, selection, false, editor);
115
105
  return;
116
106
  }
117
-
118
107
  $basicInsertStrategy(nodes, selection);
119
108
  return;
120
109
  }
121
-
122
110
  function $basicInsertStrategy(nodes, selection) {
123
111
  // Wrap text and inline nodes in paragraph nodes so we have all blocks at the top-level
124
112
  const topLevelBlocks = [];
125
113
  let currentBlock = null;
126
-
127
114
  for (let i = 0; i < nodes.length; i++) {
128
115
  const node = nodes[i];
129
116
  const isLineBreakNode = lexical.$isLineBreakNode(node);
130
-
131
117
  if (isLineBreakNode || lexical.$isDecoratorNode(node) && node.isInline() || lexical.$isElementNode(node) && node.isInline() || lexical.$isTextNode(node) || node.isParentRequired()) {
132
118
  if (currentBlock === null) {
133
119
  currentBlock = node.createParentElementNode();
134
- topLevelBlocks.push(currentBlock); // In the case of LineBreakNode, we just need to
120
+ topLevelBlocks.push(currentBlock);
121
+ // In the case of LineBreakNode, we just need to
135
122
  // add an empty ParagraphNode to the topLevelBlocks.
136
-
137
123
  if (isLineBreakNode) {
138
124
  continue;
139
125
  }
140
126
  }
141
-
142
127
  if (currentBlock !== null) {
143
128
  currentBlock.append(node);
144
129
  }
@@ -147,30 +132,25 @@ function $basicInsertStrategy(nodes, selection) {
147
132
  currentBlock = null;
148
133
  }
149
134
  }
150
-
151
135
  if (lexical.$isRangeSelection(selection)) {
152
136
  selection.insertNodes(topLevelBlocks);
153
137
  } else if (lexical.DEPRECATED_$isGridSelection(selection)) {
154
138
  // If there's an active grid selection and a non grid is pasted, add to the anchor.
155
139
  const anchorCell = selection.anchor.getNode();
156
-
157
140
  if (!lexical.DEPRECATED_$isGridCellNode(anchorCell)) {
158
141
  {
159
142
  throw Error(`Expected Grid Cell in Grid Selection`);
160
143
  }
161
144
  }
162
-
163
145
  anchorCell.append(...topLevelBlocks);
164
146
  }
165
147
  }
166
-
167
148
  function $mergeGridNodesStrategy(nodes, selection, isFromLexical, editor) {
168
149
  if (nodes.length !== 1 || !lexical.DEPRECATED_$isGridNode(nodes[0])) {
169
150
  {
170
151
  throw Error(`$mergeGridNodesStrategy: Expected Grid insertion.`);
171
152
  }
172
153
  }
173
-
174
154
  const newGrid = nodes[0];
175
155
  const newGridRows = newGrid.getChildren();
176
156
  const newColumnCount = newGrid.getFirstChildOrThrow().getChildrenSize();
@@ -178,13 +158,11 @@ function $mergeGridNodesStrategy(nodes, selection, isFromLexical, editor) {
178
158
  const gridCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => lexical.DEPRECATED_$isGridCellNode(n));
179
159
  const gridRowNode = gridCellNode && utils.$findMatchingParent(gridCellNode, n => lexical.DEPRECATED_$isGridRowNode(n));
180
160
  const gridNode = gridRowNode && utils.$findMatchingParent(gridRowNode, n => lexical.DEPRECATED_$isGridNode(n));
181
-
182
161
  if (!lexical.DEPRECATED_$isGridCellNode(gridCellNode) || !lexical.DEPRECATED_$isGridRowNode(gridRowNode) || !lexical.DEPRECATED_$isGridNode(gridNode)) {
183
162
  {
184
163
  throw Error(`$mergeGridNodesStrategy: Expected selection to be inside of a Grid.`);
185
164
  }
186
165
  }
187
-
188
166
  const startY = gridRowNode.getIndexWithinParent();
189
167
  const stopY = Math.min(gridNode.getChildrenSize() - 1, startY + newRowCount - 1);
190
168
  const startX = gridCellNode.getIndexWithinParent();
@@ -197,51 +175,40 @@ function $mergeGridNodesStrategy(nodes, selection, isFromLexical, editor) {
197
175
  let newRowIdx = 0;
198
176
  let newAnchorCellKey;
199
177
  let newFocusCellKey;
200
-
201
178
  for (let r = fromY; r <= toY; r++) {
202
179
  const currentGridRowNode = gridRowNodes[r];
203
-
204
180
  if (!lexical.DEPRECATED_$isGridRowNode(currentGridRowNode)) {
205
181
  {
206
182
  throw Error(`getNodes: expected to find GridRowNode`);
207
183
  }
208
184
  }
209
-
210
185
  const newGridRowNode = newGridRows[newRowIdx];
211
-
212
186
  if (!lexical.DEPRECATED_$isGridRowNode(newGridRowNode)) {
213
187
  {
214
188
  throw Error(`getNodes: expected to find GridRowNode`);
215
189
  }
216
190
  }
217
-
218
191
  const gridCellNodes = currentGridRowNode.getChildren();
219
192
  const newGridCellNodes = newGridRowNode.getChildren();
220
193
  let newColumnIdx = 0;
221
-
222
194
  for (let c = fromX; c <= toX; c++) {
223
195
  const currentGridCellNode = gridCellNodes[c];
224
-
225
196
  if (!lexical.DEPRECATED_$isGridCellNode(currentGridCellNode)) {
226
197
  {
227
198
  throw Error(`getNodes: expected to find GridCellNode`);
228
199
  }
229
200
  }
230
-
231
201
  const newGridCellNode = newGridCellNodes[newColumnIdx];
232
-
233
202
  if (!lexical.DEPRECATED_$isGridCellNode(newGridCellNode)) {
234
203
  {
235
204
  throw Error(`getNodes: expected to find GridCellNode`);
236
205
  }
237
206
  }
238
-
239
207
  if (r === fromY && c === fromX) {
240
208
  newAnchorCellKey = currentGridCellNode.getKey();
241
209
  } else if (r === toY && c === toX) {
242
210
  newFocusCellKey = currentGridCellNode.getKey();
243
211
  }
244
-
245
212
  const originalChildren = currentGridCellNode.getChildren();
246
213
  newGridCellNode.getChildren().forEach(child => {
247
214
  if (lexical.$isTextNode(child)) {
@@ -255,10 +222,8 @@ function $mergeGridNodesStrategy(nodes, selection, isFromLexical, editor) {
255
222
  originalChildren.forEach(n => n.remove());
256
223
  newColumnIdx++;
257
224
  }
258
-
259
225
  newRowIdx++;
260
226
  }
261
-
262
227
  if (newAnchorCellKey && newFocusCellKey) {
263
228
  const newGridSelection = lexical.DEPRECATED_$createGridSelection();
264
229
  newGridSelection.set(gridNode.getKey(), newAnchorCellKey, newFocusCellKey);
@@ -266,20 +231,19 @@ function $mergeGridNodesStrategy(nodes, selection, isFromLexical, editor) {
266
231
  editor.dispatchCommand(lexical.SELECTION_CHANGE_COMMAND, undefined);
267
232
  }
268
233
  }
269
-
270
234
  function exportNodeToJSON(node) {
271
235
  const serializedNode = node.exportJSON();
272
- const nodeClass = node.constructor; // @ts-expect-error TODO Replace Class utility type with InstanceType
236
+ const nodeClass = node.constructor;
273
237
 
238
+ // @ts-expect-error TODO Replace Class utility type with InstanceType
274
239
  if (serializedNode.type !== nodeClass.getType()) {
275
240
  {
276
241
  throw Error(`LexicalNode: Node ${nodeClass.name} does not implement .exportJSON().`);
277
242
  }
278
- } // @ts-expect-error TODO Replace Class utility type with InstanceType
279
-
243
+ }
280
244
 
245
+ // @ts-expect-error TODO Replace Class utility type with InstanceType
281
246
  const serializedChildren = serializedNode.children;
282
-
283
247
  if (lexical.$isElementNode(node)) {
284
248
  if (!Array.isArray(serializedChildren)) {
285
249
  {
@@ -287,50 +251,44 @@ function exportNodeToJSON(node) {
287
251
  }
288
252
  }
289
253
  }
290
-
291
254
  return serializedNode;
292
255
  }
293
-
294
256
  function $appendNodesToJSON(editor, selection$1, currentNode, targetArray = []) {
295
257
  let shouldInclude = selection$1 != null ? currentNode.isSelected(selection$1) : true;
296
258
  const shouldExclude = lexical.$isElementNode(currentNode) && currentNode.excludeFromCopy('html');
297
259
  let target = currentNode;
298
-
299
260
  if (selection$1 !== null) {
300
261
  let clone = selection.$cloneWithProperties(currentNode);
301
262
  clone = lexical.$isTextNode(clone) && selection$1 != null ? selection.$sliceSelectedTextNodeContent(selection$1, clone) : clone;
302
263
  target = clone;
303
264
  }
304
-
305
265
  const children = lexical.$isElementNode(target) ? target.getChildren() : [];
306
- const serializedNode = exportNodeToJSON(target); // TODO: TextNode calls getTextContent() (NOT node.__text) within it's exportJSON method
266
+ const serializedNode = exportNodeToJSON(target);
267
+
268
+ // TODO: TextNode calls getTextContent() (NOT node.__text) within it's exportJSON method
307
269
  // which uses getLatest() to get the text from the original node with the same key.
308
270
  // This is a deeper issue with the word "clone" here, it's still a reference to the
309
271
  // same node as far as the LexicalEditor is concerned since it shares a key.
310
272
  // We need a way to create a clone of a Node in memory with it's own key, but
311
273
  // until then this hack will work for the selected text extract use case.
312
-
313
274
  if (lexical.$isTextNode(target)) {
314
- const text = target.__text; // If an uncollapsed selection ends or starts at the end of a line of specialized,
275
+ const text = target.__text;
276
+ // If an uncollapsed selection ends or starts at the end of a line of specialized,
315
277
  // TextNodes, such as code tokens, we will get a 'blank' TextNode here, i.e., one
316
278
  // with text of length 0. We don't want this, it makes a confusing mess. Reset!
317
-
318
279
  if (text.length > 0) {
319
280
  serializedNode.text = text;
320
281
  } else {
321
282
  shouldInclude = false;
322
283
  }
323
284
  }
324
-
325
285
  for (let i = 0; i < children.length; i++) {
326
286
  const childNode = children[i];
327
287
  const shouldIncludeChild = $appendNodesToJSON(editor, selection$1, childNode, serializedNode.children);
328
-
329
288
  if (!shouldInclude && lexical.$isElementNode(currentNode) && shouldIncludeChild && currentNode.extractWithChild(childNode, selection$1, 'clone')) {
330
289
  shouldInclude = true;
331
290
  }
332
291
  }
333
-
334
292
  if (shouldInclude && !shouldExclude) {
335
293
  targetArray.push(serializedNode);
336
294
  } else if (Array.isArray(serializedNode.children)) {
@@ -339,21 +297,18 @@ function $appendNodesToJSON(editor, selection$1, currentNode, targetArray = [])
339
297
  targetArray.push(serializedChildNode);
340
298
  }
341
299
  }
342
-
343
300
  return shouldInclude;
344
- } // TODO why $ function with Editor instance?
345
-
301
+ }
346
302
 
303
+ // TODO why $ function with Editor instance?
347
304
  function $generateJSONFromSelectedNodes(editor, selection) {
348
305
  const nodes = [];
349
306
  const root = lexical.$getRoot();
350
307
  const topLevelChildren = root.getChildren();
351
-
352
308
  for (let i = 0; i < topLevelChildren.length; i++) {
353
309
  const topLevelNode = topLevelChildren[i];
354
310
  $appendNodesToJSON(editor, selection, topLevelNode, nodes);
355
311
  }
356
-
357
312
  return {
358
313
  namespace: editor._config.namespace,
359
314
  nodes
@@ -361,31 +316,27 @@ function $generateJSONFromSelectedNodes(editor, selection) {
361
316
  }
362
317
  function $generateNodesFromSerializedNodes(serializedNodes) {
363
318
  const nodes = [];
364
-
365
319
  for (let i = 0; i < serializedNodes.length; i++) {
366
320
  const serializedNode = serializedNodes[i];
367
321
  const node = lexical.$parseSerializedNode(serializedNode);
368
-
369
322
  if (lexical.$isTextNode(node)) {
370
323
  selection.$addNodeStyle(node);
371
324
  }
372
-
373
325
  nodes.push(node);
374
326
  }
375
-
376
327
  return nodes;
377
328
  }
378
329
  const EVENT_LATENCY = 50;
379
- let clipboardEventTimeout = null; // TODO custom selection
380
- // TODO potentially have a node customizable version for plain text
330
+ let clipboardEventTimeout = null;
381
331
 
332
+ // TODO custom selection
333
+ // TODO potentially have a node customizable version for plain text
382
334
  async function copyToClipboard__EXPERIMENTAL(editor, event) {
383
335
  if (clipboardEventTimeout !== null) {
384
336
  // Prevent weird race conditions that can happen when this function is run multiple times
385
337
  // synchronously. In the future, we can do better, we can cancel/override the previously running job.
386
338
  return false;
387
339
  }
388
-
389
340
  if (event !== null) {
390
341
  return new Promise((resolve, reject) => {
391
342
  editor.update(() => {
@@ -393,14 +344,11 @@ async function copyToClipboard__EXPERIMENTAL(editor, event) {
393
344
  });
394
345
  });
395
346
  }
396
-
397
347
  const rootElement = editor.getRootElement();
398
348
  const domSelection = document.getSelection();
399
-
400
349
  if (rootElement === null || domSelection === null) {
401
350
  return false;
402
351
  }
403
-
404
352
  const element = document.createElement('span');
405
353
  element.style.cssText = 'position: fixed; top: -1000px;';
406
354
  element.append(document.createTextNode('#'));
@@ -414,20 +362,17 @@ async function copyToClipboard__EXPERIMENTAL(editor, event) {
414
362
  const removeListener = editor.registerCommand(lexical.COPY_COMMAND, secondEvent => {
415
363
  if (secondEvent instanceof ClipboardEvent) {
416
364
  removeListener();
417
-
418
365
  if (clipboardEventTimeout !== null) {
419
366
  window.clearTimeout(clipboardEventTimeout);
420
367
  clipboardEventTimeout = null;
421
368
  }
422
-
423
369
  resolve($copyToClipboardEvent(editor, secondEvent));
424
- } // Block the entire copy flow while we wait for the next ClipboardEvent
425
-
426
-
370
+ }
371
+ // Block the entire copy flow while we wait for the next ClipboardEvent
427
372
  return true;
428
- }, lexical.COMMAND_PRIORITY_CRITICAL); // If the above hack execCommand hack works, this timeout code should never fire. Otherwise,
373
+ }, lexical.COMMAND_PRIORITY_CRITICAL);
374
+ // If the above hack execCommand hack works, this timeout code should never fire. Otherwise,
429
375
  // the listener will be quickly freed so that the user can reuse it again
430
-
431
376
  clipboardEventTimeout = window.setTimeout(() => {
432
377
  removeListener();
433
378
  clipboardEventTimeout = null;
@@ -436,46 +381,37 @@ async function copyToClipboard__EXPERIMENTAL(editor, event) {
436
381
  document.execCommand('copy');
437
382
  element.remove();
438
383
  });
439
- } // TODO shouldn't pass editor (pass namespace directly)
384
+ }
440
385
 
386
+ // TODO shouldn't pass editor (pass namespace directly)
441
387
  function $copyToClipboardEvent(editor, event) {
442
388
  const domSelection = window.getSelection();
443
-
444
389
  if (!domSelection) {
445
390
  return false;
446
391
  }
447
-
448
392
  const anchorDOM = domSelection.anchorNode;
449
393
  const focusDOM = domSelection.focusNode;
450
-
451
394
  if (anchorDOM !== null && focusDOM !== null && !lexical.isSelectionWithinEditor(editor, anchorDOM, focusDOM)) {
452
395
  return false;
453
396
  }
454
-
455
397
  event.preventDefault();
456
398
  const clipboardData = event.clipboardData;
457
399
  const selection = lexical.$getSelection();
458
-
459
400
  if (clipboardData === null || selection === null) {
460
401
  return false;
461
402
  }
462
-
463
403
  const htmlString = $getHtmlContent(editor);
464
404
  const lexicalString = $getLexicalContent(editor);
465
405
  let plainString = '';
466
-
467
406
  if (selection !== null) {
468
407
  plainString = selection.getTextContent();
469
408
  }
470
-
471
409
  if (htmlString !== null) {
472
410
  clipboardData.setData('text/html', htmlString);
473
411
  }
474
-
475
412
  if (lexicalString !== null) {
476
413
  clipboardData.setData('application/x-lexical-editor', lexicalString);
477
414
  }
478
-
479
415
  clipboardData.setData('text/plain', plainString);
480
416
  return true;
481
417
  }
package/package.json CHANGED
@@ -9,16 +9,16 @@
9
9
  "paste"
10
10
  ],
11
11
  "license": "MIT",
12
- "version": "0.7.8",
12
+ "version": "0.7.9",
13
13
  "main": "LexicalClipboard.js",
14
14
  "peerDependencies": {
15
- "lexical": "0.7.8"
15
+ "lexical": "0.7.9"
16
16
  },
17
17
  "dependencies": {
18
- "@lexical/utils": "0.7.8",
19
- "@lexical/list": "0.7.8",
20
- "@lexical/selection": "0.7.8",
21
- "@lexical/html": "0.7.8"
18
+ "@lexical/utils": "0.7.9",
19
+ "@lexical/list": "0.7.9",
20
+ "@lexical/selection": "0.7.9",
21
+ "@lexical/html": "0.7.9"
22
22
  },
23
23
  "repository": {
24
24
  "type": "git",