@lexical/react 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.
Files changed (54) hide show
  1. package/LexicalAutoEmbedPlugin.d.ts +3 -2
  2. package/LexicalAutoEmbedPlugin.dev.js +4 -14
  3. package/LexicalAutoEmbedPlugin.prod.js +4 -3
  4. package/LexicalAutoFocusPlugin.dev.js +0 -1
  5. package/LexicalAutoLinkPlugin.dev.js +126 -73
  6. package/LexicalAutoLinkPlugin.prod.js +11 -7
  7. package/LexicalBlockWithAlignableContents.dev.js +0 -10
  8. package/LexicalCharacterLimitPlugin.dev.js +7 -46
  9. package/LexicalCheckListPlugin.dev.js +10 -48
  10. package/LexicalClearEditorPlugin.dev.js +1 -1
  11. package/LexicalClickableLinkPlugin.dev.js +2 -20
  12. package/LexicalCollaborationContext.dev.js +0 -3
  13. package/LexicalCollaborationPlugin.dev.js +8 -37
  14. package/LexicalComposer.d.ts +3 -7
  15. package/LexicalComposer.dev.js +9 -11
  16. package/LexicalComposer.js.flow +4 -4
  17. package/LexicalComposer.prod.js +2 -1
  18. package/LexicalComposerContext.dev.js +0 -6
  19. package/LexicalContentEditable.dev.js +1 -2
  20. package/LexicalContextMenuPlugin.d.ts +3 -2
  21. package/LexicalContextMenuPlugin.dev.js +28 -82
  22. package/LexicalContextMenuPlugin.prod.js +16 -15
  23. package/LexicalDecoratorBlockNode.dev.js +0 -6
  24. package/LexicalEditorRefPlugin.dev.js +0 -3
  25. package/LexicalHashtagPlugin.dev.js +73 -43
  26. package/LexicalHorizontalRuleNode.dev.js +0 -21
  27. package/LexicalHorizontalRulePlugin.dev.js +0 -4
  28. package/LexicalLinkPlugin.dev.js +4 -10
  29. package/LexicalListPlugin.dev.js +0 -2
  30. package/LexicalMarkdownShortcutPlugin.dev.js +2 -2
  31. package/LexicalNestedComposer.d.ts +2 -2
  32. package/LexicalNestedComposer.dev.js +18 -16
  33. package/LexicalNestedComposer.js.flow +7 -2
  34. package/LexicalNestedComposer.prod.js +4 -3
  35. package/LexicalNodeEventPlugin.dev.js +2 -6
  36. package/LexicalNodeMenuPlugin.d.ts +3 -2
  37. package/LexicalNodeMenuPlugin.dev.js +27 -83
  38. package/LexicalNodeMenuPlugin.prod.js +15 -15
  39. package/LexicalOnChangePlugin.dev.js +1 -1
  40. package/LexicalPlainTextPlugin.dev.js +8 -12
  41. package/LexicalRichTextPlugin.dev.js +8 -12
  42. package/LexicalTabIndentationPlugin.dev.js +7 -16
  43. package/LexicalTableOfContents.dev.js +5 -33
  44. package/LexicalTablePlugin.dev.js +11 -28
  45. package/LexicalTreeView.dev.js +14 -79
  46. package/LexicalTypeaheadMenuPlugin.d.ts +4 -3
  47. package/LexicalTypeaheadMenuPlugin.dev.js +39 -176
  48. package/LexicalTypeaheadMenuPlugin.prod.js +19 -20
  49. package/package.json +19 -19
  50. package/shared/LexicalMenu.d.ts +3 -2
  51. package/useLexicalEditable.dev.js +1 -5
  52. package/useLexicalIsTextContentEmpty.dev.js +1 -0
  53. package/useLexicalNodeSelection.dev.js +0 -7
  54. package/useLexicalSubscription.dev.js +1 -3
@@ -22,6 +22,7 @@ var richText = require('@lexical/rich-text');
22
22
  * LICENSE file in the root directory of this source tree.
23
23
  *
24
24
  */
25
+
25
26
  const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
26
27
 
27
28
  /**
@@ -41,12 +42,10 @@ var useLayoutEffect = useLayoutEffectImpl;
41
42
  * LICENSE file in the root directory of this source tree.
42
43
  *
43
44
  */
