@lexical/react 0.1.8 → 0.1.11

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.
Files changed (91) hide show
  1. package/DEPRECATED_useLexical.dev.js +3 -38
  2. package/DEPRECATED_useLexical.prod.js +1 -2
  3. package/DEPRECATED_useLexicalAutoFormatter.dev.js +202 -74
  4. package/DEPRECATED_useLexicalAutoFormatter.prod.js +21 -16
  5. package/DEPRECATED_useLexicalCanShowPlaceholder.prod.js +1 -1
  6. package/DEPRECATED_useLexicalCharacterLimit.dev.js +23 -21
  7. package/DEPRECATED_useLexicalCharacterLimit.prod.js +8 -8
  8. package/DEPRECATED_useLexicalDecorators.prod.js +1 -1
  9. package/DEPRECATED_useLexicalEditor.dev.js +1 -25
  10. package/DEPRECATED_useLexicalEditor.prod.js +1 -1
  11. package/DEPRECATED_useLexicalEditorEvents.prod.js +1 -1
  12. package/DEPRECATED_useLexicalHistory.dev.js +18 -15
  13. package/DEPRECATED_useLexicalHistory.prod.js +7 -7
  14. package/DEPRECATED_useLexicalList.dev.js +6 -0
  15. package/DEPRECATED_useLexicalList.prod.js +1 -1
  16. package/DEPRECATED_useLexicalPlainText.dev.js +79 -70
  17. package/DEPRECATED_useLexicalPlainText.prod.js +15 -16
  18. package/DEPRECATED_useLexicalRichText.dev.js +124 -250
  19. package/DEPRECATED_useLexicalRichText.prod.js +25 -29
  20. package/LexicalAutoFormatterPlugin.dev.js +202 -74
  21. package/LexicalAutoFormatterPlugin.js.flow +10 -0
  22. package/LexicalAutoFormatterPlugin.prod.js +21 -17
  23. package/LexicalAutoLinkPlugin.js.flow +23 -0
  24. package/LexicalAutoLinkPlugin.prod.js +4 -4
  25. package/LexicalCharacterLimitPlugin.dev.js +23 -21
  26. package/LexicalCharacterLimitPlugin.js.flow +12 -0
  27. package/LexicalCharacterLimitPlugin.prod.js +9 -8
  28. package/LexicalClearEditorPlugin.dev.js +52 -0
  29. package/LexicalClearEditorPlugin.js +9 -0
  30. package/LexicalClearEditorPlugin.js.flow +14 -0
  31. package/LexicalClearEditorPlugin.prod.js +7 -0
  32. package/LexicalCollaborationPlugin.dev.js +31 -36
  33. package/LexicalCollaborationPlugin.js.flow +55 -0
  34. package/LexicalCollaborationPlugin.prod.js +7 -8
  35. package/LexicalComposer.dev.js +8 -6
  36. package/LexicalComposer.js.flow +23 -0
  37. package/LexicalComposer.prod.js +3 -3
  38. package/LexicalComposerContext.js.flow +27 -0
  39. package/LexicalComposerContext.prod.js +1 -1
  40. package/LexicalContentEditable.dev.js +14 -8
  41. package/LexicalContentEditable.js.flow +35 -0
  42. package/LexicalContentEditable.prod.js +3 -3
  43. package/LexicalHashtagPlugin.js.flow +20 -0
  44. package/LexicalHashtagPlugin.prod.js +1 -1
  45. package/LexicalHistoryPlugin.dev.js +18 -15
  46. package/LexicalHistoryPlugin.js.flow +34 -0
  47. package/LexicalHistoryPlugin.prod.js +7 -7
  48. package/LexicalHorizontalRuleNode.dev.js +66 -0
  49. package/LexicalHorizontalRuleNode.js +9 -0
  50. package/LexicalHorizontalRuleNode.js.flow +25 -0
  51. package/LexicalHorizontalRuleNode.prod.js +8 -0
  52. package/LexicalLinkPlugin.dev.js +0 -1
  53. package/LexicalLinkPlugin.js.flow +10 -0
  54. package/LexicalLinkPlugin.prod.js +3 -3
  55. package/LexicalListPlugin.dev.js +6 -0
  56. package/LexicalListPlugin.js.flow +10 -0
  57. package/LexicalListPlugin.prod.js +2 -2
  58. package/LexicalNestedComposer.js.flow +21 -0
  59. package/LexicalNestedComposer.prod.js +1 -1
  60. package/LexicalOnChangePlugin.js.flow +14 -0
  61. package/LexicalOnChangePlugin.prod.js +1 -1
  62. package/LexicalPlainTextPlugin.dev.js +72 -44
  63. package/LexicalPlainTextPlugin.js.flow +18 -0
  64. package/LexicalPlainTextPlugin.prod.js +12 -11
  65. package/LexicalRichTextPlugin.dev.js +115 -222
  66. package/LexicalRichTextPlugin.js.flow +18 -0
  67. package/LexicalRichTextPlugin.prod.js +21 -25
  68. package/LexicalTablePlugin.dev.js +43 -39
  69. package/LexicalTablePlugin.js.flow +10 -0
  70. package/LexicalTablePlugin.prod.js +4 -3
  71. package/LexicalTreeView.dev.js +10 -2
  72. package/LexicalTreeView.js.flow +19 -0
  73. package/LexicalTreeView.prod.js +9 -8
  74. package/README.md +0 -1
  75. package/package.json +5 -4
  76. package/useLexicalDecoratorMap.js.flow +16 -0
  77. package/useLexicalDecoratorMap.prod.js +1 -1
  78. package/useLexicalIsTextContentEmpty.js.flow +15 -0
  79. package/useLexicalIsTextContentEmpty.prod.js +1 -1
  80. package/useLexicalNodeSelection.dev.js +70 -0
  81. package/useLexicalNodeSelection.js +9 -0
  82. package/useLexicalNodeSelection.js.flow +14 -0
  83. package/useLexicalNodeSelection.prod.js +8 -0
  84. package/withSubscriptions.js.flow +13 -0
  85. package/withSubscriptions.prod.js +1 -1
  86. package/LexicalBootstrapPlugin.dev.js +0 -124
  87. package/LexicalBootstrapPlugin.js +0 -9
  88. package/LexicalBootstrapPlugin.prod.js +0 -8
  89. package/LexicalHorizontalRulePlugin.dev.js +0 -51
  90. package/LexicalHorizontalRulePlugin.js +0 -9
  91. package/LexicalHorizontalRulePlugin.prod.js +0 -7
