@octanejs/lexical 0.1.2

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 (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +67 -0
  3. package/package.json +104 -0
  4. package/src/LexicalAutoEmbedPlugin.tsrx +144 -0
  5. package/src/LexicalAutoFocusPlugin.tsrx +25 -0
  6. package/src/LexicalAutoLinkPlugin.tsrx +32 -0
  7. package/src/LexicalBlockWithAlignableContents.tsrx +85 -0
  8. package/src/LexicalCharacterLimitPlugin.tsrx +66 -0
  9. package/src/LexicalCheckListPlugin.tsrx +16 -0
  10. package/src/LexicalClearEditorPlugin.tsrx +12 -0
  11. package/src/LexicalClickableLinkPlugin.tsrx +17 -0
  12. package/src/LexicalCollaborationContext.ts +67 -0
  13. package/src/LexicalComposer.tsrx +100 -0
  14. package/src/LexicalComposerContext.ts +49 -0
  15. package/src/LexicalContentEditable.tsrx +48 -0
  16. package/src/LexicalDecoratorBlockNode.ts +78 -0
  17. package/src/LexicalDraggableBlockPlugin.tsrx +499 -0
  18. package/src/LexicalEditorRefPlugin.tsrx +19 -0
  19. package/src/LexicalErrorBoundary.tsrx +23 -0
  20. package/src/LexicalHashtagPlugin.tsrx +17 -0
  21. package/src/LexicalHistoryPlugin.tsrx +23 -0
  22. package/src/LexicalHorizontalRuleNode.tsrx +99 -0
  23. package/src/LexicalHorizontalRulePlugin.tsrx +34 -0
  24. package/src/LexicalLinkPlugin.tsrx +27 -0
  25. package/src/LexicalListPlugin.tsrx +37 -0
  26. package/src/LexicalMarkdownShortcutPlugin.tsrx +39 -0
  27. package/src/LexicalNestedComposer.tsrx +124 -0
  28. package/src/LexicalNodeContextMenuPlugin.tsrx +252 -0
  29. package/src/LexicalNodeEventPlugin.tsrx +48 -0
  30. package/src/LexicalNodeMenuPlugin.tsrx +84 -0
  31. package/src/LexicalOnChangePlugin.tsrx +36 -0
  32. package/src/LexicalPlainTextPlugin.tsrx +33 -0
  33. package/src/LexicalRichTextPlugin.tsrx +35 -0
  34. package/src/LexicalSelectionAlwaysOnDisplay.tsrx +11 -0
  35. package/src/LexicalTabIndentationPlugin.tsrx +21 -0
  36. package/src/LexicalTableOfContentsPlugin.tsrx +213 -0
  37. package/src/LexicalTablePlugin.tsrx +67 -0
  38. package/src/LexicalTypeaheadMenuPlugin.tsrx +141 -0
  39. package/src/autoEmbedShared.ts +43 -0
  40. package/src/index.ts +99 -0
  41. package/src/shared/LexicalContentEditableElement.tsrx +101 -0
  42. package/src/shared/LexicalDecorators.tsrx +88 -0
  43. package/src/shared/LexicalMenu.tsrx +275 -0
  44. package/src/shared/internal.ts +32 -0
  45. package/src/shared/menuShared.ts +152 -0
  46. package/src/shared/point.ts +46 -0
  47. package/src/shared/rect.ts +136 -0
  48. package/src/shared/useCanShowPlaceholder.ts +38 -0
  49. package/src/shared/useCharacterLimit.ts +288 -0
  50. package/src/shared/useDynamicPositioning.ts +72 -0
  51. package/src/shared/useMenuAnchorRef.ts +131 -0
  52. package/src/shared/usePlainTextSetup.ts +15 -0
  53. package/src/shared/useRichTextSetup.ts +16 -0
  54. package/src/tsrx-modules.d.ts +4 -0
  55. package/src/typeaheadShared.ts +132 -0
  56. package/src/types.ts +29 -0
  57. package/src/useLexicalEditable.ts +22 -0
  58. package/src/useLexicalIsTextContentEmpty.ts +35 -0
  59. package/src/useLexicalNodeSelection.ts +91 -0
  60. package/src/useLexicalSlotRef.ts +26 -0
  61. package/src/useLexicalSubscription.ts +55 -0
  62. package/src/useLexicalTextEntity.ts +24 -0
@@ -0,0 +1,33 @@
1
+ // Ported from @lexical/react/src/LexicalPlainTextPlugin.tsx — the plain-text twin
2
+ // of RichTextPlugin (registerPlainText instead of registerRichText).
3
+ import { useLexicalComposerContext } from './LexicalComposerContext';
4
+ import { useLexicalEditable } from './useLexicalEditable';
5
+ import { useCanShowPlaceholder } from './shared/useCanShowPlaceholder';
6
+ import { usePlainTextSetup } from './shared/usePlainTextSetup';
7
+ import { Decorators } from './shared/LexicalDecorators.tsrx';
8
+
9
+ function Placeholder(props) {
10
+ const [editor] = useLexicalComposerContext();
11
+ const showPlaceholder = useCanShowPlaceholder(editor);
12
+ const editable = useLexicalEditable();
13
+
14
+ if (!showPlaceholder) {
15
+ return null;
16
+ }
17
+ const content = props.content;
18
+ if (typeof content === 'function') {
19
+ return content(editable);
20
+ }
21
+ return content != null ? content : null;
22
+ }
23
+
24
+ export function PlainTextPlugin(props) @{
25
+ const [editor] = useLexicalComposerContext();
26
+ usePlainTextSetup(editor);
27
+
28
+ <>
29
+ {props.contentEditable}
30
+ <Placeholder content={props.placeholder} />
31
+ <Decorators editor={editor} ErrorBoundary={props.ErrorBoundary} />
32
+ </>
33
+ }
@@ -0,0 +1,35 @@
1
+ // Ported from @lexical/react/src/LexicalRichTextPlugin.tsx. Wires the core
2
+ // rich-text commands and renders the provided contentEditable, an optional
3
+ // placeholder, and decorator nodes. (The legacy `isUsingReactExtension` branch is
4
+ // omitted — that's the @lexical/extension path handled in a later phase.)
5
+ import { useLexicalComposerContext } from './LexicalComposerContext';
6
+ import { useLexicalEditable } from './useLexicalEditable';
7
+ import { useCanShowPlaceholder } from './shared/useCanShowPlaceholder';
8
+ import { useRichTextSetup } from './shared/useRichTextSetup';
9
+ import { Decorators } from './shared/LexicalDecorators.tsrx';
10
+
11
+ function Placeholder(props) {
12
+ const [editor] = useLexicalComposerContext();
13
+ const showPlaceholder = useCanShowPlaceholder(editor);
14
+ const editable = useLexicalEditable();
15
+
16
+ if (!showPlaceholder) {
17
+ return null;
18
+ }
19
+ const content = props.content;
20
+ if (typeof content === 'function') {
21
+ return content(editable);
22
+ }
23
+ return content != null ? content : null;
24
+ }
25
+
26
+ export function RichTextPlugin(props) @{
27
+ const [editor] = useLexicalComposerContext();
28
+ useRichTextSetup(editor);
29
+
30
+ <>
31
+ {props.contentEditable}
32
+ <Placeholder content={props.placeholder} />
33
+ <Decorators editor={editor} ErrorBoundary={props.ErrorBoundary} />
34
+ </>
35
+ }
@@ -0,0 +1,11 @@
1
+ // Ported from @lexical/react/src/LexicalSelectionAlwaysOnDisplay.tsx.
2
+ import { selectionAlwaysOnDisplay } from '@lexical/utils';
3
+ import { useLexicalComposerContext } from './LexicalComposerContext';
4
+ import { useEffect } from 'octane';
5
+
6
+ export function SelectionAlwaysOnDisplay(props) {
7
+ const [editor] = useLexicalComposerContext();
8
+ const onReposition = props.onReposition;
9
+ useEffect(() => selectionAlwaysOnDisplay(editor, onReposition), [editor, onReposition]);
10
+ return null;
11
+ }
@@ -0,0 +1,21 @@
1
+ // Ported from @lexical/react/src/LexicalTabIndentationPlugin.tsx.
2
+ // registerTabIndentation is from @lexical/extension's framework-agnostic core.
3
+ import { registerTabIndentation } from '@lexical/extension';
4
+ import { useLexicalComposerContext } from './LexicalComposerContext';
5
+ import { useEffect } from 'octane';
6
+
7
+ export { registerTabIndentation } from '@lexical/extension';
8
+
9
+ export function TabIndentationPlugin(props) {
10
+ const [editor] = useLexicalComposerContext();
11
+ const maxIndent = props.maxIndent;
12
+ const canIndent = props.$canIndent;
13
+
14
+ useEffect(() => registerTabIndentation(editor, maxIndent, canIndent), [
15
+ editor,
16
+ maxIndent,
17
+ canIndent,
18
+ ]);
19
+
20
+ return null;
21
+ }
@@ -0,0 +1,213 @@
1
+ // Ported from @lexical/react/src/LexicalTableOfContentsPlugin.tsx. Keeps an ordered
2
+ // list of [key, text, tag] entries in sync with every HeadingNode. Render-prop
3
+ // component: `children(entries, editor)` returns the element to render.
4
+ import { useLexicalComposerContext } from './LexicalComposerContext';
5
+ import { $isHeadingNode, HeadingNode } from '@lexical/rich-text';
6
+ import { $getNextRightPreorderNode } from '@lexical/utils';
7
+ import { $getNodeByKey, $getRoot, $isElementNode, TextNode } from 'lexical';
8
+ import { useEffect, useState } from 'octane';
9
+
10
+ function toEntry(heading) {
11
+ return [heading.getKey(), heading.getTextContent(), heading.getTag()];
12
+ }
13
+
14
+ function $insertHeadingIntoTableOfContents(prevHeading, newHeading, currentTableOfContents) {
15
+ if (newHeading === null) {
16
+ return currentTableOfContents;
17
+ }
18
+ const newEntry = toEntry(newHeading);
19
+ let newTableOfContents = [];
20
+ if (prevHeading === null) {
21
+ if (currentTableOfContents.length > 0 && currentTableOfContents[0][0] === newHeading.__key) {
22
+ return currentTableOfContents;
23
+ }
24
+ newTableOfContents = [newEntry, ...currentTableOfContents];
25
+ } else {
26
+ for (let i = 0; i < currentTableOfContents.length; i++) {
27
+ const key = currentTableOfContents[i][0];
28
+ newTableOfContents.push(currentTableOfContents[i]);
29
+ if (key === prevHeading.getKey() && key !== newHeading.getKey()) {
30
+ if (
31
+ i + 1 < currentTableOfContents.length &&
32
+ currentTableOfContents[i + 1][0] === newHeading.__key
33
+ ) {
34
+ return currentTableOfContents;
35
+ }
36
+ newTableOfContents.push(newEntry);
37
+ }
38
+ }
39
+ }
40
+ return newTableOfContents;
41
+ }
42
+
43
+ function $deleteHeadingFromTableOfContents(key, currentTableOfContents) {
44
+ const newTableOfContents = [];
45
+ for (const heading of currentTableOfContents) {
46
+ if (heading[0] !== key) {
47
+ newTableOfContents.push(heading);
48
+ }
49
+ }
50
+ return newTableOfContents;
51
+ }
52
+
53
+ function $updateHeadingInTableOfContents(heading, currentTableOfContents) {
54
+ const newTableOfContents = [];
55
+ for (const oldHeading of currentTableOfContents) {
56
+ if (oldHeading[0] === heading.getKey()) {
57
+ newTableOfContents.push(toEntry(heading));
58
+ } else {
59
+ newTableOfContents.push(oldHeading);
60
+ }
61
+ }
62
+ return newTableOfContents;
63
+ }
64
+
65
+ function $updateHeadingPosition(prevHeading, heading, currentTableOfContents) {
66
+ const newTableOfContents = [];
67
+ const newEntry = toEntry(heading);
68
+
69
+ if (!prevHeading) {
70
+ newTableOfContents.push(newEntry);
71
+ }
72
+ for (const oldHeading of currentTableOfContents) {
73
+ if (oldHeading[0] === heading.getKey()) {
74
+ continue;
75
+ }
76
+ newTableOfContents.push(oldHeading);
77
+ if (prevHeading && oldHeading[0] === prevHeading.getKey()) {
78
+ newTableOfContents.push(newEntry);
79
+ }
80
+ }
81
+
82
+ return newTableOfContents;
83
+ }
84
+
85
+ function $getPreviousHeading(node) {
86
+ let prevHeading = $getNextRightPreorderNode(node);
87
+ while (prevHeading !== null && !$isHeadingNode(prevHeading)) {
88
+ prevHeading = $getNextRightPreorderNode(prevHeading);
89
+ }
90
+ return prevHeading;
91
+ }
92
+
93
+ export function TableOfContentsPlugin(props) {
94
+ const [tableOfContents, setTableOfContents] = useState([]);
95
+ const [editor] = useLexicalComposerContext();
96
+
97
+ useEffect(() => {
98
+ let currentTableOfContents = [];
99
+ editor.read('latest', () => {
100
+ const updateCurrentTableOfContents = (node) => {
101
+ for (const child of node.getChildren()) {
102
+ if ($isHeadingNode(child)) {
103
+ currentTableOfContents.push([
104
+ child.getKey(),
105
+ child.getTextContent(),
106
+ child.getTag(),
107
+ ]);
108
+ } else if ($isElementNode(child)) {
109
+ updateCurrentTableOfContents(child);
110
+ }
111
+ }
112
+ };
113
+
114
+ updateCurrentTableOfContents($getRoot());
115
+ setTableOfContents(currentTableOfContents);
116
+ });
117
+
118
+ const removeRootUpdateListener = editor.registerUpdateListener(({
119
+ editorState,
120
+ dirtyElements,
121
+ }) => {
122
+ editorState.read(() => {
123
+ const updateChildHeadings = (node) => {
124
+ for (const child of node.getChildren()) {
125
+ if ($isHeadingNode(child)) {
126
+ const prevHeading = $getPreviousHeading(child);
127
+ currentTableOfContents = $updateHeadingPosition(
128
+ prevHeading,
129
+ child,
130
+ currentTableOfContents,
131
+ );
132
+ setTableOfContents(currentTableOfContents);
133
+ } else if ($isElementNode(child)) {
134
+ updateChildHeadings(child);
135
+ }
136
+ }
137
+ };
138
+
139
+ $getRoot().getChildren().forEach((node) => {
140
+ if ($isElementNode(node) && dirtyElements.get(node.__key)) {
141
+ updateChildHeadings(node);
142
+ }
143
+ });
144
+ });
145
+ });
146
+
147
+ const removeHeaderMutationListener = editor.registerMutationListener(HeadingNode, (
148
+ mutatedNodes,
149
+ ) => {
150
+ editor.read('latest', () => {
151
+ for (const [nodeKey, mutation] of mutatedNodes) {
152
+ if (mutation === 'created') {
153
+ const newHeading = $getNodeByKey(nodeKey);
154
+ if ($isHeadingNode(newHeading)) {
155
+ const prevHeading = $getPreviousHeading(newHeading);
156
+ currentTableOfContents = $insertHeadingIntoTableOfContents(
157
+ prevHeading,
158
+ newHeading,
159
+ currentTableOfContents,
160
+ );
161
+ }
162
+ } else if (mutation === 'destroyed') {
163
+ currentTableOfContents = $deleteHeadingFromTableOfContents(
164
+ nodeKey,
165
+ currentTableOfContents,
166
+ );
167
+ } else if (mutation === 'updated') {
168
+ const newHeading = $getNodeByKey(nodeKey);
169
+ if ($isHeadingNode(newHeading)) {
170
+ const prevHeading = $getPreviousHeading(newHeading);
171
+ currentTableOfContents = $updateHeadingPosition(
172
+ prevHeading,
173
+ newHeading,
174
+ currentTableOfContents,
175
+ );
176
+ }
177
+ }
178
+ }
179
+ setTableOfContents(currentTableOfContents);
180
+ });
181
+ }, { skipInitialization: true });
182
+
183
+ const removeTextNodeMutationListener = editor.registerMutationListener(TextNode, (
184
+ mutatedNodes,
185
+ ) => {
186
+ editor.read('latest', () => {
187
+ for (const [nodeKey, mutation] of mutatedNodes) {
188
+ if (mutation === 'updated') {
189
+ const currNode = $getNodeByKey(nodeKey);
190
+ if (currNode !== null) {
191
+ const parentNode = currNode.getParentOrThrow();
192
+ if ($isHeadingNode(parentNode)) {
193
+ currentTableOfContents = $updateHeadingInTableOfContents(
194
+ parentNode,
195
+ currentTableOfContents,
196
+ );
197
+ setTableOfContents(currentTableOfContents);
198
+ }
199
+ }
200
+ }
201
+ }
202
+ });
203
+ }, { skipInitialization: true });
204
+
205
+ return () => {
206
+ removeHeaderMutationListener();
207
+ removeTextNodeMutationListener();
208
+ removeRootUpdateListener();
209
+ };
210
+ }, [editor]);
211
+
212
+ return props.children(tableOfContents, editor);
213
+ }
@@ -0,0 +1,67 @@
1
+ // Ported from @lexical/react/src/LexicalTablePlugin.ts (usePropSignal inlined).
2
+ import { signal } from '@lexical/extension';
3
+ import {
4
+ $isScrollableTablesActive,
5
+ registerTableCellUnmergeTransform,
6
+ registerTablePlugin,
7
+ registerTableSelectionObserver,
8
+ setScrollableTablesActive,
9
+ TableCellNode,
10
+ } from '@lexical/table';
11
+ import { $fullReconcile } from 'lexical';
12
+ import { useLexicalComposerContext } from './LexicalComposerContext';
13
+ import { useEffect, useState } from 'octane';
14
+
15
+ export function TablePlugin(props) {
16
+ const [editor] = useLexicalComposerContext();
17
+ const hasCellMerge =
18
+ props.hasCellMerge === undefined ? true : props.hasCellMerge;
19
+ const hasCellBackgroundColor =
20
+ props.hasCellBackgroundColor === undefined ? true : props.hasCellBackgroundColor;
21
+ const hasTabHandler =
22
+ props.hasTabHandler === undefined ? true : props.hasTabHandler;
23
+ const hasHorizontalScroll =
24
+ props.hasHorizontalScroll === undefined ? false : props.hasHorizontalScroll;
25
+ const hasNestedTables =
26
+ props.hasNestedTables === undefined ? false : props.hasNestedTables;
27
+
28
+ useEffect(() => {
29
+ const hadHorizontalScroll = $isScrollableTablesActive(editor);
30
+ if (hadHorizontalScroll !== hasHorizontalScroll) {
31
+ setScrollableTablesActive(editor, hasHorizontalScroll);
32
+ editor.update($fullReconcile);
33
+ }
34
+ }, [editor, hasHorizontalScroll]);
35
+
36
+ // usePropSignal — inlined.
37
+ const [hasNestedTablesSignal] = useState(() => signal(hasNestedTables));
38
+ if (hasNestedTablesSignal.peek() !== hasNestedTables) {
39
+ hasNestedTablesSignal.value = hasNestedTables;
40
+ }
41
+
42
+ useEffect(() => registerTablePlugin(editor, { hasNestedTables: hasNestedTablesSignal }), [
43
+ editor,
44
+ hasNestedTablesSignal,
45
+ ]);
46
+
47
+ useEffect(() => registerTableSelectionObserver(editor, hasTabHandler), [editor, hasTabHandler]);
48
+
49
+ useEffect(() => {
50
+ if (!hasCellMerge) {
51
+ return registerTableCellUnmergeTransform(editor);
52
+ }
53
+ }, [editor, hasCellMerge]);
54
+
55
+ useEffect(() => {
56
+ if (hasCellBackgroundColor) {
57
+ return;
58
+ }
59
+ return editor.registerNodeTransform(TableCellNode, (node) => {
60
+ if (node.getBackgroundColor() !== null) {
61
+ node.setBackgroundColor(null);
62
+ }
63
+ });
64
+ }, [editor, hasCellBackgroundColor, hasCellMerge]);
65
+
66
+ return null;
67
+ }
@@ -0,0 +1,141 @@
1
+ // Ported from @lexical/react/src/LexicalTypeaheadMenuPlugin.tsx — renders a
2
+ // floating menu (mentions / slash-command) while the text before the cursor
3
+ // matches `triggerFn`. `.tsrx` component (auto-slotted hooks); uses octane's
4
+ // startTransition (the React version used a reactPatches shim).
5
+ import { useLexicalComposerContext } from './LexicalComposerContext';
6
+ import { LexicalMenu } from './shared/LexicalMenu.tsrx';
7
+ import { useMenuAnchorRef } from './shared/useMenuAnchorRef';
8
+ import {
9
+ getQueryTextForSearch,
10
+ isSelectionOnEntityBoundary,
11
+ tryToPositionRange,
12
+ } from './typeaheadShared';
13
+ import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_LOW } from 'lexical';
14
+ import { startTransition, useCallback, useEffect, useState } from 'octane';
15
+
16
+ export { useBasicTypeaheadTriggerMatch, PUNCTUATION } from './typeaheadShared';
17
+ export { MenuOption, SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND } from './shared/menuShared';
18
+ export { useDynamicPositioning } from './shared/useDynamicPositioning';
19
+
20
+ export function LexicalTypeaheadMenuPlugin(props) {
21
+ const options = props.options;
22
+ const onQueryChange = props.onQueryChange;
23
+ const onSelectOption = props.onSelectOption;
24
+ const onOpen = props.onOpen;
25
+ const onClose = props.onClose;
26
+ const menuRenderFn = props.menuRenderFn;
27
+ const triggerFn = props.triggerFn;
28
+ const anchorClassName = props.anchorClassName;
29
+ const commandPriority =
30
+ props.commandPriority != null ? props.commandPriority : COMMAND_PRIORITY_LOW;
31
+ const parent = props.parent;
32
+ const preselectFirstItem = props.preselectFirstItem !== false;
33
+ const ignoreEntityBoundary = props.ignoreEntityBoundary === true;
34
+
35
+ const [editor] = useLexicalComposerContext();
36
+ const [resolution, setResolution] = useState(null);
37
+ const anchorElementRef = useMenuAnchorRef(resolution, setResolution, anchorClassName, parent);
38
+
39
+ const closeTypeahead = useCallback(() => {
40
+ if (resolution === null) {
41
+ return;
42
+ }
43
+ const finish = () => setResolution(null);
44
+ let result;
45
+ try {
46
+ result = onClose && onClose();
47
+ } finally {
48
+ if (result) {
49
+ result.then(finish, finish);
50
+ } else {
51
+ finish();
52
+ }
53
+ }
54
+ }, [onClose, resolution]);
55
+
56
+ const openTypeahead = useCallback((res) => {
57
+ setResolution(res);
58
+ if (onOpen != null && resolution === null) {
59
+ onOpen(res);
60
+ }
61
+ }, [onOpen, resolution]);
62
+
63
+ useEffect(() => {
64
+ const updateListener = () => {
65
+ editor.read('latest', () => {
66
+ if (!editor.isEditable()) {
67
+ closeTypeahead();
68
+ return;
69
+ }
70
+ if (editor.isComposing()) {
71
+ return;
72
+ }
73
+ const editorWindow = editor._window || window;
74
+ const range = editorWindow.document.createRange();
75
+ const selection = $getSelection();
76
+ const text = getQueryTextForSearch(editor);
77
+ if (
78
+ !$isRangeSelection(selection) || !selection.isCollapsed() || text === null ||
79
+ range === null
80
+ ) {
81
+ closeTypeahead();
82
+ return;
83
+ }
84
+ const match = triggerFn(text, editor);
85
+ onQueryChange(match ? match.matchingString : null);
86
+ if (
87
+ match !== null &&
88
+ (ignoreEntityBoundary || !isSelectionOnEntityBoundary(editor, match.leadOffset))
89
+ ) {
90
+ const isRangePositioned = tryToPositionRange(
91
+ match.leadOffset,
92
+ range,
93
+ editorWindow,
94
+ editor.getRootElement(),
95
+ );
96
+ if (isRangePositioned !== null) {
97
+ startTransition(
98
+ () => openTypeahead({ getRect: () => range.getBoundingClientRect(), match }),
99
+ );
100
+ return;
101
+ }
102
+ }
103
+ closeTypeahead();
104
+ });
105
+ };
106
+ return editor.registerUpdateListener(updateListener);
107
+ }, [
108
+ editor,
109
+ triggerFn,
110
+ onQueryChange,
111
+ resolution,
112
+ closeTypeahead,
113
+ openTypeahead,
114
+ ignoreEntityBoundary,
115
+ ]);
116
+
117
+ useEffect(
118
+ () => editor.registerEditableListener((isEditable) => {
119
+ if (!isEditable) {
120
+ closeTypeahead();
121
+ }
122
+ }),
123
+ [editor, closeTypeahead],
124
+ );
125
+
126
+ if (resolution === null || editor === null || anchorElementRef.current === null) {
127
+ return null;
128
+ }
129
+ return <LexicalMenu
130
+ close={closeTypeahead}
131
+ resolution={resolution}
132
+ editor={editor}
133
+ anchorElementRef={anchorElementRef}
134
+ options={options}
135
+ menuRenderFn={menuRenderFn}
136
+ shouldSplitNodeWithQuery={true}
137
+ onSelectOption={onSelectOption}
138
+ commandPriority={commandPriority}
139
+ preselectFirstItem={preselectFirstItem}
140
+ />;
141
+ }
@@ -0,0 +1,43 @@
1
+ // Typed / agnostic surface of @lexical/react/src/LexicalAutoEmbedPlugin.tsx — the
2
+ // types, the URL matcher, the INSERT_EMBED_COMMAND, and the AutoEmbedOption class.
3
+ // The component lives in LexicalAutoEmbedPlugin.tsrx.
4
+ import type { LexicalCommand, LexicalEditor, LexicalNode } from 'lexical';
5
+
6
+ import { createCommand } from 'lexical';
7
+ import { MenuOption } from './shared/menuShared';
8
+
9
+ export type EmbedMatchResult<TEmbedMatchResult = unknown> = {
10
+ url: string;
11
+ id: string;
12
+ data?: TEmbedMatchResult;
13
+ };
14
+
15
+ export interface EmbedConfig<
16
+ TEmbedMatchResultData = unknown,
17
+ TEmbedMatchResult = EmbedMatchResult<TEmbedMatchResultData>,
18
+ > {
19
+ type: string;
20
+ parseUrl: (text: string) => Promise<TEmbedMatchResult | null> | TEmbedMatchResult | null;
21
+ insertNode: (editor: LexicalEditor, result: TEmbedMatchResult) => void;
22
+ }
23
+
24
+ export const URL_MATCHER =
25
+ /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
26
+
27
+ export const INSERT_EMBED_COMMAND: LexicalCommand<EmbedConfig['type']> =
28
+ createCommand('INSERT_EMBED_COMMAND');
29
+
30
+ export class AutoEmbedOption extends MenuOption {
31
+ title: string;
32
+ onSelect: (targetNode: LexicalNode | null) => void;
33
+ constructor(
34
+ title: string,
35
+ options: {
36
+ onSelect: (targetNode: LexicalNode | null) => void;
37
+ },
38
+ ) {
39
+ super(title);
40
+ this.title = title;
41
+ this.onSelect = options.onSelect.bind(this);
42
+ }
43
+ }
package/src/index.ts ADDED
@@ -0,0 +1,99 @@
1
+ // @octanejs/lexical — Octane bindings for Lexical, a port of @lexical/react.
2
+ //
3
+ // @lexical/react has NO root barrel — it ships per-subpath modules. This package
4
+ // mirrors that layout (import from `@octanejs/lexical/LexicalComposer`, etc.), and
5
+ // ALSO provides this convenience barrel that re-exports everything.
6
+ //
7
+ // Components/nodes live in `.tsrx` files (the octane compiler handles them; tsgo
8
+ // treats them as `any` via the `*.tsrx` ambient module). Genuinely-typed surface —
9
+ // config types and the framework-agnostic helpers — is re-exported from real `.ts`
10
+ // sources / the `@lexical/*` packages so consumers keep those types.
11
+
12
+ // --- Core editor ---
13
+ export {
14
+ LexicalComposerContext,
15
+ createLexicalComposerContext,
16
+ useLexicalComposerContext,
17
+ } from './LexicalComposerContext';
18
+ export type {
19
+ LexicalComposerContextType,
20
+ LexicalComposerContextWithEditor,
21
+ } from './LexicalComposerContext';
22
+ export type { InitialConfigType, InitialEditorStateType } from './types';
23
+
24
+ export { LexicalComposer } from './LexicalComposer.tsrx';
25
+ export { ContentEditable, ContentEditableElement } from './LexicalContentEditable.tsrx';
26
+ export { RichTextPlugin } from './LexicalRichTextPlugin.tsrx';
27
+ export { PlainTextPlugin } from './LexicalPlainTextPlugin.tsrx';
28
+ export { LexicalErrorBoundary } from './LexicalErrorBoundary.tsrx';
29
+
30
+ export { useLexicalEditable } from './useLexicalEditable';
31
+ export { useLexicalSubscription, type LexicalSubscription } from './useLexicalSubscription';
32
+
33
+ // --- Phase 2 plugins ---
34
+ export { HistoryPlugin } from './LexicalHistoryPlugin.tsrx';
35
+ export { createEmptyHistoryState } from '@lexical/history';
36
+ export type { HistoryState } from '@lexical/history';
37
+ export { OnChangePlugin } from './LexicalOnChangePlugin.tsrx';
38
+ export { AutoFocusPlugin } from './LexicalAutoFocusPlugin.tsrx';
39
+ export { ClearEditorPlugin } from './LexicalClearEditorPlugin.tsrx';
40
+
41
+ // --- Phase 3 plugins ---
42
+ export { ListPlugin } from './LexicalListPlugin.tsrx';
43
+ export { CheckListPlugin } from './LexicalCheckListPlugin.tsrx';
44
+ export { LinkPlugin } from './LexicalLinkPlugin.tsrx';
45
+ export { AutoLinkPlugin } from './LexicalAutoLinkPlugin.tsrx';
46
+ export { createLinkMatcherWithRegExp } from '@lexical/link';
47
+ export { HashtagPlugin } from './LexicalHashtagPlugin.tsrx';
48
+ export { TabIndentationPlugin } from './LexicalTabIndentationPlugin.tsrx';
49
+ export { registerTabIndentation } from '@lexical/extension';
50
+ export { MarkdownShortcutPlugin, DEFAULT_TRANSFORMERS } from './LexicalMarkdownShortcutPlugin.tsrx';
51
+ export { TablePlugin } from './LexicalTablePlugin.tsrx';
52
+ export { NodeEventPlugin } from './LexicalNodeEventPlugin.tsrx';
53
+ export { EditorRefPlugin } from './LexicalEditorRefPlugin.tsrx';
54
+ export { HorizontalRulePlugin } from './LexicalHorizontalRulePlugin.tsrx';
55
+ export { HorizontalRuleNode, $createHorizontalRuleNode } from './LexicalHorizontalRuleNode.tsrx';
56
+ export { $isHorizontalRuleNode, INSERT_HORIZONTAL_RULE_COMMAND } from '@lexical/extension';
57
+
58
+ // --- Phase 3 hooks ---
59
+ export { useLexicalNodeSelection } from './useLexicalNodeSelection';
60
+ export { useLexicalTextEntity } from './useLexicalTextEntity';
61
+ export { useLexicalIsTextContentEmpty } from './useLexicalIsTextContentEmpty';
62
+
63
+ // --- Phase 4: menus + utilities ---
64
+ export { LexicalTypeaheadMenuPlugin } from './LexicalTypeaheadMenuPlugin.tsrx';
65
+ export { LexicalNodeMenuPlugin } from './LexicalNodeMenuPlugin.tsrx';
66
+ export { useBasicTypeaheadTriggerMatch, PUNCTUATION } from './typeaheadShared';
67
+ export { MenuOption, SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND } from './shared/menuShared';
68
+ export type {
69
+ MenuRef,
70
+ MenuRenderFn,
71
+ MenuResolution,
72
+ MenuTextMatch,
73
+ TriggerFn,
74
+ } from './shared/menuShared';
75
+ export { useDynamicPositioning } from './shared/useDynamicPositioning';
76
+ export { useMenuAnchorRef } from './shared/useMenuAnchorRef';
77
+ export { ClickableLinkPlugin } from './LexicalClickableLinkPlugin.tsrx';
78
+ export { SelectionAlwaysOnDisplay } from './LexicalSelectionAlwaysOnDisplay.tsrx';
79
+ export { useLexicalSlotRef } from './useLexicalSlotRef';
80
+
81
+ // --- Phase 4: nested editor + decorator-block building blocks ---
82
+ export { LexicalNestedComposer } from './LexicalNestedComposer.tsrx';
83
+ export { DecoratorBlockNode, $isDecoratorBlockNode } from './LexicalDecoratorBlockNode';
84
+ export type { SerializedDecoratorBlockNode } from './LexicalDecoratorBlockNode';
85
+ export { BlockWithAlignableContents } from './LexicalBlockWithAlignableContents.tsrx';
86
+ export { CollaborationContext, useCollaborationContext } from './LexicalCollaborationContext';
87
+ export type { CollaborationContextType } from './LexicalCollaborationContext';
88
+ export { CharacterLimitPlugin } from './LexicalCharacterLimitPlugin.tsrx';
89
+ export { useCharacterLimit } from './shared/useCharacterLimit';
90
+ export { TableOfContentsPlugin } from './LexicalTableOfContentsPlugin.tsrx';
91
+ export { LexicalAutoEmbedPlugin } from './LexicalAutoEmbedPlugin.tsrx';
92
+ export { URL_MATCHER, INSERT_EMBED_COMMAND, AutoEmbedOption } from './autoEmbedShared';
93
+ export type { EmbedConfig, EmbedMatchResult } from './autoEmbedShared';
94
+ export { DraggableBlockPlugin_EXPERIMENTAL } from './LexicalDraggableBlockPlugin.tsrx';
95
+ export {
96
+ NodeContextMenuPlugin,
97
+ NodeContextMenuOption,
98
+ NodeContextMenuSeparator,
99
+ } from './LexicalNodeContextMenuPlugin.tsrx';