44
-
45
45
  function canShowPlaceholderFromCurrentEditorState(editor) {
46
46
  const currentCanShowPlaceholder = editor.getEditorState().read(text.$canShowPlaceholderCurry(editor.isComposing()));
47
47
  return currentCanShowPlaceholder;
48
48
  }
49
-
50
49
  function useCanShowPlaceholder(editor) {
51
50
  const [canShowPlaceholder, setCanShowPlaceholder] = React.useState(() => canShowPlaceholderFromCurrentEditorState(editor));
52
51
  useLayoutEffect(() => {
@@ -54,7 +53,6 @@ function useCanShowPlaceholder(editor) {
54
53
  const currentCanShowPlaceholder = canShowPlaceholderFromCurrentEditorState(editor);
55
54
  setCanShowPlaceholder(currentCanShowPlaceholder);
56
55
  }
57
-
58
56
  resetCanShowPlaceholder();
59
57
  return utils.mergeRegister(editor.registerUpdateListener(() => {
60
58
  resetCanShowPlaceholder();
@@ -73,8 +71,9 @@ function useCanShowPlaceholder(editor) {
73
71
  *
74
72
  */
75
73
  function useDecorators(editor, ErrorBoundary) {
76
- const [decorators, setDecorators] = React.useState(() => editor.getDecorators()); // Subscribe to changes
74
+ const [decorators, setDecorators] = React.useState(() => editor.getDecorators());
77
75
 
76
+ // Subscribe to changes
78
77
  useLayoutEffect(() => {
79
78
  return editor.registerDecoratorListener(nextDecorators => {
80
79
  reactDom.flushSync(() => {
@@ -87,12 +86,12 @@ function useDecorators(editor, ErrorBoundary) {
87
86
  // nothing will be rendered on initial pass. We can get around that by
88
87
  // ensuring that we set the value.
89
88
  setDecorators(editor.getDecorators());
90
- }, [editor]); // Return decorators defined as React Portals
89
+ }, [editor]);
91
90
 
91
+ // Return decorators defined as React Portals
92
92
  return React.useMemo(() => {
93
93
  const decoratedPortals = [];
94
94
  const decoratorKeys = Object.keys(decorators);
95
-
96
95
  for (let i = 0; i < decoratorKeys.length; i++) {
97
96
  const nodeKey = decoratorKeys[i];
98
97
  const reactDecorator = /*#__PURE__*/React.createElement(ErrorBoundary, {
@@ -101,12 +100,10 @@ function useDecorators(editor, ErrorBoundary) {
101
100
  fallback: null
102
101
  }, decorators[nodeKey]));
103
102
  const element = editor.getElementByKey(nodeKey);
104
-
105
103
  if (element !== null) {
106
104
  decoratedPortals.push( /*#__PURE__*/reactDom.createPortal(reactDecorator, element, nodeKey));
107
105
  }
108
106
  }
109
-
110
107
  return decoratedPortals;
111
108
  }, [ErrorBoundary, decorators, editor]);
112
109
  }
@@ -120,7 +117,9 @@ function useDecorators(editor, ErrorBoundary) {
120
117
  */
121
118
  function useRichTextSetup(editor) {
122
119
  useLayoutEffect(() => {
123
- return utils.mergeRegister(richText.registerRichText(editor), dragon.registerDragonSupport(editor)); // We only do this for init
120
+ return utils.mergeRegister(richText.registerRichText(editor), dragon.registerDragonSupport(editor));
121
+
122
+ // We only do this for init
124
123
  // eslint-disable-next-line react-hooks/exhaustive-deps
125
124
  }, [editor]);
126
125
  }
@@ -144,18 +143,15 @@ function RichTextPlugin({
144
143
  content: placeholder
145
144
  }), decorators);
146
145
  }
147
-
148
146
  function Placeholder({
149
147
  content
150
148
  }) {
151
149
  const [editor] = LexicalComposerContext.useLexicalComposerContext();
152
150
  const showPlaceholder = useCanShowPlaceholder(editor);
153
151
  const editable = useLexicalEditable();
154
-
155
152
  if (!showPlaceholder) {
156
153
  return null;
157
154
  }
158
-
159
155
  if (typeof content === 'function') {
160
156
  return content(editable);
161
157
  } else {
@@ -18,7 +18,6 @@ var react = require('react');
18
18
  * LICENSE file in the root directory of this source tree.
19
19
  *
20
20
  */
21
-
22
21
  function indentOverTab(selection) {
23
22
  // const handled = new Set();
24
23
  const nodes = selection.getNodes();
@@ -26,56 +25,48 @@ function indentOverTab(selection) {
26
25
  if (lexical.$isBlockElementNode(node) && node.canIndent()) {
27
26
  return node;
28
27
  }
29
-
30
28
  return null;
31
- }); // 1. If selection spans across canIndent block nodes: indent
32
-
29
+ });
30
+ // 1. If selection spans across canIndent block nodes: indent
33
31
  if (canIndentBlockNodes.length > 0) {
34
32
  return true;
35
- } // 2. If first (anchor/focus) is at block start: indent
36
-
37
-
33
+ }
34
+ // 2. If first (anchor/focus) is at block start: indent
38
35
  const anchor = selection.anchor;
39
36
  const focus = selection.focus;
40
37
  const first = focus.isBefore(anchor) ? focus : anchor;
41
38
  const firstNode = first.getNode();
42
39
  const firstBlock = utils.$getNearestBlockElementAncestorOrThrow(firstNode);
43
-
44
40
  if (firstBlock.canIndent()) {
45
41
  const firstBlockKey = firstBlock.getKey();
46
42
  let selectionAtStart = lexical.$createRangeSelection();
47
43
  selectionAtStart.anchor.set(firstBlockKey, 0, 'element');
48
44
  selectionAtStart.focus.set(firstBlockKey, 0, 'element');
49
45
  selectionAtStart = lexical.$normalizeSelection__EXPERIMENTAL(selectionAtStart);
50
-
51
46
  if (selectionAtStart.anchor.is(first)) {
52
47
  return true;
53
48
  }
54
- } // 3. Else: tab
55
-
56
-
49
+ }
50
+ // 3. Else: tab
57
51
  return false;
58
52
  }
59
-
60
53
  function registerTabIndentation(editor) {
61
54
  return editor.registerCommand(lexical.KEY_TAB_COMMAND, event => {
62
55
  const selection = lexical.$getSelection();
63
-
64
56
  if (!lexical.$isRangeSelection(selection)) {
65
57
  return false;
66
58
  }
67
-
68
59
  event.preventDefault();
69
60
  const command = indentOverTab(selection) ? event.shiftKey ? lexical.OUTDENT_CONTENT_COMMAND : lexical.INDENT_CONTENT_COMMAND : lexical.INSERT_TAB_COMMAND;
70
61
  return editor.dispatchCommand(command, undefined);
71
62
  }, lexical.COMMAND_PRIORITY_EDITOR);
72
63
  }
64
+
73
65
  /**
74
66
  * This plugin adds the ability to indent content using the tab key. Generally, we don't
75
67
  * recommend using this plugin as it could negatively affect acessibility for keyboard
76
68
  * users, causing focus to become trapped within the editor.
77
69
  */
78
-
79
70
  function TabIndentationPlugin() {
80
71
  const [editor] = LexicalComposerContext.useLexicalComposerContext();
81
72
  react.useEffect(() => {
@@ -18,50 +18,39 @@ var react = require('react');
18
18
  * LICENSE file in the root directory of this source tree.
19
19
  *
20
20
  */
21
-
22
21
  function toEntry(heading) {
23
22
  return [heading.getKey(), heading.getTextContent(), heading.getTag()];
24
23
  }
25
-
26
24
  function $insertHeadingIntoTableOfContents(prevHeading, newHeading, currentTableOfContents) {
27
25
  if (newHeading === null) {
28
26
  return currentTableOfContents;
29
27
  }
30
-
31
28
  const newEntry = toEntry(newHeading);
32
29
  let newTableOfContents = [];
33
-
34
30
  if (prevHeading === null) {
35
31
  newTableOfContents = [newEntry, ...currentTableOfContents];
36
32
  } else {
37
33
  for (let i = 0; i < currentTableOfContents.length; i++) {
38
34
  const key = currentTableOfContents[i][0];
39
35
  newTableOfContents.push(currentTableOfContents[i]);
40
-
41
36
  if (key === prevHeading.getKey() && key !== newHeading.getKey()) {
42
37
  newTableOfContents.push(newEntry);
43
38
  }
44
39
  }
45
40
  }
46
-
47
41
  return newTableOfContents;
48
42
  }
49
-
50
43
  function $deleteHeadingFromTableOfContents(key, currentTableOfContents) {
51
44
  const newTableOfContents = [];
52
-
53
45
  for (const heading of currentTableOfContents) {
54
46
  if (heading[0] !== key) {
55
47
  newTableOfContents.push(heading);
56
48
  }
57
49
  }
58
-
59
50
  return newTableOfContents;
60
51
  }
61
-
62
52
  function $updateHeadingInTableOfContents(heading, currentTableOfContents) {
63
53
  const newTableOfContents = [];
64
-
65
54
  for (const oldHeading of currentTableOfContents) {
66
55
  if (oldHeading[0] === heading.getKey()) {
67
56
  newTableOfContents.push(toEntry(heading));
@@ -69,38 +58,30 @@ function $updateHeadingInTableOfContents(heading, currentTableOfContents) {
69
58
  newTableOfContents.push(oldHeading);
70
59
  }
71
60
  }
72
-
73
61
  return newTableOfContents;
74
62
  }
63
+
75
64
  /**
76
65
  * Returns the updated table of contents, placing the given `heading` before the given `prevHeading`. If `prevHeading`
77
66
  * is undefined, `heading` is placed at the start of table of contents
78
67
  */
79
-
80
-
81
68
  function $updateHeadingPosition(prevHeading, heading, currentTableOfContents) {
82
69
  const newTableOfContents = [];
83
70
  const newEntry = toEntry(heading);
84
-
85
71
  if (!prevHeading) {
86
72
  newTableOfContents.push(newEntry);
87
73
  }
88
-
89
74
  for (const oldHeading of currentTableOfContents) {
90
75
  if (oldHeading[0] === heading.getKey()) {
91
76
  continue;
92
77
  }
93
-
94
78
  newTableOfContents.push(oldHeading);
95
-
96
79
  if (prevHeading && oldHeading[0] === prevHeading.getKey()) {
97
80
  newTableOfContents.push(newEntry);
98
81
  }
99
82
  }
100
-
101
83
  return newTableOfContents;
102
84
  }
103
-
104
85
  function LexicalTableOfContentsPlugin({
105
86
  children
106
87
  }) {
@@ -112,61 +93,52 @@ function LexicalTableOfContentsPlugin({
112
93
  editor.getEditorState().read(() => {
113
94
  const root = lexical.$getRoot();
114
95
  const rootChildren = root.getChildren();
115
-
116
96
  for (const child of rootChildren) {
117
97
  if (richText.$isHeadingNode(child)) {
118
98
  currentTableOfContents.push([child.getKey(), child.getTextContent(), child.getTag()]);
119
99
  }
120
100
  }
121
-
122
101
  setTableOfContents(currentTableOfContents);
123
- }); // Listen to updates to heading mutations and update state
102
+ });
124
103
 
104
+ // Listen to updates to heading mutations and update state
125
105
  const removeHeaderMutationListener = editor.registerMutationListener(richText.HeadingNode, mutatedNodes => {
126
106
  editor.getEditorState().read(() => {
127
107
  for (const [nodeKey, mutation] of mutatedNodes) {
128
108
  if (mutation === 'created') {
129
109
  const newHeading = lexical.$getNodeByKey(nodeKey);
130
-
131
110
  if (newHeading !== null) {
132
111
  let prevHeading = newHeading.getPreviousSibling();
133
-
134
112
  while (prevHeading !== null && !richText.$isHeadingNode(prevHeading)) {
135
113
  prevHeading = prevHeading.getPreviousSibling();
136
114
  }
137
-
138
115
  currentTableOfContents = $insertHeadingIntoTableOfContents(prevHeading, newHeading, currentTableOfContents);
139
116
  }
140
117
  } else if (mutation === 'destroyed') {
141
118
  currentTableOfContents = $deleteHeadingFromTableOfContents(nodeKey, currentTableOfContents);
142
119
  } else if (mutation === 'updated') {
143
120
  const newHeading = lexical.$getNodeByKey(nodeKey);
144
-
145
121
  if (newHeading !== null) {
146
122
  let prevHeading = newHeading.getPreviousSibling();
147
-
148
123
  while (prevHeading !== null && !richText.$isHeadingNode(prevHeading)) {
149
124
  prevHeading = prevHeading.getPreviousSibling();
150
125
  }
151
-
152
126
  currentTableOfContents = $updateHeadingPosition(prevHeading, newHeading, currentTableOfContents);
153
127
  }
154
128
  }
155
129
  }
156
-
157
130
  setTableOfContents(currentTableOfContents);
158
131
  });
159
- }); // Listen to text node mutation updates
132
+ });
160
133
 
134
+ // Listen to text node mutation updates
161
135
  const removeTextNodeMutationListener = editor.registerMutationListener(lexical.TextNode, mutatedNodes => {
162
136
  editor.getEditorState().read(() => {
163
137
  for (const [nodeKey, mutation] of mutatedNodes) {
164
138
  if (mutation === 'updated') {
165
139
  const currNode = lexical.$getNodeByKey(nodeKey);
166
-
167
140
  if (currNode !== null) {
168
141
  const parentNode = currNode.getParentOrThrow();
169
-
170
142
  if (richText.$isHeadingNode(parentNode)) {
171
143
  currentTableOfContents = $updateHeadingInTableOfContents(parentNode, currentTableOfContents);
172
144
  setTableOfContents(currentTableOfContents);
@@ -31,7 +31,6 @@ function TablePlugin({
31
31
  throw Error(`TablePlugin: TableNode, TableCellNode or TableRowNode not registered on editor`);
32
32
  }
33
33
  }
34
-
35
34
  return editor.registerCommand(table.INSERT_TABLE_COMMAND, ({
36
35
  columns,
37
36
  rows,
@@ -40,32 +39,27 @@ function TablePlugin({
40
39
  const tableNode = table.$createTableNodeWithDimensions(Number(rows), Number(columns), includeHeaders);
41
40
  utils.$insertNodeToNearestRoot(tableNode);
42
41
  const firstDescendant = tableNode.getFirstDescendant();
43
-
44
42
  if (lexical.$isTextNode(firstDescendant)) {
45
43
  firstDescendant.select();
46
44
  }
47
-
48
45
  return true;
49
46
  }, lexical.COMMAND_PRIORITY_EDITOR);
50
47
  }, [editor]);
51
48
  react.useEffect(() => {
52
49
  const tableSelections = new Map();
53
-
54
50
  const initializeTableNode = tableNode => {
55
51
  const nodeKey = tableNode.getKey();
56
52
  const tableElement = editor.getElementByKey(nodeKey);
57
-
58
53
  if (tableElement && !tableSelections.has(nodeKey)) {
59
54
  const tableSelection = table.applyTableHandlers(tableNode, tableElement, editor, hasTabHandler);
60
55
  tableSelections.set(nodeKey, tableSelection);
61
56
  }
62
- }; // Plugins might be loaded _after_ initial content is set, hence existing table nodes
63
- // won't be initialized from mutation[create] listener. Instead doing it here,
64
-
57
+ };
65
58
 
59
+ // Plugins might be loaded _after_ initial content is set, hence existing table nodes
60
+ // won't be initialized from mutation[create] listener. Instead doing it here,
66
61
  editor.getEditorState().read(() => {
67
62
  const tableNodes = lexical.$nodesOfType(table.TableNode);
68
-
69
63
  for (const tableNode of tableNodes) {
70
64
  if (table.$isTableNode(tableNode)) {
71
65
  initializeTableNode(tableNode);
@@ -77,14 +71,12 @@ function TablePlugin({
77
71
  if (mutation === 'created') {
78
72
  editor.getEditorState().read(() => {
79
73
  const tableNode = lexical.$getNodeByKey(nodeKey);
80
-
81
74
  if (table.$isTableNode(tableNode)) {
82
75
  initializeTableNode(tableNode);
83
76
  }
84
77
  });
85
78
  } else if (mutation === 'destroyed') {
86
79
  const tableSelection = tableSelections.get(nodeKey);
87
-
88
80
  if (tableSelection !== undefined) {
89
81
  tableSelection.removeListeners();
90
82
  tableSelections.delete(nodeKey);
@@ -93,58 +85,50 @@ function TablePlugin({
93
85
  }
94
86
  });
95
87
  return () => {
96
- unregisterMutationListener(); // Hook might be called multiple times so cleaning up tables listeners as well,
88
+ unregisterMutationListener();
89
+ // Hook might be called multiple times so cleaning up tables listeners as well,
97
90
  // as it'll be reinitialized during recurring call
98
-
99
91
  for (const [, tableSelection] of tableSelections) {
100
92
  tableSelection.removeListeners();
101
93
  }
102
94
  };
103
- }, [editor, hasTabHandler]); // Unmerge cells when the feature isn't enabled
95
+ }, [editor, hasTabHandler]);
104
96
 
97
+ // Unmerge cells when the feature isn't enabled
105
98
  react.useEffect(() => {
106
99
  if (hasCellMerge) {
107
100
  return;
108
101
  }
109
-
110
102
  return editor.registerNodeTransform(table.TableCellNode, node => {
111
103
  if (node.getColSpan() > 1 || node.getRowSpan() > 1) {
112
104
  // When we have rowSpan we have to map the entire Table to understand where the new Cells
113
105
  // fit best; let's analyze all Cells at once to save us from further transform iterations
114
106
  const [,, gridNode] = lexical.DEPRECATED_$getNodeTriplet(node);
115
- const [gridMap] = lexical.DEPRECATED_$computeGridMap(gridNode, node, node); // TODO this function expects Tables to be normalized. Look into this once it exists
116
-
107
+ const [gridMap] = lexical.DEPRECATED_$computeGridMap(gridNode, node, node);
108
+ // TODO this function expects Tables to be normalized. Look into this once it exists
117
109
  const rowsCount = gridMap.length;
118
110
  const columnsCount = gridMap[0].length;
119
111
  let row = gridNode.getFirstChild();
120
-
121
112
  if (!lexical.DEPRECATED_$isGridRowNode(row)) {
122
113
  throw Error(`Expected TableNode first child to be a RowNode`);
123
114
  }
124
-
125
115
  const unmerged = [];
126
-
127
116
  for (let i = 0; i < rowsCount; i++) {
128
117
  if (i !== 0) {
129
118
  row = row.getNextSibling();
130
-
131
119
  if (!lexical.DEPRECATED_$isGridRowNode(row)) {
132
120
  throw Error(`Expected TableNode first child to be a RowNode`);
133
121
  }
134
122
  }
135
-
136
123
  let lastRowCell = null;
137
-
138
124
  for (let j = 0; j < columnsCount; j++) {
139
125
  const cellMap = gridMap[i][j];
140
126
  const cell = cellMap.cell;
141
-
142
127
  if (cellMap.startRow === i && cellMap.startColumn === j) {
143
128
  lastRowCell = cell;
144
129
  unmerged.push(cell);
145
130
  } else if (cell.getColSpan() > 1 || cell.getRowSpan() > 1) {
146
131
  const newCell = table.$createTableCellNode(cell.__headerState);
147
-
148
132
  if (lastRowCell !== null) {
149
133
  lastRowCell.insertAfter(newCell);
150
134
  } else {
@@ -153,20 +137,19 @@ function TablePlugin({
153
137
  }
154
138
  }
155
139
  }
156
-
157
140
  for (const cell of unmerged) {
158
141
  cell.setColSpan(1);
159
142
  cell.setRowSpan(1);
160
143
  }
161
144
  }
162
145
  });
163
- }, [editor, hasCellMerge]); // Remove cell background color when feature is disabled
146
+ }, [editor, hasCellMerge]);
164
147
 
148
+ // Remove cell background color when feature is disabled
165
149
  react.useEffect(() => {
166
150
  if (hasCellBackgroundColor) {
167
151
  return;
168
152
  }
169
-
170
153
  return editor.registerNodeTransform(table.TableCellNode, node => {
171
154
  if (node.getBackgroundColor() !== null) {
172
155
  node.setBackgroundColor(null);