@@ -18,35 +18,11 @@ var useLexicalCanShowPlaceholder = require('@lexical/react/DEPRECATED_useLexical
18
18
  *
19
19
  *
20
20
  */
21
- const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
22
-
23
- /**
24
- * Copyright (c) Meta Platforms, Inc. and affiliates.
25
- *
26
- * This source code is licensed under the MIT license found in the
27
- * LICENSE file in the root directory of this source tree.
28
- *
29
- *
30
- */
31
- const useLayoutEffectImpl = CAN_USE_DOM ? react.useLayoutEffect : react.useEffect;
32
- var useLayoutEffect = useLayoutEffectImpl;
33
-
34
- /**
35
- * Copyright (c) Meta Platforms, Inc. and affiliates.
36
- *
37
- * This source code is licensed under the MIT license found in the
38
- * LICENSE file in the root directory of this source tree.
39
- *
40
- *
41
- */
42
- function useLexicalEditor(editor, onError) {
21
+ function useLexicalEditor(editor) {
43
22
  const showPlaceholder = useLexicalCanShowPlaceholder(editor);
44
23
  const rootElementRef = react.useCallback(rootElement => {
45
24
  editor.setRootElement(rootElement);
46
25
  }, [editor]);
47
- useLayoutEffect(() => {
48
- return editor.addListener('error', onError);
49
- }, [editor, onError]);
50
26
  return [rootElementRef, showPlaceholder];
51
27
  }
52
28
 
@@ -58,26 +34,15 @@ function useLexicalEditor(editor, onError) {
58
34
  *
59
35
  *
60
36
  */
61
-
62
- function defaultOnErrorHandler(e) {
63
- throw e;
64
- }
65
-
66
37
  function useLexical(editorConfig) {
67
- const onError = editorConfig !== undefined && editorConfig.onError || defaultOnErrorHandler;
68
38
  const editor = react.useMemo(() => {
69
39
  if (editorConfig !== undefined) {
70
- // eslint-disable-next-line no-unused-vars
71
- const {
72
- onError: _onError,
73
- ...config
74
- } = editorConfig;
75
- return lexical.createEditor(config);
40
+ return lexical.createEditor(editorConfig);
76
41
  }
77
42
 
78
43
  return lexical.createEditor(editorConfig);
79
44
  }, [editorConfig]);
80
- const [rootElementRef, showPlaceholder] = useLexicalEditor(editor, onError);
45
+ const [rootElementRef, showPlaceholder] = useLexicalEditor(editor);
81
46
  return [editor, rootElementRef, showPlaceholder];
82
47
  }
83
48
 
@@ -4,5 +4,4 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
- 'use strict';var b=require("lexical"),g=require("react"),h=require("@lexical/react/DEPRECATED_useLexicalCanShowPlaceholder"),k="undefined"!==typeof window&&"undefined"!==typeof window.document&&"undefined"!==typeof window.document.createElement?g.useLayoutEffect:g.useEffect;function l(a,c){const d=h(a),f=g.useCallback(e=>{a.setRootElement(e)},[a]);k(()=>a.addListener("error",c),[a,c]);return[f,d]}function m(a){throw a;}
8
- module.exports=function(a){const c=void 0!==a&&a.onError||m,d=g.useMemo(()=>{if(void 0!==a){const {onError:p,...n}=a;return b.createEditor(n)}return b.createEditor(a)},[a]),[f,e]=l(d,c);return[d,f,e]};
7
+ var d=require("lexical"),e=require("react"),f=require("@lexical/react/DEPRECATED_useLexicalCanShowPlaceholder");function g(a){const b=f(a);return[e.useCallback(c=>{a.setRootElement(c)},[a]),b]}module.exports=function(a){const b=e.useMemo(()=>d.createEditor(a),[a]),[c,h]=g(b);return[b,c,h]};
@@ -10,6 +10,7 @@ var list = require('@lexical/list');
10
10
  var lexical = require('lexical');
11
11
  var CodeNode = require('lexical/CodeNode');
12
12
  var react = require('react');
13
+ var LexicalHorizontalRuleNode = require('@lexical/react/LexicalHorizontalRuleNode');
13
14
  var HeadingNode = require('lexical/HeadingNode');
14
15
  var QuoteNode = require('lexical/QuoteNode');
15
16
 
@@ -72,17 +73,26 @@ function $findNodeWithOffsetFromJoinedText(elementNode, joinedTextLength, offset
72
73
  const children = elementNode.getChildren();
73
74
  const childrenLength = children.length;
74
75
  let runningLength = 0;
76
+ let isPriorNodeTextNode = false;
75
77
 
76
78
  for (let i = 0; i < childrenLength; ++i) {
77
- if (runningLength >= joinedTextLength) {
79
+ // We must examine the offsetInJoinedText that is located
80
+ // at the length of the string.
81
+ // For example, given "hello", the length is 5, yet
82
+ // the caller still wants the node + offset at the
83
+ // right edge of the "o".
84
+ if (runningLength > joinedTextLength) {
78
85
  break;
79
86
  }
80
87
 
81
88
  const child = children[i];
82
- const childContentLength = lexical.$isTextNode(child) ? child.getTextContent().length : separatorLength;
89
+ const isChildNodeTestNode = lexical.$isTextNode(child);
90
+ const childContentLength = isChildNodeTestNode ? child.getTextContent().length : separatorLength;
83
91
  const newRunningLength = runningLength + childContentLength;
92
+ const isJoinedOffsetWithinNode = isPriorNodeTextNode === false && runningLength === offsetInJoinedText || runningLength === 0 && runningLength === offsetInJoinedText || runningLength < offsetInJoinedText && offsetInJoinedText <= newRunningLength;
84
93
 
85
- if (runningLength <= offsetInJoinedText && offsetInJoinedText < newRunningLength && lexical.$isTextNode(child)) {
94
+ if (isJoinedOffsetWithinNode && lexical.$isTextNode(child)) {
95
+ // Check isTextNode again for flow.
86
96
  return {
87
97
  node: child,
88
98
  offset: offsetInJoinedText - runningLength
@@ -90,6 +100,7 @@ function $findNodeWithOffsetFromJoinedText(elementNode, joinedTextLength, offset
90
100
  }
91
101
 
92
102
  runningLength = newRunningLength;
103
+ isPriorNodeTextNode = isChildNodeTestNode;
93
104
  }
94
105
 
95
106
  return null;
@@ -114,8 +125,6 @@ const SEPARATOR_BETWEEN_TEXT_AND_NON_TEXT_NODES = '\u0004'; // Select an unused
114
125
  const autoFormatBase = {
115
126
  nodeTransformationKind: null,
116
127
  regEx: /(?:)/,
117
- regExCaptureGroupsToDelete: null,
118
- regExExpectedCaptureGroupCount: 1,
119
128
  requiresParagraphStart: false
120
129
  };
121
130
  const paragraphStartBase = { ...autoFormatBase,
@@ -151,38 +160,77 @@ const markdownCodeBlock = { ...paragraphStartBase,
151
160
  };
152
161
  const markdownOrderedList = { ...paragraphStartBase,
153
162
  nodeTransformationKind: 'paragraphOrderedList',
154
- regEx: /^(\d+)\.\s/,
155
- regExExpectedCaptureGroupCount: 2
156
- /*e.g. '321. ' returns '321. ' & '321'*/
157
-
163
+ regEx: /^(\d+)\.\s/
164
+ };
165
+ const markdownHorizontalRule = { ...paragraphStartBase,
166
+ nodeTransformationKind: 'horizontalRule',
167
+ regEx: /(?:\*\*\* )/
168
+ };
169
+ const markdownHorizontalRuleUsingDashes = { ...paragraphStartBase,
170
+ nodeTransformationKind: 'horizontalRule',
171
+ regEx: /(?:--- )/
172
+ };
173
+ const markdownItalic = { ...autoFormatBase,
174
+ nodeTransformationKind: 'italic',
175
+ regEx: /(\*)(\s*\b)([^\*]*)(\b\s*)(\*\s)$/
158
176
  };
159
177
  const markdownBold = { ...autoFormatBase,
160
- nodeTransformationKind: 'textBold',
161
- // regEx: /(\*)(?:\s*\b)(?:[^\*]*)(?:\b\s*)(\*\s)$/, // The $ will find the target at the end of the string.
162
- regEx: /(\*)(\s*\b)([^\*]*)(\b\s*)(\*\s)$/,
163
- // Remove the first and last capture groups. Remeber, the 0th capture group is the entire string.
164
- // e.g. "*Hello* " requires removing both "*" as well as bolding "Hello".
165
- regExCaptureGroupsToDelete: [1, 5],
166
- // The $ will find the target at the end of the string.
167
- regExExpectedCaptureGroupCount: 6
178
+ nodeTransformationKind: 'bold',
179
+ regEx: /(\*\*)(\s*\b)([^\*\*]*)(\b\s*)(\*\*\s)$/
168
180
  };
169
- const allAutoFormatCriteriaForTextNodes = [markdownBold];
170
- const allAutoFormatCriteria = [markdownHeader1, markdownHeader2, markdownHeader3, markdownBlockQuote, markdownUnorderedListDash, markdownUnorderedListAsterisk, markdownOrderedList, markdownCodeBlock, ...allAutoFormatCriteriaForTextNodes];
181
+ const markdownBoldWithUnderlines = { ...autoFormatBase,
182
+ nodeTransformationKind: 'bold',
183
+ regEx: /(__)(\s*)([^__]*)(\s*)(__\s)$/
184
+ };
185
+ const markdownBoldItalic = { ...autoFormatBase,
186
+ nodeTransformationKind: 'bold_italic',
187
+ regEx: /(\*\*\*)(\s*\b)([^\*\*\*]*)(\b\s*)(\*\*\*\s)$/
188
+ }; // Markdown does not support underline, but we can allow folks to use
189
+ // the HTML tags for underline.
190
+
191
+ const fakeMarkdownUnderline = { ...autoFormatBase,
192
+ nodeTransformationKind: 'underline',
193
+ regEx: /(\<u\>)(\s*\b)([^\<]*)(\b\s*)(\<\/u\>\s)$/
194
+ };
195
+ const markdownStrikethrough = { ...autoFormatBase,
196
+ nodeTransformationKind: 'strikethrough',
197
+ regEx: /(~~)(\s*\b)([^~~]*)(\b\s*)(~~\s)$/
198
+ };
199
+ const allAutoFormatCriteriaForTextNodes = [markdownBoldItalic, markdownItalic, markdownBold, markdownBoldWithUnderlines, fakeMarkdownUnderline, markdownStrikethrough];
200
+ const allAutoFormatCriteria = [markdownHeader1, markdownHeader2, markdownHeader3, markdownBlockQuote, markdownUnorderedListDash, markdownUnorderedListAsterisk, markdownOrderedList, markdownCodeBlock, markdownHorizontalRule, markdownHorizontalRuleUsingDashes, ...allAutoFormatCriteriaForTextNodes];
171
201
  function getAllAutoFormatCriteriaForTextNodes() {
172
202
  return allAutoFormatCriteriaForTextNodes;
173
203
  }
174
204
  function getAllAutoFormatCriteria() {
175
205
  return allAutoFormatCriteria;
176
206
  }
207
+ function getInitialScanningContext(textNodeWithOffset, triggerState) {
208
+ return {
209
+ autoFormatCriteria: {
210
+ nodeTransformationKind: 'noTransformation',
211
+ regEx: /(?:)/,
212
+ // Empty reg ex will do until the precise criteria is discovered.
213
+ requiresParagraphStart: null
214
+ },
215
+ joinedText: null,
216
+ matchResultContext: {
217
+ offsetInJoinedTextForCollapsedSelection: 0,
218
+ regExCaptureGroups: []
219
+ },
220
+ textNodeWithOffset,
221
+ triggerState
222
+ };
223
+ }
177
224
 
178
- function getMatchResultContextWithRegEx(textToSearch, matchMustAppearAtStartOfString, matchMustAppearAtEndOfString, regEx, regExExpectedCaptureGroupCount, scanningContext) {
225
+ function getMatchResultContextWithRegEx(textToSearch, matchMustAppearAtStartOfString, matchMustAppearAtEndOfString, regEx) {
179
226
  const matchResultContext = {
180
- regExCaptureGroups: [],
181
- triggerState: null
227
+ offsetInJoinedTextForCollapsedSelection: 0,
228
+ regExCaptureGroups: []
182
229
  };
183
230
  const regExMatches = textToSearch.match(regEx);
184
231
 
185
- if (regExMatches !== null && regExMatches.length > 0 && regExMatches.length === regExExpectedCaptureGroupCount && (matchMustAppearAtStartOfString === false || regExMatches.index === 0) && (matchMustAppearAtEndOfString === false || regExMatches.index + regExMatches[0].length === textToSearch.length)) {
232
+ if (regExMatches !== null && regExMatches.length > 0 && (matchMustAppearAtStartOfString === false || regExMatches.index === 0) && (matchMustAppearAtEndOfString === false || regExMatches.index + regExMatches[0].length === textToSearch.length)) {
233
+ matchResultContext.offsetInJoinedTextForCollapsedSelection = textToSearch.length;
186
234
  const captureGroupsCount = regExMatches.length;
187
235
  let runningLength = regExMatches.index;
188
236
 
@@ -214,7 +262,7 @@ function getMatchResultContextForParagraphs(autoFormatCriteria, scanningContext)
214
262
 
215
263
  if (textNodeWithOffset.node.getPreviousSibling() === null) {
216
264
  const textToSearch = scanningContext.textNodeWithOffset.node.getTextContent();
217
- return getMatchResultContextWithRegEx(textToSearch, true, false, autoFormatCriteria.regEx, autoFormatCriteria.regExExpectedCaptureGroupCount);
265
+ return getMatchResultContextWithRegEx(textToSearch, true, false, autoFormatCriteria.regEx);
218
266
  }
219
267
 
220
268
  return null;
@@ -229,17 +277,14 @@ function getMatchResultContextForText(autoFormatCriteria, scanningContext) {
229
277
  // Lazy calculate the text to search.
230
278
  scanningContext.joinedText = $joinTextNodesInElementNode(parentNode, SEPARATOR_BETWEEN_TEXT_AND_NON_TEXT_NODES, scanningContext.textNodeWithOffset);
231
279
  }
232
-
233
- return getMatchResultContextWithRegEx(scanningContext.joinedText, false, true, autoFormatCriteria.regEx, autoFormatCriteria.regExExpectedCaptureGroupCount);
234
280
  } else {
235
281
  {
236
282
  throw Error(`Expected node ${parentNode.__key} to to be a ElementNode.`);
237
283
  }
238
284
  }
239
- } // This is a placeholder function for following PR's related to character based transformations.
240
-
285
+ }
241
286
 
242
- return null;
287
+ return getMatchResultContextWithRegEx(scanningContext.joinedText, false, true, autoFormatCriteria.regEx);
243
288
  }
244
289
 
245
290
  function getMatchResultContextForCriteria(autoFormatCriteria, scanningContext) {
@@ -250,8 +295,11 @@ function getMatchResultContextForCriteria(autoFormatCriteria, scanningContext) {
250
295
  return getMatchResultContextForText(autoFormatCriteria, scanningContext);
251
296
  }
252
297
 
253
- function getNewNodeForCriteria(autoFormatCriteria, matchResultContext, children) {
298
+ function getNewNodeForCriteria(scanningContext, element) {
254
299
  let newNode = null;
300
+ const children = element.getChildren();
301
+ const autoFormatCriteria = scanningContext.autoFormatCriteria;
302
+ const matchResultContext = scanningContext.matchResultContext;
255
303
 
256
304
  if (autoFormatCriteria.nodeTransformationKind != null) {
257
305
  switch (autoFormatCriteria.nodeTransformationKind) {
@@ -306,7 +354,7 @@ function getNewNodeForCriteria(autoFormatCriteria, matchResultContext, children)
306
354
  case 'paragraphCodeBlock':
307
355
  {
308
356
  // Toggle code and paragraph nodes.
309
- if (matchResultContext.triggerState != null && matchResultContext.triggerState.isCodeBlock) {
357
+ if (scanningContext.triggerState != null && scanningContext.triggerState.isCodeBlock) {
310
358
  newNode = lexical.$createParagraphNode();
311
359
  } else {
312
360
  newNode = CodeNode.$createCodeNode();
@@ -315,6 +363,14 @@ function getNewNodeForCriteria(autoFormatCriteria, matchResultContext, children)
315
363
  newNode.append(...children);
316
364
  return newNode;
317
365
  }
366
+
367
+ case 'horizontalRule':
368
+ {
369
+ // return null for newNode. Insert the HR here.
370
+ const horizontalRuleNode = LexicalHorizontalRuleNode.$createHorizontalRuleNode();
371
+ element.insertBefore(horizontalRuleNode);
372
+ break;
373
+ }
318
374
  }
319
375
  }
320
376
 
@@ -330,41 +386,70 @@ function updateTextNode(node, count) {
330
386
  }
331
387
  }
332
388
 
333
- function transformTextNodeForAutoFormatCriteria(scanningContext, autoFormatCriteria, matchResultContext) {
334
- if (autoFormatCriteria.requiresParagraphStart) {
335
- transformTextNodeForParagraphs(scanningContext, autoFormatCriteria, matchResultContext);
389
+ function transformTextNodeForAutoFormatCriteria(scanningContext) {
390
+ if (scanningContext.autoFormatCriteria.requiresParagraphStart) {
391
+ transformTextNodeForParagraphs(scanningContext);
336
392
  } else {
337
- transformTextNodeForText(scanningContext, autoFormatCriteria, matchResultContext);
393
+ transformTextNodeForText(scanningContext);
338
394
  }
339
395
  }
340
396
 
341
- function transformTextNodeForParagraphs(scanningContext, autoFormatCriteria, matchResultContext) {
397
+ function transformTextNodeForParagraphs(scanningContext) {
342
398
  const textNodeWithOffset = scanningContext.textNodeWithOffset;
343
399
  const element = textNodeWithOffset.node.getParentOrThrow();
344
- const text = matchResultContext.regExCaptureGroups[0].text;
400
+ const text = scanningContext.matchResultContext.regExCaptureGroups[0].text;
345
401
  updateTextNode(textNodeWithOffset.node, text.length);
346
- const elementNode = getNewNodeForCriteria(autoFormatCriteria, matchResultContext, element.getChildren());
402
+ const elementNode = getNewNodeForCriteria(scanningContext, element);
347
403
 
348
404
  if (elementNode !== null) {
349
405
  element.replace(elementNode);
350
406
  }
351
407
  }
352
408
 
353
- function transformTextNodeForText(scanningContext, autoFormatCriteria, matchResultContext) {
409
+ function getTextFormatType(nodeTransformationKind) {
410
+ switch (nodeTransformationKind) {
411
+ case 'italic':
412
+ case 'bold':
413
+ case 'underline':
414
+ case 'strikethrough':
415
+ return [nodeTransformationKind];
416
+
417
+ case 'bold_italic':
418
+ {
419
+ return ['bold', 'italic'];
420
+ }
421
+ }
422
+
423
+ return null;
424
+ }
425
+
426
+ function transformTextNodeForText(scanningContext) {
427
+ const autoFormatCriteria = scanningContext.autoFormatCriteria;
428
+ const matchResultContext = scanningContext.matchResultContext;
429
+
354
430
  if (autoFormatCriteria.nodeTransformationKind != null) {
355
- switch (autoFormatCriteria.nodeTransformationKind) {
356
- case 'textBold':
357
- {
358
- matchResultContext.regExCaptureGroups = getCaptureGroupsByResolvingAllDetails(scanningContext, autoFormatCriteria, matchResultContext);
431
+ if (matchResultContext.regExCaptureGroups.length !== 6) {
432
+ // For BIUS and other formatts which have a pattern + text + pattern,
433
+ // the expected reg ex pattern should have 6 groups.
434
+ // If it does not, then break and fail silently.
435
+ // e2e tests validate the regEx pattern.
436
+ return;
437
+ }
359
438
 
360
- if (autoFormatCriteria.regExCaptureGroupsToDelete != null) {
361
- // Remove unwanted text in reg ex patterh.
362
- removeTextInCaptureGroups(autoFormatCriteria.regExCaptureGroupsToDelete, matchResultContext);
363
- formatTextInCaptureGroupIndex('bold', 3, matchResultContext);
364
- }
439
+ const formatting = getTextFormatType(autoFormatCriteria.nodeTransformationKind);
365
440
 
366
- break;
367
- }
441
+ if (formatting != null) {
442
+ const captureGroupsToDelete = [1, 5];
443
+ const formatCaptureGroup = 3;
444
+ matchResultContext.regExCaptureGroups = getCaptureGroupsByResolvingAllDetails(scanningContext);
445
+
446
+ if (captureGroupsToDelete.length > 0) {
447
+ // Remove unwanted text in reg ex pattern.
448
+ removeTextInCaptureGroups(captureGroupsToDelete, matchResultContext);
449
+ }
450
+
451
+ formatTextInCaptureGroupIndex(formatting, formatCaptureGroup, matchResultContext);
452
+ makeCollapsedSelectionAtOffsetInJoinedText(matchResultContext.offsetInJoinedTextForCollapsedSelection, matchResultContext.offsetInJoinedTextForCollapsedSelection + 1, scanningContext.textNodeWithOffset.node.getParentOrThrow());
368
453
  }
369
454
  }
370
455
  } // Some Capture Group Details were left lazily unresolved as their calculation
@@ -372,7 +457,9 @@ function transformTextNodeForText(scanningContext, autoFormatCriteria, matchResu
372
457
  // known, the details may be fully resolved without incurring unwasted performance cost.
373
458
 
374
459
 
375
- function getCaptureGroupsByResolvingAllDetails(scanningContext, autoFormatCriteria, matchResultContext) {
460
+ function getCaptureGroupsByResolvingAllDetails(scanningContext) {
461
+ const autoFormatCriteria = scanningContext.autoFormatCriteria;
462
+ const matchResultContext = scanningContext.matchResultContext;
376
463
  const textNodeWithOffset = scanningContext.textNodeWithOffset;
377
464
  const regExCaptureGroups = matchResultContext.regExCaptureGroups;
378
465
  const captureGroupsCount = regExCaptureGroups.length;
@@ -419,7 +506,7 @@ function removeTextInCaptureGroups(regExCaptureGroupsToDelete, matchResultContex
419
506
  lexical.$setSelection(newSelection);
420
507
  const currentSelection = lexical.$getSelection();
421
508
 
422
- if (currentSelection != null) {
509
+ if (lexical.$isRangeSelection(currentSelection)) {
423
510
  currentSelection.removeText(); // Shift offsets for capture groups which are within the same node
424
511
 
425
512
  if (anchorTextNodeWithOffset.node.getKey() === focusTextNodeWithOffset.node.getKey()) {
@@ -444,6 +531,12 @@ function removeTextInCaptureGroups(regExCaptureGroupsToDelete, matchResultContex
444
531
  }
445
532
 
446
533
  function shiftCaptureGroupOffsets(delta, applyAtOrAfterOffset, node, startingCaptureGroupIndex, matchResultContext) {
534
+ matchResultContext.offsetInJoinedTextForCollapsedSelection += delta;
535
+
536
+ if (!(matchResultContext.offsetInJoinedTextForCollapsedSelection > 0)) {
537
+ throw Error(`The text content string length does not correlate with insertions/deletions of new text.`);
538
+ }
539
+
447
540
  const regExCaptureGroups = matchResultContext.regExCaptureGroups;
448
541
  const regExCaptureGroupsCount = regExCaptureGroups.length;
449
542
 
@@ -460,7 +553,7 @@ function shiftCaptureGroupOffsets(delta, applyAtOrAfterOffset, node, startingCap
460
553
  }
461
554
  }
462
555
 
463
- function formatTextInCaptureGroupIndex(formatType, captureGroupIndex, matchResultContext) {
556
+ function formatTextInCaptureGroupIndex(formatTypes, captureGroupIndex, matchResultContext) {
464
557
  const regExCaptureGroups = matchResultContext.regExCaptureGroups;
465
558
  const regExCaptureGroupsCount = regExCaptureGroups.length;
466
559
 
@@ -479,12 +572,30 @@ function formatTextInCaptureGroupIndex(formatType, captureGroupIndex, matchResul
479
572
  lexical.$setSelection(newSelection);
480
573
  const currentSelection = lexical.$getSelection();
481
574
 
482
- if (currentSelection != null) {
483
- currentSelection.formatText(formatType);
575
+ if (lexical.$isRangeSelection(currentSelection)) {
576
+ for (let i = 0; i < formatTypes.length; i++) {
577
+ currentSelection.formatText(formatTypes[i]);
578
+ }
579
+
580
+ const finalSelection = lexical.$createRangeSelection();
581
+ finalSelection.anchor.set(focusTextNodeWithOffset.node.getKey(), focusTextNodeWithOffset.offset + 1, 'text');
582
+ finalSelection.focus.set(focusTextNodeWithOffset.node.getKey(), focusTextNodeWithOffset.offset + 1, 'text');
583
+ lexical.$setSelection(finalSelection);
484
584
  }
485
585
  }
486
586
  }
487
587
 
588
+ function makeCollapsedSelectionAtOffsetInJoinedText(offsetInJoinedText, joinedTextLength, parentElementNode) {
589
+ const textNodeWithOffset = $findNodeWithOffsetFromJoinedText(parentElementNode, joinedTextLength, offsetInJoinedText, TRIGGER_STRING_LENGTH);
590
+
591
+ if (textNodeWithOffset != null) {
592
+ const newSelection = lexical.$createRangeSelection();
593
+ newSelection.anchor.set(textNodeWithOffset.node.getKey(), textNodeWithOffset.offset, 'text');
594
+ newSelection.focus.set(textNodeWithOffset.node.getKey(), textNodeWithOffset.offset, 'text');
595
+ lexical.$setSelection(newSelection);
596
+ }
597
+ }
598
+
488
599
  /**
489
600
  * Copyright (c) Meta Platforms, Inc. and affiliates.
490
601
  *
@@ -494,17 +605,17 @@ function formatTextInCaptureGroupIndex(formatType, captureGroupIndex, matchResul
494
605
  *
495
606
  */
496
607
 
497
- function getCriteriaWithMatchResultContext(autoFormatCriteriaArray, currentTriggerState, scanningContext) {
608
+ function getCriteriaWithMatchResultContext(autoFormatCriteriaArray, scanningContext) {
609
+ const currentTriggerState = scanningContext.triggerState;
498
610
  const count = autoFormatCriteriaArray.length;
499
611
 
500
612
  for (let i = 0; i < count; i++) {
501
613
  const autoFormatCriteria = autoFormatCriteriaArray[i]; // Skip code block nodes, unless the nodeTransformationKind calls for toggling the code block.
502
614
 
503
- if (currentTriggerState.isCodeBlock === false || autoFormatCriteria.nodeTransformationKind === 'paragraphCodeBlock') {
615
+ if (currentTriggerState != null && currentTriggerState.isCodeBlock === false || autoFormatCriteria.nodeTransformationKind === 'paragraphCodeBlock') {
504
616
  const matchResultContext = getMatchResultContextForCriteria(autoFormatCriteria, scanningContext);
505
617
 
506
618
  if (matchResultContext != null) {
507
- matchResultContext.triggerState = currentTriggerState;
508
619
  return {
509
620
  autoFormatCriteria: autoFormatCriteria,
510
621
  matchResultContext
@@ -520,7 +631,7 @@ function getCriteriaWithMatchResultContext(autoFormatCriteriaArray, currentTrigg
520
631
  }
521
632
 
522
633
  function getTextNodeForAutoFormatting(selection) {
523
- if (selection == null) {
634
+ if (!lexical.$isRangeSelection(selection)) {
524
635
  return null;
525
636
  }
526
637
 
@@ -536,8 +647,17 @@ function getTextNodeForAutoFormatting(selection) {
536
647
  };
537
648
  }
538
649
 
539
- function updateAutoFormatting(editor, currentTriggerState) {
650
+ function updateAutoFormatting(editor, scanningContext) {
540
651
  editor.update(() => {
652
+ transformTextNodeForAutoFormatCriteria(scanningContext);
653
+ }, {
654
+ tag: 'history-push'
655
+ });
656
+ }
657
+
658
+ function findScanningContextWithValidMatch(editorState, currentTriggerState) {
659
+ let scanningContext = null;
660
+ editorState.read(() => {
541
661
  const textNodeWithOffset = getTextNodeForAutoFormatting(lexical.$getSelection());
542
662
 
543
663
  if (textNodeWithOffset === null) {
@@ -545,24 +665,25 @@ function updateAutoFormatting(editor, currentTriggerState) {
545
665
  } // Please see the declaration of ScanningContext for a detailed explanation.
546
666
 
547
667
 
548
- const scanningContext = {
549
- joinedText: null,
550
- textNodeWithOffset
551
- };
668
+ const initialScanningContext = getInitialScanningContext(textNodeWithOffset, currentTriggerState);
552
669
  const criteriaWithMatchResultContext = getCriteriaWithMatchResultContext( // Do not apply paragraph node changes like blockQuote or H1 to listNodes. Also, do not attempt to transform a list into a list using * or -.
553
- currentTriggerState.isParentAListItemNode === false ? getAllAutoFormatCriteria() : getAllAutoFormatCriteriaForTextNodes(), currentTriggerState, scanningContext);
670
+ currentTriggerState.isParentAListItemNode === false ? getAllAutoFormatCriteria() : getAllAutoFormatCriteriaForTextNodes(), initialScanningContext);
554
671
 
555
672
  if (criteriaWithMatchResultContext.autoFormatCriteria === null || criteriaWithMatchResultContext.matchResultContext === null) {
556
673
  return;
557
674
  }
558
675
 
559
- transformTextNodeForAutoFormatCriteria(scanningContext, criteriaWithMatchResultContext.autoFormatCriteria, criteriaWithMatchResultContext.matchResultContext);
676
+ scanningContext = initialScanningContext; // Lazy fill-in the particular format criteria and any matching result information.
677
+
678
+ scanningContext.autoFormatCriteria = criteriaWithMatchResultContext.autoFormatCriteria;
679
+ scanningContext.matchResultContext = criteriaWithMatchResultContext.matchResultContext;
560
680
  });
681
+ return scanningContext;
561
682
  }
562
683
 
563
- function shouldAttemptToAutoFormat(currentTriggerState, priorTriggerState) {
684
+ function findScanningContext(editorState, currentTriggerState, priorTriggerState) {
564
685
  if (currentTriggerState == null || priorTriggerState == null) {
565
- return false;
686
+ return null;
566
687
  } // The below checks needs to execute relativey quickly, so perform the light-weight ones first.
567
688
  // The substr check is a quick way to avoid autoformat parsing in that it looks for the autoformat
568
689
  // trigger which is the trigger string (" ").
@@ -571,7 +692,12 @@ function shouldAttemptToAutoFormat(currentTriggerState, priorTriggerState) {
571
692
  const triggerStringLength = TRIGGER_STRING.length;
572
693
  const currentTextContentLength = currentTriggerState.textContent.length;
573
694
  const triggerOffset = currentTriggerState.anchorOffset - triggerStringLength;
574
- return currentTriggerState.hasParentNode === true && currentTriggerState.isSimpleText && currentTriggerState.isSelectionCollapsed && currentTriggerState.nodeKey === priorTriggerState.nodeKey && currentTriggerState.anchorOffset !== priorTriggerState.anchorOffset && triggerOffset >= 0 && triggerOffset + triggerStringLength <= currentTextContentLength && currentTriggerState.textContent.substr(triggerOffset, triggerStringLength) === TRIGGER_STRING && currentTriggerState.textContent !== priorTriggerState.textContent;
695
+
696
+ if ((currentTriggerState.hasParentNode === true && currentTriggerState.isSimpleText && currentTriggerState.isSelectionCollapsed && currentTriggerState.nodeKey === priorTriggerState.nodeKey && currentTriggerState.anchorOffset !== priorTriggerState.anchorOffset && triggerOffset >= 0 && triggerOffset + triggerStringLength <= currentTextContentLength && currentTriggerState.textContent.substr(triggerOffset, triggerStringLength) === TRIGGER_STRING && currentTriggerState.textContent !== priorTriggerState.textContent) === false) {
697
+ return null;
698
+ }
699
+
700
+ return findScanningContextWithValidMatch(editorState, currentTriggerState);
575
701
  }
576
702
 
577
703
  function getTriggerState(editorState) {
@@ -579,7 +705,7 @@ function getTriggerState(editorState) {
579
705
  editorState.read(() => {
580
706
  const selection = lexical.$getSelection();
581
707
 
582
- if (selection == null || !selection.isCollapsed()) {
708
+ if (!lexical.$isRangeSelection(selection) || !selection.isCollapsed()) {
583
709
  return;
584
710
  }
585
711
 
@@ -608,15 +734,17 @@ function useAutoFormatter(editor) {
608
734
  // For example, typing "#" and then " ", shoud trigger an format.
609
735
  // However, given "#A B", where the user delets "A" should not.
610
736
  let priorTriggerState = null;
611
- editor.addListener('update', ({
737
+ return editor.addListener('update', ({
612
738
  tags
613
739
  }) => {
614
740
  // Examine historic so that we are not running autoformatting within markdown.
615
741
  if (tags.has('historic') === false) {
616
- const currentTriggerState = getTriggerState(editor.getEditorState());
742
+ const editorState = editor.getEditorState();
743
+ const currentTriggerState = getTriggerState(editorState);
744
+ const scanningContext = currentTriggerState == null ? null : findScanningContext(editorState, currentTriggerState, priorTriggerState);
617
745
 
618
- if (shouldAttemptToAutoFormat(currentTriggerState, priorTriggerState) && currentTriggerState != null) {
619
- updateAutoFormatting(editor, currentTriggerState);
746
+ if (scanningContext != null) {
747
+ updateAutoFormatting(editor, scanningContext);
620
748
  }
621
749
 
622
750
  priorTriggerState = currentTriggerState;